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.js 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
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
652
- import * as path17 from "path";
656
+ import * as path18 from "path";
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 @@ import { mkdirSync, readFileSync, writeFileSync } from "fs";
1075
1081
  import * as path from "path";
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
  import { existsSync as existsSync8 } from "fs";
1269
- import * as path12 from "path";
1275
+ import * as path13 from "path";
1270
1276
  import { performance as performance3 } from "perf_hooks";
1271
1277
 
1272
1278
  // src/indexer/index.ts
1273
1279
  import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync2, renameSync, unlinkSync, mkdirSync as mkdirSync2, promises as fsPromises2 } from "fs";
1274
- import * as path9 from "path";
1280
+ import * as path10 from "path";
1275
1281
  import { performance as performance2 } from "perf_hooks";
1282
+ import { execFile as execFile2 } from "child_process";
1283
+ import { promisify as promisify2 } from "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 {
@@ -3675,6 +3685,15 @@ function createMockNativeBinding() {
3675
3685
  close() {
3676
3686
  throw error;
3677
3687
  }
3688
+ getTransitiveReachability() {
3689
+ throw error;
3690
+ }
3691
+ detectCommunities() {
3692
+ throw error;
3693
+ }
3694
+ computeCentrality() {
3695
+ throw error;
3696
+ }
3678
3697
  }
3679
3698
  };
3680
3699
  }
@@ -4225,6 +4244,14 @@ var Database = class {
4225
4244
  this.throwIfClosed();
4226
4245
  return this.inner.getSymbolsByNameCi(name);
4227
4246
  }
