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.
package/dist/index.cjs CHANGED
@@ -495,7 +495,7 @@ var require_ignore = __commonJS({
495
495
  // path matching.
496
496
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
497
497
  // @returns {TestResult} true if a file is ignored
498
- test(path22, checkUnignored, mode) {
498
+ test(path23, checkUnignored, mode) {
499
499
  let ignored = false;
500
500
  let unignored = false;
501
501
  let matchedRule;
@@ -504,7 +504,7 @@ var require_ignore = __commonJS({
504
504
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
505
505
  return;
506
506
  }
507
- const matched = rule[mode].test(path22);
507
+ const matched = rule[mode].test(path23);
508
508
  if (!matched) {
509
509
  return;
510
510
  }
@@ -525,17 +525,17 @@ var require_ignore = __commonJS({
525
525
  var throwError = (message, Ctor) => {
526
526
  throw new Ctor(message);
527
527
  };
528
- var checkPath = (path22, originalPath, doThrow) => {
529
- if (!isString(path22)) {
528
+ var checkPath = (path23, originalPath, doThrow) => {
529
+ if (!isString(path23)) {
530
530
  return doThrow(
531
531
  `path must be a string, but got \`${originalPath}\``,
532
532
  TypeError
533
533
  );
534
534
  }
535
- if (!path22) {
535
+ if (!path23) {
536
536
  return doThrow(`path must not be empty`, TypeError);
537
537
  }
538
- if (checkPath.isNotRelative(path22)) {
538
+ if (checkPath.isNotRelative(path23)) {
539
539
  const r = "`path.relative()`d";
540
540
  return doThrow(
541
541
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -544,7 +544,7 @@ var require_ignore = __commonJS({
544
544
  }
545
545
  return true;
546
546
  };
547
- var isNotRelative = (path22) => REGEX_TEST_INVALID_PATH.test(path22);
547
+ var isNotRelative = (path23) => REGEX_TEST_INVALID_PATH.test(path23);
548
548
  checkPath.isNotRelative = isNotRelative;
549
549
  checkPath.convert = (p) => p;
550
550
  var Ignore2 = class {
@@ -574,19 +574,19 @@ var require_ignore = __commonJS({
574
574
  }
575
575
  // @returns {TestResult}
576
576
  _test(originalPath, cache, checkUnignored, slices) {
577
- const path22 = originalPath && checkPath.convert(originalPath);
577
+ const path23 = originalPath && checkPath.convert(originalPath);
578
578
  checkPath(
579
- path22,
579
+ path23,
580
580
  originalPath,
581
581
  this._strictPathCheck ? throwError : RETURN_FALSE
582
582
  );
583
- return this._t(path22, cache, checkUnignored, slices);
583
+ return this._t(path23, cache, checkUnignored, slices);
584
584
  }
585
- checkIgnore(path22) {
586
- if (!REGEX_TEST_TRAILING_SLASH.test(path22)) {
587
- return this.test(path22);
585
+ checkIgnore(path23) {
586
+ if (!REGEX_TEST_TRAILING_SLASH.test(path23)) {
587
+ return this.test(path23);
588
588
  }
589
- const slices = path22.split(SLASH2).filter(Boolean);
589
+ const slices = path23.split(SLASH2).filter(Boolean);
590
590
  slices.pop();
591
591
  if (slices.length) {
592
592
  const parent = this._t(
@@ -599,18 +599,18 @@ var require_ignore = __commonJS({
599
599
  return parent;
600
600
  }
601
601
  }
602
- return this._rules.test(path22, false, MODE_CHECK_IGNORE);
602
+ return this._rules.test(path23, false, MODE_CHECK_IGNORE);
603
603
  }
604
- _t(path22, cache, checkUnignored, slices) {
605
- if (path22 in cache) {
606
- return cache[path22];
604
+ _t(path23, cache, checkUnignored, slices) {
605
+ if (path23 in cache) {
606
+ return cache[path23];
607
607
  }
608
608
  if (!slices) {
609
- slices = path22.split(SLASH2).filter(Boolean);
609
+ slices = path23.split(SLASH2).filter(Boolean);
610
610
  }
611
611
  slices.pop();
612
612
  if (!slices.length) {
613
- return cache[path22] = this._rules.test(path22, checkUnignored, MODE_IGNORE);
613
+ return cache[path23] = this._rules.test(path23, checkUnignored, MODE_IGNORE);
614
614
  }
615
615
  const parent = this._t(
616
616
  slices.join(SLASH2) + SLASH2,
@@ -618,29 +618,29 @@ var require_ignore = __commonJS({
618
618
  checkUnignored,
619
619
  slices
620
620
  );
621
- return cache[path22] = parent.ignored ? parent : this._rules.test(path22, checkUnignored, MODE_IGNORE);
621
+ return cache[path23] = parent.ignored ? parent : this._rules.test(path23, checkUnignored, MODE_IGNORE);
622
622
  }
623
- ignores(path22) {
624
- return this._test(path22, this._ignoreCache, false).ignored;
623
+ ignores(path23) {
624
+ return this._test(path23, this._ignoreCache, false).ignored;
625
625
  }
626
626
  createFilter() {
627
- return (path22) => !this.ignores(path22);
627
+ return (path23) => !this.ignores(path23);
628
628
  }
629
629
  filter(paths) {
630
630
  return makeArray(paths).filter(this.createFilter());
631
631
  }
632
632
  // @returns {TestResult}
633
- test(path22) {
634
- return this._test(path22, this._testCache, true);
633
+ test(path23) {
634
+ return this._test(path23, this._testCache, true);
635
635
  }
636
636
  };
637
637
  var factory = (options) => new Ignore2(options);
638
- var isPathValid = (path22) => checkPath(path22 && checkPath.convert(path22), path22, RETURN_FALSE);
638
+ var isPathValid = (path23) => checkPath(path23 && checkPath.convert(path23), path23, RETURN_FALSE);
639
639
  var setupWindows = () => {
640
640
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
641
641
  checkPath.convert = makePosix;
642
642
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
643
- checkPath.isNotRelative = (path22) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path22) || isNotRelative(path22);
643
+ checkPath.isNotRelative = (path23) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path23) || isNotRelative(path23);
644
644
  };
645
645
  if (
646
646
  // Detect `process` so that it can run in browsers.
@@ -662,7 +662,7 @@ __export(index_exports, {
662
662
  });
663
663
  module.exports = __toCommonJS(index_exports);
664
664
  var os5 = __toESM(require("os"), 1);
665
- var path21 = __toESM(require("path"), 1);
665
+ var path22 = __toESM(require("path"), 1);
666
666
  var import_url2 = require("url");
667
667
 
668
668
  // src/config/constants.ts
@@ -795,7 +795,8 @@ function getDefaultIndexingConfig() {
795
795
  requireProjectMarker: true,
796
796
  maxDepth: 5,
797
797
  maxFilesPerDirectory: 100,
798
- fallbackToTextOnMaxChunks: true
798
+ fallbackToTextOnMaxChunks: true,
799
+ gitBlame: { enabled: false }
799
800
  };
800
801
  }
801
802
  function getDefaultSearchConfig() {
@@ -919,7 +920,10 @@ function parseConfig(raw) {
919
920
  requireProjectMarker: typeof rawIndexing.requireProjectMarker === "boolean" ? rawIndexing.requireProjectMarker : defaultIndexing.requireProjectMarker,
920
921
  maxDepth: typeof rawIndexing.maxDepth === "number" ? rawIndexing.maxDepth < -1 ? -1 : rawIndexing.maxDepth : defaultIndexing.maxDepth,
921
922
  maxFilesPerDirectory: typeof rawIndexing.maxFilesPerDirectory === "number" ? Math.max(1, rawIndexing.maxFilesPerDirectory) : defaultIndexing.maxFilesPerDirectory,
922
- fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks
923
+ fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks,
924
+ gitBlame: {
925
+ enabled: rawIndexing.gitBlame && typeof rawIndexing.gitBlame === "object" && typeof rawIndexing.gitBlame.enabled === "boolean" ? rawIndexing.gitBlame.enabled : defaultIndexing.gitBlame.enabled
926
+ }
923
927
  };
924
928
  const rawSearch = input.search && typeof input.search === "object" ? input.search : {};
925
929
  const search = {
@@ -1589,14 +1593,14 @@ function loadMergedConfig(projectRoot, host = "opencode") {
1589
1593
 
1590
1594
  // src/tools/operations.ts
1591
1595
  var import_fs9 = require("fs");
1592
- var path14 = __toESM(require("path"), 1);
1596
+ var path15 = __toESM(require("path"), 1);
1593
1597
 
1594
1598
  // src/indexer/index.ts
1595
1599
  var import_fs7 = require("fs");
1596
- var path11 = __toESM(require("path"), 1);
1600
+ var path12 = __toESM(require("path"), 1);
1597
1601
  var import_perf_hooks = require("perf_hooks");
1598
- var import_child_process2 = require("child_process");
1599
- var import_util2 = require("util");
1602
+ var import_child_process3 = require("child_process");
1603
+ var import_util3 = require("util");
1600
1604
 
1601
1605
  // node_modules/eventemitter3/index.mjs
1602
1606
  var import_index = __toESM(require_eventemitter3(), 1);
@@ -4784,8 +4788,8 @@ function normalizeFiles(rawFiles, projectRoot) {
4784
4788
  const trimmed = raw.trim();
4785
4789
  if (!trimmed) continue;
4786
4790
  const absolute = path10.resolve(projectRoot, trimmed);
4787
- const relative10 = path10.relative(projectRoot, absolute);
4788
- const cleaned = relative10.startsWith("./") ? relative10.slice(2) : relative10;
4791
+ const relative11 = path10.relative(projectRoot, absolute);
4792
+ const cleaned = relative11.startsWith("./") ? relative11.slice(2) : relative11;
4789
4793
  if (!seen.has(cleaned)) {
4790
4794
  seen.add(cleaned);
4791
4795
  result.push(cleaned);
@@ -4794,8 +4798,61 @@ function normalizeFiles(rawFiles, projectRoot) {
4794
4798
  return result;
4795
4799
  }
4796
4800
 
4801
+ // src/indexer/git-blame.ts
4802
+ var import_child_process2 = require("child_process");
4803
+ var path11 = __toESM(require("path"), 1);
4804
+ var import_util2 = require("util");
4805
+ var execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
4806
+ function parseGitBlamePorcelain(output) {
4807
+ const commits = /* @__PURE__ */ new Map();
4808
+ let current;
4809
+ for (const line of output.split("\n")) {
4810
+ if (/^[0-9a-f]{40} /.test(line)) {
4811
+ const sha = line.slice(0, 40);
4812
+ current = commits.get(sha) ?? {
4813
+ sha,
4814
+ author: "",
4815
+ authorEmail: "",
4816
+ committedAt: 0,
4817
+ summary: "",
4818
+ lines: 0
4819
+ };
4820
+ commits.set(sha, current);
4821
+ continue;
4822
+ }
4823
+ if (!current) {
4824
+ continue;
4825
+ }
4826
+ if (line.startsWith("author ")) {
4827
+ current.author = line.slice("author ".length);
4828
+ } else if (line.startsWith("author-mail ")) {
4829
+ current.authorEmail = line.slice("author-mail ".length).replace(/^<|>$/g, "");
4830
+ } else if (line.startsWith("author-time ")) {
4831
+ current.committedAt = Number.parseInt(line.slice("author-time ".length), 10);
4832
+ } else if (line.startsWith("summary ")) {
4833
+ current.summary = line.slice("summary ".length);
4834
+ } else if (line.startsWith(" ")) {
4835
+ current.lines += 1;
4836
+ }
4837
+ }
4838
+ return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
4839
+ }
4840
+ async function getChunkGitBlame(projectRoot, filePath, startLine, endLine) {
4841
+ const relativePath = path11.relative(projectRoot, filePath);
4842
+ try {
4843
+ const { stdout } = await execFileAsync2(
4844
+ "git",
4845
+ ["blame", "--line-porcelain", "-L", `${startLine},${endLine}`, "--", relativePath],
4846
+ { cwd: projectRoot, timeout: 3e4 }
4847
+ );
4848
+ return parseGitBlamePorcelain(stdout);
4849
+ } catch {
4850
+ return void 0;
4851
+ }
4852
+ }
4853
+
4797
4854
  // src/indexer/index.ts
4798
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
4855
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
4799
4856
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
4800
4857
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
4801
4858
  "function_declaration",
@@ -4875,6 +4932,45 @@ function isSqliteCorruptionError(error) {
4875
4932
  return message.includes("database disk image is malformed") || message.includes("file is not a database") || message.includes("database schema is corrupt") || message.includes("sqlite_corrupt");
4876
4933
  }
4877
4934
  var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
4935
+ function metadataFromBlame(blame) {
4936
+ if (!blame) {
4937
+ return {};
4938
+ }
4939
+ return {
4940
+ blameSha: blame.sha,
4941
+ blameAuthor: blame.author,
4942
+ blameAuthorEmail: blame.authorEmail,
4943
+ blameCommittedAt: blame.committedAt,
4944
+ blameSummary: blame.summary
4945
+ };
4946
+ }
4947
+ function blameFromChunkData(chunk) {
4948
+ if (!chunk?.blameSha || !chunk.blameAuthor || !chunk.blameAuthorEmail || chunk.blameCommittedAt === void 0 || !chunk.blameSummary) {
4949
+ return void 0;
4950
+ }
4951
+ return {
4952
+ sha: chunk.blameSha,
4953
+ author: chunk.blameAuthor,
4954
+ authorEmail: chunk.blameAuthorEmail,
4955
+ committedAt: chunk.blameCommittedAt,
4956
+ summary: chunk.blameSummary
4957
+ };
4958
+ }
4959
+ function blameFromMetadata(metadata) {
4960
+ if (!metadata.blameSha || !metadata.blameAuthor || !metadata.blameAuthorEmail || metadata.blameCommittedAt === void 0 || !metadata.blameSummary) {
4961
+ return void 0;
4962
+ }
4963
+ return {
4964
+ sha: metadata.blameSha,
4965
+ author: metadata.blameAuthor,
4966
+ authorEmail: metadata.blameAuthorEmail,
4967
+ committedAt: metadata.blameCommittedAt,
4968
+ summary: metadata.blameSummary
4969
+ };
4970
+ }
4971
+ function hasBlameMetadata(metadata) {
4972
+ return blameFromMetadata(metadata) !== void 0;
4973
+ }
4878
4974
  var INDEX_METADATA_VERSION = "1";
4879
4975
  var EMBEDDING_STRATEGY_VERSION = "2";
4880
4976
  var RANKING_TOKEN_CACHE_LIMIT = 4096;
@@ -5033,9 +5129,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
5033
5129
  return true;
5034
5130
  }
5035
5131
  function isPathWithinRoot(filePath, rootPath) {
5036
- const normalizedFilePath = path11.resolve(filePath);
5037
- const normalizedRoot = path11.resolve(rootPath);
5038
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path11.sep}`);
5132
+ const normalizedFilePath = path12.resolve(filePath);
5133
+ const normalizedRoot = path12.resolve(rootPath);
5134
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path12.sep}`);
5039
5135
  }
5040
5136
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
5041
5137
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -5794,7 +5890,8 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
5794
5890
  chunkType,
5795
5891
  name: chunk.name ?? void 0,
5796
5892
  language: chunk.language,
5797
- hash: chunk.contentHash
5893
+ hash: chunk.contentHash,
5894
+ ...metadataFromBlame(blameFromChunkData(chunk))
5798
5895
  };
5799
5896
  const baselineScore = existing?.score ?? 0.5;
5800
5897
  candidateUnion.set(chunk.chunkId, {
@@ -5878,7 +5975,8 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
5878
5975
  chunkType,
5879
5976
  name: chunk.name ?? void 0,
5880
5977
  language: chunk.language,
5881
- hash: chunk.contentHash
5978
+ hash: chunk.contentHash,
5979
+ ...metadataFromBlame(blameFromChunkData(chunk))
5882
5980
  }
5883
5981
  });
5884
5982
  }
@@ -6047,6 +6145,21 @@ function matchesSearchFilters(candidate, options, minScore) {
6047
6145
  if (options?.chunkType && candidate.metadata.chunkType !== options.chunkType) {
6048
6146
  return false;
6049
6147
  }
6148
+ if (options?.blameAuthor) {
6149
+ const author = options.blameAuthor.toLowerCase();
6150
+ const candidateAuthor = candidate.metadata.blameAuthor?.toLowerCase();
6151
+ const candidateEmail = candidate.metadata.blameAuthorEmail?.toLowerCase();
6152
+ if (candidateAuthor !== author && candidateEmail !== author) return false;
6153
+ }
6154
+ if (options?.blameSha && !candidate.metadata.blameSha?.toLowerCase().startsWith(options.blameSha.toLowerCase())) {
6155
+ return false;
6156
+ }
6157
+ if (options?.blameSince) {
6158
+ const sinceMs = Date.parse(options.blameSince);
6159
+ if (Number.isNaN(sinceMs)) return false;
6160
+ const committedAt = candidate.metadata.blameCommittedAt;
6161
+ if (committedAt === void 0 || committedAt < Math.floor(sinceMs / 1e3)) return false;
6162
+ }
6050
6163
  return true;
6051
6164
  }
6052
6165
  function unionCandidates(semanticCandidates, keywordCandidates) {
@@ -6090,9 +6203,9 @@ var Indexer = class {
6090
6203
  this.config = config;
6091
6204
  this.host = host;
6092
6205
  this.indexPath = this.getIndexPath();
6093
- this.fileHashCachePath = path11.join(this.indexPath, "file-hashes.json");
6094
- this.failedBatchesPath = path11.join(this.indexPath, "failed-batches.json");
6095
- this.indexingLockPath = path11.join(this.indexPath, "indexing.lock");
6206
+ this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
6207
+ this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
6208
+ this.indexingLockPath = path12.join(this.indexPath, "indexing.lock");
6096
6209
  this.logger = initializeLogger(config.debug);
6097
6210
  }
6098
6211
  getIndexPath() {
@@ -6124,14 +6237,14 @@ var Indexer = class {
6124
6237
  }
6125
6238
  atomicWriteSync(targetPath, data) {
6126
6239
  const tempPath = `${targetPath}.tmp`;
6127
- (0, import_fs7.mkdirSync)(path11.dirname(targetPath), { recursive: true });
6240
+ (0, import_fs7.mkdirSync)(path12.dirname(targetPath), { recursive: true });
6128
6241
  (0, import_fs7.writeFileSync)(tempPath, data);
6129
6242
  (0, import_fs7.renameSync)(tempPath, targetPath);
6130
6243
  }
6131
6244
  getScopedRoots() {
6132
- const roots = /* @__PURE__ */ new Set([path11.resolve(this.projectRoot)]);
6245
+ const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
6133
6246
  for (const kbRoot of this.config.knowledgeBases) {
6134
- roots.add(path11.resolve(this.projectRoot, kbRoot));
6247
+ roots.add(path12.resolve(this.projectRoot, kbRoot));
6135
6248
  }
6136
6249
  return Array.from(roots);
6137
6250
  }
@@ -6140,29 +6253,29 @@ var Indexer = class {
6140
6253
  if (this.config.scope !== "global") {
6141
6254
  return branchName;
6142
6255
  }
6143
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6256
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6144
6257
  return `${projectHash}:${branchName}`;
6145
6258
  }
6146
6259
  getBranchCatalogKeyFor(branchName) {
6147
6260
  if (this.config.scope !== "global") {
6148
6261
  return branchName;
6149
6262
  }
6150
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6263
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6151
6264
  return `${projectHash}:${branchName}`;
6152
6265
  }
6153
6266
  getLegacyBranchCatalogKey() {
6154
6267
  return this.currentBranch || "default";
6155
6268
  }
6156
6269
  getLegacyMigrationMetadataKey() {
6157
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6270
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6158
6271
  return `index.globalBranchMigration.${projectHash}`;
6159
6272
  }
6160
6273
  getProjectEmbeddingStrategyMetadataKey() {
6161
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6274
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6162
6275
  return `index.embeddingStrategyVersion.${projectHash}`;
6163
6276
  }
6164
6277
  getProjectForceReembedMetadataKey() {
6165
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6278
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6166
6279
  return `index.forceReembed.${projectHash}`;
6167
6280
  }
6168
6281
  hasProjectForceReembedPending() {
@@ -6256,7 +6369,7 @@ var Indexer = class {
6256
6369
  if (!this.database) {
6257
6370
  return { chunkIds, symbolIds };
6258
6371
  }
6259
- const projectRootPath = path11.resolve(this.projectRoot);
6372
+ const projectRootPath = path12.resolve(this.projectRoot);
6260
6373
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
6261
6374
  ...Array.from(this.fileHashCache.keys()).filter(
6262
6375
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -6279,7 +6392,7 @@ var Indexer = class {
6279
6392
  if (this.config.scope !== "global") {
6280
6393
  return this.getBranchCatalogCleanupKeys();
6281
6394
  }
6282
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6395
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6283
6396
  const keys = /* @__PURE__ */ new Set();
6284
6397
  const projectChunkIdSet = new Set(projectChunkIds);
6285
6398
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -6363,7 +6476,7 @@ var Indexer = class {
6363
6476
  if (!this.database || this.config.scope !== "global") {
6364
6477
  return false;
6365
6478
  }
6366
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6479
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6367
6480
  const roots = this.getScopedRoots();
6368
6481
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
6369
6482
  return this.database.getAllBranches().some(
@@ -6397,7 +6510,7 @@ var Indexer = class {
6397
6510
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
6398
6511
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
6399
6512
  ]);
6400
- const projectRootPath = path11.resolve(this.projectRoot);
6513
+ const projectRootPath = path12.resolve(this.projectRoot);
6401
6514
  const projectLocalFilePaths = new Set(
6402
6515
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
6403
6516
  );
@@ -6757,13 +6870,13 @@ var Indexer = class {
6757
6870
  }
6758
6871
  await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
6759
6872
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
6760
- const storePath = path11.join(this.indexPath, "vectors");
6873
+ const storePath = path12.join(this.indexPath, "vectors");
6761
6874
  this.store = new VectorStore(storePath, dimensions);
6762
- const indexFilePath = path11.join(this.indexPath, "vectors.usearch");
6875
+ const indexFilePath = path12.join(this.indexPath, "vectors.usearch");
6763
6876
  if ((0, import_fs7.existsSync)(indexFilePath)) {
6764
6877
  this.store.load();
6765
6878
  }
6766
- const invertedIndexPath = path11.join(this.indexPath, "inverted-index.json");
6879
+ const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
6767
6880
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6768
6881
  try {
6769
6882
  this.invertedIndex.load();
@@ -6773,7 +6886,7 @@ var Indexer = class {
6773
6886
  }
6774
6887
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6775
6888
  }
6776
- const dbPath = path11.join(this.indexPath, "codebase.db");
6889
+ const dbPath = path12.join(this.indexPath, "codebase.db");
6777
6890
  let dbIsNew = !(0, import_fs7.existsSync)(dbPath);
6778
6891
  try {
6779
6892
  this.database = new Database(dbPath);
@@ -6854,7 +6967,7 @@ var Indexer = class {
6854
6967
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
6855
6968
  return {
6856
6969
  resetCorruptedIndex: true,
6857
- warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
6970
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
6858
6971
  };
6859
6972
  }
6860
6973
  throw error;
@@ -6869,7 +6982,7 @@ var Indexer = class {
6869
6982
  return;
6870
6983
  }
6871
6984
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
6872
- const storeBasePath = path11.join(this.indexPath, "vectors");
6985
+ const storeBasePath = path12.join(this.indexPath, "vectors");
6873
6986
  const storeIndexPath = `${storeBasePath}.usearch`;
6874
6987
  const storeMetadataPath = `${storeBasePath}.meta.json`;
6875
6988
  const backupIndexPath = `${storeIndexPath}.bak`;
@@ -6954,7 +7067,7 @@ var Indexer = class {
6954
7067
  if (!isSqliteCorruptionError(error)) {
6955
7068
  return false;
6956
7069
  }
6957
- const dbPath = path11.join(this.indexPath, "codebase.db");
7070
+ const dbPath = path12.join(this.indexPath, "codebase.db");
6958
7071
  const warning = this.getCorruptedIndexWarning(dbPath);
6959
7072
  const errorMessage = getErrorMessage2(error);
6960
7073
  if (this.config.scope === "global") {
@@ -6977,15 +7090,15 @@ var Indexer = class {
6977
7090
  this.indexCompatibility = null;
6978
7091
  this.fileHashCache.clear();
6979
7092
  const resetPaths = [
6980
- path11.join(this.indexPath, "codebase.db"),
6981
- path11.join(this.indexPath, "codebase.db-shm"),
6982
- path11.join(this.indexPath, "codebase.db-wal"),
6983
- path11.join(this.indexPath, "vectors.usearch"),
6984
- path11.join(this.indexPath, "inverted-index.json"),
6985
- path11.join(this.indexPath, "file-hashes.json"),
6986
- path11.join(this.indexPath, "failed-batches.json"),
6987
- path11.join(this.indexPath, "indexing.lock"),
6988
- path11.join(this.indexPath, "vectors")
7093
+ path12.join(this.indexPath, "codebase.db"),
7094
+ path12.join(this.indexPath, "codebase.db-shm"),
7095
+ path12.join(this.indexPath, "codebase.db-wal"),
7096
+ path12.join(this.indexPath, "vectors.usearch"),
7097
+ path12.join(this.indexPath, "inverted-index.json"),
7098
+ path12.join(this.indexPath, "file-hashes.json"),
7099
+ path12.join(this.indexPath, "failed-batches.json"),
7100
+ path12.join(this.indexPath, "indexing.lock"),
7101
+ path12.join(this.indexPath, "vectors")
6989
7102
  ];
6990
7103
  await Promise.all(resetPaths.map(async (targetPath) => {
6991
7104
  try {
@@ -7222,6 +7335,7 @@ var Indexer = class {
7222
7335
  this.logger.debug("Parsed changed files", { parsedCount: parsedFiles.length, parseMs: parseMs.toFixed(2) });
7223
7336
  const existingChunks = /* @__PURE__ */ new Map();
7224
7337
  const existingChunksByFile = /* @__PURE__ */ new Map();
7338
+ const existingMetadataById = /* @__PURE__ */ new Map();
7225
7339
  for (const { key, metadata } of store.getAllMetadata()) {
7226
7340
  if (scopedRoots && !this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
7227
7341
  continue;
@@ -7230,6 +7344,7 @@ var Indexer = class {
7230
7344
  continue;
7231
7345
  }
7232
7346
  existingChunks.set(key, metadata.hash);
7347
+ existingMetadataById.set(key, metadata);
7233
7348
  const fileChunks = existingChunksByFile.get(metadata.filePath) || /* @__PURE__ */ new Set();
7234
7349
  fileChunks.add(key);
7235
7350
  existingChunksByFile.set(metadata.filePath, fileChunks);
@@ -7237,6 +7352,8 @@ var Indexer = class {
7237
7352
  const currentChunkIds = /* @__PURE__ */ new Set();
7238
7353
  const currentFilePaths = /* @__PURE__ */ new Set();
7239
7354
  const pendingChunks = [];
7355
+ const gitBlameEnabled = this.config.indexing.gitBlame.enabled && isGitRepo(this.projectRoot);
7356
+ let backfilledBlameMetadata = false;
7240
7357
  for (const filePath of unchangedFilePaths) {
7241
7358
  currentFilePaths.add(filePath);
7242
7359
  const fileChunks = existingChunksByFile.get(filePath);
@@ -7247,10 +7364,52 @@ var Indexer = class {
7247
7364
  }
7248
7365
  }
7249
7366
  const chunkDataBatch = [];
7367
+ if (gitBlameEnabled) {
7368
+ const backfillItems = [];
7369
+ for (const chunkId of currentChunkIds) {
7370
+ const metadata = existingMetadataById.get(chunkId);
7371
+ if (!metadata || hasBlameMetadata(metadata)) {
7372
+ continue;
7373
+ }
7374
+ const chunk = database.getChunk(chunkId);
7375
+ if (!chunk) {
7376
+ continue;
7377
+ }
7378
+ const blame = await getChunkGitBlame(this.projectRoot, chunk.filePath, chunk.startLine, chunk.endLine);
7379
+ const blameMetadata = metadataFromBlame(blame);
7380
+ if (!blameMetadata.blameSha) {
7381
+ continue;
7382
+ }
7383
+ chunkDataBatch.push({
7384
+ ...chunk,
7385
+ blameSha: blameMetadata.blameSha,
7386
+ blameAuthor: blameMetadata.blameAuthor,
7387
+ blameAuthorEmail: blameMetadata.blameAuthorEmail,
7388
+ blameCommittedAt: blameMetadata.blameCommittedAt,
7389
+ blameSummary: blameMetadata.blameSummary
7390
+ });
7391
+ const embeddingBuffer = database.getEmbedding(chunk.contentHash);
7392
+ if (!embeddingBuffer) {
7393
+ continue;
7394
+ }
7395
+ backfillItems.push({
7396
+ id: chunkId,
7397
+ vector: Array.from(bufferToFloat32Array(embeddingBuffer)),
7398
+ metadata: {
7399
+ ...metadata,
7400
+ ...blameMetadata
7401
+ }
7402
+ });
7403
+ }
7404
+ if (backfillItems.length > 0) {
7405
+ store.addBatch(backfillItems);
7406
+ backfilledBlameMetadata = true;
7407
+ }
7408
+ }
7250
7409
  for (const parsed of parsedFiles) {
7251
7410
  currentFilePaths.add(parsed.path);
7252
7411
  if (parsed.chunks.length === 0) {
7253
- const relativePath = path11.relative(this.projectRoot, parsed.path);
7412
+ const relativePath = path12.relative(this.projectRoot, parsed.path);
7254
7413
  stats.parseFailures.push(relativePath);
7255
7414
  }
7256
7415
  let fileChunkCount = 0;
@@ -7271,6 +7430,10 @@ var Indexer = class {
7271
7430
  }
7272
7431
  const id = generateChunkId(parsed.path, chunk);
7273
7432
  const contentHash = generateChunkHash(chunk);
7433
+ const existingContentHash = existingChunks.get(id);
7434
+ const existingChunk = gitBlameEnabled ? database.getChunk(id) : null;
7435
+ const blame = gitBlameEnabled && existingContentHash !== contentHash ? await getChunkGitBlame(this.projectRoot, parsed.path, chunk.startLine, chunk.endLine) : blameFromChunkData(existingChunk);
7436
+ const blameMetadata = metadataFromBlame(blame);
7274
7437
  currentChunkIds.add(id);
7275
7438
  chunkDataBatch.push({
7276
7439
  chunkId: id,
@@ -7280,9 +7443,14 @@ var Indexer = class {
7280
7443
  endLine: chunk.endLine,
7281
7444
  nodeType: chunk.chunkType,
7282
7445
  name: chunk.name,
7283
- language: chunk.language
7446
+ language: chunk.language,
7447
+ blameSha: blameMetadata.blameSha,
7448
+ blameAuthor: blameMetadata.blameAuthor,
7449
+ blameAuthorEmail: blameMetadata.blameAuthorEmail,
7450
+ blameCommittedAt: blameMetadata.blameCommittedAt,
7451
+ blameSummary: blameMetadata.blameSummary
7284
7452
  });
7285
- if (existingChunks.get(id) === contentHash) {
7453
+ if (existingContentHash === contentHash) {
7286
7454
  fileChunkCount++;
7287
7455
  continue;
7288
7456
  }
@@ -7297,7 +7465,8 @@ var Indexer = class {
7297
7465
  chunkType: chunk.chunkType,
7298
7466
  name: chunk.name,
7299
7467
  language: chunk.language,
7300
- hash: contentHash
7468
+ hash: contentHash,
7469
+ ...blameMetadata
7301
7470
  };
7302
7471
  pendingChunks.push({
7303
7472
  id,
@@ -7440,6 +7609,9 @@ var Indexer = class {
7440
7609
  database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
7441
7610
  database.clearBranchSymbols(branchCatalogKey);
7442
7611
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7612
+ if (backfilledBlameMetadata) {
7613
+ store.save();
7614
+ }
7443
7615
  if (scopedRoots) {
7444
7616
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7445
7617
  this.clearScopedFailedBatches(scopedRoots);
@@ -8003,7 +8175,8 @@ var Indexer = class {
8003
8175
  content,
8004
8176
  score: r.score,
8005
8177
  chunkType: r.metadata.chunkType,
8006
- name: r.metadata.name
8178
+ name: r.metadata.name,
8179
+ blame: blameFromMetadata(r.metadata)
8007
8180
  };
8008
8181
  })
8009
8182
  );
@@ -8095,12 +8268,12 @@ var Indexer = class {
8095
8268
  this.indexCompatibility = compatibility;
8096
8269
  return;
8097
8270
  }
8098
- const localProjectIndexPaths = [path11.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
8271
+ const localProjectIndexPaths = [path12.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
8099
8272
  if (this.host !== "opencode") {
8100
- localProjectIndexPaths.push(path11.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
8273
+ localProjectIndexPaths.push(path12.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
8101
8274
  }
8102
8275
  const isLocalProjectIndex = localProjectIndexPaths.some(
8103
- (localPath) => path11.resolve(this.indexPath) === path11.resolve(localPath)
8276
+ (localPath) => path12.resolve(this.indexPath) === path12.resolve(localPath)
8104
8277
  );
8105
8278
  if (!isLocalProjectIndex) {
8106
8279
  throw new Error(
@@ -8190,7 +8363,7 @@ var Indexer = class {
8190
8363
  gcOrphanSymbols: 0,
8191
8364
  gcOrphanCallEdges: 0,
8192
8365
  resetCorruptedIndex: true,
8193
- warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
8366
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
8194
8367
  };
8195
8368
  }
8196
8369
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -8510,7 +8683,8 @@ var Indexer = class {
8510
8683
  content,
8511
8684
  score: r.score,
8512
8685
  chunkType: r.metadata.chunkType,
8513
- name: r.metadata.name
8686
+ name: r.metadata.name,
8687
+ blame: blameFromMetadata(r.metadata)
8514
8688
  };
8515
8689
  })
8516
8690
  );
@@ -8547,9 +8721,9 @@ var Indexer = class {
8547
8721
  const { database } = await this.ensureInitialized();
8548
8722
  let shortest = [];
8549
8723
  for (const branchKey of this.getBranchCatalogKeys()) {
8550
- const path22 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
8551
- if (path22.length > 0 && (shortest.length === 0 || path22.length < shortest.length)) {
8552
- shortest = path22;
8724
+ const path23 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
8725
+ if (path23.length > 0 && (shortest.length === 0 || path23.length < shortest.length)) {
8726
+ shortest = path23;
8553
8727
  }
8554
8728
  }
8555
8729
  return shortest;
@@ -8581,7 +8755,7 @@ var Indexer = class {
8581
8755
  }
8582
8756
  async getPrImpact(opts) {
8583
8757
  const { database } = await this.ensureInitialized();
8584
- const execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
8758
+ const execFileAsync3 = (0, import_util3.promisify)(import_child_process3.execFile);
8585
8759
  const changedFilesResult = await getChangedFiles({
8586
8760
  pr: opts.pr,
8587
8761
  branch: opts.branch,
@@ -8603,7 +8777,7 @@ var Indexer = class {
8603
8777
  "Run index_codebase first to build the call graph and symbol index for this branch."
8604
8778
  );
8605
8779
  }
8606
- const absoluteChangedFiles = changedFiles.map((f) => path11.resolve(this.projectRoot, f));
8780
+ const absoluteChangedFiles = changedFiles.map((f) => path12.resolve(this.projectRoot, f));
8607
8781
  const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
8608
8782
  const directIds = directSymbols.map((s) => s.id);
8609
8783
  const direction = opts.direction ?? "both";
@@ -8665,7 +8839,7 @@ var Indexer = class {
8665
8839
  if (opts.checkConflicts) {
8666
8840
  conflictingPRs = [];
8667
8841
  try {
8668
- const { stdout } = await execFileAsync2(
8842
+ const { stdout } = await execFileAsync3(
8669
8843
  "gh",
8670
8844
  ["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
8671
8845
  { cwd: this.projectRoot, timeout: 3e4 }
@@ -8686,7 +8860,7 @@ var Indexer = class {
8686
8860
  projectRoot: this.projectRoot,
8687
8861
  baseBranch: this.baseBranch
8688
8862
  });
8689
- const otherAbsolute = otherChanged.files.map((f) => path11.resolve(this.projectRoot, f));
8863
+ const otherAbsolute = otherChanged.files.map((f) => path12.resolve(this.projectRoot, f));
8690
8864
  const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
8691
8865
  const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
8692
8866
  const otherLabels = /* @__PURE__ */ new Set();
@@ -8749,12 +8923,12 @@ var Indexer = class {
8749
8923
  if (meta.filePath) filePaths.add(meta.filePath);
8750
8924
  }
8751
8925
  const directory = options?.directory?.replace(/\/$/, "");
8752
- const absoluteDirectoryFilter = directory ? path11.resolve(this.projectRoot, directory) : void 0;
8926
+ const absoluteDirectoryFilter = directory ? path12.resolve(this.projectRoot, directory) : void 0;
8753
8927
  for (const filePath of filePaths) {
8754
8928
  if (directory) {
8755
- const absoluteFilePath = path11.resolve(filePath);
8929
+ const absoluteFilePath = path12.resolve(filePath);
8756
8930
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
8757
- const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path11.sep));
8931
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path12.sep));
8758
8932
  if (!matchesRelative && !matchesProjectRelative) {
8759
8933
  continue;
8760
8934
  }
@@ -8786,43 +8960,43 @@ var Indexer = class {
8786
8960
  };
8787
8961
 
8788
8962
  // src/tools/knowledge-base-paths.ts
8789
- var path12 = __toESM(require("path"), 1);
8963
+ var path13 = __toESM(require("path"), 1);
8790
8964
  function resolveConfigPathValue(value, baseDir) {
8791
8965
  const trimmed = value.trim();
8792
8966
  if (!trimmed) {
8793
8967
  return trimmed;
8794
8968
  }
8795
- const absolutePath = path12.isAbsolute(trimmed) ? trimmed : path12.resolve(baseDir, trimmed);
8796
- return path12.normalize(absolutePath);
8969
+ const absolutePath = path13.isAbsolute(trimmed) ? trimmed : path13.resolve(baseDir, trimmed);
8970
+ return path13.normalize(absolutePath);
8797
8971
  }
8798
8972
  function serializeConfigPathValue(value, baseDir) {
8799
8973
  const trimmed = value.trim();
8800
8974
  if (!trimmed) {
8801
8975
  return trimmed;
8802
8976
  }
8803
- if (!path12.isAbsolute(trimmed)) {
8804
- return normalizePathSeparators(path12.normalize(trimmed));
8977
+ if (!path13.isAbsolute(trimmed)) {
8978
+ return normalizePathSeparators(path13.normalize(trimmed));
8805
8979
  }
8806
- const relativePath = path12.relative(baseDir, trimmed);
8807
- if (!relativePath || !relativePath.startsWith("..") && !path12.isAbsolute(relativePath)) {
8808
- return normalizePathSeparators(path12.normalize(relativePath || "."));
8980
+ const relativePath = path13.relative(baseDir, trimmed);
8981
+ if (!relativePath || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath)) {
8982
+ return normalizePathSeparators(path13.normalize(relativePath || "."));
8809
8983
  }
8810
- return path12.normalize(trimmed);
8984
+ return path13.normalize(trimmed);
8811
8985
  }
8812
8986
  function resolveKnowledgeBasePath(value, projectRoot) {
8813
- return path12.isAbsolute(value) ? value : path12.resolve(projectRoot, value);
8987
+ return path13.isAbsolute(value) ? value : path13.resolve(projectRoot, value);
8814
8988
  }
8815
8989
  function normalizeKnowledgeBasePath2(value, projectRoot) {
8816
- return path12.normalize(resolveKnowledgeBasePath(value, projectRoot));
8990
+ return path13.normalize(resolveKnowledgeBasePath(value, projectRoot));
8817
8991
  }
8818
8992
  function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot) {
8819
- const normalizedInput = path12.normalize(inputPath);
8993
+ const normalizedInput = path13.normalize(inputPath);
8820
8994
  return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput);
8821
8995
  }
8822
8996
  function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
8823
- const normalizedInput = path12.normalize(inputPath);
8997
+ const normalizedInput = path13.normalize(inputPath);
8824
8998
  return knowledgeBases.findIndex(
8825
- (kb) => path12.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
8999
+ (kb) => path13.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
8826
9000
  );
8827
9001
  }
8828
9002
 
@@ -8974,7 +9148,7 @@ function formatCodebasePeek(results) {
8974
9148
  const formatted = results.map((r, idx) => {
8975
9149
  const location = `${r.filePath}:${r.startLine}-${r.endLine}`;
8976
9150
  const name = r.name ? `"${r.name}"` : "(anonymous)";
8977
- return `[${idx + 1}] ${r.chunkType} ${name} at ${location} (score: ${r.score.toFixed(2)})`;
9151
+ return `[${idx + 1}] ${r.chunkType} ${name} at ${location} (score: ${r.score.toFixed(2)})${formatBlame(r)}`;
8978
9152
  });
8979
9153
  return formatted.join("\n");
8980
9154
  }
@@ -9006,16 +9180,57 @@ function formatHealthCheck(result) {
9006
9180
  }
9007
9181
  return lines.join("\n");
9008
9182
  }
9183
+ function formatCallGraphCallers(name, callers, relationshipType) {
9184
+ if (callers.length === 0) {
9185
+ return `No callers found for "${name}"${relationshipType ? ` with type ${relationshipType}` : ""}. It may not be called by any tracked function, or the index needs updating.`;
9186
+ }
9187
+ const formatted = callers.map((edge, index) => {
9188
+ const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
9189
+ 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]"}`;
9190
+ });
9191
+ return `"${name}" is called by ${callers.length} function(s):
9192
+
9193
+ ${formatted.join("\n")}`;
9194
+ }
9195
+ function formatCallGraphCallees(symbolId, callees, relationshipType) {
9196
+ if (callees.length === 0) {
9197
+ return `No callees found for symbol ${symbolId}${relationshipType ? ` with type ${relationshipType}` : ""}. The function may not call any other tracked functions.`;
9198
+ }
9199
+ return callees.map((edge, index) => {
9200
+ const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
9201
+ return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
9202
+ }).join("\n");
9203
+ }
9204
+ function formatCallGraphPath(from, to, path23) {
9205
+ if (path23.length === 0) {
9206
+ return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
9207
+ }
9208
+ const formatted = path23.map((hop, index) => {
9209
+ const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
9210
+ const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
9211
+ return `${prefix} ${hop.symbolName}${location}`;
9212
+ });
9213
+ return `Path (${path23.length} hops):
9214
+ ${formatted.join("\n")}`;
9215
+ }
9009
9216
  function formatResultHeader(result, index) {
9010
9217
  return result.name ? `[${index + 1}] ${result.chunkType} "${result.name}" in ${result.filePath}:${result.startLine}-${result.endLine}` : `[${index + 1}] ${result.chunkType} in ${result.filePath}:${result.startLine}-${result.endLine}`;
9011
9218
  }
9219
+ function formatBlame(result) {
9220
+ if (!result.blame) {
9221
+ return "";
9222
+ }
9223
+ const date = new Date(result.blame.committedAt * 1e3).toISOString().slice(0, 10);
9224
+ return `
9225
+ ${result.blame.sha.slice(0, 7)} | ${result.blame.author} | ${date} | ${result.blame.summary}`;
9226
+ }
9012
9227
  function formatDefinitionLookup(results, query) {
9013
9228
  if (results.length === 0) {
9014
9229
  return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
9015
9230
  }
9016
9231
  const formatted = results.map((r, idx) => {
9017
9232
  const header = formatResultHeader(r, idx);
9018
- return `${header} (score: ${r.score.toFixed(2)})
9233
+ return `${header} (score: ${r.score.toFixed(2)})${formatBlame(r)}
9019
9234
  \`\`\`
9020
9235
  ${truncateContent(r.content)}
9021
9236
  \`\`\``;
@@ -9026,7 +9241,7 @@ function formatSearchResults(results, scoreFormat = "similarity") {
9026
9241
  const formatted = results.map((r, idx) => {
9027
9242
  const header = formatResultHeader(r, idx);
9028
9243
  const scoreLabel = scoreFormat === "similarity" ? `(similarity: ${(r.score * 100).toFixed(1)}%)` : `(score: ${r.score.toFixed(2)})`;
9029
- return `${header} ${scoreLabel}
9244
+ return `${header} ${scoreLabel}${formatBlame(r)}
9030
9245
  \`\`\`
9031
9246
  ${truncateContent(r.content)}
9032
9247
  \`\`\``;
@@ -9036,7 +9251,7 @@ ${truncateContent(r.content)}
9036
9251
 
9037
9252
  // src/tools/config-state.ts
9038
9253
  var import_fs8 = require("fs");
9039
- var path13 = __toESM(require("path"), 1);
9254
+ var path14 = __toESM(require("path"), 1);
9040
9255
  function normalizeKnowledgeBasePaths(config, projectRoot) {
9041
9256
  const normalized = { ...config };
9042
9257
  if (Array.isArray(normalized.knowledgeBases)) {
@@ -9063,8 +9278,8 @@ function loadEditableConfig(projectRoot, host = "opencode") {
9063
9278
  }
9064
9279
  function saveConfig(projectRoot, config, host = "opencode") {
9065
9280
  const configPath = getConfigPath(projectRoot, host);
9066
- const configDir = path13.dirname(configPath);
9067
- const configBaseDir = path13.dirname(configDir);
9281
+ const configDir = path14.dirname(configPath);
9282
+ const configBaseDir = path14.dirname(configDir);
9068
9283
  if (!(0, import_fs8.existsSync)(configDir)) {
9069
9284
  (0, import_fs8.mkdirSync)(configDir, { recursive: true });
9070
9285
  }
@@ -9125,7 +9340,7 @@ function refreshIndexerForDirectory(projectRoot, host = "opencode", config = par
9125
9340
  }
9126
9341
  function shouldForceLocalizeProjectIndex(projectRoot, host = "opencode") {
9127
9342
  const root = getProjectRoot(projectRoot, host);
9128
- const localIndexPath = path14.join(root, getHostProjectIndexRelativePath(host));
9343
+ const localIndexPath = path15.join(root, getHostProjectIndexRelativePath(host));
9129
9344
  if ((0, import_fs9.existsSync)(localIndexPath)) {
9130
9345
  return false;
9131
9346
  }
@@ -9140,7 +9355,10 @@ async function searchCodebase(projectRoot, host, query, options = {}) {
9140
9355
  chunkType: options.chunkType,
9141
9356
  contextLines: options.contextLines,
9142
9357
  metadataOnly: options.metadataOnly,
9143
- definitionIntent: options.definitionIntent
9358
+ definitionIntent: options.definitionIntent,
9359
+ blameAuthor: options.blameAuthor,
9360
+ blameSha: options.blameSha,
9361
+ blameSince: options.blameSince
9144
9362
  });
9145
9363
  }
9146
9364
  async function findSimilarCode(projectRoot, host, code, options = {}) {
@@ -9272,8 +9490,8 @@ async function getIndexLogs(projectRoot, host, args) {
9272
9490
  function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
9273
9491
  const root = getProjectRoot(projectRoot, host);
9274
9492
  const inputPath = knowledgeBasePath.trim();
9275
- const normalizedPath = path14.resolve(
9276
- path14.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
9493
+ const normalizedPath = path15.resolve(
9494
+ path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
9277
9495
  );
9278
9496
  if (!(0, import_fs9.existsSync)(normalizedPath)) {
9279
9497
  return `Error: Directory does not exist: ${normalizedPath}`;
@@ -9309,7 +9527,7 @@ function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
9309
9527
  }
9310
9528
  }
9311
9529
  for (const dotDir of sensitiveDotDirs) {
9312
- const sensitiveDir = path14.join(homeDir, dotDir);
9530
+ const sensitiveDir = path15.join(homeDir, dotDir);
9313
9531
  if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
9314
9532
  return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
9315
9533
  }
@@ -9372,7 +9590,7 @@ function listKnowledgeBases(projectRoot, host) {
9372
9590
  }
9373
9591
  result += "\n";
9374
9592
  }
9375
- const hasHostConfig = (0, import_fs9.existsSync)(path14.join(root, getHostProjectConfigRelativePath(host)));
9593
+ const hasHostConfig = (0, import_fs9.existsSync)(path15.join(root, getHostProjectConfigRelativePath(host)));
9376
9594
  if (hasHostConfig) {
9377
9595
  result += `
9378
9596
  Config sources: 1 file(s).`;
@@ -9495,7 +9713,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
9495
9713
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
9496
9714
  const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
9497
9715
  if (wantBigintFsStats) {
9498
- this._stat = (path22) => statMethod(path22, { bigint: true });
9716
+ this._stat = (path23) => statMethod(path23, { bigint: true });
9499
9717
  } else {
9500
9718
  this._stat = statMethod;
9501
9719
  }
@@ -9520,8 +9738,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
9520
9738
  const par = this.parent;
9521
9739
  const fil = par && par.files;
9522
9740
  if (fil && fil.length > 0) {
9523
- const { path: path22, depth } = par;
9524
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path22));
9741
+ const { path: path23, depth } = par;
9742
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path23));
9525
9743
  const awaited = await Promise.all(slice);
9526
9744
  for (const entry of awaited) {
9527
9745
  if (!entry)
@@ -9561,20 +9779,20 @@ var ReaddirpStream = class extends import_node_stream.Readable {
9561
9779
  this.reading = false;
9562
9780
  }
9563
9781
  }
9564
- async _exploreDir(path22, depth) {
9782
+ async _exploreDir(path23, depth) {
9565
9783
  let files;
9566
9784
  try {
9567
- files = await (0, import_promises.readdir)(path22, this._rdOptions);
9785
+ files = await (0, import_promises.readdir)(path23, this._rdOptions);
9568
9786
  } catch (error) {
9569
9787
  this._onError(error);
9570
9788
  }
9571
- return { files, depth, path: path22 };
9789
+ return { files, depth, path: path23 };
9572
9790
  }
9573
- async _formatEntry(dirent, path22) {
9791
+ async _formatEntry(dirent, path23) {
9574
9792
  let entry;
9575
9793
  const basename5 = this._isDirent ? dirent.name : dirent;
9576
9794
  try {
9577
- const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path22, basename5));
9795
+ const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path23, basename5));
9578
9796
  entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename5 };
9579
9797
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
9580
9798
  } catch (err) {
@@ -9974,16 +10192,16 @@ var delFromSet = (main, prop, item) => {
9974
10192
  };
9975
10193
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
9976
10194
  var FsWatchInstances = /* @__PURE__ */ new Map();
9977
- function createFsWatchInstance(path22, options, listener, errHandler, emitRaw) {
10195
+ function createFsWatchInstance(path23, options, listener, errHandler, emitRaw) {
9978
10196
  const handleEvent = (rawEvent, evPath) => {
9979
- listener(path22);
9980
- emitRaw(rawEvent, evPath, { watchedPath: path22 });
9981
- if (evPath && path22 !== evPath) {
9982
- fsWatchBroadcast(sp.resolve(path22, evPath), KEY_LISTENERS, sp.join(path22, evPath));
10197
+ listener(path23);
10198
+ emitRaw(rawEvent, evPath, { watchedPath: path23 });
10199
+ if (evPath && path23 !== evPath) {
10200
+ fsWatchBroadcast(sp.resolve(path23, evPath), KEY_LISTENERS, sp.join(path23, evPath));
9983
10201
  }
9984
10202
  };
9985
10203
  try {
9986
- return (0, import_node_fs.watch)(path22, {
10204
+ return (0, import_node_fs.watch)(path23, {
9987
10205
  persistent: options.persistent
9988
10206
  }, handleEvent);
9989
10207
  } catch (error) {
@@ -9999,12 +10217,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
9999
10217
  listener(val1, val2, val3);
10000
10218
  });
10001
10219
  };
10002
- var setFsWatchListener = (path22, fullPath, options, handlers) => {
10220
+ var setFsWatchListener = (path23, fullPath, options, handlers) => {
10003
10221
  const { listener, errHandler, rawEmitter } = handlers;
10004
10222
  let cont = FsWatchInstances.get(fullPath);
10005
10223
  let watcher;
10006
10224
  if (!options.persistent) {
10007
- watcher = createFsWatchInstance(path22, options, listener, errHandler, rawEmitter);
10225
+ watcher = createFsWatchInstance(path23, options, listener, errHandler, rawEmitter);
10008
10226
  if (!watcher)
10009
10227
  return;
10010
10228
  return watcher.close.bind(watcher);
@@ -10015,7 +10233,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
10015
10233
  addAndConvert(cont, KEY_RAW, rawEmitter);
10016
10234
  } else {
10017
10235
  watcher = createFsWatchInstance(
10018
- path22,
10236
+ path23,
10019
10237
  options,
10020
10238
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
10021
10239
  errHandler,
@@ -10030,7 +10248,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
10030
10248
  cont.watcherUnusable = true;
10031
10249
  if (isWindows && error.code === "EPERM") {
10032
10250
  try {
10033
- const fd = await (0, import_promises2.open)(path22, "r");
10251
+ const fd = await (0, import_promises2.open)(path23, "r");
10034
10252
  await fd.close();
10035
10253
  broadcastErr(error);
10036
10254
  } catch (err) {
@@ -10061,7 +10279,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
10061
10279
  };
10062
10280
  };
10063
10281
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
10064
- var setFsWatchFileListener = (path22, fullPath, options, handlers) => {
10282
+ var setFsWatchFileListener = (path23, fullPath, options, handlers) => {
10065
10283
  const { listener, rawEmitter } = handlers;
10066
10284
  let cont = FsWatchFileInstances.get(fullPath);
10067
10285
  const copts = cont && cont.options;
@@ -10083,7 +10301,7 @@ var setFsWatchFileListener = (path22, fullPath, options, handlers) => {
10083
10301
  });
10084
10302
  const currmtime = curr.mtimeMs;
10085
10303
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
10086
- foreach(cont.listeners, (listener2) => listener2(path22, curr));
10304
+ foreach(cont.listeners, (listener2) => listener2(path23, curr));
10087
10305
  }
10088
10306
  })
10089
10307
  };
@@ -10113,13 +10331,13 @@ var NodeFsHandler = class {
10113
10331
  * @param listener on fs change
10114
10332
  * @returns closer for the watcher instance
10115
10333
  */
10116
- _watchWithNodeFs(path22, listener) {
10334
+ _watchWithNodeFs(path23, listener) {
10117
10335
  const opts = this.fsw.options;
10118
- const directory = sp.dirname(path22);
10119
- const basename5 = sp.basename(path22);
10336
+ const directory = sp.dirname(path23);
10337
+ const basename5 = sp.basename(path23);
10120
10338
  const parent = this.fsw._getWatchedDir(directory);
10121
10339
  parent.add(basename5);
10122
- const absolutePath = sp.resolve(path22);
10340
+ const absolutePath = sp.resolve(path23);
10123
10341
  const options = {
10124
10342
  persistent: opts.persistent
10125
10343
  };
@@ -10129,12 +10347,12 @@ var NodeFsHandler = class {
10129
10347
  if (opts.usePolling) {
10130
10348
  const enableBin = opts.interval !== opts.binaryInterval;
10131
10349
  options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
10132
- closer = setFsWatchFileListener(path22, absolutePath, options, {
10350
+ closer = setFsWatchFileListener(path23, absolutePath, options, {
10133
10351
  listener,
10134
10352
  rawEmitter: this.fsw._emitRaw
10135
10353
  });
10136
10354
  } else {
10137
- closer = setFsWatchListener(path22, absolutePath, options, {
10355
+ closer = setFsWatchListener(path23, absolutePath, options, {
10138
10356
  listener,
10139
10357
  errHandler: this._boundHandleError,
10140
10358
  rawEmitter: this.fsw._emitRaw
@@ -10156,7 +10374,7 @@ var NodeFsHandler = class {
10156
10374
  let prevStats = stats;
10157
10375
  if (parent.has(basename5))
10158
10376
  return;
10159
- const listener = async (path22, newStats) => {
10377
+ const listener = async (path23, newStats) => {
10160
10378
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
10161
10379
  return;
10162
10380
  if (!newStats || newStats.mtimeMs === 0) {
@@ -10170,11 +10388,11 @@ var NodeFsHandler = class {
10170
10388
  this.fsw._emit(EV.CHANGE, file, newStats2);
10171
10389
  }
10172
10390
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
10173
- this.fsw._closeFile(path22);
10391
+ this.fsw._closeFile(path23);
10174
10392
  prevStats = newStats2;
10175
10393
  const closer2 = this._watchWithNodeFs(file, listener);
10176
10394
  if (closer2)
10177
- this.fsw._addPathCloser(path22, closer2);
10395
+ this.fsw._addPathCloser(path23, closer2);
10178
10396
  } else {
10179
10397
  prevStats = newStats2;
10180
10398
  }
@@ -10206,7 +10424,7 @@ var NodeFsHandler = class {
10206
10424
  * @param item basename of this item
10207
10425
  * @returns true if no more processing is needed for this entry.
10208
10426
  */
10209
- async _handleSymlink(entry, directory, path22, item) {
10427
+ async _handleSymlink(entry, directory, path23, item) {
10210
10428
  if (this.fsw.closed) {
10211
10429
  return;
10212
10430
  }
@@ -10216,7 +10434,7 @@ var NodeFsHandler = class {
10216
10434
  this.fsw._incrReadyCount();
10217
10435
  let linkPath;
10218
10436
  try {
10219
- linkPath = await (0, import_promises2.realpath)(path22);
10437
+ linkPath = await (0, import_promises2.realpath)(path23);
10220
10438
  } catch (e) {
10221
10439
  this.fsw._emitReady();
10222
10440
  return true;
@@ -10226,12 +10444,12 @@ var NodeFsHandler = class {
10226
10444
  if (dir.has(item)) {
10227
10445
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
10228
10446
  this.fsw._symlinkPaths.set(full, linkPath);
10229
- this.fsw._emit(EV.CHANGE, path22, entry.stats);
10447
+ this.fsw._emit(EV.CHANGE, path23, entry.stats);
10230
10448
  }
10231
10449
  } else {
10232
10450
  dir.add(item);
10233
10451
  this.fsw._symlinkPaths.set(full, linkPath);
10234
- this.fsw._emit(EV.ADD, path22, entry.stats);
10452
+ this.fsw._emit(EV.ADD, path23, entry.stats);
10235
10453
  }
10236
10454
  this.fsw._emitReady();
10237
10455
  return true;
@@ -10261,9 +10479,9 @@ var NodeFsHandler = class {
10261
10479
  return;
10262
10480
  }
10263
10481
  const item = entry.path;
10264
- let path22 = sp.join(directory, item);
10482
+ let path23 = sp.join(directory, item);
10265
10483
  current.add(item);
10266
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path22, item)) {
10484
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path23, item)) {
10267
10485
  return;
10268
10486
  }
10269
10487
  if (this.fsw.closed) {
@@ -10272,8 +10490,8 @@ var NodeFsHandler = class {
10272
10490
  }
10273
10491
  if (item === target || !target && !previous.has(item)) {
10274
10492
  this.fsw._incrReadyCount();
10275
- path22 = sp.join(dir, sp.relative(dir, path22));
10276
- this._addToNodeFs(path22, initialAdd, wh, depth + 1);
10493
+ path23 = sp.join(dir, sp.relative(dir, path23));
10494
+ this._addToNodeFs(path23, initialAdd, wh, depth + 1);
10277
10495
  }
10278
10496
  }).on(EV.ERROR, this._boundHandleError);
10279
10497
  return new Promise((resolve13, reject) => {
@@ -10342,13 +10560,13 @@ var NodeFsHandler = class {
10342
10560
  * @param depth Child path actually targeted for watch
10343
10561
  * @param target Child path actually targeted for watch
10344
10562
  */
10345
- async _addToNodeFs(path22, initialAdd, priorWh, depth, target) {
10563
+ async _addToNodeFs(path23, initialAdd, priorWh, depth, target) {
10346
10564
  const ready = this.fsw._emitReady;
10347
- if (this.fsw._isIgnored(path22) || this.fsw.closed) {
10565
+ if (this.fsw._isIgnored(path23) || this.fsw.closed) {
10348
10566
  ready();
10349
10567
  return false;
10350
10568
  }
10351
- const wh = this.fsw._getWatchHelpers(path22);
10569
+ const wh = this.fsw._getWatchHelpers(path23);
10352
10570
  if (priorWh) {
10353
10571
  wh.filterPath = (entry) => priorWh.filterPath(entry);
10354
10572
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -10364,8 +10582,8 @@ var NodeFsHandler = class {
10364
10582
  const follow = this.fsw.options.followSymlinks;
10365
10583
  let closer;
10366
10584
  if (stats.isDirectory()) {
10367
- const absPath = sp.resolve(path22);
10368
- const targetPath = follow ? await (0, import_promises2.realpath)(path22) : path22;
10585
+ const absPath = sp.resolve(path23);
10586
+ const targetPath = follow ? await (0, import_promises2.realpath)(path23) : path23;
10369
10587
  if (this.fsw.closed)
10370
10588
  return;
10371
10589
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -10375,29 +10593,29 @@ var NodeFsHandler = class {
10375
10593
  this.fsw._symlinkPaths.set(absPath, targetPath);
10376
10594
  }
10377
10595
  } else if (stats.isSymbolicLink()) {
10378
- const targetPath = follow ? await (0, import_promises2.realpath)(path22) : path22;
10596
+ const targetPath = follow ? await (0, import_promises2.realpath)(path23) : path23;
10379
10597
  if (this.fsw.closed)
10380
10598
  return;
10381
10599
  const parent = sp.dirname(wh.watchPath);
10382
10600
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
10383
10601
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
10384
- closer = await this._handleDir(parent, stats, initialAdd, depth, path22, wh, targetPath);
10602
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path23, wh, targetPath);
10385
10603
  if (this.fsw.closed)
10386
10604
  return;
10387
10605
  if (targetPath !== void 0) {
10388
- this.fsw._symlinkPaths.set(sp.resolve(path22), targetPath);
10606
+ this.fsw._symlinkPaths.set(sp.resolve(path23), targetPath);
10389
10607
  }
10390
10608
  } else {
10391
10609
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
10392
10610
  }
10393
10611
  ready();
10394
10612
  if (closer)
10395
- this.fsw._addPathCloser(path22, closer);
10613
+ this.fsw._addPathCloser(path23, closer);
10396
10614
  return false;
10397
10615
  } catch (error) {
10398
10616
  if (this.fsw._handleError(error)) {
10399
10617
  ready();
10400
- return path22;
10618
+ return path23;
10401
10619
  }
10402
10620
  }
10403
10621
  }
@@ -10429,35 +10647,35 @@ function createPattern(matcher) {
10429
10647
  if (matcher.path === string)
10430
10648
  return true;
10431
10649
  if (matcher.recursive) {
10432
- const relative10 = sp2.relative(matcher.path, string);
10433
- if (!relative10) {
10650
+ const relative11 = sp2.relative(matcher.path, string);
10651
+ if (!relative11) {
10434
10652
  return false;
10435
10653
  }
10436
- return !relative10.startsWith("..") && !sp2.isAbsolute(relative10);
10654
+ return !relative11.startsWith("..") && !sp2.isAbsolute(relative11);
10437
10655
  }
10438
10656
  return false;
10439
10657
  };
10440
10658
  }
10441
10659
  return () => false;
10442
10660
  }
10443
- function normalizePath(path22) {
10444
- if (typeof path22 !== "string")
10661
+ function normalizePath(path23) {
10662
+ if (typeof path23 !== "string")
10445
10663
  throw new Error("string expected");
10446
- path22 = sp2.normalize(path22);
10447
- path22 = path22.replace(/\\/g, "/");
10664
+ path23 = sp2.normalize(path23);
10665
+ path23 = path23.replace(/\\/g, "/");
10448
10666
  let prepend = false;
10449
- if (path22.startsWith("//"))
10667
+ if (path23.startsWith("//"))
10450
10668
  prepend = true;
10451
- path22 = path22.replace(DOUBLE_SLASH_RE, "/");
10669
+ path23 = path23.replace(DOUBLE_SLASH_RE, "/");
10452
10670
  if (prepend)
10453
- path22 = "/" + path22;
10454
- return path22;
10671
+ path23 = "/" + path23;
10672
+ return path23;
10455
10673
  }
10456
10674
  function matchPatterns(patterns, testString, stats) {
10457
- const path22 = normalizePath(testString);
10675
+ const path23 = normalizePath(testString);
10458
10676
  for (let index = 0; index < patterns.length; index++) {
10459
10677
  const pattern = patterns[index];
10460
- if (pattern(path22, stats)) {
10678
+ if (pattern(path23, stats)) {
10461
10679
  return true;
10462
10680
  }
10463
10681
  }
@@ -10495,19 +10713,19 @@ var toUnix = (string) => {
10495
10713
  }
10496
10714
  return str;
10497
10715
  };
10498
- var normalizePathToUnix = (path22) => toUnix(sp2.normalize(toUnix(path22)));
10499
- var normalizeIgnored = (cwd = "") => (path22) => {
10500
- if (typeof path22 === "string") {
10501
- return normalizePathToUnix(sp2.isAbsolute(path22) ? path22 : sp2.join(cwd, path22));
10716
+ var normalizePathToUnix = (path23) => toUnix(sp2.normalize(toUnix(path23)));
10717
+ var normalizeIgnored = (cwd = "") => (path23) => {
10718
+ if (typeof path23 === "string") {
10719
+ return normalizePathToUnix(sp2.isAbsolute(path23) ? path23 : sp2.join(cwd, path23));
10502
10720
  } else {
10503
- return path22;
10721
+ return path23;
10504
10722
  }
10505
10723
  };
10506
- var getAbsolutePath = (path22, cwd) => {
10507
- if (sp2.isAbsolute(path22)) {
10508
- return path22;
10724
+ var getAbsolutePath = (path23, cwd) => {
10725
+ if (sp2.isAbsolute(path23)) {
10726
+ return path23;
10509
10727
  }
10510
- return sp2.join(cwd, path22);
10728
+ return sp2.join(cwd, path23);
10511
10729
  };
10512
10730
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
10513
10731
  var DirEntry = class {
@@ -10572,10 +10790,10 @@ var WatchHelper = class {
10572
10790
  dirParts;
10573
10791
  followSymlinks;
10574
10792
  statMethod;
10575
- constructor(path22, follow, fsw) {
10793
+ constructor(path23, follow, fsw) {
10576
10794
  this.fsw = fsw;
10577
- const watchPath = path22;
10578
- this.path = path22 = path22.replace(REPLACER_RE, "");
10795
+ const watchPath = path23;
10796
+ this.path = path23 = path23.replace(REPLACER_RE, "");
10579
10797
  this.watchPath = watchPath;
10580
10798
  this.fullWatchPath = sp2.resolve(watchPath);
10581
10799
  this.dirParts = [];
@@ -10715,20 +10933,20 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10715
10933
  this._closePromise = void 0;
10716
10934
  let paths = unifyPaths(paths_);
10717
10935
  if (cwd) {
10718
- paths = paths.map((path22) => {
10719
- const absPath = getAbsolutePath(path22, cwd);
10936
+ paths = paths.map((path23) => {
10937
+ const absPath = getAbsolutePath(path23, cwd);
10720
10938
  return absPath;
10721
10939
  });
10722
10940
  }
10723
- paths.forEach((path22) => {
10724
- this._removeIgnoredPath(path22);
10941
+ paths.forEach((path23) => {
10942
+ this._removeIgnoredPath(path23);
10725
10943
  });
10726
10944
  this._userIgnored = void 0;
10727
10945
  if (!this._readyCount)
10728
10946
  this._readyCount = 0;
10729
10947
  this._readyCount += paths.length;
10730
- Promise.all(paths.map(async (path22) => {
10731
- const res = await this._nodeFsHandler._addToNodeFs(path22, !_internal, void 0, 0, _origAdd);
10948
+ Promise.all(paths.map(async (path23) => {
10949
+ const res = await this._nodeFsHandler._addToNodeFs(path23, !_internal, void 0, 0, _origAdd);
10732
10950
  if (res)
10733
10951
  this._emitReady();
10734
10952
  return res;
@@ -10750,17 +10968,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10750
10968
  return this;
10751
10969
  const paths = unifyPaths(paths_);
10752
10970
  const { cwd } = this.options;
10753
- paths.forEach((path22) => {
10754
- if (!sp2.isAbsolute(path22) && !this._closers.has(path22)) {
10971
+ paths.forEach((path23) => {
10972
+ if (!sp2.isAbsolute(path23) && !this._closers.has(path23)) {
10755
10973
  if (cwd)
10756
- path22 = sp2.join(cwd, path22);
10757
- path22 = sp2.resolve(path22);
10974
+ path23 = sp2.join(cwd, path23);
10975
+ path23 = sp2.resolve(path23);
10758
10976
  }
10759
- this._closePath(path22);
10760
- this._addIgnoredPath(path22);
10761
- if (this._watched.has(path22)) {
10977
+ this._closePath(path23);
10978
+ this._addIgnoredPath(path23);
10979
+ if (this._watched.has(path23)) {
10762
10980
  this._addIgnoredPath({
10763
- path: path22,
10981
+ path: path23,
10764
10982
  recursive: true
10765
10983
  });
10766
10984
  }
@@ -10824,38 +11042,38 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10824
11042
  * @param stats arguments to be passed with event
10825
11043
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
10826
11044
  */
10827
- async _emit(event, path22, stats) {
11045
+ async _emit(event, path23, stats) {
10828
11046
  if (this.closed)
10829
11047
  return;
10830
11048
  const opts = this.options;
10831
11049
  if (isWindows)
10832
- path22 = sp2.normalize(path22);
11050
+ path23 = sp2.normalize(path23);
10833
11051
  if (opts.cwd)
10834
- path22 = sp2.relative(opts.cwd, path22);
10835
- const args = [path22];
11052
+ path23 = sp2.relative(opts.cwd, path23);
11053
+ const args = [path23];
10836
11054
  if (stats != null)
10837
11055
  args.push(stats);
10838
11056
  const awf = opts.awaitWriteFinish;
10839
11057
  let pw;
10840
- if (awf && (pw = this._pendingWrites.get(path22))) {
11058
+ if (awf && (pw = this._pendingWrites.get(path23))) {
10841
11059
  pw.lastChange = /* @__PURE__ */ new Date();
10842
11060
  return this;
10843
11061
  }
10844
11062
  if (opts.atomic) {
10845
11063
  if (event === EVENTS.UNLINK) {
10846
- this._pendingUnlinks.set(path22, [event, ...args]);
11064
+ this._pendingUnlinks.set(path23, [event, ...args]);
10847
11065
  setTimeout(() => {
10848
- this._pendingUnlinks.forEach((entry, path23) => {
11066
+ this._pendingUnlinks.forEach((entry, path24) => {
10849
11067
  this.emit(...entry);
10850
11068
  this.emit(EVENTS.ALL, ...entry);
10851
- this._pendingUnlinks.delete(path23);
11069
+ this._pendingUnlinks.delete(path24);
10852
11070
  });
10853
11071
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
10854
11072
  return this;
10855
11073
  }
10856
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path22)) {
11074
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path23)) {
10857
11075
  event = EVENTS.CHANGE;
10858
- this._pendingUnlinks.delete(path22);
11076
+ this._pendingUnlinks.delete(path23);
10859
11077
  }
10860
11078
  }
10861
11079
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -10873,16 +11091,16 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10873
11091
  this.emitWithAll(event, args);
10874
11092
  }
10875
11093
  };
10876
- this._awaitWriteFinish(path22, awf.stabilityThreshold, event, awfEmit);
11094
+ this._awaitWriteFinish(path23, awf.stabilityThreshold, event, awfEmit);
10877
11095
  return this;
10878
11096
  }
10879
11097
  if (event === EVENTS.CHANGE) {
10880
- const isThrottled = !this._throttle(EVENTS.CHANGE, path22, 50);
11098
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path23, 50);
10881
11099
  if (isThrottled)
10882
11100
  return this;
10883
11101
  }
10884
11102
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
10885
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path22) : path22;
11103
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path23) : path23;
10886
11104
  let stats2;
10887
11105
  try {
10888
11106
  stats2 = await (0, import_promises3.stat)(fullPath);
@@ -10913,23 +11131,23 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10913
11131
  * @param timeout duration of time to suppress duplicate actions
10914
11132
  * @returns tracking object or false if action should be suppressed
10915
11133
  */
10916
- _throttle(actionType, path22, timeout) {
11134
+ _throttle(actionType, path23, timeout) {
10917
11135
  if (!this._throttled.has(actionType)) {
10918
11136
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
10919
11137
  }
10920
11138
  const action = this._throttled.get(actionType);
10921
11139
  if (!action)
10922
11140
  throw new Error("invalid throttle");
10923
- const actionPath = action.get(path22);
11141
+ const actionPath = action.get(path23);
10924
11142
  if (actionPath) {
10925
11143
  actionPath.count++;
10926
11144
  return false;
10927
11145
  }
10928
11146
  let timeoutObject;
10929
11147
  const clear = () => {
10930
- const item = action.get(path22);
11148
+ const item = action.get(path23);
10931
11149
  const count = item ? item.count : 0;
10932
- action.delete(path22);
11150
+ action.delete(path23);
10933
11151
  clearTimeout(timeoutObject);
10934
11152
  if (item)
10935
11153
  clearTimeout(item.timeoutObject);
@@ -10937,7 +11155,7 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10937
11155
  };
10938
11156
  timeoutObject = setTimeout(clear, timeout);
10939
11157
  const thr = { timeoutObject, clear, count: 0 };
10940
- action.set(path22, thr);
11158
+ action.set(path23, thr);
10941
11159
  return thr;
10942
11160
  }
10943
11161
  _incrReadyCount() {
@@ -10951,44 +11169,44 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10951
11169
  * @param event
10952
11170
  * @param awfEmit Callback to be called when ready for event to be emitted.
10953
11171
  */
10954
- _awaitWriteFinish(path22, threshold, event, awfEmit) {
11172
+ _awaitWriteFinish(path23, threshold, event, awfEmit) {
10955
11173
  const awf = this.options.awaitWriteFinish;
10956
11174
  if (typeof awf !== "object")
10957
11175
  return;
10958
11176
  const pollInterval = awf.pollInterval;
10959
11177
  let timeoutHandler;
10960
- let fullPath = path22;
10961
- if (this.options.cwd && !sp2.isAbsolute(path22)) {
10962
- fullPath = sp2.join(this.options.cwd, path22);
11178
+ let fullPath = path23;
11179
+ if (this.options.cwd && !sp2.isAbsolute(path23)) {
11180
+ fullPath = sp2.join(this.options.cwd, path23);
10963
11181
  }
10964
11182
  const now = /* @__PURE__ */ new Date();
10965
11183
  const writes = this._pendingWrites;
10966
11184
  function awaitWriteFinishFn(prevStat) {
10967
11185
  (0, import_node_fs2.stat)(fullPath, (err, curStat) => {
10968
- if (err || !writes.has(path22)) {
11186
+ if (err || !writes.has(path23)) {
10969
11187
  if (err && err.code !== "ENOENT")
10970
11188
  awfEmit(err);
10971
11189
  return;
10972
11190
  }
10973
11191
  const now2 = Number(/* @__PURE__ */ new Date());
10974
11192
  if (prevStat && curStat.size !== prevStat.size) {
10975
- writes.get(path22).lastChange = now2;
11193
+ writes.get(path23).lastChange = now2;
10976
11194
  }
10977
- const pw = writes.get(path22);
11195
+ const pw = writes.get(path23);
10978
11196
  const df = now2 - pw.lastChange;
10979
11197
  if (df >= threshold) {
10980
- writes.delete(path22);
11198
+ writes.delete(path23);
10981
11199
  awfEmit(void 0, curStat);
10982
11200
  } else {
10983
11201
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
10984
11202
  }
10985
11203
  });
10986
11204
  }
10987
- if (!writes.has(path22)) {
10988
- writes.set(path22, {
11205
+ if (!writes.has(path23)) {
11206
+ writes.set(path23, {
10989
11207
  lastChange: now,
10990
11208
  cancelWait: () => {
10991
- writes.delete(path22);
11209
+ writes.delete(path23);
10992
11210
  clearTimeout(timeoutHandler);
10993
11211
  return event;
10994
11212
  }
@@ -10999,8 +11217,8 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10999
11217
  /**
11000
11218
  * Determines whether user has asked to ignore this path.
11001
11219
  */
11002
- _isIgnored(path22, stats) {
11003
- if (this.options.atomic && DOT_RE.test(path22))
11220
+ _isIgnored(path23, stats) {
11221
+ if (this.options.atomic && DOT_RE.test(path23))
11004
11222
  return true;
11005
11223
  if (!this._userIgnored) {
11006
11224
  const { cwd } = this.options;
@@ -11010,17 +11228,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
11010
11228
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
11011
11229
  this._userIgnored = anymatch(list, void 0);
11012
11230
  }
11013
- return this._userIgnored(path22, stats);
11231
+ return this._userIgnored(path23, stats);
11014
11232
  }
11015
- _isntIgnored(path22, stat4) {
11016
- return !this._isIgnored(path22, stat4);
11233
+ _isntIgnored(path23, stat4) {
11234
+ return !this._isIgnored(path23, stat4);
11017
11235
  }
11018
11236
  /**
11019
11237
  * Provides a set of common helpers and properties relating to symlink handling.
11020
11238
  * @param path file or directory pattern being watched
11021
11239
  */
11022
- _getWatchHelpers(path22) {
11023
- return new WatchHelper(path22, this.options.followSymlinks, this);
11240
+ _getWatchHelpers(path23) {
11241
+ return new WatchHelper(path23, this.options.followSymlinks, this);
11024
11242
  }
11025
11243
  // Directory helpers
11026
11244
  // -----------------
@@ -11052,63 +11270,63 @@ var FSWatcher = class extends import_node_events.EventEmitter {
11052
11270
  * @param item base path of item/directory
11053
11271
  */
11054
11272
  _remove(directory, item, isDirectory) {
11055
- const path22 = sp2.join(directory, item);
11056
- const fullPath = sp2.resolve(path22);
11057
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path22) || this._watched.has(fullPath);
11058
- if (!this._throttle("remove", path22, 100))
11273
+ const path23 = sp2.join(directory, item);
11274
+ const fullPath = sp2.resolve(path23);
11275
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path23) || this._watched.has(fullPath);
11276
+ if (!this._throttle("remove", path23, 100))
11059
11277
  return;
11060
11278
  if (!isDirectory && this._watched.size === 1) {
11061
11279
  this.add(directory, item, true);
11062
11280
  }
11063
- const wp = this._getWatchedDir(path22);
11281
+ const wp = this._getWatchedDir(path23);
11064
11282
  const nestedDirectoryChildren = wp.getChildren();
11065
- nestedDirectoryChildren.forEach((nested) => this._remove(path22, nested));
11283
+ nestedDirectoryChildren.forEach((nested) => this._remove(path23, nested));
11066
11284
  const parent = this._getWatchedDir(directory);
11067
11285
  const wasTracked = parent.has(item);
11068
11286
  parent.remove(item);
11069
11287
  if (this._symlinkPaths.has(fullPath)) {
11070
11288
  this._symlinkPaths.delete(fullPath);
11071
11289
  }
11072
- let relPath = path22;
11290
+ let relPath = path23;
11073
11291
  if (this.options.cwd)
11074
- relPath = sp2.relative(this.options.cwd, path22);
11292
+ relPath = sp2.relative(this.options.cwd, path23);
11075
11293
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
11076
11294
  const event = this._pendingWrites.get(relPath).cancelWait();
11077
11295
  if (event === EVENTS.ADD)
11078
11296
  return;
11079
11297
  }
11080
- this._watched.delete(path22);
11298
+ this._watched.delete(path23);
11081
11299
  this._watched.delete(fullPath);
11082
11300
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
11083
- if (wasTracked && !this._isIgnored(path22))
11084
- this._emit(eventName, path22);
11085
- this._closePath(path22);
11301
+ if (wasTracked && !this._isIgnored(path23))
11302
+ this._emit(eventName, path23);
11303
+ this._closePath(path23);
11086
11304
  }
11087
11305
  /**
11088
11306
  * Closes all watchers for a path
11089
11307
  */
11090
- _closePath(path22) {
11091
- this._closeFile(path22);
11092
- const dir = sp2.dirname(path22);
11093
- this._getWatchedDir(dir).remove(sp2.basename(path22));
11308
+ _closePath(path23) {
11309
+ this._closeFile(path23);
11310
+ const dir = sp2.dirname(path23);
11311
+ this._getWatchedDir(dir).remove(sp2.basename(path23));
11094
11312
  }
11095
11313
  /**
11096
11314
  * Closes only file-specific watchers
11097
11315
  */
11098
- _closeFile(path22) {
11099
- const closers = this._closers.get(path22);
11316
+ _closeFile(path23) {
11317
+ const closers = this._closers.get(path23);
11100
11318
  if (!closers)
11101
11319
  return;
11102
11320
  closers.forEach((closer) => closer());
11103
- this._closers.delete(path22);
11321
+ this._closers.delete(path23);
11104
11322
  }
11105
- _addPathCloser(path22, closer) {
11323
+ _addPathCloser(path23, closer) {
11106
11324
  if (!closer)
11107
11325
  return;
11108
- let list = this._closers.get(path22);
11326
+ let list = this._closers.get(path23);
11109
11327
  if (!list) {
11110
11328
  list = [];
11111
- this._closers.set(path22, list);
11329
+ this._closers.set(path23, list);
11112
11330
  }
11113
11331
  list.push(closer);
11114
11332
  }
@@ -11138,7 +11356,7 @@ function watch(paths, options = {}) {
11138
11356
  var chokidar_default = { watch, FSWatcher };
11139
11357
 
11140
11358
  // src/watcher/file-watcher.ts
11141
- var path15 = __toESM(require("path"), 1);
11359
+ var path16 = __toESM(require("path"), 1);
11142
11360
  var FileWatcher = class {
11143
11361
  watcher = null;
11144
11362
  projectRoot;
@@ -11164,15 +11382,15 @@ var FileWatcher = class {
11164
11382
  const watchTargets = this.configPath ? [this.projectRoot, this.configPath] : this.projectRoot;
11165
11383
  this.watcher = chokidar_default.watch(watchTargets, {
11166
11384
  ignored: (filePath) => {
11167
- const relativePath = path15.relative(this.projectRoot, filePath);
11385
+ const relativePath = path16.relative(this.projectRoot, filePath);
11168
11386
  if (!relativePath) return false;
11169
11387
  if (this.isProjectConfigPathOrAncestor(relativePath)) {
11170
11388
  return false;
11171
11389
  }
11172
- if (hasFilteredPathSegment(relativePath, path15.sep)) {
11390
+ if (hasFilteredPathSegment(relativePath, path16.sep)) {
11173
11391
  return true;
11174
11392
  }
11175
- if (isRestrictedDirectory(relativePath, path15.sep)) {
11393
+ if (isRestrictedDirectory(relativePath, path16.sep)) {
11176
11394
  return true;
11177
11395
  }
11178
11396
  if (ignoreFilter.ignores(relativePath)) {
@@ -11218,24 +11436,24 @@ var FileWatcher = class {
11218
11436
  this.scheduleFlush();
11219
11437
  }
11220
11438
  isProjectConfigPath(filePath) {
11221
- const relativePath = path15.relative(this.projectRoot, filePath);
11222
- const normalizedRelativePath = path15.normalize(relativePath);
11439
+ const relativePath = path16.relative(this.projectRoot, filePath);
11440
+ const normalizedRelativePath = path16.normalize(relativePath);
11223
11441
  return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
11224
11442
  }
11225
11443
  isProjectConfigPathOrAncestor(relativePath) {
11226
- const normalizedRelativePath = path15.normalize(relativePath);
11444
+ const normalizedRelativePath = path16.normalize(relativePath);
11227
11445
  return this.getProjectConfigRelativePaths().some(
11228
- (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path15.sep}`)
11446
+ (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path16.sep}`)
11229
11447
  );
11230
11448
  }
11231
11449
  getProjectConfigRelativePaths() {
11232
11450
  if (this.configPath) {
11233
- return [path15.normalize(path15.relative(this.projectRoot, this.configPath))];
11451
+ return [path16.normalize(path16.relative(this.projectRoot, this.configPath))];
11234
11452
  }
11235
11453
  return [
11236
11454
  resolveProjectConfigPath(this.projectRoot, this.host),
11237
11455
  resolveWritableProjectConfigPath(this.projectRoot, this.host)
11238
- ].map((configPath) => path15.normalize(path15.relative(this.projectRoot, configPath)));
11456
+ ].map((configPath) => path16.normalize(path16.relative(this.projectRoot, configPath)));
11239
11457
  }
11240
11458
  scheduleFlush() {
11241
11459
  if (this.debounceTimer) {
@@ -11250,7 +11468,7 @@ var FileWatcher = class {
11250
11468
  return;
11251
11469
  }
11252
11470
  const changes = Array.from(this.pendingChanges.entries()).map(
11253
- ([path22, type]) => ({ path: path22, type })
11471
+ ([path23, type]) => ({ path: path23, type })
11254
11472
  );
11255
11473
  this.pendingChanges.clear();
11256
11474
  try {
@@ -11277,7 +11495,7 @@ var FileWatcher = class {
11277
11495
  };
11278
11496
 
11279
11497
  // src/watcher/git-head-watcher.ts
11280
- var path16 = __toESM(require("path"), 1);
11498
+ var path17 = __toESM(require("path"), 1);
11281
11499
  var GitHeadWatcher = class {
11282
11500
  watcher = null;
11283
11501
  projectRoot;
@@ -11299,7 +11517,7 @@ var GitHeadWatcher = class {
11299
11517
  this.onBranchChange = handler;
11300
11518
  this.currentBranch = getCurrentBranch(this.projectRoot);
11301
11519
  const headPath = getHeadPath(this.projectRoot);
11302
- const refsPath = path16.join(this.projectRoot, ".git", "refs", "heads");
11520
+ const refsPath = path17.join(this.projectRoot, ".git", "refs", "heads");
11303
11521
  this.watcher = chokidar_default.watch([headPath, refsPath], {
11304
11522
  persistent: true,
11305
11523
  ignoreInitial: true,
@@ -11532,11 +11750,11 @@ var pr_impact = (0, import_plugin.tool)({
11532
11750
  // src/tools/index.ts
11533
11751
  var import_fs10 = require("fs");
11534
11752
  var os4 = __toESM(require("os"), 1);
11535
- var path19 = __toESM(require("path"), 1);
11753
+ var path20 = __toESM(require("path"), 1);
11536
11754
 
11537
11755
  // src/tools/visualize/activity.ts
11538
- var import_child_process3 = require("child_process");
11539
- var path17 = __toESM(require("path"), 1);
11756
+ var import_child_process4 = require("child_process");
11757
+ var path18 = __toESM(require("path"), 1);
11540
11758
  function attachRecentActivity(data, projectRoot) {
11541
11759
  const activity = readGitActivity(projectRoot);
11542
11760
  const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
@@ -11547,7 +11765,7 @@ function attachRecentActivity(data, projectRoot) {
11547
11765
  }
11548
11766
  function readGitActivity(projectRoot) {
11549
11767
  try {
11550
- const output = (0, import_child_process3.execFileSync)(
11768
+ const output = (0, import_child_process4.execFileSync)(
11551
11769
  "git",
11552
11770
  ["-C", projectRoot, "log", "--since=90.days", "--numstat", "--date=short", "--pretty=format:__COMMIT__%x09%h%x09%ad%x09%s"],
11553
11771
  { encoding: "utf8", maxBuffer: 8 * 1024 * 1024, stdio: ["ignore", "pipe", "ignore"] }
@@ -11698,7 +11916,7 @@ function normalizePath2(filePath) {
11698
11916
  return filePath.replace(/\\/g, "/");
11699
11917
  }
11700
11918
  function toGitRelativePath(projectRoot, filePath) {
11701
- const relativePath = path17.isAbsolute(filePath) ? path17.relative(projectRoot, filePath) : filePath;
11919
+ const relativePath = path18.isAbsolute(filePath) ? path18.relative(projectRoot, filePath) : filePath;
11702
11920
  return normalizePath2(relativePath);
11703
11921
  }
11704
11922
 
@@ -11956,7 +12174,7 @@ render();
11956
12174
  }
11957
12175
 
11958
12176
  // src/tools/visualize/transform.ts
11959
- var path18 = __toESM(require("path"), 1);
12177
+ var path19 = __toESM(require("path"), 1);
11960
12178
 
11961
12179
  // src/tools/visualize/modules.ts
11962
12180
  var MAX_MODULES = 18;
@@ -12089,8 +12307,8 @@ function compactModules(prefixToNodes) {
12089
12307
  function deriveModules(nodes) {
12090
12308
  const initial = /* @__PURE__ */ new Map();
12091
12309
  for (const node of nodes) {
12092
- const relative10 = stripToProjectRelative(node.filePath);
12093
- const prefix = modulePrefixFromRelativePath(relative10);
12310
+ const relative11 = stripToProjectRelative(node.filePath);
12311
+ const prefix = modulePrefixFromRelativePath(relative11);
12094
12312
  if (!initial.has(prefix)) initial.set(prefix, []);
12095
12313
  initial.get(prefix)?.push(node);
12096
12314
  }
@@ -12216,7 +12434,7 @@ function transformForVisualization(symbols, edges, options = {}) {
12216
12434
  filePath: s.filePath,
12217
12435
  kind: s.kind,
12218
12436
  line: s.startLine,
12219
- directory: path18.dirname(s.filePath),
12437
+ directory: path19.dirname(s.filePath),
12220
12438
  moduleId: "",
12221
12439
  moduleLabel: ""
12222
12440
  }));
@@ -12267,7 +12485,10 @@ var codebase_peek = (0, import_plugin2.tool)({
12267
12485
  limit: z2.number().optional().default(10).describe("Maximum number of results to return"),
12268
12486
  fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
12269
12487
  directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
12270
- chunkType: z2.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type")
12488
+ chunkType: z2.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type"),
12489
+ blameAuthor: z2.string().optional().describe("Filter by git blame author name or email"),
12490
+ blameSha: z2.string().optional().describe("Filter by git blame commit SHA or prefix"),
12491
+ blameSince: z2.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)")
12271
12492
  },
12272
12493
  async execute(args, context) {
12273
12494
  const results = await searchCodebase(context?.worktree, DEFAULT_HOST, args.query, {
@@ -12275,7 +12496,10 @@ var codebase_peek = (0, import_plugin2.tool)({
12275
12496
  fileType: args.fileType,
12276
12497
  directory: args.directory,
12277
12498
  chunkType: args.chunkType,
12278
- metadataOnly: true
12499
+ metadataOnly: true,
12500
+ blameAuthor: args.blameAuthor,
12501
+ blameSha: args.blameSha,
12502
+ blameSince: args.blameSince
12279
12503
  });
12280
12504
  return formatCodebasePeek(results);
12281
12505
  }
@@ -12358,7 +12582,10 @@ var codebase_search = (0, import_plugin2.tool)({
12358
12582
  fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
12359
12583
  directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
12360
12584
  chunkType: z2.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type"),
12361
- contextLines: z2.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
12585
+ contextLines: z2.number().optional().describe("Number of extra lines to include before/after each match (default: 0)"),
12586
+ blameAuthor: z2.string().optional().describe("Filter by git blame author name or email"),
12587
+ blameSha: z2.string().optional().describe("Filter by git blame commit SHA or prefix"),
12588
+ blameSince: z2.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)")
12362
12589
  },
12363
12590
  async execute(args, context) {
12364
12591
  const results = await searchCodebase(context?.worktree, DEFAULT_HOST, args.query, {
@@ -12366,7 +12593,10 @@ var codebase_search = (0, import_plugin2.tool)({
12366
12593
  fileType: args.fileType,
12367
12594
  directory: args.directory,
12368
12595
  chunkType: args.chunkType,
12369
- contextLines: args.contextLines
12596
+ contextLines: args.contextLines,
12597
+ blameAuthor: args.blameAuthor,
12598
+ blameSha: args.blameSha,
12599
+ blameSince: args.blameSince
12370
12600
  });
12371
12601
  if (results.length === 0) {
12372
12602
  return "No matching code found. Try a different query or run index_codebase first.";
@@ -12405,22 +12635,10 @@ var call_graph = (0, import_plugin2.tool)({
12405
12635
  return "Error: 'symbolId' is required when direction is 'callees'. First use direction='callers' to find the symbol ID.";
12406
12636
  }
12407
12637
  const { callees } = await getCallGraphData(context?.worktree, DEFAULT_HOST, args);
12408
- if (callees.length === 0) {
12409
- return `No callees found for symbol ${args.symbolId}${args.relationshipType ? ` with type ${args.relationshipType}` : ""}. The function may not call any other tracked functions.`;
12410
- }
12411
- return callees.map((edge, index) => {
12412
- const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
12413
- return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
12414
- }).join("\n");
12638
+ return formatCallGraphCallees(args.symbolId, callees, args.relationshipType);
12415
12639
  }
12416
12640
  const { callers } = await getCallGraphData(context?.worktree, DEFAULT_HOST, args);
12417
- if (callers.length === 0) {
12418
- return `No callers found for "${args.name}"${args.relationshipType ? ` with type ${args.relationshipType}` : ""}. It may not be called by any tracked function, or the index needs updating.`;
12419
- }
12420
- return callers.map((edge, index) => {
12421
- const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
12422
- return `[${index + 1}] \u2190 from ${edge.fromSymbolName ?? "<unknown>"} in ${edge.fromSymbolFilePath ?? "<unknown file>"} [${edge.fromSymbolId}] (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? " [resolved]" : " [unresolved]"}`;
12423
- }).join("\n");
12641
+ return formatCallGraphCallers(args.name, callers, args.relationshipType);
12424
12642
  }
12425
12643
  });
12426
12644
  var call_graph_path = (0, import_plugin2.tool)({
@@ -12431,17 +12649,8 @@ var call_graph_path = (0, import_plugin2.tool)({
12431
12649
  maxDepth: z2.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
12432
12650
  },
12433
12651
  async execute(args, context) {
12434
- const path22 = await getCallGraphPath(context?.worktree, DEFAULT_HOST, args.from, args.to, args.maxDepth);
12435
- if (path22.length === 0) {
12436
- return `No path found between "${args.from}" and "${args.to}". They may be in disconnected components, or the call graph index needs updating.`;
12437
- }
12438
- const formatted = path22.map((hop, index) => {
12439
- const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
12440
- const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
12441
- return `${prefix} ${hop.symbolName}${location}`;
12442
- });
12443
- return `Path (${path22.length} hops):
12444
- ${formatted.join("\n")}`;
12652
+ const path23 = await getCallGraphPath(context?.worktree, DEFAULT_HOST, args.from, args.to, args.maxDepth);
12653
+ return formatCallGraphPath(args.from, args.to, path23);
12445
12654
  }
12446
12655
  });
12447
12656
  var add_knowledge_base = (0, import_plugin2.tool)({
@@ -12494,7 +12703,7 @@ var index_visualize = (0, import_plugin2.tool)({
12494
12703
  return "No connected symbols found for visualization. Try including orphans with includeOrphans=true, or check that the call graph has resolved edges.";
12495
12704
  }
12496
12705
  const html = generateVisualizationHtml(vizData);
12497
- const outputPath = path19.join(os4.tmpdir(), `call-graph-${Date.now()}.html`);
12706
+ const outputPath = path20.join(os4.tmpdir(), `call-graph-${Date.now()}.html`);
12498
12707
  (0, import_fs10.writeFileSync)(outputPath, html, "utf-8");
12499
12708
  let result = `Temporal call graph visualization generated: ${outputPath}
12500
12709
 
@@ -12517,7 +12726,7 @@ var index_visualize = (0, import_plugin2.tool)({
12517
12726
 
12518
12727
  // src/commands/loader.ts
12519
12728
  var import_fs11 = require("fs");
12520
- var path20 = __toESM(require("path"), 1);
12729
+ var path21 = __toESM(require("path"), 1);
12521
12730
  function parseFrontmatter(content) {
12522
12731
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
12523
12732
  const match = content.match(frontmatterRegex);
@@ -12543,7 +12752,7 @@ function loadCommandsFromDirectory(commandsDir) {
12543
12752
  }
12544
12753
  const files = (0, import_fs11.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
12545
12754
  for (const file of files) {
12546
- const filePath = path20.join(commandsDir, file);
12755
+ const filePath = path21.join(commandsDir, file);
12547
12756
  let content;
12548
12757
  try {
12549
12758
  content = (0, import_fs11.readFileSync)(filePath, "utf-8");
@@ -12552,7 +12761,7 @@ function loadCommandsFromDirectory(commandsDir) {
12552
12761
  throw new Error(`Failed to load command file ${filePath}: ${message}`);
12553
12762
  }
12554
12763
  const { frontmatter, body } = parseFrontmatter(content);
12555
- const name = path20.basename(file, ".md");
12764
+ const name = path21.basename(file, ".md");
12556
12765
  const description = frontmatter.description || `Run the ${name} command`;
12557
12766
  commands.set(name, {
12558
12767
  description,
@@ -12885,9 +13094,9 @@ function replaceActiveWatcher(projectRoot, nextWatcher) {
12885
13094
  function getCommandsDir() {
12886
13095
  let currentDir = process.cwd();
12887
13096
  if (typeof import_meta2 !== "undefined" && import_meta2.url) {
12888
- currentDir = path21.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
13097
+ currentDir = path22.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
12889
13098
  }
12890
- return path21.join(currentDir, "..", "commands");
13099
+ return path22.join(currentDir, "..", "commands");
12891
13100
  }
12892
13101
  function appendRoutingHints(output, hints, preferredRole) {
12893
13102
  const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
@@ -12907,7 +13116,7 @@ var plugin = async ({ directory, worktree }) => {
12907
13116
  initializeTools2(projectRoot, config);
12908
13117
  const getProjectIndexer = () => getIndexerForProject2(projectRoot);
12909
13118
  const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus(), 200, config.search.routingGraphHandoffHints) : null;
12910
- const isHomeDir = path21.resolve(projectRoot) === path21.resolve(os5.homedir());
13119
+ const isHomeDir = path22.resolve(projectRoot) === path22.resolve(os5.homedir());
12911
13120
  const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
12912
13121
  if (isHomeDir) {
12913
13122
  console.warn(