opencode-codebase-index 0.10.0 → 0.12.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/cli.cjs CHANGED
@@ -8,7 +8,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
8
8
  var __getProtoOf = Object.getPrototypeOf;
9
9
  var __hasOwnProp = Object.prototype.hasOwnProperty;
10
10
  var __commonJS = (cb, mod) => function __require() {
11
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ try {
12
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
13
+ } catch (e) {
14
+ throw mod = 0, e;
15
+ }
12
16
  };
13
17
  var __copyProps = (to, from, except, desc) => {
14
18
  if (from && typeof from === "object" || typeof from === "function") {
@@ -487,7 +491,7 @@ var require_ignore = __commonJS({
487
491
  // path matching.
488
492
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
489
493
  // @returns {TestResult} true if a file is ignored
490
- test(path18, checkUnignored, mode) {
494
+ test(path19, checkUnignored, mode) {
491
495
  let ignored = false;
492
496
  let unignored = false;
493
497
  let matchedRule;
@@ -496,7 +500,7 @@ var require_ignore = __commonJS({
496
500
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
497
501
  return;
498
502
  }
499
- const matched = rule[mode].test(path18);
503
+ const matched = rule[mode].test(path19);
500
504
  if (!matched) {
501
505
  return;
502
506
  }
@@ -517,17 +521,17 @@ var require_ignore = __commonJS({
517
521
  var throwError = (message, Ctor) => {
518
522
  throw new Ctor(message);
519
523
  };
520
- var checkPath = (path18, originalPath, doThrow) => {
521
- if (!isString(path18)) {
524
+ var checkPath = (path19, originalPath, doThrow) => {
525
+ if (!isString(path19)) {
522
526
  return doThrow(
523
527
  `path must be a string, but got \`${originalPath}\``,
524
528
  TypeError
525
529
  );
526
530
  }
527
- if (!path18) {
531
+ if (!path19) {
528
532
  return doThrow(`path must not be empty`, TypeError);
529
533
  }
530
- if (checkPath.isNotRelative(path18)) {
534
+ if (checkPath.isNotRelative(path19)) {
531
535
  const r = "`path.relative()`d";
532
536
  return doThrow(
533
537
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -536,7 +540,7 @@ var require_ignore = __commonJS({
536
540
  }
537
541
  return true;
538
542
  };
539
- var isNotRelative = (path18) => REGEX_TEST_INVALID_PATH.test(path18);
543
+ var isNotRelative = (path19) => REGEX_TEST_INVALID_PATH.test(path19);
540
544
  checkPath.isNotRelative = isNotRelative;
541
545
  checkPath.convert = (p) => p;
542
546
  var Ignore2 = class {
@@ -566,19 +570,19 @@ var require_ignore = __commonJS({
566
570
  }
567
571
  // @returns {TestResult}
568
572
  _test(originalPath, cache, checkUnignored, slices) {
569
- const path18 = originalPath && checkPath.convert(originalPath);
573
+ const path19 = originalPath && checkPath.convert(originalPath);
570
574
  checkPath(
571
- path18,
575
+ path19,
572
576
  originalPath,
573
577
  this._strictPathCheck ? throwError : RETURN_FALSE
574
578
  );
575
- return this._t(path18, cache, checkUnignored, slices);
579
+ return this._t(path19, cache, checkUnignored, slices);
576
580
  }
577
- checkIgnore(path18) {
578
- if (!REGEX_TEST_TRAILING_SLASH.test(path18)) {
579
- return this.test(path18);
581
+ checkIgnore(path19) {
582
+ if (!REGEX_TEST_TRAILING_SLASH.test(path19)) {
583
+ return this.test(path19);
580
584
  }
581
- const slices = path18.split(SLASH).filter(Boolean);
585
+ const slices = path19.split(SLASH).filter(Boolean);
582
586
  slices.pop();
583
587
  if (slices.length) {
584
588
  const parent = this._t(
@@ -591,18 +595,18 @@ var require_ignore = __commonJS({
591
595
  return parent;
592
596
  }
593
597
  }
594
- return this._rules.test(path18, false, MODE_CHECK_IGNORE);
598
+ return this._rules.test(path19, false, MODE_CHECK_IGNORE);
595
599
  }
596
- _t(path18, cache, checkUnignored, slices) {
597
- if (path18 in cache) {
598
- return cache[path18];
600
+ _t(path19, cache, checkUnignored, slices) {
601
+ if (path19 in cache) {
602
+ return cache[path19];
599
603
  }
600
604
  if (!slices) {
601
- slices = path18.split(SLASH).filter(Boolean);
605
+ slices = path19.split(SLASH).filter(Boolean);
602
606
  }
603
607
  slices.pop();
604
608
  if (!slices.length) {
605
- return cache[path18] = this._rules.test(path18, checkUnignored, MODE_IGNORE);
609
+ return cache[path19] = this._rules.test(path19, checkUnignored, MODE_IGNORE);
606
610
  }
607
611
  const parent = this._t(
608
612
  slices.join(SLASH) + SLASH,
@@ -610,29 +614,29 @@ var require_ignore = __commonJS({
610
614
  checkUnignored,
611
615
  slices
612
616
  );
613
- return cache[path18] = parent.ignored ? parent : this._rules.test(path18, checkUnignored, MODE_IGNORE);
617
+ return cache[path19] = parent.ignored ? parent : this._rules.test(path19, checkUnignored, MODE_IGNORE);
614
618
  }
615
- ignores(path18) {
616
- return this._test(path18, this._ignoreCache, false).ignored;
619
+ ignores(path19) {
620
+ return this._test(path19, this._ignoreCache, false).ignored;
617
621
  }
618
622
  createFilter() {
619
- return (path18) => !this.ignores(path18);
623
+ return (path19) => !this.ignores(path19);
620
624
  }
621
625
  filter(paths) {
622
626
  return makeArray(paths).filter(this.createFilter());
623
627
  }
624
628
  // @returns {TestResult}
625
- test(path18) {
626
- return this._test(path18, this._testCache, true);
629
+ test(path19) {
630
+ return this._test(path19, this._testCache, true);
627
631
  }
628
632
  };
629
633
  var factory = (options) => new Ignore2(options);
630
- var isPathValid = (path18) => checkPath(path18 && checkPath.convert(path18), path18, RETURN_FALSE);
634
+ var isPathValid = (path19) => checkPath(path19 && checkPath.convert(path19), path19, RETURN_FALSE);
631
635
  var setupWindows = () => {
632
636
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
633
637
  checkPath.convert = makePosix;
634
638
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
635
- checkPath.isNotRelative = (path18) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path18) || isNotRelative(path18);
639
+ checkPath.isNotRelative = (path19) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path19) || isNotRelative(path19);
636
640
  };
637
641
  if (
638
642
  // Detect `process` so that it can run in browsers.
@@ -649,7 +653,7 @@ var require_ignore = __commonJS({
649
653
 
650
654
  // src/cli.ts
651
655
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
652
- var path17 = __toESM(require("path"), 1);
656
+ var path18 = __toESM(require("path"), 1);
653
657
 
654
658
  // src/config/constants.ts
655
659
  var DEFAULT_INCLUDE = [
@@ -795,6 +799,7 @@ function getDefaultSearchConfig() {
795
799
  rerankTopN: 20,
796
800
  contextLines: 0,
797
801
  routingHints: true,
802
+ routingGraphHandoffHints: false,
798
803
  routingHintRole: "system"
799
804
  };
800
805
  }
@@ -917,6 +922,7 @@ function parseConfig(raw) {
917
922
  rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
918
923
  contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines,
919
924
  routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints,
925
+ routingGraphHandoffHints: typeof rawSearch.routingGraphHandoffHints === "boolean" ? rawSearch.routingGraphHandoffHints : defaultSearch.routingGraphHandoffHints,
920
926
  routingHintRole: rawSearch.routingHintRole === "developer" || rawSearch.routingHintRole === "system" ? rawSearch.routingHintRole : defaultSearch.routingHintRole
921
927
  };
922
928
  const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
@@ -1075,9 +1081,9 @@ var import_fs = require("fs");
1075
1081
  var path = __toESM(require("path"), 1);
1076
1082
 
1077
1083
  // src/eval/report-formatters.ts
1078
- function assertFiniteNumber(value, path18) {
1084
+ function assertFiniteNumber(value, path19) {
1079
1085
  if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
1080
- throw new Error(`${path18} must be a finite number`);
1086
+ throw new Error(`${path19} must be a finite number`);
1081
1087
  }
1082
1088
  return value;
1083
1089
  }
@@ -1266,13 +1272,15 @@ function buildPerQueryArtifact(perQuery) {
1266
1272
 
1267
1273
  // src/eval/runner.ts
1268
1274
  var import_fs10 = require("fs");
1269
- var path12 = __toESM(require("path"), 1);
1275
+ var path13 = __toESM(require("path"), 1);
1270
1276
  var import_perf_hooks2 = require("perf_hooks");
1271
1277
 
1272
1278
  // src/indexer/index.ts
1273
1279
  var import_fs7 = require("fs");
1274
- var path9 = __toESM(require("path"), 1);
1280
+ var path10 = __toESM(require("path"), 1);
1275
1281
  var import_perf_hooks = require("perf_hooks");
1282
+ var import_child_process2 = require("child_process");
1283
+ var import_util2 = require("util");
1276
1284
 
1277
1285
  // node_modules/eventemitter3/index.mjs
1278
1286
  var import_index = __toESM(require_eventemitter3(), 1);
@@ -1296,7 +1304,7 @@ function pTimeout(promise, options) {
1296
1304
  } = options;
1297
1305
  let timer;
1298
1306
  let abortHandler;
1299
- const wrappedPromise = new Promise((resolve10, reject) => {
1307
+ const wrappedPromise = new Promise((resolve11, reject) => {
1300
1308
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
1301
1309
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
1302
1310
  }
@@ -1310,7 +1318,7 @@ function pTimeout(promise, options) {
1310
1318
  };
1311
1319
  signal.addEventListener("abort", abortHandler, { once: true });
1312
1320
  }
1313
- promise.then(resolve10, reject);
1321
+ promise.then(resolve11, reject);
1314
1322
  if (milliseconds === Number.POSITIVE_INFINITY) {
1315
1323
  return;
1316
1324
  }
@@ -1318,7 +1326,7 @@ function pTimeout(promise, options) {
1318
1326
  timer = customTimers.setTimeout.call(void 0, () => {
1319
1327
  if (fallback) {
1320
1328
  try {
1321
- resolve10(fallback());
1329
+ resolve11(fallback());
1322
1330
  } catch (error) {
1323
1331
  reject(error);
1324
1332
  }
@@ -1328,7 +1336,7 @@ function pTimeout(promise, options) {
1328
1336
  promise.cancel();
1329
1337
  }
1330
1338
  if (message === false) {
1331
- resolve10();
1339
+ resolve11();
1332
1340
  } else if (message instanceof Error) {
1333
1341
  reject(message);
1334
1342
  } else {
@@ -1730,7 +1738,7 @@ var PQueue = class extends import_index.default {
1730
1738
  // Assign unique ID if not provided
1731
1739
  id: options.id ?? (this.#idAssigner++).toString()
1732
1740
  };
1733
- return new Promise((resolve10, reject) => {
1741
+ return new Promise((resolve11, reject) => {
1734
1742
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
1735
1743
  let cleanupQueueAbortHandler = () => void 0;
1736
1744
  const run = async () => {
@@ -1770,7 +1778,7 @@ var PQueue = class extends import_index.default {
1770
1778
  })]);
1771
1779
  }
1772
1780
  const result = await operation;
1773
- resolve10(result);
1781
+ resolve11(result);
1774
1782
  this.emit("completed", result);
1775
1783
  } catch (error) {
1776
1784
  reject(error);
@@ -1958,13 +1966,13 @@ var PQueue = class extends import_index.default {
1958
1966
  });
1959
1967
  }
1960
1968
  async #onEvent(event, filter) {
1961
- return new Promise((resolve10) => {
1969
+ return new Promise((resolve11) => {
1962
1970
  const listener = () => {
1963
1971
  if (filter && !filter()) {
1964
1972
  return;
1965
1973
  }
1966
1974
  this.off(event, listener);
1967
- resolve10();
1975
+ resolve11();
1968
1976
  };
1969
1977
  this.on(event, listener);
1970
1978
  });
@@ -2250,7 +2258,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
2250
2258
  const finalDelay = Math.min(delayTime, remainingTime);
2251
2259
  options.signal?.throwIfAborted();
2252
2260
  if (finalDelay > 0) {
2253
- await new Promise((resolve10, reject) => {
2261
+ await new Promise((resolve11, reject) => {
2254
2262
  const onAbort = () => {
2255
2263
  clearTimeout(timeoutToken);
2256
2264
  options.signal?.removeEventListener("abort", onAbort);
@@ -2258,7 +2266,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
2258
2266
  };
2259
2267
  const timeoutToken = setTimeout(() => {
2260
2268
  options.signal?.removeEventListener("abort", onAbort);
2261
- resolve10();
2269
+ resolve11();
2262
2270
  }, finalDelay);
2263
2271
  if (options.unref) {
2264
2272
  timeoutToken.unref?.();
@@ -2499,6 +2507,8 @@ var BaseEmbeddingProvider = class {
2499
2507
  this.credentials = credentials;
2500
2508
  this.modelInfo = modelInfo;
2501
2509
  }
2510
+ credentials;
2511
+ modelInfo;
2502
2512
  async embedQuery(query) {
2503
2513
  const result = await this.embedBatch([query]);
2504
2514
  return {
@@ -3676,6 +3686,15 @@ function createMockNativeBinding() {
3676
3686
  close() {
3677
3687
  throw error;
3678
3688
  }
3689
+ getTransitiveReachability() {
3690
+ throw error;
3691
+ }
3692
+ detectCommunities() {
3693
+ throw error;
3694
+ }
3695
+ computeCentrality() {
3696
+ throw error;
3697
+ }
3679
3698
  }
3680
3699
  };
3681
3700
  }
@@ -4226,6 +4245,14 @@ var Database = class {
4226
4245
  this.throwIfClosed();
4227
4246
  return this.inner.getSymbolsByNameCi(name);
4228
4247
  }
4248
+ getSymbolsForBranch(branch) {
4249
+ this.throwIfClosed();
4250
+ return this.inner.getSymbolsForBranch(branch);
4251
+ }
4252
+ getSymbolsForFiles(filePaths, branch) {
4253
+ this.throwIfClosed();
4254
+ return this.inner.getSymbolsForFiles(filePaths, branch);
4255
+ }
4229
4256
  deleteSymbolsByFile(filePath) {
4230
4257
  this.throwIfClosed();
4231
4258
  return this.inner.deleteSymbolsByFile(filePath);
@@ -4239,17 +4266,17 @@ var Database = class {
4239
4266
  if (edges.length === 0) return;
4240
4267
  this.inner.upsertCallEdgesBatch(edges);
4241
4268
  }
4242
- getCallers(targetName, branch) {
4269
+ getCallers(targetName, branch, callTypeFilter) {
4243
4270
  this.throwIfClosed();
4244
- return this.inner.getCallers(targetName, branch);
4271
+ return this.inner.getCallers(targetName, branch, callTypeFilter ?? null);
4245
4272
  }
4246
- getCallersWithContext(targetName, branch) {
4273
+ getCallersWithContext(targetName, branch, callTypeFilter) {
4247
4274
  this.throwIfClosed();
4248
- return this.inner.getCallersWithContext(targetName, branch);
4275
+ return this.inner.getCallersWithContext(targetName, branch, callTypeFilter ?? null);
4249
4276
  }
4250
- getCallees(symbolId, branch) {
4277
+ getCallees(symbolId, branch, callTypeFilter) {
4251
4278
  this.throwIfClosed();
4252
- return this.inner.getCallees(symbolId, branch);
4279
+ return this.inner.getCallees(symbolId, branch, callTypeFilter ?? null);
4253
4280
  }
4254
4281
  deleteCallEdgesByFile(filePath) {
4255
4282
  this.throwIfClosed();
@@ -4259,6 +4286,10 @@ var Database = class {
4259
4286
  this.throwIfClosed();
4260
4287
  this.inner.resolveCallEdge(edgeId, toSymbolId);
4261
4288
  }
4289
+ findShortestPath(fromName, toName, branch, maxDepth) {
4290
+ this.throwIfClosed();
4291
+ return this.inner.findShortestPath(fromName, toName, branch, maxDepth ?? null);
4292
+ }
4262
4293
  addSymbolsToBranch(branch, symbolIds) {
4263
4294
  this.throwIfClosed();
4264
4295
  this.inner.addSymbolsToBranch(branch, symbolIds);
@@ -4299,6 +4330,23 @@ var Database = class {
4299
4330
  this.throwIfClosed();
4300
4331
  return this.inner.gcOrphanCallEdges();
4301
4332
  }
4333
+ getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth) {
4334
+ this.throwIfClosed();
4335
+ return this.inner.getTransitiveReachability(
4336
+ rootSymbolIds,
4337
+ branch,
4338
+ direction,
4339
+ maxDepth ?? null
4340
+ );
4341
+ }
4342
+ detectCommunities(branch, symbolIds) {
4343
+ this.throwIfClosed();
4344
+ return this.inner.detectCommunities(branch, symbolIds ?? null);
4345
+ }
4346
+ computeCentrality(branch) {
4347
+ this.throwIfClosed();
4348
+ return this.inner.computeCentrality(branch);
4349
+ }
4302
4350
  };
4303
4351
 
4304
4352
  // src/git/index.ts
@@ -4472,6 +4520,106 @@ function resolveProjectIndexPath(projectRoot, scope) {
4472
4520
  return resolveWorktreeFallbackPath(projectRoot, PROJECT_INDEX_RELATIVE_PATH) ?? localIndexPath;
4473
4521
  }
4474
4522
 
4523
+ // src/tools/changed-files.ts
4524
+ var path9 = __toESM(require("path"), 1);
4525
+ var import_child_process = require("child_process");
4526
+ var import_util = require("util");
4527
+ var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
4528
+ function getErrorMessage(error) {
4529
+ if (error instanceof Error) return error.message;
4530
+ return String(error);
4531
+ }
4532
+ async function getChangedFiles(opts) {
4533
+ const { pr, branch, projectRoot, baseBranch = "main" } = opts;
4534
+ if (pr !== void 0) {
4535
+ return getChangedFilesForPr(pr, projectRoot, baseBranch);
4536
+ }
4537
+ return getChangedFilesForBranch(branch, projectRoot, baseBranch);
4538
+ }
4539
+ async function getChangedFilesForPr(pr, projectRoot, baseBranch) {
4540
+ let headRefName;
4541
+ let actualBaseBranch = baseBranch;
4542
+ try {
4543
+ const { stdout } = await execFileAsync(
4544
+ "gh",
4545
+ ["pr", "view", String(pr), "--json", "headRefName,baseRefName,files"],
4546
+ { cwd: projectRoot, timeout: 3e4 }
4547
+ );
4548
+ const data = JSON.parse(stdout);
4549
+ headRefName = data.headRefName;
4550
+ actualBaseBranch = data.baseRefName || baseBranch;
4551
+ if (data.files && data.files.length > 0) {
4552
+ return {
4553
+ files: normalizeFiles(
4554
+ data.files.map((f) => f.path),
4555
+ projectRoot
4556
+ ),
4557
+ baseBranch: actualBaseBranch,
4558
+ source: "gh",
4559
+ headRefName
4560
+ };
4561
+ }
4562
+ } catch (error) {
4563
+ throw new Error(
4564
+ `Failed to retrieve PR #${pr} via gh CLI: ${getErrorMessage(error)}`
4565
+ );
4566
+ }
4567
+ if (headRefName === void 0) {
4568
+ throw new Error(
4569
+ `PR #${pr} returned no usable branch or file information.`
4570
+ );
4571
+ }
4572
+ const result = await getChangedFilesForBranch(headRefName, projectRoot, actualBaseBranch);
4573
+ return { ...result, headRefName };
4574
+ }
4575
+ async function getChangedFilesForBranch(branch, projectRoot, baseBranch) {
4576
+ const targetBranch = branch || await getCurrentBranch2(projectRoot);
4577
+ const mergeBase = await getMergeBase(projectRoot, baseBranch, targetBranch);
4578
+ const { stdout } = await execFileAsync(
4579
+ "git",
4580
+ ["diff", "--name-only", `${mergeBase}...${targetBranch}`],
4581
+ { cwd: projectRoot, timeout: 3e4 }
4582
+ );
4583
+ return {
4584
+ files: normalizeFiles(stdout.split("\n"), projectRoot),
4585
+ baseBranch,
4586
+ source: "git",
4587
+ headRefName: targetBranch
4588
+ };
4589
+ }
4590
+ async function getCurrentBranch2(projectRoot) {
4591
+ const { stdout } = await execFileAsync(
4592
+ "git",
4593
+ ["branch", "--show-current"],
4594
+ { cwd: projectRoot, timeout: 3e4 }
4595
+ );
4596
+ return stdout.trim() || "HEAD";
4597
+ }
4598
+ async function getMergeBase(projectRoot, baseBranch, branch) {
4599
+ const { stdout } = await execFileAsync(
4600
+ "git",
4601
+ ["merge-base", baseBranch, branch],
4602
+ { cwd: projectRoot, timeout: 3e4 }
4603
+ );
4604
+ return stdout.trim();
4605
+ }
4606
+ function normalizeFiles(rawFiles, projectRoot) {
4607
+ const seen = /* @__PURE__ */ new Set();
4608
+ const result = [];
4609
+ for (const raw of rawFiles) {
4610
+ const trimmed = raw.trim();
4611
+ if (!trimmed) continue;
4612
+ const absolute = path9.resolve(projectRoot, trimmed);
4613
+ const relative5 = path9.relative(projectRoot, absolute);
4614
+ const cleaned = relative5.startsWith("./") ? relative5.slice(2) : relative5;
4615
+ if (!seen.has(cleaned)) {
4616
+ seen.add(cleaned);
4617
+ result.push(cleaned);
4618
+ }
4619
+ }
4620
+ return result;
4621
+ }
4622
+
4475
4623
  // src/indexer/index.ts
4476
4624
  var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
4477
4625
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
@@ -4479,6 +4627,7 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
4479
4627
  "function_declaration",
4480
4628
  "function",
4481
4629
  "arrow_function",
4630
+ "export_statement",
4482
4631
  "method_definition",
4483
4632
  "class_declaration",
4484
4633
  "interface_declaration",
@@ -4517,7 +4666,7 @@ function float32ArrayToBuffer(arr) {
4517
4666
  function bufferToFloat32Array(buf) {
4518
4667
  return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
4519
4668
  }
4520
- function getErrorMessage(error) {
4669
+ function getErrorMessage2(error) {
4521
4670
  if (error instanceof Error) {
4522
4671
  return error.message;
4523
4672
  }
@@ -4530,7 +4679,7 @@ function getErrorMessage(error) {
4530
4679
  return String(error);
4531
4680
  }
4532
4681
  function isRateLimitError(error) {
4533
- const message = getErrorMessage(error);
4682
+ const message = getErrorMessage2(error);
4534
4683
  return message.includes("429") || message.toLowerCase().includes("rate limit") || message.toLowerCase().includes("too many requests");
4535
4684
  }
4536
4685
  function getSafeEmbeddingChunkTokenLimit(provider) {
@@ -4548,7 +4697,7 @@ function getDynamicBatchOptions(provider) {
4548
4697
  return {};
4549
4698
  }
4550
4699
  function isSqliteCorruptionError(error) {
4551
- const message = getErrorMessage(error).toLowerCase();
4700
+ const message = getErrorMessage2(error).toLowerCase();
4552
4701
  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");
4553
4702
  }
4554
4703
  var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
@@ -4710,9 +4859,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
4710
4859
  return true;
4711
4860
  }
4712
4861
  function isPathWithinRoot(filePath, rootPath) {
4713
- const normalizedFilePath = path9.resolve(filePath);
4714
- const normalizedRoot = path9.resolve(rootPath);
4715
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path9.sep}`);
4862
+ const normalizedFilePath = path10.resolve(filePath);
4863
+ const normalizedRoot = path10.resolve(rootPath);
4864
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path10.sep}`);
4716
4865
  }
4717
4866
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
4718
4867
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -5765,9 +5914,9 @@ var Indexer = class {
5765
5914
  this.projectRoot = projectRoot;
5766
5915
  this.config = config;
5767
5916
  this.indexPath = this.getIndexPath();
5768
- this.fileHashCachePath = path9.join(this.indexPath, "file-hashes.json");
5769
- this.failedBatchesPath = path9.join(this.indexPath, "failed-batches.json");
5770
- this.indexingLockPath = path9.join(this.indexPath, "indexing.lock");
5917
+ this.fileHashCachePath = path10.join(this.indexPath, "file-hashes.json");
5918
+ this.failedBatchesPath = path10.join(this.indexPath, "failed-batches.json");
5919
+ this.indexingLockPath = path10.join(this.indexPath, "indexing.lock");
5771
5920
  this.logger = initializeLogger(config.debug);
5772
5921
  }
5773
5922
  getIndexPath() {
@@ -5799,14 +5948,14 @@ var Indexer = class {
5799
5948
  }
5800
5949
  atomicWriteSync(targetPath, data) {
5801
5950
  const tempPath = `${targetPath}.tmp`;
5802
- (0, import_fs7.mkdirSync)(path9.dirname(targetPath), { recursive: true });
5951
+ (0, import_fs7.mkdirSync)(path10.dirname(targetPath), { recursive: true });
5803
5952
  (0, import_fs7.writeFileSync)(tempPath, data);
5804
5953
  (0, import_fs7.renameSync)(tempPath, targetPath);
5805
5954
  }
5806
5955
  getScopedRoots() {
5807
- const roots = /* @__PURE__ */ new Set([path9.resolve(this.projectRoot)]);
5956
+ const roots = /* @__PURE__ */ new Set([path10.resolve(this.projectRoot)]);
5808
5957
  for (const kbRoot of this.config.knowledgeBases) {
5809
- roots.add(path9.resolve(this.projectRoot, kbRoot));
5958
+ roots.add(path10.resolve(this.projectRoot, kbRoot));
5810
5959
  }
5811
5960
  return Array.from(roots);
5812
5961
  }
@@ -5815,22 +5964,29 @@ var Indexer = class {
5815
5964
  if (this.config.scope !== "global") {
5816
5965
  return branchName;
5817
5966
  }
5818
- const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
5967
+ const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
5968
+ return `${projectHash}:${branchName}`;
5969
+ }
5970
+ getBranchCatalogKeyFor(branchName) {
5971
+ if (this.config.scope !== "global") {
5972
+ return branchName;
5973
+ }
5974
+ const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
5819
5975
  return `${projectHash}:${branchName}`;
5820
5976
  }
5821
5977
  getLegacyBranchCatalogKey() {
5822
5978
  return this.currentBranch || "default";
5823
5979
  }
5824
5980
  getLegacyMigrationMetadataKey() {
5825
- const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
5981
+ const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
5826
5982
  return `index.globalBranchMigration.${projectHash}`;
5827
5983
  }
5828
5984
  getProjectEmbeddingStrategyMetadataKey() {
5829
- const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
5985
+ const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
5830
5986
  return `index.embeddingStrategyVersion.${projectHash}`;
5831
5987
  }
5832
5988
  getProjectForceReembedMetadataKey() {
5833
- const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
5989
+ const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
5834
5990
  return `index.forceReembed.${projectHash}`;
5835
5991
  }
5836
5992
  hasProjectForceReembedPending() {
@@ -5924,7 +6080,7 @@ var Indexer = class {
5924
6080
  if (!this.database) {
5925
6081
  return { chunkIds, symbolIds };
5926
6082
  }
5927
- const projectRootPath = path9.resolve(this.projectRoot);
6083
+ const projectRootPath = path10.resolve(this.projectRoot);
5928
6084
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
5929
6085
  ...Array.from(this.fileHashCache.keys()).filter(
5930
6086
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -5947,7 +6103,7 @@ var Indexer = class {
5947
6103
  if (this.config.scope !== "global") {
5948
6104
  return this.getBranchCatalogCleanupKeys();
5949
6105
  }
5950
- const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
6106
+ const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
5951
6107
  const keys = /* @__PURE__ */ new Set();
5952
6108
  const projectChunkIdSet = new Set(projectChunkIds);
5953
6109
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -6031,7 +6187,7 @@ var Indexer = class {
6031
6187
  if (!this.database || this.config.scope !== "global") {
6032
6188
  return false;
6033
6189
  }
6034
- const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
6190
+ const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
6035
6191
  const roots = this.getScopedRoots();
6036
6192
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
6037
6193
  return this.database.getAllBranches().some(
@@ -6065,7 +6221,7 @@ var Indexer = class {
6065
6221
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
6066
6222
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
6067
6223
  ]);
6068
- const projectRootPath = path9.resolve(this.projectRoot);
6224
+ const projectRootPath = path10.resolve(this.projectRoot);
6069
6225
  const projectLocalFilePaths = new Set(
6070
6226
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
6071
6227
  );
@@ -6318,7 +6474,7 @@ var Indexer = class {
6318
6474
  this.logger.search("warn", "External reranker failed; using deterministic order", {
6319
6475
  provider: reranker.provider,
6320
6476
  model: reranker.model,
6321
- error: getErrorMessage(error)
6477
+ error: getErrorMessage2(error)
6322
6478
  });
6323
6479
  return candidates;
6324
6480
  }
@@ -6425,13 +6581,13 @@ var Indexer = class {
6425
6581
  }
6426
6582
  await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
6427
6583
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
6428
- const storePath = path9.join(this.indexPath, "vectors");
6584
+ const storePath = path10.join(this.indexPath, "vectors");
6429
6585
  this.store = new VectorStore(storePath, dimensions);
6430
- const indexFilePath = path9.join(this.indexPath, "vectors.usearch");
6586
+ const indexFilePath = path10.join(this.indexPath, "vectors.usearch");
6431
6587
  if ((0, import_fs7.existsSync)(indexFilePath)) {
6432
6588
  this.store.load();
6433
6589
  }
6434
- const invertedIndexPath = path9.join(this.indexPath, "inverted-index.json");
6590
+ const invertedIndexPath = path10.join(this.indexPath, "inverted-index.json");
6435
6591
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6436
6592
  try {
6437
6593
  this.invertedIndex.load();
@@ -6441,7 +6597,7 @@ var Indexer = class {
6441
6597
  }
6442
6598
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6443
6599
  }
6444
- const dbPath = path9.join(this.indexPath, "codebase.db");
6600
+ const dbPath = path10.join(this.indexPath, "codebase.db");
6445
6601
  let dbIsNew = !(0, import_fs7.existsSync)(dbPath);
6446
6602
  try {
6447
6603
  this.database = new Database(dbPath);
@@ -6522,7 +6678,7 @@ var Indexer = class {
6522
6678
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
6523
6679
  return {
6524
6680
  resetCorruptedIndex: true,
6525
- warning: this.getCorruptedIndexWarning(path9.join(this.indexPath, "codebase.db"))
6681
+ warning: this.getCorruptedIndexWarning(path10.join(this.indexPath, "codebase.db"))
6526
6682
  };
6527
6683
  }
6528
6684
  throw error;
@@ -6537,7 +6693,7 @@ var Indexer = class {
6537
6693
  return;
6538
6694
  }
6539
6695
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
6540
- const storeBasePath = path9.join(this.indexPath, "vectors");
6696
+ const storeBasePath = path10.join(this.indexPath, "vectors");
6541
6697
  const storeIndexPath = `${storeBasePath}.usearch`;
6542
6698
  const storeMetadataPath = `${storeBasePath}.meta.json`;
6543
6699
  const backupIndexPath = `${storeIndexPath}.bak`;
@@ -6622,9 +6778,9 @@ var Indexer = class {
6622
6778
  if (!isSqliteCorruptionError(error)) {
6623
6779
  return false;
6624
6780
  }
6625
- const dbPath = path9.join(this.indexPath, "codebase.db");
6781
+ const dbPath = path10.join(this.indexPath, "codebase.db");
6626
6782
  const warning = this.getCorruptedIndexWarning(dbPath);
6627
- const errorMessage = getErrorMessage(error);
6783
+ const errorMessage = getErrorMessage2(error);
6628
6784
  if (this.config.scope === "global") {
6629
6785
  this.logger.error("Detected corrupted shared global index database", {
6630
6786
  stage,
@@ -6645,15 +6801,15 @@ var Indexer = class {
6645
6801
  this.indexCompatibility = null;
6646
6802
  this.fileHashCache.clear();
6647
6803
  const resetPaths = [
6648
- path9.join(this.indexPath, "codebase.db"),
6649
- path9.join(this.indexPath, "codebase.db-shm"),
6650
- path9.join(this.indexPath, "codebase.db-wal"),
6651
- path9.join(this.indexPath, "vectors.usearch"),
6652
- path9.join(this.indexPath, "inverted-index.json"),
6653
- path9.join(this.indexPath, "file-hashes.json"),
6654
- path9.join(this.indexPath, "failed-batches.json"),
6655
- path9.join(this.indexPath, "indexing.lock"),
6656
- path9.join(this.indexPath, "vectors")
6804
+ path10.join(this.indexPath, "codebase.db"),
6805
+ path10.join(this.indexPath, "codebase.db-shm"),
6806
+ path10.join(this.indexPath, "codebase.db-wal"),
6807
+ path10.join(this.indexPath, "vectors.usearch"),
6808
+ path10.join(this.indexPath, "inverted-index.json"),
6809
+ path10.join(this.indexPath, "file-hashes.json"),
6810
+ path10.join(this.indexPath, "failed-batches.json"),
6811
+ path10.join(this.indexPath, "indexing.lock"),
6812
+ path10.join(this.indexPath, "vectors")
6657
6813
  ];
6658
6814
  await Promise.all(resetPaths.map(async (targetPath) => {
6659
6815
  try {
@@ -6918,7 +7074,7 @@ var Indexer = class {
6918
7074
  for (const parsed of parsedFiles) {
6919
7075
  currentFilePaths.add(parsed.path);
6920
7076
  if (parsed.chunks.length === 0) {
6921
- const relativePath = path9.relative(this.projectRoot, parsed.path);
7077
+ const relativePath = path10.relative(this.projectRoot, parsed.path);
6922
7078
  stats.parseFailures.push(relativePath);
6923
7079
  }
6924
7080
  let fileChunkCount = 0;
@@ -7057,6 +7213,7 @@ var Indexer = class {
7057
7213
  targetName: site.calleeName,
7058
7214
  toSymbolId: void 0,
7059
7215
  callType: site.callType,
7216
+ confidence: site.confidence,
7060
7217
  line: site.line,
7061
7218
  col: site.column,
7062
7219
  isResolved: false
@@ -7201,7 +7358,7 @@ var Indexer = class {
7201
7358
  for (const requestBatch of requestBatches) {
7202
7359
  queue.add(async () => {
7203
7360
  if (rateLimitBackoffMs > 0) {
7204
- await new Promise((resolve10) => setTimeout(resolve10, rateLimitBackoffMs));
7361
+ await new Promise((resolve11) => setTimeout(resolve11, rateLimitBackoffMs));
7205
7362
  }
7206
7363
  try {
7207
7364
  const result = await pRetry(
@@ -7216,7 +7373,7 @@ var Indexer = class {
7216
7373
  factor: 2,
7217
7374
  shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError),
7218
7375
  onFailedAttempt: (error) => {
7219
- const message = getErrorMessage(error);
7376
+ const message = getErrorMessage2(error);
7220
7377
  if (isRateLimitError(error)) {
7221
7378
  rateLimitBackoffMs = Math.min(providerRateLimits.maxRetryMs, (rateLimitBackoffMs || providerRateLimits.minRetryMs) * 2);
7222
7379
  this.logger.embedding("warn", `Rate limited, backing off`, {
@@ -7323,7 +7480,7 @@ var Indexer = class {
7323
7480
  });
7324
7481
  } catch (error) {
7325
7482
  const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id));
7326
- const failureMessage = getErrorMessage(error);
7483
+ const failureMessage = getErrorMessage2(error);
7327
7484
  const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
7328
7485
  for (const chunk of failedChunks) {
7329
7486
  if (!failedChunkIds.has(chunk.id)) {
@@ -7762,8 +7919,8 @@ var Indexer = class {
7762
7919
  this.indexCompatibility = compatibility;
7763
7920
  return;
7764
7921
  }
7765
- const localProjectIndexPath = path9.join(this.projectRoot, ".opencode", "index");
7766
- if (path9.resolve(this.indexPath) !== path9.resolve(localProjectIndexPath)) {
7922
+ const localProjectIndexPath = path10.join(this.projectRoot, ".opencode", "index");
7923
+ if (path10.resolve(this.indexPath) !== path10.resolve(localProjectIndexPath)) {
7767
7924
  throw new Error(
7768
7925
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
7769
7926
  );
@@ -7851,7 +8008,7 @@ var Indexer = class {
7851
8008
  gcOrphanSymbols: 0,
7852
8009
  gcOrphanCallEdges: 0,
7853
8010
  resetCorruptedIndex: true,
7854
- warning: this.getCorruptedIndexWarning(path9.join(this.indexPath, "codebase.db"))
8011
+ warning: this.getCorruptedIndexWarning(path10.join(this.indexPath, "codebase.db"))
7855
8012
  };
7856
8013
  }
7857
8014
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -8002,7 +8159,7 @@ var Indexer = class {
8002
8159
  succeeded += successfulResults.length;
8003
8160
  stillFailing.push(...failedChunksForBatch.values());
8004
8161
  } catch (error) {
8005
- const failureMessage = getErrorMessage(error);
8162
+ const failureMessage = getErrorMessage2(error);
8006
8163
  const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
8007
8164
  const unaccountedChunks = batch.chunks.filter(
8008
8165
  (chunk) => !failedChunksForBatch.has(chunk.id) && !completedChunkIds.has(chunk.id)
@@ -8176,12 +8333,12 @@ var Indexer = class {
8176
8333
  })
8177
8334
  );
8178
8335
  }
8179
- async getCallers(targetName) {
8336
+ async getCallers(targetName, callTypeFilter) {
8180
8337
  const { database } = await this.ensureInitialized();
8181
8338
  const seen = /* @__PURE__ */ new Set();
8182
8339
  const results = [];
8183
8340
  for (const branchKey of this.getBranchCatalogKeys()) {
8184
- for (const edge of database.getCallersWithContext(targetName, branchKey)) {
8341
+ for (const edge of database.getCallersWithContext(targetName, branchKey, callTypeFilter)) {
8185
8342
  if (!seen.has(edge.id)) {
8186
8343
  seen.add(edge.id);
8187
8344
  results.push(edge);
@@ -8190,12 +8347,12 @@ var Indexer = class {
8190
8347
  }
8191
8348
  return results;
8192
8349
  }
8193
- async getCallees(symbolId) {
8350
+ async getCallees(symbolId, callTypeFilter) {
8194
8351
  const { database } = await this.ensureInitialized();
8195
8352
  const seen = /* @__PURE__ */ new Set();
8196
8353
  const results = [];
8197
8354
  for (const branchKey of this.getBranchCatalogKeys()) {
8198
- for (const edge of database.getCallees(symbolId, branchKey)) {
8355
+ for (const edge of database.getCallees(symbolId, branchKey, callTypeFilter)) {
8199
8356
  if (!seen.has(edge.id)) {
8200
8357
  seen.add(edge.id);
8201
8358
  results.push(edge);
@@ -8204,6 +8361,198 @@ var Indexer = class {
8204
8361
  }
8205
8362
  return results;
8206
8363
  }
8364
+ async findCallPath(fromName, toName, maxDepth) {
8365
+ const { database } = await this.ensureInitialized();
8366
+ let shortest = [];
8367
+ for (const branchKey of this.getBranchCatalogKeys()) {
8368
+ const path19 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
8369
+ if (path19.length > 0 && (shortest.length === 0 || path19.length < shortest.length)) {
8370
+ shortest = path19;
8371
+ }
8372
+ }
8373
+ return shortest;
8374
+ }
8375
+ async getSymbolsForBranch(branch) {
8376
+ const { database } = await this.ensureInitialized();
8377
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
8378
+ return database.getSymbolsForBranch(resolvedBranch);
8379
+ }
8380
+ async getSymbolsForFiles(filePaths, branch) {
8381
+ const { database } = await this.ensureInitialized();
8382
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
8383
+ return database.getSymbolsForFiles(filePaths, resolvedBranch);
8384
+ }
8385
+ async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
8386
+ const { database } = await this.ensureInitialized();
8387
+ const branch = this.getBranchCatalogKey();
8388
+ return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
8389
+ }
8390
+ async detectCommunities(branch, symbolIds) {
8391
+ const { database } = await this.ensureInitialized();
8392
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
8393
+ return database.detectCommunities(resolvedBranch, symbolIds);
8394
+ }
8395
+ async computeCentrality(branch) {
8396
+ const { database } = await this.ensureInitialized();
8397
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
8398
+ return database.computeCentrality(resolvedBranch);
8399
+ }
8400
+ async getPrImpact(opts) {
8401
+ const { database } = await this.ensureInitialized();
8402
+ const execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
8403
+ const changedFilesResult = await getChangedFiles({
8404
+ pr: opts.pr,
8405
+ branch: opts.branch,
8406
+ projectRoot: this.projectRoot,
8407
+ baseBranch: this.baseBranch
8408
+ });
8409
+ const changedFiles = changedFilesResult.files;
8410
+ const headRefName = changedFilesResult.headRefName;
8411
+ if (opts.pr !== void 0 && headRefName === void 0) {
8412
+ throw new Error(
8413
+ `Could not resolve head branch for PR #${opts.pr}. Run index_codebase on the PR branch first.`
8414
+ );
8415
+ }
8416
+ const resolvedBranch = opts.pr !== void 0 ? headRefName : opts.branch || this.currentBranch;
8417
+ const branchKey = this.getBranchCatalogKeyFor(resolvedBranch || "default");
8418
+ const branchSymbols = database.getSymbolsForBranch(branchKey);
8419
+ if (branchSymbols.length === 0) {
8420
+ throw new Error(
8421
+ "Run index_codebase first to build the call graph and symbol index for this branch."
8422
+ );
8423
+ }
8424
+ const absoluteChangedFiles = changedFiles.map((f) => path10.resolve(this.projectRoot, f));
8425
+ const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
8426
+ const directIds = directSymbols.map((s) => s.id);
8427
+ const direction = opts.direction ?? "both";
8428
+ const maxDepth = opts.maxDepth ?? 5;
8429
+ const transitiveCallers = database.getTransitiveReachability(
8430
+ directIds,
8431
+ branchKey,
8432
+ direction,
8433
+ maxDepth
8434
+ );
8435
+ const affectedIdsSet = new Set(directIds);
8436
+ for (const caller of transitiveCallers) {
8437
+ affectedIdsSet.add(caller.symbolId);
8438
+ }
8439
+ const allAffectedIds = Array.from(affectedIdsSet);
8440
+ const communitiesData = database.detectCommunities(branchKey, allAffectedIds);
8441
+ const communityMap = /* @__PURE__ */ new Map();
8442
+ for (const c of communitiesData) {
8443
+ if (!communityMap.has(c.communityLabel)) {
8444
+ communityMap.set(c.communityLabel, {
8445
+ label: c.communityLabel,
8446
+ symbolCount: 0,
8447
+ directSymbols: /* @__PURE__ */ new Set()
8448
+ });
8449
+ }
8450
+ const entry = communityMap.get(c.communityLabel);
8451
+ entry.symbolCount++;
8452
+ if (directIds.includes(c.symbolId)) {
8453
+ entry.directSymbols.add(c.symbolId);
8454
+ }
8455
+ }
8456
+ const communities = Array.from(communityMap.values()).map((c) => ({
8457
+ label: c.label,
8458
+ symbolCount: c.symbolCount,
8459
+ directSymbols: Array.from(c.directSymbols)
8460
+ }));
8461
+ const centralityData = database.computeCentrality(branchKey);
8462
+ const hubThreshold = opts.hubThreshold ?? 10;
8463
+ const hubNodes = centralityData.filter((c) => directIds.includes(c.symbolId) && c.callerCount >= hubThreshold).map((c) => ({
8464
+ id: c.symbolId,
8465
+ name: c.symbolName,
8466
+ callerCount: c.callerCount,
8467
+ filePath: c.filePath
8468
+ }));
8469
+ const totalAffected = allAffectedIds.length;
8470
+ let riskLevel;
8471
+ let riskReason;
8472
+ if (totalAffected < 5 && hubNodes.length === 0) {
8473
+ riskLevel = "LOW";
8474
+ riskReason = `Small impact: ${totalAffected} affected symbols, no hub nodes touched.`;
8475
+ } else if (totalAffected > 20 || hubNodes.length > 1) {
8476
+ riskLevel = "HIGH";
8477
+ riskReason = `Large impact: ${totalAffected} affected symbols${hubNodes.length > 0 ? `, ${hubNodes.length} hub nodes touched` : ""}.`;
8478
+ } else {
8479
+ riskLevel = "MEDIUM";
8480
+ riskReason = `Moderate impact: ${totalAffected} affected symbols${hubNodes.length === 1 ? ", 1 hub node touched" : ""}.`;
8481
+ }
8482
+ let conflictingPRs;
8483
+ if (opts.checkConflicts) {
8484
+ conflictingPRs = [];
8485
+ try {
8486
+ const { stdout } = await execFileAsync2(
8487
+ "gh",
8488
+ ["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
8489
+ { cwd: this.projectRoot, timeout: 3e4 }
8490
+ );
8491
+ const openPRs = JSON.parse(stdout);
8492
+ const currentCommunityLabels = new Set(communities.map((c) => c.label));
8493
+ const allCommunitiesData = database.detectCommunities(branchKey);
8494
+ const symbolToCommunity = /* @__PURE__ */ new Map();
8495
+ const structuralKey = (filePath, name) => `${filePath.toLowerCase()}:${name.toLowerCase()}`;
8496
+ for (const c of allCommunitiesData) {
8497
+ symbolToCommunity.set(structuralKey(c.filePath, c.symbolName), c.communityLabel);
8498
+ }
8499
+ for (const openPr of openPRs) {
8500
+ if (openPr.number === opts.pr) continue;
8501
+ try {
8502
+ const otherChanged = await getChangedFiles({
8503
+ pr: openPr.number,
8504
+ projectRoot: this.projectRoot,
8505
+ baseBranch: this.baseBranch
8506
+ });
8507
+ const otherAbsolute = otherChanged.files.map((f) => path10.resolve(this.projectRoot, f));
8508
+ const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
8509
+ const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
8510
+ const otherLabels = /* @__PURE__ */ new Set();
8511
+ for (const sym of otherSymbols) {
8512
+ const label = symbolToCommunity.get(structuralKey(sym.filePath, sym.name));
8513
+ if (label) {
8514
+ otherLabels.add(label);
8515
+ }
8516
+ }
8517
+ const overlapping = Array.from(otherLabels).filter(
8518
+ (l) => currentCommunityLabels.has(l)
8519
+ );
8520
+ if (overlapping.length > 0) {
8521
+ conflictingPRs.push({
8522
+ pr: openPr.number,
8523
+ branch: openPr.headRefName,
8524
+ overlappingCommunities: overlapping
8525
+ });
8526
+ }
8527
+ } catch {
8528
+ }
8529
+ }
8530
+ } catch {
8531
+ }
8532
+ }
8533
+ return {
8534
+ changedFiles,
8535
+ directSymbols: directSymbols.map((s) => ({
8536
+ id: s.id,
8537
+ name: s.name,
8538
+ kind: s.kind,
8539
+ filePath: s.filePath
8540
+ })),
8541
+ transitiveCallers: transitiveCallers.map((c) => ({
8542
+ id: c.symbolId,
8543
+ name: c.symbolName,
8544
+ filePath: c.filePath,
8545
+ depth: c.depth
8546
+ })),
8547
+ totalAffected,
8548
+ communities,
8549
+ hubNodes,
8550
+ riskLevel,
8551
+ riskReason,
8552
+ direction,
8553
+ conflictingPRs
8554
+ };
8555
+ }
8207
8556
  async close() {
8208
8557
  await this.database?.close();
8209
8558
  this.database = null;
@@ -8463,13 +8812,13 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
8463
8812
  // src/eval/runner-config.ts
8464
8813
  var import_fs8 = require("fs");
8465
8814
  var os4 = __toESM(require("os"), 1);
8466
- var path11 = __toESM(require("path"), 1);
8815
+ var path12 = __toESM(require("path"), 1);
8467
8816
 
8468
8817
  // src/config/rebase.ts
8469
- var path10 = __toESM(require("path"), 1);
8818
+ var path11 = __toESM(require("path"), 1);
8470
8819
  function isWithinRoot(rootDir, targetPath) {
8471
- const relativePath = path10.relative(rootDir, targetPath);
8472
- return relativePath === "" || !relativePath.startsWith("..") && !path10.isAbsolute(relativePath);
8820
+ const relativePath = path11.relative(rootDir, targetPath);
8821
+ return relativePath === "" || !relativePath.startsWith("..") && !path11.isAbsolute(relativePath);
8473
8822
  }
8474
8823
  function rebasePathEntries(values, fromDir, toDir) {
8475
8824
  if (!Array.isArray(values)) {
@@ -8477,10 +8826,10 @@ function rebasePathEntries(values, fromDir, toDir) {
8477
8826
  }
8478
8827
  return values.filter((value) => typeof value === "string").map((value) => {
8479
8828
  const trimmed = value.trim();
8480
- if (!trimmed || path10.isAbsolute(trimmed)) {
8829
+ if (!trimmed || path11.isAbsolute(trimmed)) {
8481
8830
  return trimmed;
8482
8831
  }
8483
- return normalizePathSeparators(path10.normalize(path10.relative(toDir, path10.resolve(fromDir, trimmed))));
8832
+ return normalizePathSeparators(path11.normalize(path11.relative(toDir, path11.resolve(fromDir, trimmed))));
8484
8833
  }).filter(Boolean);
8485
8834
  }
8486
8835
  function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
@@ -8492,17 +8841,17 @@ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
8492
8841
  if (!trimmed) {
8493
8842
  return trimmed;
8494
8843
  }
8495
- if (path10.isAbsolute(trimmed)) {
8844
+ if (path11.isAbsolute(trimmed)) {
8496
8845
  if (isWithinRoot(sourceRoot, trimmed)) {
8497
- return normalizePathSeparators(path10.normalize(path10.relative(sourceRoot, trimmed) || "."));
8846
+ return normalizePathSeparators(path11.normalize(path11.relative(sourceRoot, trimmed) || "."));
8498
8847
  }
8499
- return path10.normalize(trimmed);
8848
+ return path11.normalize(trimmed);
8500
8849
  }
8501
- const resolvedFromSource = path10.resolve(sourceRoot, trimmed);
8850
+ const resolvedFromSource = path11.resolve(sourceRoot, trimmed);
8502
8851
  if (isWithinRoot(sourceRoot, resolvedFromSource)) {
8503
- return normalizePathSeparators(path10.normalize(trimmed));
8852
+ return normalizePathSeparators(path11.normalize(trimmed));
8504
8853
  }
8505
- return normalizePathSeparators(path10.normalize(path10.relative(targetRoot, resolvedFromSource)));
8854
+ return normalizePathSeparators(path11.normalize(path11.relative(targetRoot, resolvedFromSource)));
8506
8855
  }).filter(Boolean);
8507
8856
  }
8508
8857
 
@@ -8550,20 +8899,20 @@ function parseJsonConfigFile(filePath) {
8550
8899
  }
8551
8900
  }
8552
8901
  function toAbsolute(projectRoot, maybeRelative) {
8553
- return path11.isAbsolute(maybeRelative) ? maybeRelative : path11.join(projectRoot, maybeRelative);
8902
+ return path12.isAbsolute(maybeRelative) ? maybeRelative : path12.join(projectRoot, maybeRelative);
8554
8903
  }
8555
8904
  function isProjectScopedConfigPath(configPath) {
8556
- return path11.basename(configPath) === "codebase-index.json" && path11.basename(path11.dirname(configPath)) === ".opencode";
8905
+ return path12.basename(configPath) === "codebase-index.json" && path12.basename(path12.dirname(configPath)) === ".opencode";
8557
8906
  }
8558
8907
  function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfigPath) {
8559
8908
  const config = rawConfig && typeof rawConfig === "object" ? { ...rawConfig } : {};
8560
8909
  const rebaseEntries = (values) => isProjectScopedConfigPath(resolvedConfigPath) ? resolveInheritedKnowledgeBaseEntries(
8561
8910
  values,
8562
- path11.dirname(path11.dirname(resolvedConfigPath)),
8911
+ path12.dirname(path12.dirname(resolvedConfigPath)),
8563
8912
  projectRoot
8564
8913
  ) : rebasePathEntries(
8565
8914
  values,
8566
- path11.dirname(resolvedConfigPath),
8915
+ path12.dirname(resolvedConfigPath),
8567
8916
  projectRoot
8568
8917
  );
8569
8918
  if (Array.isArray(config.knowledgeBases)) {
@@ -8591,7 +8940,7 @@ function loadRawConfig(projectRoot, configPath) {
8591
8940
  projectConfig
8592
8941
  );
8593
8942
  }
8594
- const globalConfig = path11.join(os4.homedir(), ".config", "opencode", "codebase-index.json");
8943
+ const globalConfig = path12.join(os4.homedir(), ".config", "opencode", "codebase-index.json");
8595
8944
  if ((0, import_fs8.existsSync)(globalConfig)) {
8596
8945
  return parseJsonConfigFile(globalConfig);
8597
8946
  }
@@ -8601,10 +8950,10 @@ function getIndexRootPath(projectRoot, scope) {
8601
8950
  return scope === "global" ? getGlobalIndexPath() : resolveProjectIndexPath(projectRoot, scope);
8602
8951
  }
8603
8952
  function getLocalProjectIndexRoot(projectRoot) {
8604
- return path11.join(projectRoot, ".opencode", "index");
8953
+ return path12.join(projectRoot, ".opencode", "index");
8605
8954
  }
8606
8955
  function getLocalProjectConfigPath(projectRoot) {
8607
- return path11.join(projectRoot, ".opencode", "codebase-index.json");
8956
+ return path12.join(projectRoot, ".opencode", "codebase-index.json");
8608
8957
  }
8609
8958
  function clearIndexRoot(projectRoot, scope) {
8610
8959
  const indexRoot = scope === "global" ? getIndexRootPath(projectRoot, scope) : getLocalProjectIndexRoot(projectRoot);
@@ -8626,7 +8975,7 @@ function ensureLocalEvalProjectConfig(projectRoot, configPath) {
8626
8975
  projectRoot,
8627
8976
  resolvedConfigPath
8628
8977
  );
8629
- (0, import_fs8.mkdirSync)(path11.dirname(localConfigPath), { recursive: true });
8978
+ (0, import_fs8.mkdirSync)(path12.dirname(localConfigPath), { recursive: true });
8630
8979
  (0, import_fs8.writeFileSync)(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
8631
8980
  return localConfigPath;
8632
8981
  }
@@ -8676,23 +9025,23 @@ function isRecord2(value) {
8676
9025
  function isStringArray3(value) {
8677
9026
  return Array.isArray(value) && value.every((item) => typeof item === "string");
8678
9027
  }
8679
- function asPositiveNumber(value, path18) {
9028
+ function asPositiveNumber(value, path19) {
8680
9029
  if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
8681
- throw new Error(`${path18} must be a non-negative number`);
9030
+ throw new Error(`${path19} must be a non-negative number`);
8682
9031
  }
8683
9032
  return value;
8684
9033
  }
8685
- function parseQueryType(value, path18) {
9034
+ function parseQueryType(value, path19) {
8686
9035
  if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
8687
9036
  return value;
8688
9037
  }
8689
9038
  throw new Error(
8690
- `${path18} must be one of: definition, implementation-intent, similarity, keyword-heavy`
9039
+ `${path19} must be one of: definition, implementation-intent, similarity, keyword-heavy`
8691
9040
  );
8692
9041
  }
8693
- function parseExpected(input, path18) {
9042
+ function parseExpected(input, path19) {
8694
9043
  if (!isRecord2(input)) {
8695
- throw new Error(`${path18} must be an object`);
9044
+ throw new Error(`${path19} must be an object`);
8696
9045
  }
8697
9046
  const filePathRaw = input.filePath;
8698
9047
  const acceptableFilesRaw = input.acceptableFiles;
@@ -8701,16 +9050,16 @@ function parseExpected(input, path18) {
8701
9050
  const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
8702
9051
  const acceptableFiles = isStringArray3(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
8703
9052
  if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
8704
- throw new Error(`${path18} must include either expected.filePath or expected.acceptableFiles`);
9053
+ throw new Error(`${path19} must include either expected.filePath or expected.acceptableFiles`);
8705
9054
  }
8706
9055
  if (acceptableFilesRaw !== void 0 && !isStringArray3(acceptableFilesRaw)) {
8707
- throw new Error(`${path18}.acceptableFiles must be an array of strings`);
9056
+ throw new Error(`${path19}.acceptableFiles must be an array of strings`);
8708
9057
  }
8709
9058
  if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
8710
- throw new Error(`${path18}.symbol must be a string when provided`);
9059
+ throw new Error(`${path19}.symbol must be a string when provided`);
8711
9060
  }
8712
9061
  if (branchRaw !== void 0 && typeof branchRaw !== "string") {
8713
- throw new Error(`${path18}.branch must be a string when provided`);
9062
+ throw new Error(`${path19}.branch must be a string when provided`);
8714
9063
  }
8715
9064
  return {
8716
9065
  filePath,
@@ -8720,25 +9069,25 @@ function parseExpected(input, path18) {
8720
9069
  };
8721
9070
  }
8722
9071
  function parseQuery(input, index) {
8723
- const path18 = `queries[${index}]`;
9072
+ const path19 = `queries[${index}]`;
8724
9073
  if (!isRecord2(input)) {
8725
- throw new Error(`${path18} must be an object`);
9074
+ throw new Error(`${path19} must be an object`);
8726
9075
  }
8727
9076
  const id = input.id;
8728
9077
  const query = input.query;
8729
9078
  const queryType = input.queryType;
8730
9079
  const expected = input.expected;
8731
9080
  if (typeof id !== "string" || id.trim().length === 0) {
8732
- throw new Error(`${path18}.id must be a non-empty string`);
9081
+ throw new Error(`${path19}.id must be a non-empty string`);
8733
9082
  }
8734
9083
  if (typeof query !== "string" || query.trim().length === 0) {
8735
- throw new Error(`${path18}.query must be a non-empty string`);
9084
+ throw new Error(`${path19}.query must be a non-empty string`);
8736
9085
  }
8737
9086
  return {
8738
9087
  id,
8739
9088
  query,
8740
- queryType: parseQueryType(queryType, `${path18}.queryType`),
8741
- expected: parseExpected(expected, `${path18}.expected`)
9089
+ queryType: parseQueryType(queryType, `${path19}.queryType`),
9090
+ expected: parseExpected(expected, `${path19}.expected`)
8742
9091
  };
8743
9092
  }
8744
9093
  function parseGoldenDataset(raw, sourceLabel) {
@@ -8921,13 +9270,13 @@ async function runEvaluation(options) {
8921
9270
  };
8922
9271
  const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
8923
9272
  const perQueryArtifact = buildPerQueryArtifact(perQuery);
8924
- writeJson(path12.join(outputDir, "summary.json"), summary);
8925
- writeJson(path12.join(outputDir, "per-query.json"), perQueryArtifact);
9273
+ writeJson(path13.join(outputDir, "summary.json"), summary);
9274
+ writeJson(path13.join(outputDir, "per-query.json"), perQueryArtifact);
8926
9275
  let comparison;
8927
9276
  if (againstPath) {
8928
9277
  const baseline = loadSummary(againstPath);
8929
9278
  comparison = compareSummaries(summary, baseline, againstPath);
8930
- writeJson(path12.join(outputDir, "compare.json"), comparison);
9279
+ writeJson(path13.join(outputDir, "compare.json"), comparison);
8931
9280
  }
8932
9281
  let gate;
8933
9282
  if (options.ciMode) {
@@ -8940,7 +9289,7 @@ async function runEvaluation(options) {
8940
9289
  if ((0, import_fs10.existsSync)(resolvedBaseline)) {
8941
9290
  const baselineSummary = loadSummary(resolvedBaseline);
8942
9291
  comparison = compareSummaries(summary, baselineSummary, resolvedBaseline);
8943
- writeJson(path12.join(outputDir, "compare.json"), comparison);
9292
+ writeJson(path13.join(outputDir, "compare.json"), comparison);
8944
9293
  } else if (budget.failOnMissingBaseline) {
8945
9294
  throw new Error(
8946
9295
  `Budget baseline is missing: ${resolvedBaseline}. Set failOnMissingBaseline=false to allow CI run without baseline.`
@@ -8950,7 +9299,7 @@ async function runEvaluation(options) {
8950
9299
  gate = evaluateBudgetGate(budget, summary, comparison);
8951
9300
  }
8952
9301
  const markdown = createSummaryMarkdown(summary, comparison, gate);
8953
- writeText(path12.join(outputDir, "summary.md"), markdown);
9302
+ writeText(path13.join(outputDir, "summary.md"), markdown);
8954
9303
  return { outputDir, summary, perQuery, comparison, gate };
8955
9304
  } finally {
8956
9305
  await indexer.close();
@@ -9008,23 +9357,23 @@ async function runSweep(options, sweep) {
9008
9357
  bestByMrrAt10,
9009
9358
  bestByP95Latency
9010
9359
  };
9011
- writeJson(path12.join(outputDir, "compare.json"), aggregate);
9360
+ writeJson(path13.join(outputDir, "compare.json"), aggregate);
9012
9361
  const md = createSummaryMarkdown(
9013
9362
  bestByHitAt5?.summary ?? runs[0].summary,
9014
9363
  bestByHitAt5?.comparison,
9015
9364
  void 0,
9016
9365
  aggregate
9017
9366
  );
9018
- writeText(path12.join(outputDir, "summary.md"), md);
9019
- writeJson(path12.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
9367
+ writeText(path13.join(outputDir, "summary.md"), md);
9368
+ writeJson(path13.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
9020
9369
  return { outputDir, aggregate };
9021
9370
  }
9022
9371
 
9023
9372
  // src/eval/cli.ts
9024
- var path14 = __toESM(require("path"), 1);
9373
+ var path15 = __toESM(require("path"), 1);
9025
9374
 
9026
9375
  // src/eval/cli-parser.ts
9027
- var path13 = __toESM(require("path"), 1);
9376
+ var path14 = __toESM(require("path"), 1);
9028
9377
  function printUsage() {
9029
9378
  console.log(`
9030
9379
  Usage:
@@ -9096,12 +9445,12 @@ function parseEvalArgs(argv, cwd) {
9096
9445
  const arg = argv[i];
9097
9446
  const next = argv[i + 1];
9098
9447
  if (arg === "--project" && next) {
9099
- parsed.projectRoot = path13.resolve(cwd, next);
9448
+ parsed.projectRoot = path14.resolve(cwd, next);
9100
9449
  i += 1;
9101
9450
  continue;
9102
9451
  }
9103
9452
  if (arg === "--config" && next) {
9104
- parsed.configPath = path13.resolve(cwd, next);
9453
+ parsed.configPath = path14.resolve(cwd, next);
9105
9454
  i += 1;
9106
9455
  continue;
9107
9456
  }
@@ -9297,22 +9646,22 @@ async function handleEvalCommand(args, cwd) {
9297
9646
  if (!parsed.againstPath.endsWith(".json")) {
9298
9647
  throw new Error("eval diff --against must point to a summary JSON file");
9299
9648
  }
9300
- const currentSummary = loadSummary(path14.resolve(parsed.projectRoot, currentPath), {
9649
+ const currentSummary = loadSummary(path15.resolve(parsed.projectRoot, currentPath), {
9301
9650
  allowLegacyDiversityMetrics: true
9302
9651
  });
9303
- const baselineSummary = loadSummary(path14.resolve(parsed.projectRoot, parsed.againstPath), {
9652
+ const baselineSummary = loadSummary(path15.resolve(parsed.projectRoot, parsed.againstPath), {
9304
9653
  allowLegacyDiversityMetrics: true
9305
9654
  });
9306
9655
  const comparison = compareSummaries(
9307
9656
  currentSummary,
9308
9657
  baselineSummary,
9309
- path14.resolve(parsed.projectRoot, parsed.againstPath)
9658
+ path15.resolve(parsed.projectRoot, parsed.againstPath)
9310
9659
  );
9311
- const outputDir = createRunDirectory(path14.resolve(parsed.projectRoot, parsed.outputRoot));
9660
+ const outputDir = createRunDirectory(path15.resolve(parsed.projectRoot, parsed.outputRoot));
9312
9661
  const summaryMd = createSummaryMarkdown(currentSummary, comparison);
9313
- writeJson(path14.join(outputDir, "compare.json"), comparison);
9314
- writeText(path14.join(outputDir, "summary.md"), summaryMd);
9315
- writeJson(path14.join(outputDir, "summary.json"), currentSummary);
9662
+ writeJson(path15.join(outputDir, "compare.json"), comparison);
9663
+ writeText(path15.join(outputDir, "summary.md"), summaryMd);
9664
+ writeJson(path15.join(outputDir, "summary.json"), currentSummary);
9316
9665
  console.log(`Eval diff complete. Artifacts: ${outputDir}`);
9317
9666
  return 0;
9318
9667
  }
@@ -9321,7 +9670,7 @@ async function handleEvalCommand(args, cwd) {
9321
9670
 
9322
9671
  // src/mcp-server.ts
9323
9672
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
9324
- var path16 = __toESM(require("path"), 1);
9673
+ var path17 = __toESM(require("path"), 1);
9325
9674
  var import_fs12 = require("fs");
9326
9675
 
9327
9676
  // src/mcp-server/register-prompts.ts
@@ -9420,7 +9769,7 @@ var import_zod2 = require("zod");
9420
9769
  // src/config/merger.ts
9421
9770
  var import_fs11 = require("fs");
9422
9771
  var os5 = __toESM(require("os"), 1);
9423
- var path15 = __toESM(require("path"), 1);
9772
+ var path16 = __toESM(require("path"), 1);
9424
9773
  var PROJECT_OVERRIDE_KEYS = [
9425
9774
  "embeddingProvider",
9426
9775
  "customProvider",
@@ -9453,8 +9802,8 @@ function mergeUniqueStringArray(values) {
9453
9802
  return [...new Set(values.map((value) => String(value).trim()))];
9454
9803
  }
9455
9804
  function normalizeKnowledgeBasePath(value) {
9456
- let normalized = path15.normalize(String(value).trim());
9457
- const root = path15.parse(normalized).root;
9805
+ let normalized = path16.normalize(String(value).trim());
9806
+ const root = path16.parse(normalized).root;
9458
9807
  while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
9459
9808
  normalized = normalized.slice(0, -1);
9460
9809
  }
@@ -9504,8 +9853,8 @@ function loadJsonFile(filePath) {
9504
9853
  return null;
9505
9854
  }
9506
9855
  function materializeLocalProjectConfig(projectRoot, config) {
9507
- const localConfigPath = path15.join(projectRoot, ".opencode", "codebase-index.json");
9508
- (0, import_fs11.mkdirSync)(path15.dirname(localConfigPath), { recursive: true });
9856
+ const localConfigPath = path16.join(projectRoot, ".opencode", "codebase-index.json");
9857
+ (0, import_fs11.mkdirSync)(path16.dirname(localConfigPath), { recursive: true });
9509
9858
  (0, import_fs11.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
9510
9859
  return localConfigPath;
9511
9860
  }
@@ -9516,7 +9865,7 @@ function loadProjectConfigLayer(projectRoot) {
9516
9865
  return {};
9517
9866
  }
9518
9867
  const normalizedConfig = { ...projectConfig };
9519
- const projectConfigBaseDir = path15.dirname(path15.dirname(projectConfigPath));
9868
+ const projectConfigBaseDir = path16.dirname(path16.dirname(projectConfigPath));
9520
9869
  if (Array.isArray(normalizedConfig.knowledgeBases)) {
9521
9870
  normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
9522
9871
  normalizedConfig.knowledgeBases,
@@ -9735,6 +10084,57 @@ ${truncateContent(r.content)}
9735
10084
  return formatted.join("\n\n");
9736
10085
  }
9737
10086
 
10087
+ // src/tools/format-pr-impact.ts
10088
+ function formatPrImpact(result) {
10089
+ const lines = [];
10090
+ lines.push(`\u2192 Files changed: ${result.changedFiles.length}`);
10091
+ for (const file of result.changedFiles) {
10092
+ lines.push(` - ${file}`);
10093
+ }
10094
+ const directCount = result.directSymbols.length;
10095
+ const transitiveCount = result.transitiveCallers.length;
10096
+ const directionLabel = result.direction === "callees" ? "callees" : result.direction === "both" ? "reachable (callers + callees)" : "callers";
10097
+ lines.push(
10098
+ `\u2192 Symbols affected: ${result.totalAffected} (${directCount} direct, ${transitiveCount} transitive ${directionLabel})`
10099
+ );
10100
+ if (directCount > 0) {
10101
+ lines.push(
10102
+ ` Direct: ${result.directSymbols.map((s) => `${s.name} (${s.kind})`).join(", ")}`
10103
+ );
10104
+ }
10105
+ if (transitiveCount > 0) {
10106
+ lines.push(
10107
+ ` Transitive ${directionLabel}: ${result.transitiveCallers.map((s) => s.name).join(", ")}`
10108
+ );
10109
+ }
10110
+ if (result.communities.length > 0) {
10111
+ lines.push(
10112
+ `\u2192 Communities touched: ${result.communities.map((c) => c.label).join(", ")}`
10113
+ );
10114
+ for (const community of result.communities) {
10115
+ lines.push(
10116
+ ` - ${community.label}: ${community.symbolCount} symbols`
10117
+ );
10118
+ }
10119
+ } else {
10120
+ lines.push("\u2192 Communities touched: none");
10121
+ }
10122
+ lines.push(`\u2192 Risk: ${result.riskLevel} \u2014 ${result.riskReason}`);
10123
+ if (result.hubNodes.length > 0) {
10124
+ lines.push(" Hub nodes in change scope:");
10125
+ for (const hub of result.hubNodes) {
10126
+ lines.push(` - ${hub.name} (${hub.callerCount} callers) at ${hub.filePath}`);
10127
+ }
10128
+ }
10129
+ if (result.conflictingPRs && result.conflictingPRs.length > 0) {
10130
+ const conflictList = result.conflictingPRs.map(
10131
+ (c) => `PR #${c.pr} (also touches ${c.overlappingCommunities.join(", ")} community)`
10132
+ );
10133
+ lines.push(`\u2192 Potential conflicts with: ${conflictList.join(", ")}`);
10134
+ }
10135
+ return lines.join("\n");
10136
+ }
10137
+
9738
10138
  // src/mcp-server/shared.ts
9739
10139
  var MAX_CONTENT_LINES2 = 30;
9740
10140
  function truncateContent2(content) {
@@ -9979,11 +10379,12 @@ ${formatted.join("\n\n")}` }] };
9979
10379
  );
9980
10380
  server.tool(
9981
10381
  "call_graph",
9982
- "Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies.",
10382
+ "Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies. Supports relationship types: Call, MethodCall, Constructor, Import, Inherits, Implements.",
9983
10383
  {
9984
10384
  name: import_zod2.z.string().describe("Function or method name to query"),
9985
10385
  direction: import_zod2.z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
9986
- symbolId: import_zod2.z.string().optional().describe("Symbol ID (required for 'callees' direction)")
10386
+ symbolId: import_zod2.z.string().optional().describe("Symbol ID (required for 'callees' direction)"),
10387
+ relationshipType: import_zod2.z.enum(["Call", "MethodCall", "Constructor", "Import", "Inherits", "Implements"]).optional().describe("Filter by relationship type. Omit to show all.")
9987
10388
  },
9988
10389
  async (args) => {
9989
10390
  await runtime.ensureInitialized();
@@ -9992,29 +10393,85 @@ ${formatted.join("\n\n")}` }] };
9992
10393
  if (!args.symbolId) {
9993
10394
  return { content: [{ type: "text", text: "Error: 'symbolId' is required when direction is 'callees'." }] };
9994
10395
  }
9995
- const callees = await indexer.getCallees(args.symbolId);
10396
+ const callees = await indexer.getCallees(args.symbolId, args.relationshipType);
9996
10397
  if (callees.length === 0) {
9997
- return { content: [{ type: "text", text: `No callees found for symbol ${args.symbolId}.` }] };
10398
+ return { content: [{ type: "text", text: `No callees found for symbol ${args.symbolId}${args.relationshipType ? ` with type ${args.relationshipType}` : ""}.` }] };
9998
10399
  }
9999
- const formatted2 = callees.map(
10000
- (e, i) => `[${i + 1}] \u2192 ${e.targetName} (${e.callType}) at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`
10001
- );
10400
+ const formatted2 = callees.map((e, i) => {
10401
+ const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
10402
+ return `[${i + 1}] \u2192 ${e.targetName} (${e.callType})${conf} at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`;
10403
+ });
10002
10404
  return { content: [{ type: "text", text: `Callees (${callees.length}):
10003
10405
 
10004
10406
  ${formatted2.join("\n")}` }] };
10005
10407
  }
10006
- const callers = await indexer.getCallers(args.name);
10408
+ const callers = await indexer.getCallers(args.name, args.relationshipType);
10007
10409
  if (callers.length === 0) {
10008
- return { content: [{ type: "text", text: `No callers found for "${args.name}".` }] };
10410
+ return { content: [{ type: "text", text: `No callers found for "${args.name}"${args.relationshipType ? ` with type ${args.relationshipType}` : ""}.` }] };
10009
10411
  }
10010
- const formatted = callers.map(
10011
- (e, i) => `[${i + 1}] \u2190 from ${e.fromSymbolName ?? "<unknown>"} in ${e.fromSymbolFilePath ?? "<unknown file>"} [${e.fromSymbolId}] (${e.callType}) at line ${e.line}${e.isResolved ? " [resolved]" : " [unresolved]"}`
10012
- );
10412
+ const formatted = callers.map((e, i) => {
10413
+ const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
10414
+ return `[${i + 1}] \u2190 from ${e.fromSymbolName ?? "<unknown>"} in ${e.fromSymbolFilePath ?? "<unknown file>"} [${e.fromSymbolId}] (${e.callType})${conf} at line ${e.line}${e.isResolved ? " [resolved]" : " [unresolved]"}`;
10415
+ });
10013
10416
  return { content: [{ type: "text", text: `"${args.name}" is called by ${callers.length} function(s):
10014
10417
 
10015
10418
  ${formatted.join("\n")}` }] };
10016
10419
  }
10017
10420
  );
10421
+ server.tool(
10422
+ "call_graph_path",
10423
+ "Find the shortest connection path between two symbols in the call graph. Returns the chain of calls connecting them.",
10424
+ {
10425
+ from: import_zod2.z.string().describe("Source function/method name (starting point)"),
10426
+ to: import_zod2.z.string().describe("Target function/method name (destination)"),
10427
+ maxDepth: import_zod2.z.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
10428
+ },
10429
+ async (args) => {
10430
+ await runtime.ensureInitialized();
10431
+ const indexer = runtime.getIndexer();
10432
+ const path19 = await indexer.findCallPath(args.from, args.to, args.maxDepth);
10433
+ if (path19.length === 0) {
10434
+ return { content: [{ type: "text", text: `No path found between "${args.from}" and "${args.to}". They may be in disconnected components, or the call graph index needs updating.` }] };
10435
+ }
10436
+ const formatted = path19.map((hop, i) => {
10437
+ const prefix = i === 0 ? "[start]" : `--${hop.callType}-->`;
10438
+ const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
10439
+ return `${prefix} ${hop.symbolName}${location}`;
10440
+ });
10441
+ return { content: [{ type: "text", text: `Path (${path19.length} hops):
10442
+ ${formatted.join("\n")}` }] };
10443
+ }
10444
+ );
10445
+ server.tool(
10446
+ "pr_impact",
10447
+ "Analyze the impact of a pull request or branch by examining changed files, affected symbols, transitive callers, communities touched, hub nodes, and risk level. Use to understand blast radius before merging.",
10448
+ {
10449
+ pr: import_zod2.z.number().optional().describe("Pull request number to analyze"),
10450
+ branch: import_zod2.z.string().optional().describe("Branch name to analyze (defaults to current branch)"),
10451
+ maxDepth: import_zod2.z.number().optional().default(5).describe("Maximum traversal depth for transitive callers (default: 5)"),
10452
+ hubThreshold: import_zod2.z.number().optional().default(10).describe("Minimum caller count to flag a symbol as a hub node (default: 10)"),
10453
+ checkConflicts: import_zod2.z.boolean().optional().default(false).describe("Check for conflicting open PRs touching the same communities (default: false)"),
10454
+ direction: import_zod2.z.enum(["callers", "callees", "both"]).optional().default("both").describe("Call-graph traversal direction: 'callers' for upstream, 'callees' for downstream, 'both' for union (default: both)")
10455
+ },
10456
+ async (args) => {
10457
+ await runtime.ensureInitialized();
10458
+ const indexer = runtime.getIndexer();
10459
+ try {
10460
+ const result = await indexer.getPrImpact({
10461
+ pr: args.pr,
10462
+ branch: args.branch,
10463
+ maxDepth: args.maxDepth,
10464
+ hubThreshold: args.hubThreshold,
10465
+ checkConflicts: args.checkConflicts,
10466
+ direction: args.direction
10467
+ });
10468
+ return { content: [{ type: "text", text: formatPrImpact(result) }] };
10469
+ } catch (error) {
10470
+ const message = error instanceof Error ? error.message : String(error);
10471
+ return { content: [{ type: "text", text: `Error analyzing PR impact: ${message}` }] };
10472
+ }
10473
+ }
10474
+ );
10018
10475
  }
10019
10476
 
10020
10477
  // src/mcp-server.ts
@@ -10033,12 +10490,12 @@ function createMcpServer(projectRoot, config) {
10033
10490
  if (config.scope !== "project") {
10034
10491
  return false;
10035
10492
  }
10036
- const localIndexPath = path16.join(projectRoot, ".opencode", "index");
10493
+ const localIndexPath = path17.join(projectRoot, ".opencode", "index");
10037
10494
  const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
10038
10495
  if (!mainRepoRoot) {
10039
10496
  return false;
10040
10497
  }
10041
- const inheritedIndexPath = path16.join(mainRepoRoot, ".opencode", "index");
10498
+ const inheritedIndexPath = path17.join(mainRepoRoot, ".opencode", "index");
10042
10499
  return !(0, import_fs12.existsSync)(localIndexPath) && (0, import_fs12.existsSync)(inheritedIndexPath);
10043
10500
  }
10044
10501
  async function ensureInitialized() {
@@ -10064,9 +10521,9 @@ function parseArgs(argv) {
10064
10521
  let config;
10065
10522
  for (let i = 2; i < argv.length; i++) {
10066
10523
  if (argv[i] === "--project" && argv[i + 1]) {
10067
- project = path17.resolve(argv[++i]);
10524
+ project = path18.resolve(argv[++i]);
10068
10525
  } else if (argv[i] === "--config" && argv[i + 1]) {
10069
- config = path17.resolve(argv[++i]);
10526
+ config = path18.resolve(argv[++i]);
10070
10527
  }
10071
10528
  }
10072
10529
  return { project, config };