4247
+ getSymbolsForBranch(branch) {
4248
+ this.throwIfClosed();
4249
+ return this.inner.getSymbolsForBranch(branch);
4250
+ }
4251
+ getSymbolsForFiles(filePaths, branch) {
4252
+ this.throwIfClosed();
4253
+ return this.inner.getSymbolsForFiles(filePaths, branch);
4254
+ }
4228
4255
  deleteSymbolsByFile(filePath) {
4229
4256
  this.throwIfClosed();
4230
4257
  return this.inner.deleteSymbolsByFile(filePath);
@@ -4238,17 +4265,17 @@ var Database = class {
4238
4265
  if (edges.length === 0) return;
4239
4266
  this.inner.upsertCallEdgesBatch(edges);
4240
4267
  }
4241
- getCallers(targetName, branch) {
4268
+ getCallers(targetName, branch, callTypeFilter) {
4242
4269
  this.throwIfClosed();
4243
- return this.inner.getCallers(targetName, branch);
4270
+ return this.inner.getCallers(targetName, branch, callTypeFilter ?? null);
4244
4271
  }
4245
- getCallersWithContext(targetName, branch) {
4272
+ getCallersWithContext(targetName, branch, callTypeFilter) {
4246
4273
  this.throwIfClosed();
4247
- return this.inner.getCallersWithContext(targetName, branch);
4274
+ return this.inner.getCallersWithContext(targetName, branch, callTypeFilter ?? null);
4248
4275
  }
4249
- getCallees(symbolId, branch) {
4276
+ getCallees(symbolId, branch, callTypeFilter) {
4250
4277
  this.throwIfClosed();
4251
- return this.inner.getCallees(symbolId, branch);
4278
+ return this.inner.getCallees(symbolId, branch, callTypeFilter ?? null);
4252
4279
  }
4253
4280
  deleteCallEdgesByFile(filePath) {
4254
4281
  this.throwIfClosed();
@@ -4258,6 +4285,10 @@ var Database = class {
4258
4285
  this.throwIfClosed();
4259
4286
  this.inner.resolveCallEdge(edgeId, toSymbolId);
4260
4287
  }
4288
+ findShortestPath(fromName, toName, branch, maxDepth) {
4289
+ this.throwIfClosed();
4290
+ return this.inner.findShortestPath(fromName, toName, branch, maxDepth ?? null);
4291
+ }
4261
4292
  addSymbolsToBranch(branch, symbolIds) {
4262
4293
  this.throwIfClosed();
4263
4294
  this.inner.addSymbolsToBranch(branch, symbolIds);
@@ -4298,6 +4329,23 @@ var Database = class {
4298
4329
  this.throwIfClosed();
4299
4330
  return this.inner.gcOrphanCallEdges();
4300
4331
  }
4332
+ getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth) {
4333
+ this.throwIfClosed();
4334
+ return this.inner.getTransitiveReachability(
4335
+ rootSymbolIds,
4336
+ branch,
4337
+ direction,
4338
+ maxDepth ?? null
4339
+ );
4340
+ }
4341
+ detectCommunities(branch, symbolIds) {
4342
+ this.throwIfClosed();
4343
+ return this.inner.detectCommunities(branch, symbolIds ?? null);
4344
+ }
4345
+ computeCentrality(branch) {
4346
+ this.throwIfClosed();
4347
+ return this.inner.computeCentrality(branch);
4348
+ }
4301
4349
  };
4302
4350
 
4303
4351
  // src/git/index.ts
@@ -4471,6 +4519,106 @@ function resolveProjectIndexPath(projectRoot, scope) {
4471
4519
  return resolveWorktreeFallbackPath(projectRoot, PROJECT_INDEX_RELATIVE_PATH) ?? localIndexPath;
4472
4520
  }
4473
4521
 
4522
+ // src/tools/changed-files.ts
4523
+ import * as path9 from "path";
4524
+ import { execFile } from "child_process";
4525
+ import { promisify } from "util";
4526
+ var execFileAsync = promisify(execFile);
4527
+ function getErrorMessage(error) {
4528
+ if (error instanceof Error) return error.message;
4529
+ return String(error);
4530
+ }
4531
+ async function getChangedFiles(opts) {
4532
+ const { pr, branch, projectRoot, baseBranch = "main" } = opts;
4533
+ if (pr !== void 0) {
4534
+ return getChangedFilesForPr(pr, projectRoot, baseBranch);
4535
+ }
4536
+ return getChangedFilesForBranch(branch, projectRoot, baseBranch);
4537
+ }
4538
+ async function getChangedFilesForPr(pr, projectRoot, baseBranch) {
4539
+ let headRefName;
4540
+ let actualBaseBranch = baseBranch;
4541
+ try {
4542
+ const { stdout } = await execFileAsync(
4543
+ "gh",
4544
+ ["pr", "view", String(pr), "--json", "headRefName,baseRefName,files"],
4545
+ { cwd: projectRoot, timeout: 3e4 }
4546
+ );
4547
+ const data = JSON.parse(stdout);
4548
+ headRefName = data.headRefName;
4549
+ actualBaseBranch = data.baseRefName || baseBranch;
4550
+ if (data.files && data.files.length > 0) {
4551
+ return {
4552
+ files: normalizeFiles(
4553
+ data.files.map((f) => f.path),
4554
+ projectRoot
4555
+ ),
4556
+ baseBranch: actualBaseBranch,
4557
+ source: "gh",
4558
+ headRefName
4559
+ };
4560
+ }
4561
+ } catch (error) {
4562
+ throw new Error(
4563
+ `Failed to retrieve PR #${pr} via gh CLI: ${getErrorMessage(error)}`
4564
+ );
4565
+ }
4566
+ if (headRefName === void 0) {
4567
+ throw new Error(
4568
+ `PR #${pr} returned no usable branch or file information.`
4569
+ );
4570
+ }
4571
+ const result = await getChangedFilesForBranch(headRefName, projectRoot, actualBaseBranch);
4572
+ return { ...result, headRefName };
4573
+ }
4574
+ async function getChangedFilesForBranch(branch, projectRoot, baseBranch) {
4575
+ const targetBranch = branch || await getCurrentBranch2(projectRoot);
4576
+ const mergeBase = await getMergeBase(projectRoot, baseBranch, targetBranch);
4577
+ const { stdout } = await execFileAsync(
4578
+ "git",
4579
+ ["diff", "--name-only", `${mergeBase}...${targetBranch}`],
4580
+ { cwd: projectRoot, timeout: 3e4 }
4581
+ );
4582
+ return {
4583
+ files: normalizeFiles(stdout.split("\n"), projectRoot),
4584
+ baseBranch,
4585
+ source: "git",
4586
+ headRefName: targetBranch
4587
+ };
4588
+ }
4589
+ async function getCurrentBranch2(projectRoot) {
4590
+ const { stdout } = await execFileAsync(
4591
+ "git",
4592
+ ["branch", "--show-current"],
4593
+ { cwd: projectRoot, timeout: 3e4 }
4594
+ );
4595
+ return stdout.trim() || "HEAD";
4596
+ }
4597
+ async function getMergeBase(projectRoot, baseBranch, branch) {
4598
+ const { stdout } = await execFileAsync(
4599
+ "git",
4600
+ ["merge-base", baseBranch, branch],
4601
+ { cwd: projectRoot, timeout: 3e4 }
4602
+ );
4603
+ return stdout.trim();
4604
+ }
4605
+ function normalizeFiles(rawFiles, projectRoot) {
4606
+ const seen = /* @__PURE__ */ new Set();
4607
+ const result = [];
4608
+ for (const raw of rawFiles) {
4609
+ const trimmed = raw.trim();
4610
+ if (!trimmed) continue;
4611
+ const absolute = path9.resolve(projectRoot, trimmed);
4612
+ const relative5 = path9.relative(projectRoot, absolute);
4613
+ const cleaned = relative5.startsWith("./") ? relative5.slice(2) : relative5;
4614
+ if (!seen.has(cleaned)) {
4615
+ seen.add(cleaned);
4616
+ result.push(cleaned);
4617
+ }
4618
+ }
4619
+ return result;
4620
+ }
4621
+
4474
4622
  // src/indexer/index.ts
4475
4623
  var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
4476
4624
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
@@ -4478,6 +4626,7 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
4478
4626
  "function_declaration",
4479
4627
  "function",
4480
4628
  "arrow_function",
4629
+ "export_statement",
4481
4630
  "method_definition",
4482
4631
  "class_declaration",
4483
4632
  "interface_declaration",
@@ -4516,7 +4665,7 @@ function float32ArrayToBuffer(arr) {
4516
4665
  function bufferToFloat32Array(buf) {
4517
4666
  return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
4518
4667
  }
4519
- function getErrorMessage(error) {
4668
+ function getErrorMessage2(error) {
4520
4669
  if (error instanceof Error) {
4521
4670
  return error.message;
4522
4671
  }
@@ -4529,7 +4678,7 @@ function getErrorMessage(error) {
4529
4678
  return String(error);
4530
4679
  }
4531
4680
  function isRateLimitError(error) {
4532
- const message = getErrorMessage(error);
4681
+ const message = getErrorMessage2(error);
4533
4682
  return message.includes("429") || message.toLowerCase().includes("rate limit") || message.toLowerCase().includes("too many requests");
4534
4683
  }
4535
4684
  function getSafeEmbeddingChunkTokenLimit(provider) {
@@ -4547,7 +4696,7 @@ function getDynamicBatchOptions(provider) {
4547
4696
  return {};
4548
4697
  }
4549
4698
  function isSqliteCorruptionError(error) {
4550
- const message = getErrorMessage(error).toLowerCase();
4699
+ const message = getErrorMessage2(error).toLowerCase();
4551
4700
  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");
4552
4701
  }
4553
4702
  var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
@@ -4709,9 +4858,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
4709
4858
  return true;
4710
4859
  }
4711
4860
  function isPathWithinRoot(filePath, rootPath) {
4712
- const normalizedFilePath = path9.resolve(filePath);
4713
- const normalizedRoot = path9.resolve(rootPath);
4714
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path9.sep}`);
4861
+ const normalizedFilePath = path10.resolve(filePath);
4862
+ const normalizedRoot = path10.resolve(rootPath);
4863
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path10.sep}`);
4715
4864
  }
4716
4865
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
4717
4866
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -5764,9 +5913,9 @@ var Indexer = class {
5764
5913
  this.projectRoot = projectRoot;
5765
5914
  this.config = config;
5766
5915
  this.indexPath = this.getIndexPath();
5767
- this.fileHashCachePath = path9.join(this.indexPath, "file-hashes.json");
5768
- this.failedBatchesPath = path9.join(this.indexPath, "failed-batches.json");
5769
- this.indexingLockPath = path9.join(this.indexPath, "indexing.lock");
5916
+ this.fileHashCachePath = path10.join(this.indexPath, "file-hashes.json");
5917
+ this.failedBatchesPath = path10.join(this.indexPath, "failed-batches.json");
5918
+ this.indexingLockPath = path10.join(this.indexPath, "indexing.lock");
5770
5919
  this.logger = initializeLogger(config.debug);
5771
5920
  }
5772
5921
  getIndexPath() {
@@ -5798,14 +5947,14 @@ var Indexer = class {
5798
5947
  }
5799
5948
  atomicWriteSync(targetPath, data) {
5800
5949
  const tempPath = `${targetPath}.tmp`;
5801
- mkdirSync2(path9.dirname(targetPath), { recursive: true });
5950
+ mkdirSync2(path10.dirname(targetPath), { recursive: true });
5802
5951
  writeFileSync2(tempPath, data);
5803
5952
  renameSync(tempPath, targetPath);
5804
5953
  }
5805
5954
  getScopedRoots() {
5806
- const roots = /* @__PURE__ */ new Set([path9.resolve(this.projectRoot)]);
5955
+ const roots = /* @__PURE__ */ new Set([path10.resolve(this.projectRoot)]);
5807
5956
  for (const kbRoot of this.config.knowledgeBases) {
5808
- roots.add(path9.resolve(this.projectRoot, kbRoot));
5957
+ roots.add(path10.resolve(this.projectRoot, kbRoot));
5809
5958
  }
5810
5959
  return Array.from(roots);
5811
5960
  }
@@ -5814,22 +5963,29 @@ var Indexer = class {
5814
5963
  if (this.config.scope !== "global") {
5815
5964
  return branchName;
5816
5965
  }
5817
- const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
5966
+ const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
5967
+ return `${projectHash}:${branchName}`;
5968
+ }
5969
+ getBranchCatalogKeyFor(branchName) {
5970
+ if (this.config.scope !== "global") {
5971
+ return branchName;
5972
+ }
5973
+ const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
5818
5974
  return `${projectHash}:${branchName}`;
5819
5975
  }
5820
5976
  getLegacyBranchCatalogKey() {
5821
5977
  return this.currentBranch || "default";
5822
5978
  }
5823
5979
  getLegacyMigrationMetadataKey() {
5824
- const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
5980
+ const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
5825
5981
  return `index.globalBranchMigration.${projectHash}`;
5826
5982
  }
5827
5983
  getProjectEmbeddingStrategyMetadataKey() {
5828
- const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
5984
+ const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
5829
5985
  return `index.embeddingStrategyVersion.${projectHash}`;
5830
5986
  }
5831
5987
  getProjectForceReembedMetadataKey() {
5832
- const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
5988
+ const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
5833
5989
  return `index.forceReembed.${projectHash}`;
5834
5990
  }
5835
5991
  hasProjectForceReembedPending() {
@@ -5923,7 +6079,7 @@ var Indexer = class {
5923
6079
  if (!this.database) {
5924
6080
  return { chunkIds, symbolIds };
5925
6081
  }
5926
- const projectRootPath = path9.resolve(this.projectRoot);
6082
+ const projectRootPath = path10.resolve(this.projectRoot);
5927
6083
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
5928
6084
  ...Array.from(this.fileHashCache.keys()).filter(
5929
6085
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -5946,7 +6102,7 @@ var Indexer = class {
5946
6102
  if (this.config.scope !== "global") {
5947
6103
  return this.getBranchCatalogCleanupKeys();
5948
6104
  }
5949
- const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
6105
+ const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
5950
6106
  const keys = /* @__PURE__ */ new Set();
5951
6107
  const projectChunkIdSet = new Set(projectChunkIds);
5952
6108
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -6030,7 +6186,7 @@ var Indexer = class {
6030
6186
  if (!this.database || this.config.scope !== "global") {
6031
6187
  return false;
6032
6188
  }
6033
- const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
6189
+ const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
6034
6190
  const roots = this.getScopedRoots();
6035
6191
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
6036
6192
  return this.database.getAllBranches().some(
@@ -6064,7 +6220,7 @@ var Indexer = class {
6064
6220
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
6065
6221
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
6066
6222
  ]);
6067
- const projectRootPath = path9.resolve(this.projectRoot);
6223
+ const projectRootPath = path10.resolve(this.projectRoot);
6068
6224
  const projectLocalFilePaths = new Set(
6069
6225
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
6070
6226
  );
@@ -6317,7 +6473,7 @@ var Indexer = class {
6317
6473
  this.logger.search("warn", "External reranker failed; using deterministic order", {
6318
6474
  provider: reranker.provider,
6319
6475
  model: reranker.model,
6320
- error: getErrorMessage(error)
6476
+ error: getErrorMessage2(error)
6321
6477
  });
6322
6478
  return candidates;
6323
6479
  }
@@ -6424,13 +6580,13 @@ var Indexer = class {
6424
6580
  }
6425
6581
  await fsPromises2.mkdir(this.indexPath, { recursive: true });
6426
6582
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
6427
- const storePath = path9.join(this.indexPath, "vectors");
6583
+ const storePath = path10.join(this.indexPath, "vectors");
6428
6584
  this.store = new VectorStore(storePath, dimensions);
6429
- const indexFilePath = path9.join(this.indexPath, "vectors.usearch");
6585
+ const indexFilePath = path10.join(this.indexPath, "vectors.usearch");
6430
6586
  if (existsSync6(indexFilePath)) {
6431
6587
  this.store.load();
6432
6588
  }
6433
- const invertedIndexPath = path9.join(this.indexPath, "inverted-index.json");
6589
+ const invertedIndexPath = path10.join(this.indexPath, "inverted-index.json");
6434
6590
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6435
6591
  try {
6436
6592
  this.invertedIndex.load();
@@ -6440,7 +6596,7 @@ var Indexer = class {
6440
6596
  }
6441
6597
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6442
6598
  }
6443
- const dbPath = path9.join(this.indexPath, "codebase.db");
6599
+ const dbPath = path10.join(this.indexPath, "codebase.db");
6444
6600
  let dbIsNew = !existsSync6(dbPath);
6445
6601
  try {
6446
6602
  this.database = new Database(dbPath);
@@ -6521,7 +6677,7 @@ var Indexer = class {
6521
6677
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
6522
6678
  return {
6523
6679
  resetCorruptedIndex: true,
6524
- warning: this.getCorruptedIndexWarning(path9.join(this.indexPath, "codebase.db"))
6680
+ warning: this.getCorruptedIndexWarning(path10.join(this.indexPath, "codebase.db"))
6525
6681
  };
6526
6682
  }
6527
6683
  throw error;
@@ -6536,7 +6692,7 @@ var Indexer = class {
6536
6692
  return;
6537
6693
  }
6538
6694
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
6539
- const storeBasePath = path9.join(this.indexPath, "vectors");
6695
+ const storeBasePath = path10.join(this.indexPath, "vectors");
6540
6696
  const storeIndexPath = `${storeBasePath}.usearch`;
6541
6697
  const storeMetadataPath = `${storeBasePath}.meta.json`;
6542
6698
  const backupIndexPath = `${storeIndexPath}.bak`;
@@ -6621,9 +6777,9 @@ var Indexer = class {
6621
6777
  if (!isSqliteCorruptionError(error)) {
6622
6778
  return false;
6623
6779
  }
6624
- const dbPath = path9.join(this.indexPath, "codebase.db");
6780
+ const dbPath = path10.join(this.indexPath, "codebase.db");
6625
6781
  const warning = this.getCorruptedIndexWarning(dbPath);
6626
- const errorMessage = getErrorMessage(error);
6782
+ const errorMessage = getErrorMessage2(error);
6627
6783
  if (this.config.scope === "global") {
6628
6784
  this.logger.error("Detected corrupted shared global index database", {
6629
6785
  stage,
@@ -6644,15 +6800,15 @@ var Indexer = class {
6644
6800
  this.indexCompatibility = null;
6645
6801
  this.fileHashCache.clear();
6646
6802
  const resetPaths = [
6647
- path9.join(this.indexPath, "codebase.db"),
6648
- path9.join(this.indexPath, "codebase.db-shm"),
6649
- path9.join(this.indexPath, "codebase.db-wal"),
6650
- path9.join(this.indexPath, "vectors.usearch"),
6651
- path9.join(this.indexPath, "inverted-index.json"),
6652
- path9.join(this.indexPath, "file-hashes.json"),
6653
- path9.join(this.indexPath, "failed-batches.json"),
6654
- path9.join(this.indexPath, "indexing.lock"),
6655
- path9.join(this.indexPath, "vectors")
6803
+ path10.join(this.indexPath, "codebase.db"),
6804
+ path10.join(this.indexPath, "codebase.db-shm"),
6805
+ path10.join(this.indexPath, "codebase.db-wal"),
6806
+ path10.join(this.indexPath, "vectors.usearch"),
6807
+ path10.join(this.indexPath, "inverted-index.json"),
6808
+ path10.join(this.indexPath, "file-hashes.json"),
6809
+ path10.join(this.indexPath, "failed-batches.json"),
6810
+ path10.join(this.indexPath, "indexing.lock"),
6811
+ path10.join(this.indexPath, "vectors")
6656
6812
  ];
6657
6813
  await Promise.all(resetPaths.map(async (targetPath) => {
6658
6814
  try {
@@ -6917,7 +7073,7 @@ var Indexer = class {
6917
7073
  for (const parsed of parsedFiles) {
6918
7074
  currentFilePaths.add(parsed.path);
6919
7075
  if (parsed.chunks.length === 0) {
6920
- const relativePath = path9.relative(this.projectRoot, parsed.path);
7076
+ const relativePath = path10.relative(this.projectRoot, parsed.path);
6921
7077
  stats.parseFailures.push(relativePath);
6922
7078
  }
6923
7079
  let fileChunkCount = 0;
@@ -7056,6 +7212,7 @@ var Indexer = class {
7056
7212
  targetName: site.calleeName,
7057
7213
  toSymbolId: void 0,
7058
7214
  callType: site.callType,
7215
+ confidence: site.confidence,
7059
7216
  line: site.line,
7060
7217
  col: site.column,
7061
7218
  isResolved: false
@@ -7200,7 +7357,7 @@ var Indexer = class {
7200
7357
  for (const requestBatch of requestBatches) {
7201
7358
  queue.add(async () => {
7202
7359
  if (rateLimitBackoffMs > 0) {
7203
- await new Promise((resolve10) => setTimeout(resolve10, rateLimitBackoffMs));
7360
+ await new Promise((resolve11) => setTimeout(resolve11, rateLimitBackoffMs));
7204
7361
  }
7205
7362
  try {
7206
7363
  const result = await pRetry(
@@ -7215,7 +7372,7 @@ var Indexer = class {
7215
7372
  factor: 2,
7216
7373
  shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError),
7217
7374
  onFailedAttempt: (error) => {
7218
- const message = getErrorMessage(error);
7375
+ const message = getErrorMessage2(error);
7219
7376
  if (isRateLimitError(error)) {
7220
7377
  rateLimitBackoffMs = Math.min(providerRateLimits.maxRetryMs, (rateLimitBackoffMs || providerRateLimits.minRetryMs) * 2);
7221
7378
  this.logger.embedding("warn", `Rate limited, backing off`, {
@@ -7322,7 +7479,7 @@ var Indexer = class {
7322
7479
  });
7323
7480
  } catch (error) {
7324
7481
  const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id));
7325
- const failureMessage = getErrorMessage(error);
7482
+ const failureMessage = getErrorMessage2(error);
7326
7483
  const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
7327
7484
  for (const chunk of failedChunks) {
7328
7485
  if (!failedChunkIds.has(chunk.id)) {
@@ -7761,8 +7918,8 @@ var Indexer = class {
7761
7918
  this.indexCompatibility = compatibility;
7762
7919
  return;
7763
7920
  }
7764
- const localProjectIndexPath = path9.join(this.projectRoot, ".opencode", "index");
7765
- if (path9.resolve(this.indexPath) !== path9.resolve(localProjectIndexPath)) {
7921
+ const localProjectIndexPath = path10.join(this.projectRoot, ".opencode", "index");
7922
+ if (path10.resolve(this.indexPath) !== path10.resolve(localProjectIndexPath)) {
7766
7923
  throw new Error(
7767
7924
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
7768
7925
  );
@@ -7850,7 +8007,7 @@ var Indexer = class {
7850
8007
  gcOrphanSymbols: 0,
7851
8008
  gcOrphanCallEdges: 0,
7852
8009
  resetCorruptedIndex: true,
7853
- warning: this.getCorruptedIndexWarning(path9.join(this.indexPath, "codebase.db"))
8010
+ warning: this.getCorruptedIndexWarning(path10.join(this.indexPath, "codebase.db"))
7854
8011
  };
7855
8012
  }
7856
8013
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -8001,7 +8158,7 @@ var Indexer = class {
8001
8158
  succeeded += successfulResults.length;
8002
8159
  stillFailing.push(...failedChunksForBatch.values());
8003
8160
  } catch (error) {
8004
- const failureMessage = getErrorMessage(error);
8161
+ const failureMessage = getErrorMessage2(error);
8005
8162
  const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
8006
8163
  const unaccountedChunks = batch.chunks.filter(
8007
8164
  (chunk) => !failedChunksForBatch.has(chunk.id) && !completedChunkIds.has(chunk.id)
@@ -8175,12 +8332,12 @@ var Indexer = class {
8175
8332
  })
8176
8333
  );
8177
8334
  }
8178
- async getCallers(targetName) {
8335
+ async getCallers(targetName, callTypeFilter) {
8179
8336
  const { database } = await this.ensureInitialized();
8180
8337
  const seen = /* @__PURE__ */ new Set();
8181
8338
  const results = [];
8182
8339
  for (const branchKey of this.getBranchCatalogKeys()) {
8183
- for (const edge of database.getCallersWithContext(targetName, branchKey)) {
8340
+ for (const edge of database.getCallersWithContext(targetName, branchKey, callTypeFilter)) {
8184
8341
  if (!seen.has(edge.id)) {
8185
8342
  seen.add(edge.id);
8186
8343
  results.push(edge);
@@ -8189,12 +8346,12 @@ var Indexer = class {
8189
8346
  }
8190
8347
  return results;
8191
8348
  }
8192
- async getCallees(symbolId) {
8349
+ async getCallees(symbolId, callTypeFilter) {
8193
8350
  const { database } = await this.ensureInitialized();
8194
8351
  const seen = /* @__PURE__ */ new Set();
8195
8352
  const results = [];
8196
8353
  for (const branchKey of this.getBranchCatalogKeys()) {
8197
- for (const edge of database.getCallees(symbolId, branchKey)) {
8354
+ for (const edge of database.getCallees(symbolId, branchKey, callTypeFilter)) {
8198
8355
  if (!seen.has(edge.id)) {
8199
8356
  seen.add(edge.id);
8200
8357
  results.push(edge);
@@ -8203,6 +8360,198 @@ var Indexer = class {
8203
8360
  }
8204
8361
  return results;
8205
8362
  }
8363
+ async findCallPath(fromName, toName, maxDepth) {
8364
+ const { database } = await this.ensureInitialized();
8365
+ let shortest = [];
8366
+ for (const branchKey of this.getBranchCatalogKeys()) {
8367
+ const path19 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
8368
+ if (path19.length > 0 && (shortest.length === 0 || path19.length < shortest.length)) {
8369
+ shortest = path19;
8370
+ }
8371
+ }
8372
+ return shortest;
8373
+ }
8374
+ async getSymbolsForBranch(branch) {
8375
+ const { database } = await this.ensureInitialized();
8376
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
8377
+ return database.getSymbolsForBranch(resolvedBranch);
8378
+ }
8379
+ async getSymbolsForFiles(filePaths, branch) {
8380
+ const { database } = await this.ensureInitialized();
8381
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
8382
+ return database.getSymbolsForFiles(filePaths, resolvedBranch);
8383
+ }
8384
+ async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
8385
+ const { database } = await this.ensureInitialized();
8386
+ const branch = this.getBranchCatalogKey();
8387
+ return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
8388
+ }
8389
+ async detectCommunities(branch, symbolIds) {
8390
+ const { database } = await this.ensureInitialized();
8391
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
8392
+ return database.detectCommunities(resolvedBranch, symbolIds);
8393
+ }
8394
+ async computeCentrality(branch) {
8395
+ const { database } = await this.ensureInitialized();
8396
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
8397
+ return database.computeCentrality(resolvedBranch);
8398
+ }
8399
+ async getPrImpact(opts) {
8400
+ const { database } = await this.ensureInitialized();
8401
+ const execFileAsync2 = promisify2(execFile2);
8402
+ const changedFilesResult = await getChangedFiles({
8403
+ pr: opts.pr,
8404
+ branch: opts.branch,
8405
+ projectRoot: this.projectRoot,
8406
+ baseBranch: this.baseBranch
8407
+ });
8408
+ const changedFiles = changedFilesResult.files;
8409
+ const headRefName = changedFilesResult.headRefName;
8410
+ if (opts.pr !== void 0 && headRefName === void 0) {
8411
+ throw new Error(
8412
+ `Could not resolve head branch for PR #${opts.pr}. Run index_codebase on the PR branch first.`
8413
+ );
8414
+ }
8415
+ const resolvedBranch = opts.pr !== void 0 ? headRefName : opts.branch || this.currentBranch;
8416
+ const branchKey = this.getBranchCatalogKeyFor(resolvedBranch || "default");
8417
+ const branchSymbols = database.getSymbolsForBranch(branchKey);
8418
+ if (branchSymbols.length === 0) {
8419
+ throw new Error(
8420
+ "Run index_codebase first to build the call graph and symbol index for this branch."
8421
+ );
8422
+ }
8423
+ const absoluteChangedFiles = changedFiles.map((f) => path10.resolve(this.projectRoot, f));
8424
+ const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
8425
+ const directIds = directSymbols.map((s) => s.id);
8426
+ const direction = opts.direction ?? "both";
8427
+ const maxDepth = opts.maxDepth ?? 5;
8428
+ const transitiveCallers = database.getTransitiveReachability(
8429
+ directIds,
8430
+ branchKey,
8431
+ direction,
8432
+ maxDepth
8433
+ );
8434
+ const affectedIdsSet = new Set(directIds);
8435
+ for (const caller of transitiveCallers) {
8436
+ affectedIdsSet.add(caller.symbolId);
8437
+ }
8438
+ const allAffectedIds = Array.from(affectedIdsSet);
8439
+ const communitiesData = database.detectCommunities(branchKey, allAffectedIds);
8440
+ const communityMap = /* @__PURE__ */ new Map();
8441
+ for (const c of communitiesData) {
8442
+ if (!communityMap.has(c.communityLabel)) {
8443
+ communityMap.set(c.communityLabel, {
8444
+ label: c.communityLabel,
8445
+ symbolCount: 0,
8446
+ directSymbols: /* @__PURE__ */ new Set()
8447
+ });
8448
+ }
8449
+ const entry = communityMap.get(c.communityLabel);
8450
+ entry.symbolCount++;
8451
+ if (directIds.includes(c.symbolId)) {
8452
+ entry.directSymbols.add(c.symbolId);
8453
+ }
8454
+ }
8455
+ const communities = Array.from(communityMap.values()).map((c) => ({
8456
+ label: c.label,
8457
+ symbolCount: c.symbolCount,
8458
+ directSymbols: Array.from(c.directSymbols)
8459
+ }));
8460
+ const centralityData = database.computeCentrality(branchKey);
8461
+ const hubThreshold = opts.hubThreshold ?? 10;
8462
+ const hubNodes = centralityData.filter((c) => directIds.includes(c.symbolId) && c.callerCount >= hubThreshold).map((c) => ({
8463
+ id: c.symbolId,
8464
+ name: c.symbolName,
8465
+ callerCount: c.callerCount,
8466
+ filePath: c.filePath
8467
+ }));
8468
+ const totalAffected = allAffectedIds.length;
8469
+ let riskLevel;
8470
+ let riskReason;
8471
+ if (totalAffected < 5 && hubNodes.length === 0) {
8472
+ riskLevel = "LOW";
8473
+ riskReason = `Small impact: ${totalAffected} affected symbols, no hub nodes touched.`;
8474
+ } else if (totalAffected > 20 || hubNodes.length > 1) {
8475
+ riskLevel = "HIGH";
8476
+ riskReason = `Large impact: ${totalAffected} affected symbols${hubNodes.length > 0 ? `, ${hubNodes.length} hub nodes touched` : ""}.`;
8477
+ } else {
8478
+ riskLevel = "MEDIUM";
8479
+ riskReason = `Moderate impact: ${totalAffected} affected symbols${hubNodes.length === 1 ? ", 1 hub node touched" : ""}.`;
8480
+ }
8481
+ let conflictingPRs;
8482
+ if (opts.checkConflicts) {
8483
+ conflictingPRs = [];
8484
+ try {
8485
+ const { stdout } = await execFileAsync2(
8486
+ "gh",
8487
+ ["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
8488
+ { cwd: this.projectRoot, timeout: 3e4 }
8489
+ );
8490
+ const openPRs = JSON.parse(stdout);
8491
+ const currentCommunityLabels = new Set(communities.map((c) => c.label));
8492
+ const allCommunitiesData = database.detectCommunities(branchKey);
8493
+ const symbolToCommunity = /* @__PURE__ */ new Map();
8494
+ const structuralKey = (filePath, name) => `${filePath.toLowerCase()}:${name.toLowerCase()}`;
8495
+ for (const c of allCommunitiesData) {
8496
+ symbolToCommunity.set(structuralKey(c.filePath, c.symbolName), c.communityLabel);
8497
+ }
8498
+ for (const openPr of openPRs) {
8499
+ if (openPr.number === opts.pr) continue;
8500
+ try {
8501
+ const otherChanged = await getChangedFiles({
8502
+ pr: openPr.number,
8503
+ projectRoot: this.projectRoot,
8504
+ baseBranch: this.baseBranch
8505
+ });
8506
+ const otherAbsolute = otherChanged.files.map((f) => path10.resolve(this.projectRoot, f));
8507
+ const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
8508
+ const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
8509
+ const otherLabels = /* @__PURE__ */ new Set();
8510
+ for (const sym of otherSymbols) {
8511
+ const label = symbolToCommunity.get(structuralKey(sym.filePath, sym.name));
8512
+ if (label) {
8513
+ otherLabels.add(label);
8514
+ }
8515
+ }
8516
+ const overlapping = Array.from(otherLabels).filter(
8517
+ (l) => currentCommunityLabels.has(l)
8518
+ );
8519
+ if (overlapping.length > 0) {
8520
+ conflictingPRs.push({
8521
+ pr: openPr.number,
8522
+ branch: openPr.headRefName,
8523
+ overlappingCommunities: overlapping
8524
+ });
8525
+ }
8526
+ } catch {
8527
+ }
8528
+ }
8529
+ } catch {
8530
+ }
8531
+ }
8532
+ return {
8533
+ changedFiles,
8534
+ directSymbols: directSymbols.map((s) => ({
8535
+ id: s.id,
8536
+ name: s.name,
8537
+ kind: s.kind,
8538
+ filePath: s.filePath
8539
+ })),
8540
+ transitiveCallers: transitiveCallers.map((c) => ({
8541
+ id: c.symbolId,
8542
+ name: c.symbolName,
8543
+ filePath: c.filePath,
8544
+ depth: c.depth
8545
+ })),
8546
+ totalAffected,
8547
+ communities,
8548
+ hubNodes,
8549
+ riskLevel,
8550
+ riskReason,
8551
+ direction,
8552
+ conflictingPRs
8553
+ };
8554
+ }
8206
8555
  async close() {
8207
8556
  await this.database?.close();
8208
8557
  this.database = null;
@@ -8462,13 +8811,13 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
8462
8811
  // src/eval/runner-config.ts
8463
8812
  import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync3 } from "fs";
8464
8813
  import * as os4 from "os";
8465
- import * as path11 from "path";
8814
+ import * as path12 from "path";
8466
8815
 
8467
8816
  // src/config/rebase.ts
8468
- import * as path10 from "path";
8817
+ import * as path11 from "path";
8469
8818
  function isWithinRoot(rootDir, targetPath) {
8470
- const relativePath = path10.relative(rootDir, targetPath);
8471
- return relativePath === "" || !relativePath.startsWith("..") && !path10.isAbsolute(relativePath);
8819
+ const relativePath = path11.relative(rootDir, targetPath);
8820
+ return relativePath === "" || !relativePath.startsWith("..") && !path11.isAbsolute(relativePath);
8472
8821
  }
8473
8822
  function rebasePathEntries(values, fromDir, toDir) {
8474
8823
  if (!Array.isArray(values)) {
@@ -8476,10 +8825,10 @@ function rebasePathEntries(values, fromDir, toDir) {
8476
8825
  }
8477
8826
  return values.filter((value) => typeof value === "string").map((value) => {
8478
8827
  const trimmed = value.trim();
8479
- if (!trimmed || path10.isAbsolute(trimmed)) {
8828
+ if (!trimmed || path11.isAbsolute(trimmed)) {
8480
8829
  return trimmed;
8481
8830
  }
8482
- return normalizePathSeparators(path10.normalize(path10.relative(toDir, path10.resolve(fromDir, trimmed))));
8831
+ return normalizePathSeparators(path11.normalize(path11.relative(toDir, path11.resolve(fromDir, trimmed))));
8483
8832
  }).filter(Boolean);
8484
8833
  }
8485
8834
  function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
@@ -8491,17 +8840,17 @@ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
8491
8840
  if (!trimmed) {
8492
8841
  return trimmed;
8493
8842
  }
8494
- if (path10.isAbsolute(trimmed)) {
8843
+ if (path11.isAbsolute(trimmed)) {
8495
8844
  if (isWithinRoot(sourceRoot, trimmed)) {
8496
- return normalizePathSeparators(path10.normalize(path10.relative(sourceRoot, trimmed) || "."));
8845
+ return normalizePathSeparators(path11.normalize(path11.relative(sourceRoot, trimmed) || "."));
8497
8846
  }
8498
- return path10.normalize(trimmed);
8847
+ return path11.normalize(trimmed);
8499
8848
  }
8500
- const resolvedFromSource = path10.resolve(sourceRoot, trimmed);
8849
+ const resolvedFromSource = path11.resolve(sourceRoot, trimmed);
8501
8850
  if (isWithinRoot(sourceRoot, resolvedFromSource)) {
8502
- return normalizePathSeparators(path10.normalize(trimmed));
8851
+ return normalizePathSeparators(path11.normalize(trimmed));
8503
8852
  }
8504
- return normalizePathSeparators(path10.normalize(path10.relative(targetRoot, resolvedFromSource)));
8853
+ return normalizePathSeparators(path11.normalize(path11.relative(targetRoot, resolvedFromSource)));
8505
8854
  }).filter(Boolean);
8506
8855
  }
8507
8856
 
@@ -8549,20 +8898,20 @@ function parseJsonConfigFile(filePath) {
8549
8898
  }
8550
8899
  }
8551
8900
  function toAbsolute(projectRoot, maybeRelative) {
8552
- return path11.isAbsolute(maybeRelative) ? maybeRelative : path11.join(projectRoot, maybeRelative);
8901
+ return path12.isAbsolute(maybeRelative) ? maybeRelative : path12.join(projectRoot, maybeRelative);
8553
8902
  }
8554
8903
  function isProjectScopedConfigPath(configPath) {
8555
- return path11.basename(configPath) === "codebase-index.json" && path11.basename(path11.dirname(configPath)) === ".opencode";
8904
+ return path12.basename(configPath) === "codebase-index.json" && path12.basename(path12.dirname(configPath)) === ".opencode";
8556
8905
  }
8557
8906
  function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfigPath) {
8558
8907
  const config = rawConfig && typeof rawConfig === "object" ? { ...rawConfig } : {};
8559
8908
  const rebaseEntries = (values) => isProjectScopedConfigPath(resolvedConfigPath) ? resolveInheritedKnowledgeBaseEntries(
8560
8909
  values,
8561
- path11.dirname(path11.dirname(resolvedConfigPath)),
8910
+ path12.dirname(path12.dirname(resolvedConfigPath)),
8562
8911
  projectRoot
8563
8912
  ) : rebasePathEntries(
8564
8913
  values,
8565
- path11.dirname(resolvedConfigPath),
8914
+ path12.dirname(resolvedConfigPath),
8566
8915
  projectRoot
8567
8916
  );
8568
8917
  if (Array.isArray(config.knowledgeBases)) {
@@ -8590,7 +8939,7 @@ function loadRawConfig(projectRoot, configPath) {
8590
8939
  projectConfig
8591
8940
  );
8592
8941
  }
8593
- const globalConfig = path11.join(os4.homedir(), ".config", "opencode", "codebase-index.json");
8942
+ const globalConfig = path12.join(os4.homedir(), ".config", "opencode", "codebase-index.json");
8594
8943
  if (existsSync7(globalConfig)) {
8595
8944
  return parseJsonConfigFile(globalConfig);
8596
8945
  }
@@ -8600,10 +8949,10 @@ function getIndexRootPath(projectRoot, scope) {
8600
8949
  return scope === "global" ? getGlobalIndexPath() : resolveProjectIndexPath(projectRoot, scope);
8601
8950
  }
8602
8951
  function getLocalProjectIndexRoot(projectRoot) {
8603
- return path11.join(projectRoot, ".opencode", "index");
8952
+ return path12.join(projectRoot, ".opencode", "index");
8604
8953
  }
8605
8954
  function getLocalProjectConfigPath(projectRoot) {
8606
- return path11.join(projectRoot, ".opencode", "codebase-index.json");
8955
+ return path12.join(projectRoot, ".opencode", "codebase-index.json");
8607
8956
  }
8608
8957
  function clearIndexRoot(projectRoot, scope) {
8609
8958
  const indexRoot = scope === "global" ? getIndexRootPath(projectRoot, scope) : getLocalProjectIndexRoot(projectRoot);
@@ -8625,7 +8974,7 @@ function ensureLocalEvalProjectConfig(projectRoot, configPath) {
8625
8974
  projectRoot,
8626
8975
  resolvedConfigPath
8627
8976
  );
8628
- mkdirSync3(path11.dirname(localConfigPath), { recursive: true });
8977
+ mkdirSync3(path12.dirname(localConfigPath), { recursive: true });
8629
8978
  writeFileSync3(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
8630
8979
  return localConfigPath;
8631
8980
  }
@@ -8675,23 +9024,23 @@ function isRecord2(value) {
8675
9024
  function isStringArray3(value) {
8676
9025
  return Array.isArray(value) && value.every((item) => typeof item === "string");
8677
9026
  }
8678
- function asPositiveNumber(value, path18) {
9027
+ function asPositiveNumber(value, path19) {
8679
9028
  if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
8680
- throw new Error(`${path18} must be a non-negative number`);
9029
+ throw new Error(`${path19} must be a non-negative number`);
8681
9030
  }
8682
9031
  return value;
8683
9032
  }
8684
- function parseQueryType(value, path18) {
9033
+ function parseQueryType(value, path19) {
8685
9034
  if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
8686
9035
  return value;
8687
9036
  }
8688
9037
  throw new Error(
8689
- `${path18} must be one of: definition, implementation-intent, similarity, keyword-heavy`
9038
+ `${path19} must be one of: definition, implementation-intent, similarity, keyword-heavy`
8690
9039
  );
8691
9040
  }
8692
- function parseExpected(input, path18) {
9041
+ function parseExpected(input, path19) {
8693
9042
  if (!isRecord2(input)) {
8694
- throw new Error(`${path18} must be an object`);
9043
+ throw new Error(`${path19} must be an object`);
8695
9044
  }
8696
9045
  const filePathRaw = input.filePath;
8697
9046
  const acceptableFilesRaw = input.acceptableFiles;
@@ -8700,16 +9049,16 @@ function parseExpected(input, path18) {
8700
9049
  const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
8701
9050
  const acceptableFiles = isStringArray3(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
8702
9051
  if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
8703
- throw new Error(`${path18} must include either expected.filePath or expected.acceptableFiles`);
9052
+ throw new Error(`${path19} must include either expected.filePath or expected.acceptableFiles`);
8704
9053
  }
8705
9054
  if (acceptableFilesRaw !== void 0 && !isStringArray3(acceptableFilesRaw)) {
8706
- throw new Error(`${path18}.acceptableFiles must be an array of strings`);
9055
+ throw new Error(`${path19}.acceptableFiles must be an array of strings`);
8707
9056
  }
8708
9057
  if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
8709
- throw new Error(`${path18}.symbol must be a string when provided`);
9058
+ throw new Error(`${path19}.symbol must be a string when provided`);
8710
9059
  }
8711
9060
  if (branchRaw !== void 0 && typeof branchRaw !== "string") {
8712
- throw new Error(`${path18}.branch must be a string when provided`);
9061
+ throw new Error(`${path19}.branch must be a string when provided`);
8713
9062
  }
8714
9063
  return {
8715
9064
  filePath,
@@ -8719,25 +9068,25 @@ function parseExpected(input, path18) {
8719
9068
  };
8720
9069
  }
8721
9070
  function parseQuery(input, index) {
8722
- const path18 = `queries[${index}]`;
9071
+ const path19 = `queries[${index}]`;
8723
9072
  if (!isRecord2(input)) {
8724
- throw new Error(`${path18} must be an object`);
9073
+ throw new Error(`${path19} must be an object`);
8725
9074
  }
8726
9075
  const id = input.id;
8727
9076
  const query = input.query;
8728
9077
  const queryType = input.queryType;
8729
9078
  const expected = input.expected;
8730
9079
  if (typeof id !== "string" || id.trim().length === 0) {
8731
- throw new Error(`${path18}.id must be a non-empty string`);
9080
+ throw new Error(`${path19}.id must be a non-empty string`);
8732
9081
  }
8733
9082
  if (typeof query !== "string" || query.trim().length === 0) {
8734
- throw new Error(`${path18}.query must be a non-empty string`);
9083
+ throw new Error(`${path19}.query must be a non-empty string`);
8735
9084
  }
8736
9085
  return {
8737
9086
  id,
8738
9087
  query,
8739
- queryType: parseQueryType(queryType, `${path18}.queryType`),
8740
- expected: parseExpected(expected, `${path18}.expected`)
9088
+ queryType: parseQueryType(queryType, `${path19}.queryType`),
9089
+ expected: parseExpected(expected, `${path19}.expected`)
8741
9090
  };
8742
9091
  }
8743
9092
  function parseGoldenDataset(raw, sourceLabel) {
@@ -8920,13 +9269,13 @@ async function runEvaluation(options) {
8920
9269
  };
8921
9270
  const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
8922
9271
  const perQueryArtifact = buildPerQueryArtifact(perQuery);
8923
- writeJson(path12.join(outputDir, "summary.json"), summary);
8924
- writeJson(path12.join(outputDir, "per-query.json"), perQueryArtifact);
9272
+ writeJson(path13.join(outputDir, "summary.json"), summary);
9273
+ writeJson(path13.join(outputDir, "per-query.json"), perQueryArtifact);
8925
9274
  let comparison;
8926
9275
  if (againstPath) {
8927
9276
  const baseline = loadSummary(againstPath);
8928
9277
  comparison = compareSummaries(summary, baseline, againstPath);
8929
- writeJson(path12.join(outputDir, "compare.json"), comparison);
9278
+ writeJson(path13.join(outputDir, "compare.json"), comparison);
8930
9279
  }
8931
9280
  let gate;
8932
9281
  if (options.ciMode) {
@@ -8939,7 +9288,7 @@ async function runEvaluation(options) {
8939
9288
  if (existsSync8(resolvedBaseline)) {
8940
9289
  const baselineSummary = loadSummary(resolvedBaseline);
8941
9290
  comparison = compareSummaries(summary, baselineSummary, resolvedBaseline);
8942
- writeJson(path12.join(outputDir, "compare.json"), comparison);
9291
+ writeJson(path13.join(outputDir, "compare.json"), comparison);
8943
9292
  } else if (budget.failOnMissingBaseline) {
8944
9293
  throw new Error(
8945
9294
  `Budget baseline is missing: ${resolvedBaseline}. Set failOnMissingBaseline=false to allow CI run without baseline.`
@@ -8949,7 +9298,7 @@ async function runEvaluation(options) {
8949
9298
  gate = evaluateBudgetGate(budget, summary, comparison);
8950
9299
  }
8951
9300
  const markdown = createSummaryMarkdown(summary, comparison, gate);
8952
- writeText(path12.join(outputDir, "summary.md"), markdown);
9301
+ writeText(path13.join(outputDir, "summary.md"), markdown);
8953
9302
  return { outputDir, summary, perQuery, comparison, gate };
8954
9303
  } finally {
8955
9304
  await indexer.close();
@@ -9007,23 +9356,23 @@ async function runSweep(options, sweep) {
9007
9356
  bestByMrrAt10,
9008
9357
  bestByP95Latency
9009
9358
  };
9010
- writeJson(path12.join(outputDir, "compare.json"), aggregate);
9359
+ writeJson(path13.join(outputDir, "compare.json"), aggregate);
9011
9360
  const md = createSummaryMarkdown(
9012
9361
  bestByHitAt5?.summary ?? runs[0].summary,
9013
9362
  bestByHitAt5?.comparison,
9014
9363
  void 0,
9015
9364
  aggregate
9016
9365
  );
9017
- writeText(path12.join(outputDir, "summary.md"), md);
9018
- writeJson(path12.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
9366
+ writeText(path13.join(outputDir, "summary.md"), md);
9367
+ writeJson(path13.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
9019
9368
  return { outputDir, aggregate };
9020
9369
  }
9021
9370
 
9022
9371
  // src/eval/cli.ts
9023
- import * as path14 from "path";
9372
+ import * as path15 from "path";
9024
9373
 
9025
9374
  // src/eval/cli-parser.ts
9026
- import * as path13 from "path";
9375
+ import * as path14 from "path";
9027
9376
  function printUsage() {
9028
9377
  console.log(`
9029
9378
  Usage:
@@ -9095,12 +9444,12 @@ function parseEvalArgs(argv, cwd) {
9095
9444
  const arg = argv[i];
9096
9445
  const next = argv[i + 1];
9097
9446
  if (arg === "--project" && next) {
9098
- parsed.projectRoot = path13.resolve(cwd, next);
9447
+ parsed.projectRoot = path14.resolve(cwd, next);
9099
9448
  i += 1;
9100
9449
  continue;
9101
9450
  }
9102
9451
  if (arg === "--config" && next) {
9103
- parsed.configPath = path13.resolve(cwd, next);
9452
+ parsed.configPath = path14.resolve(cwd, next);
9104
9453
  i += 1;
9105
9454
  continue;
9106
9455
  }
@@ -9296,22 +9645,22 @@ async function handleEvalCommand(args, cwd) {
9296
9645
  if (!parsed.againstPath.endsWith(".json")) {
9297
9646
  throw new Error("eval diff --against must point to a summary JSON file");
9298
9647
  }
9299
- const currentSummary = loadSummary(path14.resolve(parsed.projectRoot, currentPath), {
9648
+ const currentSummary = loadSummary(path15.resolve(parsed.projectRoot, currentPath), {
9300
9649
  allowLegacyDiversityMetrics: true
9301
9650
  });
9302
- const baselineSummary = loadSummary(path14.resolve(parsed.projectRoot, parsed.againstPath), {
9651
+ const baselineSummary = loadSummary(path15.resolve(parsed.projectRoot, parsed.againstPath), {
9303
9652
  allowLegacyDiversityMetrics: true
9304
9653
  });
9305
9654
  const comparison = compareSummaries(
9306
9655
  currentSummary,
9307
9656
  baselineSummary,
9308
- path14.resolve(parsed.projectRoot, parsed.againstPath)
9657
+ path15.resolve(parsed.projectRoot, parsed.againstPath)
9309
9658
  );
9310
- const outputDir = createRunDirectory(path14.resolve(parsed.projectRoot, parsed.outputRoot));
9659
+ const outputDir = createRunDirectory(path15.resolve(parsed.projectRoot, parsed.outputRoot));
9311
9660
  const summaryMd = createSummaryMarkdown(currentSummary, comparison);
9312
- writeJson(path14.join(outputDir, "compare.json"), comparison);
9313
- writeText(path14.join(outputDir, "summary.md"), summaryMd);
9314
- writeJson(path14.join(outputDir, "summary.json"), currentSummary);
9661
+ writeJson(path15.join(outputDir, "compare.json"), comparison);
9662
+ writeText(path15.join(outputDir, "summary.md"), summaryMd);
9663
+ writeJson(path15.join(outputDir, "summary.json"), currentSummary);
9315
9664
  console.log(`Eval diff complete. Artifacts: ${outputDir}`);
9316
9665
  return 0;
9317
9666
  }
@@ -9320,7 +9669,7 @@ async function handleEvalCommand(args, cwd) {
9320
9669
 
9321
9670
  // src/mcp-server.ts
9322
9671
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9323
- import * as path16 from "path";
9672
+ import * as path17 from "path";
9324
9673
  import { existsSync as existsSync10 } from "fs";
9325
9674
 
9326
9675
  // src/mcp-server/register-prompts.ts
@@ -9419,7 +9768,7 @@ import { z as z2 } from "zod";
9419
9768
  // src/config/merger.ts
9420
9769
  import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
9421
9770
  import * as os5 from "os";
9422
- import * as path15 from "path";
9771
+ import * as path16 from "path";
9423
9772
  var PROJECT_OVERRIDE_KEYS = [
9424
9773
  "embeddingProvider",
9425
9774
  "customProvider",
@@ -9452,8 +9801,8 @@ function mergeUniqueStringArray(values) {
9452
9801
  return [...new Set(values.map((value) => String(value).trim()))];
9453
9802
  }
9454
9803
  function normalizeKnowledgeBasePath(value) {
9455
- let normalized = path15.normalize(String(value).trim());
9456
- const root = path15.parse(normalized).root;
9804
+ let normalized = path16.normalize(String(value).trim());
9805
+ const root = path16.parse(normalized).root;
9457
9806
  while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
9458
9807
  normalized = normalized.slice(0, -1);
9459
9808
  }
@@ -9503,8 +9852,8 @@ function loadJsonFile(filePath) {
9503
9852
  return null;
9504
9853
  }
9505
9854
  function materializeLocalProjectConfig(projectRoot, config) {
9506
- const localConfigPath = path15.join(projectRoot, ".opencode", "codebase-index.json");
9507
- mkdirSync4(path15.dirname(localConfigPath), { recursive: true });
9855
+ const localConfigPath = path16.join(projectRoot, ".opencode", "codebase-index.json");
9856
+ mkdirSync4(path16.dirname(localConfigPath), { recursive: true });
9508
9857
  writeFileSync4(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
9509
9858
  return localConfigPath;
9510
9859
  }
@@ -9515,7 +9864,7 @@ function loadProjectConfigLayer(projectRoot) {
9515
9864
  return {};
9516
9865
  }
9517
9866
  const normalizedConfig = { ...projectConfig };
9518
- const projectConfigBaseDir = path15.dirname(path15.dirname(projectConfigPath));
9867
+ const projectConfigBaseDir = path16.dirname(path16.dirname(projectConfigPath));
9519
9868
  if (Array.isArray(normalizedConfig.knowledgeBases)) {
9520
9869
  normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
9521
9870
  normalizedConfig.knowledgeBases,
@@ -9734,6 +10083,57 @@ ${truncateContent(r.content)}
9734
10083
  return formatted.join("\n\n");
9735
10084
  }
9736
10085
 
10086
+ // src/tools/format-pr-impact.ts
10087
+ function formatPrImpact(result) {
10088
+ const lines = [];
10089
+ lines.push(`\u2192 Files changed: ${result.changedFiles.length}`);
10090
+ for (const file of result.changedFiles) {
10091
+ lines.push(` - ${file}`);
10092
+ }
10093
+ const directCount = result.directSymbols.length;
10094
+ const transitiveCount = result.transitiveCallers.length;
10095
+ const directionLabel = result.direction === "callees" ? "callees" : result.direction === "both" ? "reachable (callers + callees)" : "callers";
10096
+ lines.push(
10097
+ `\u2192 Symbols affected: ${result.totalAffected} (${directCount} direct, ${transitiveCount} transitive ${directionLabel})`
10098
+ );
10099
+ if (directCount > 0) {
10100
+ lines.push(
10101
+ ` Direct: ${result.directSymbols.map((s) => `${s.name} (${s.kind})`).join(", ")}`
10102
+ );
10103
+ }
10104
+ if (transitiveCount > 0) {
10105
+ lines.push(
10106
+ ` Transitive ${directionLabel}: ${result.transitiveCallers.map((s) => s.name).join(", ")}`
10107
+ );
10108
+ }
10109
+ if (result.communities.length > 0) {
10110
+ lines.push(
10111
+ `\u2192 Communities touched: ${result.communities.map((c) => c.label).join(", ")}`
10112
+ );
10113
+ for (const community of result.communities) {
10114
+ lines.push(
10115
+ ` - ${community.label}: ${community.symbolCount} symbols`
10116
+ );
10117
+ }
10118
+ } else {
10119
+ lines.push("\u2192 Communities touched: none");
10120
+ }
10121
+ lines.push(`\u2192 Risk: ${result.riskLevel} \u2014 ${result.riskReason}`);
10122
+ if (result.hubNodes.length > 0) {
10123
+ lines.push(" Hub nodes in change scope:");
10124
+ for (const hub of result.hubNodes) {
10125
+ lines.push(` - ${hub.name} (${hub.callerCount} callers) at ${hub.filePath}`);
10126
+ }
10127
+ }
10128
+ if (result.conflictingPRs && result.conflictingPRs.length > 0) {
10129
+ const conflictList = result.conflictingPRs.map(
10130
+ (c) => `PR #${c.pr} (also touches ${c.overlappingCommunities.join(", ")} community)`
10131
+ );
10132
+ lines.push(`\u2192 Potential conflicts with: ${conflictList.join(", ")}`);
10133
+ }
10134
+ return lines.join("\n");
10135
+ }
10136
+
9737
10137
  // src/mcp-server/shared.ts
9738
10138
  var MAX_CONTENT_LINES2 = 30;
9739
10139
  function truncateContent2(content) {
@@ -9978,11 +10378,12 @@ ${formatted.join("\n\n")}` }] };
9978
10378
  );
9979
10379
  server.tool(
9980
10380
  "call_graph",
9981
- "Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies.",
10381
+ "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.",
9982
10382
  {
9983
10383
  name: z2.string().describe("Function or method name to query"),
9984
10384
  direction: z2.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
9985
- symbolId: z2.string().optional().describe("Symbol ID (required for 'callees' direction)")
10385
+ symbolId: z2.string().optional().describe("Symbol ID (required for 'callees' direction)"),
10386
+ relationshipType: z2.enum(["Call", "MethodCall", "Constructor", "Import", "Inherits", "Implements"]).optional().describe("Filter by relationship type. Omit to show all.")
9986
10387
  },
9987
10388
  async (args) => {
9988
10389
  await runtime.ensureInitialized();
@@ -9991,29 +10392,85 @@ ${formatted.join("\n\n")}` }] };
9991
10392
  if (!args.symbolId) {
9992
10393
  return { content: [{ type: "text", text: "Error: 'symbolId' is required when direction is 'callees'." }] };
9993
10394
  }
9994
- const callees = await indexer.getCallees(args.symbolId);
10395
+ const callees = await indexer.getCallees(args.symbolId, args.relationshipType);
9995
10396
  if (callees.length === 0) {
9996
- return { content: [{ type: "text", text: `No callees found for symbol ${args.symbolId}.` }] };
10397
+ return { content: [{ type: "text", text: `No callees found for symbol ${args.symbolId}${args.relationshipType ? ` with type ${args.relationshipType}` : ""}.` }] };
9997
10398
  }
9998
- const formatted2 = callees.map(
9999
- (e, i) => `[${i + 1}] \u2192 ${e.targetName} (${e.callType}) at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`
10000
- );
10399
+ const formatted2 = callees.map((e, i) => {
10400
+ const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
10401
+ return `[${i + 1}] \u2192 ${e.targetName} (${e.callType})${conf} at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`;
10402
+ });
10001
10403
  return { content: [{ type: "text", text: `Callees (${callees.length}):
10002
10404
 
10003
10405
  ${formatted2.join("\n")}` }] };
10004
10406
  }
10005
- const callers = await indexer.getCallers(args.name);
10407
+ const callers = await indexer.getCallers(args.name, args.relationshipType);
10006
10408
  if (callers.length === 0) {
10007
- return { content: [{ type: "text", text: `No callers found for "${args.name}".` }] };
10409
+ return { content: [{ type: "text", text: `No callers found for "${args.name}"${args.relationshipType ? ` with type ${args.relationshipType}` : ""}.` }] };
10008
10410
  }
10009
- const formatted = callers.map(
10010
- (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]"}`
10011
- );
10411
+ const formatted = callers.map((e, i) => {
10412
+ const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
10413
+ 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]"}`;
10414
+ });
10012
10415
  return { content: [{ type: "text", text: `"${args.name}" is called by ${callers.length} function(s):
10013
10416
 
10014
10417
  ${formatted.join("\n")}` }] };
10015
10418
  }
10016
10419
  );
10420
+ server.tool(
10421
+ "call_graph_path",
10422
+ "Find the shortest connection path between two symbols in the call graph. Returns the chain of calls connecting them.",
10423
+ {
10424
+ from: z2.string().describe("Source function/method name (starting point)"),
10425
+ to: z2.string().describe("Target function/method name (destination)"),
10426
+ maxDepth: z2.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
10427
+ },
10428
+ async (args) => {
10429
+ await runtime.ensureInitialized();
10430
+ const indexer = runtime.getIndexer();
10431
+ const path19 = await indexer.findCallPath(args.from, args.to, args.maxDepth);
10432
+ if (path19.length === 0) {
10433
+ 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.` }] };
10434
+ }
10435
+ const formatted = path19.map((hop, i) => {
10436
+ const prefix = i === 0 ? "[start]" : `--${hop.callType}-->`;
10437
+ const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
10438
+ return `${prefix} ${hop.symbolName}${location}`;
10439
+ });
10440
+ return { content: [{ type: "text", text: `Path (${path19.length} hops):
10441
+ ${formatted.join("\n")}` }] };
10442
+ }
10443
+ );
10444
+ server.tool(
10445
+ "pr_impact",
10446
+ "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.",
10447
+ {
10448
+ pr: z2.number().optional().describe("Pull request number to analyze"),
10449
+ branch: z2.string().optional().describe("Branch name to analyze (defaults to current branch)"),
10450
+ maxDepth: z2.number().optional().default(5).describe("Maximum traversal depth for transitive callers (default: 5)"),
10451
+ hubThreshold: z2.number().optional().default(10).describe("Minimum caller count to flag a symbol as a hub node (default: 10)"),
10452
+ checkConflicts: z2.boolean().optional().default(false).describe("Check for conflicting open PRs touching the same communities (default: false)"),
10453
+ direction: z2.enum(["callers", "callees", "both"]).optional().default("both").describe("Call-graph traversal direction: 'callers' for upstream, 'callees' for downstream, 'both' for union (default: both)")
10454
+ },
10455
+ async (args) => {
10456
+ await runtime.ensureInitialized();
10457
+ const indexer = runtime.getIndexer();
10458
+ try {
10459
+ const result = await indexer.getPrImpact({
10460
+ pr: args.pr,
10461
+ branch: args.branch,
10462
+ maxDepth: args.maxDepth,
10463
+ hubThreshold: args.hubThreshold,
10464
+ checkConflicts: args.checkConflicts,
10465
+ direction: args.direction
10466
+ });
10467
+ return { content: [{ type: "text", text: formatPrImpact(result) }] };
10468
+ } catch (error) {
10469
+ const message = error instanceof Error ? error.message : String(error);
10470
+ return { content: [{ type: "text", text: `Error analyzing PR impact: ${message}` }] };
10471
+ }
10472
+ }
10473
+ );
10017
10474
  }
10018
10475
 
10019
10476
  // src/mcp-server.ts
@@ -10032,12 +10489,12 @@ function createMcpServer(projectRoot, config) {
10032
10489
  if (config.scope !== "project") {
10033
10490
  return false;
10034
10491
  }
10035
- const localIndexPath = path16.join(projectRoot, ".opencode", "index");
10492
+ const localIndexPath = path17.join(projectRoot, ".opencode", "index");
10036
10493
  const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
10037
10494
  if (!mainRepoRoot) {
10038
10495
  return false;
10039
10496
  }
10040
- const inheritedIndexPath = path16.join(mainRepoRoot, ".opencode", "index");
10497
+ const inheritedIndexPath = path17.join(mainRepoRoot, ".opencode", "index");
10041
10498
  return !existsSync10(localIndexPath) && existsSync10(inheritedIndexPath);
10042
10499
  }
10043
10500
  async function ensureInitialized() {
@@ -10063,9 +10520,9 @@ function parseArgs(argv) {
10063
10520
  let config;
10064
10521
  for (let i = 2; i < argv.length; i++) {
10065
10522
  if (argv[i] === "--project" && argv[i + 1]) {
10066
- project = path17.resolve(argv[++i]);
10523
+ project = path18.resolve(argv[++i]);
10067
10524
  } else if (argv[i] === "--config" && argv[i + 1]) {
10068
- config = path17.resolve(argv[++i]);
10525
+ config = path18.resolve(argv[++i]);
10069
10526
  }
10070
10527
  }
10071
10528
  return { project, config };