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/index.cjs CHANGED
@@ -7,7 +7,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
9
  var __commonJS = (cb, mod) => function __require() {
10
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ try {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ } catch (e) {
13
+ throw mod = 0, e;
14
+ }
11
15
  };
12
16
  var __export = (target, all) => {
13
17
  for (var name in all)
@@ -329,7 +333,7 @@ var require_ignore = __commonJS({
329
333
  // path matching.
330
334
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
331
335
  // @returns {TestResult} true if a file is ignored
332
- test(path18, checkUnignored, mode) {
336
+ test(path19, checkUnignored, mode) {
333
337
  let ignored = false;
334
338
  let unignored = false;
335
339
  let matchedRule;
@@ -338,7 +342,7 @@ var require_ignore = __commonJS({
338
342
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
339
343
  return;
340
344
  }
341
- const matched = rule[mode].test(path18);
345
+ const matched = rule[mode].test(path19);
342
346
  if (!matched) {
343
347
  return;
344
348
  }
@@ -359,17 +363,17 @@ var require_ignore = __commonJS({
359
363
  var throwError = (message, Ctor) => {
360
364
  throw new Ctor(message);
361
365
  };
362
- var checkPath = (path18, originalPath, doThrow) => {
363
- if (!isString(path18)) {
366
+ var checkPath = (path19, originalPath, doThrow) => {
367
+ if (!isString(path19)) {
364
368
  return doThrow(
365
369
  `path must be a string, but got \`${originalPath}\``,
366
370
  TypeError
367
371
  );
368
372
  }
369
- if (!path18) {
373
+ if (!path19) {
370
374
  return doThrow(`path must not be empty`, TypeError);
371
375
  }
372
- if (checkPath.isNotRelative(path18)) {
376
+ if (checkPath.isNotRelative(path19)) {
373
377
  const r = "`path.relative()`d";
374
378
  return doThrow(
375
379
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -378,7 +382,7 @@ var require_ignore = __commonJS({
378
382
  }
379
383
  return true;
380
384
  };
381
- var isNotRelative = (path18) => REGEX_TEST_INVALID_PATH.test(path18);
385
+ var isNotRelative = (path19) => REGEX_TEST_INVALID_PATH.test(path19);
382
386
  checkPath.isNotRelative = isNotRelative;
383
387
  checkPath.convert = (p) => p;
384
388
  var Ignore2 = class {
@@ -408,19 +412,19 @@ var require_ignore = __commonJS({
408
412
  }
409
413
  // @returns {TestResult}
410
414
  _test(originalPath, cache, checkUnignored, slices) {
411
- const path18 = originalPath && checkPath.convert(originalPath);
415
+ const path19 = originalPath && checkPath.convert(originalPath);
412
416
  checkPath(
413
- path18,
417
+ path19,
414
418
  originalPath,
415
419
  this._strictPathCheck ? throwError : RETURN_FALSE
416
420
  );
417
- return this._t(path18, cache, checkUnignored, slices);
421
+ return this._t(path19, cache, checkUnignored, slices);
418
422
  }
419
- checkIgnore(path18) {
420
- if (!REGEX_TEST_TRAILING_SLASH.test(path18)) {
421
- return this.test(path18);
423
+ checkIgnore(path19) {
424
+ if (!REGEX_TEST_TRAILING_SLASH.test(path19)) {
425
+ return this.test(path19);
422
426
  }
423
- const slices = path18.split(SLASH2).filter(Boolean);
427
+ const slices = path19.split(SLASH2).filter(Boolean);
424
428
  slices.pop();
425
429
  if (slices.length) {
426
430
  const parent = this._t(
@@ -433,18 +437,18 @@ var require_ignore = __commonJS({
433
437
  return parent;
434
438
  }
435
439
  }
436
- return this._rules.test(path18, false, MODE_CHECK_IGNORE);
440
+ return this._rules.test(path19, false, MODE_CHECK_IGNORE);
437
441
  }
438
- _t(path18, cache, checkUnignored, slices) {
439
- if (path18 in cache) {
440
- return cache[path18];
442
+ _t(path19, cache, checkUnignored, slices) {
443
+ if (path19 in cache) {
444
+ return cache[path19];
441
445
  }
442
446
  if (!slices) {
443
- slices = path18.split(SLASH2).filter(Boolean);
447
+ slices = path19.split(SLASH2).filter(Boolean);
444
448
  }
445
449
  slices.pop();
446
450
  if (!slices.length) {
447
- return cache[path18] = this._rules.test(path18, checkUnignored, MODE_IGNORE);
451
+ return cache[path19] = this._rules.test(path19, checkUnignored, MODE_IGNORE);
448
452
  }
449
453
  const parent = this._t(
450
454
  slices.join(SLASH2) + SLASH2,
@@ -452,29 +456,29 @@ var require_ignore = __commonJS({
452
456
  checkUnignored,
453
457
  slices
454
458
  );
455
- return cache[path18] = parent.ignored ? parent : this._rules.test(path18, checkUnignored, MODE_IGNORE);
459
+ return cache[path19] = parent.ignored ? parent : this._rules.test(path19, checkUnignored, MODE_IGNORE);
456
460
  }
457
- ignores(path18) {
458
- return this._test(path18, this._ignoreCache, false).ignored;
461
+ ignores(path19) {
462
+ return this._test(path19, this._ignoreCache, false).ignored;
459
463
  }
460
464
  createFilter() {
461
- return (path18) => !this.ignores(path18);
465
+ return (path19) => !this.ignores(path19);
462
466
  }
463
467
  filter(paths) {
464
468
  return makeArray(paths).filter(this.createFilter());
465
469
  }
466
470
  // @returns {TestResult}
467
- test(path18) {
468
- return this._test(path18, this._testCache, true);
471
+ test(path19) {
472
+ return this._test(path19, this._testCache, true);
469
473
  }
470
474
  };
471
475
  var factory = (options) => new Ignore2(options);
472
- var isPathValid = (path18) => checkPath(path18 && checkPath.convert(path18), path18, RETURN_FALSE);
476
+ var isPathValid = (path19) => checkPath(path19 && checkPath.convert(path19), path19, RETURN_FALSE);
473
477
  var setupWindows = () => {
474
478
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
475
479
  checkPath.convert = makePosix;
476
480
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
477
- checkPath.isNotRelative = (path18) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path18) || isNotRelative(path18);
481
+ checkPath.isNotRelative = (path19) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path19) || isNotRelative(path19);
478
482
  };
479
483
  if (
480
484
  // Detect `process` so that it can run in browsers.
@@ -658,7 +662,7 @@ __export(index_exports, {
658
662
  });
659
663
  module.exports = __toCommonJS(index_exports);
660
664
  var os6 = __toESM(require("os"), 1);
661
- var path17 = __toESM(require("path"), 1);
665
+ var path18 = __toESM(require("path"), 1);
662
666
  var import_url2 = require("url");
663
667
 
664
668
  // src/config/constants.ts
@@ -805,6 +809,7 @@ function getDefaultSearchConfig() {
805
809
  rerankTopN: 20,
806
810
  contextLines: 0,
807
811
  routingHints: true,
812
+ routingGraphHandoffHints: false,
808
813
  routingHintRole: "system"
809
814
  };
810
815
  }
@@ -927,6 +932,7 @@ function parseConfig(raw) {
927
932
  rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
928
933
  contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines,
929
934
  routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints,
935
+ routingGraphHandoffHints: typeof rawSearch.routingGraphHandoffHints === "boolean" ? rawSearch.routingGraphHandoffHints : defaultSearch.routingGraphHandoffHints,
930
936
  routingHintRole: rawSearch.routingHintRole === "developer" || rawSearch.routingHintRole === "system" ? rawSearch.routingHintRole : defaultSearch.routingHintRole
931
937
  };
932
938
  const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
@@ -1553,7 +1559,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
1553
1559
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
1554
1560
  const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
1555
1561
  if (wantBigintFsStats) {
1556
- this._stat = (path18) => statMethod(path18, { bigint: true });
1562
+ this._stat = (path19) => statMethod(path19, { bigint: true });
1557
1563
  } else {
1558
1564
  this._stat = statMethod;
1559
1565
  }
@@ -1578,8 +1584,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
1578
1584
  const par = this.parent;
1579
1585
  const fil = par && par.files;
1580
1586
  if (fil && fil.length > 0) {
1581
- const { path: path18, depth } = par;
1582
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path18));
1587
+ const { path: path19, depth } = par;
1588
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path19));
1583
1589
  const awaited = await Promise.all(slice);
1584
1590
  for (const entry of awaited) {
1585
1591
  if (!entry)
@@ -1619,20 +1625,20 @@ var ReaddirpStream = class extends import_node_stream.Readable {
1619
1625
  this.reading = false;
1620
1626
  }
1621
1627
  }
1622
- async _exploreDir(path18, depth) {
1628
+ async _exploreDir(path19, depth) {
1623
1629
  let files;
1624
1630
  try {
1625
- files = await (0, import_promises.readdir)(path18, this._rdOptions);
1631
+ files = await (0, import_promises.readdir)(path19, this._rdOptions);
1626
1632
  } catch (error) {
1627
1633
  this._onError(error);
1628
1634
  }
1629
- return { files, depth, path: path18 };
1635
+ return { files, depth, path: path19 };
1630
1636
  }
1631
- async _formatEntry(dirent, path18) {
1637
+ async _formatEntry(dirent, path19) {
1632
1638
  let entry;
1633
1639
  const basename5 = this._isDirent ? dirent.name : dirent;
1634
1640
  try {
1635
- const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path18, basename5));
1641
+ const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path19, basename5));
1636
1642
  entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename5 };
1637
1643
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
1638
1644
  } catch (err) {
@@ -2032,16 +2038,16 @@ var delFromSet = (main, prop, item) => {
2032
2038
  };
2033
2039
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
2034
2040
  var FsWatchInstances = /* @__PURE__ */ new Map();
2035
- function createFsWatchInstance(path18, options, listener, errHandler, emitRaw) {
2041
+ function createFsWatchInstance(path19, options, listener, errHandler, emitRaw) {
2036
2042
  const handleEvent = (rawEvent, evPath) => {
2037
- listener(path18);
2038
- emitRaw(rawEvent, evPath, { watchedPath: path18 });
2039
- if (evPath && path18 !== evPath) {
2040
- fsWatchBroadcast(sp.resolve(path18, evPath), KEY_LISTENERS, sp.join(path18, evPath));
2043
+ listener(path19);
2044
+ emitRaw(rawEvent, evPath, { watchedPath: path19 });
2045
+ if (evPath && path19 !== evPath) {
2046
+ fsWatchBroadcast(sp.resolve(path19, evPath), KEY_LISTENERS, sp.join(path19, evPath));
2041
2047
  }
2042
2048
  };
2043
2049
  try {
2044
- return (0, import_node_fs.watch)(path18, {
2050
+ return (0, import_node_fs.watch)(path19, {
2045
2051
  persistent: options.persistent
2046
2052
  }, handleEvent);
2047
2053
  } catch (error) {
@@ -2057,12 +2063,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
2057
2063
  listener(val1, val2, val3);
2058
2064
  });
2059
2065
  };
2060
- var setFsWatchListener = (path18, fullPath, options, handlers) => {
2066
+ var setFsWatchListener = (path19, fullPath, options, handlers) => {
2061
2067
  const { listener, errHandler, rawEmitter } = handlers;
2062
2068
  let cont = FsWatchInstances.get(fullPath);
2063
2069
  let watcher;
2064
2070
  if (!options.persistent) {
2065
- watcher = createFsWatchInstance(path18, options, listener, errHandler, rawEmitter);
2071
+ watcher = createFsWatchInstance(path19, options, listener, errHandler, rawEmitter);
2066
2072
  if (!watcher)
2067
2073
  return;
2068
2074
  return watcher.close.bind(watcher);
@@ -2073,7 +2079,7 @@ var setFsWatchListener = (path18, fullPath, options, handlers) => {
2073
2079
  addAndConvert(cont, KEY_RAW, rawEmitter);
2074
2080
  } else {
2075
2081
  watcher = createFsWatchInstance(
2076
- path18,
2082
+ path19,
2077
2083
  options,
2078
2084
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
2079
2085
  errHandler,
@@ -2088,7 +2094,7 @@ var setFsWatchListener = (path18, fullPath, options, handlers) => {
2088
2094
  cont.watcherUnusable = true;
2089
2095
  if (isWindows && error.code === "EPERM") {
2090
2096
  try {
2091
- const fd = await (0, import_promises2.open)(path18, "r");
2097
+ const fd = await (0, import_promises2.open)(path19, "r");
2092
2098
  await fd.close();
2093
2099
  broadcastErr(error);
2094
2100
  } catch (err) {
@@ -2119,7 +2125,7 @@ var setFsWatchListener = (path18, fullPath, options, handlers) => {
2119
2125
  };
2120
2126
  };
2121
2127
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
2122
- var setFsWatchFileListener = (path18, fullPath, options, handlers) => {
2128
+ var setFsWatchFileListener = (path19, fullPath, options, handlers) => {
2123
2129
  const { listener, rawEmitter } = handlers;
2124
2130
  let cont = FsWatchFileInstances.get(fullPath);
2125
2131
  const copts = cont && cont.options;
@@ -2141,7 +2147,7 @@ var setFsWatchFileListener = (path18, fullPath, options, handlers) => {
2141
2147
  });
2142
2148
  const currmtime = curr.mtimeMs;
2143
2149
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
2144
- foreach(cont.listeners, (listener2) => listener2(path18, curr));
2150
+ foreach(cont.listeners, (listener2) => listener2(path19, curr));
2145
2151
  }
2146
2152
  })
2147
2153
  };
@@ -2171,13 +2177,13 @@ var NodeFsHandler = class {
2171
2177
  * @param listener on fs change
2172
2178
  * @returns closer for the watcher instance
2173
2179
  */
2174
- _watchWithNodeFs(path18, listener) {
2180
+ _watchWithNodeFs(path19, listener) {
2175
2181
  const opts = this.fsw.options;
2176
- const directory = sp.dirname(path18);
2177
- const basename5 = sp.basename(path18);
2182
+ const directory = sp.dirname(path19);
2183
+ const basename5 = sp.basename(path19);
2178
2184
  const parent = this.fsw._getWatchedDir(directory);
2179
2185
  parent.add(basename5);
2180
- const absolutePath = sp.resolve(path18);
2186
+ const absolutePath = sp.resolve(path19);
2181
2187
  const options = {
2182
2188
  persistent: opts.persistent
2183
2189
  };
@@ -2187,12 +2193,12 @@ var NodeFsHandler = class {
2187
2193
  if (opts.usePolling) {
2188
2194
  const enableBin = opts.interval !== opts.binaryInterval;
2189
2195
  options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
2190
- closer = setFsWatchFileListener(path18, absolutePath, options, {
2196
+ closer = setFsWatchFileListener(path19, absolutePath, options, {
2191
2197
  listener,
2192
2198
  rawEmitter: this.fsw._emitRaw
2193
2199
  });
2194
2200
  } else {
2195
- closer = setFsWatchListener(path18, absolutePath, options, {
2201
+ closer = setFsWatchListener(path19, absolutePath, options, {
2196
2202
  listener,
2197
2203
  errHandler: this._boundHandleError,
2198
2204
  rawEmitter: this.fsw._emitRaw
@@ -2214,7 +2220,7 @@ var NodeFsHandler = class {
2214
2220
  let prevStats = stats;
2215
2221
  if (parent.has(basename5))
2216
2222
  return;
2217
- const listener = async (path18, newStats) => {
2223
+ const listener = async (path19, newStats) => {
2218
2224
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
2219
2225
  return;
2220
2226
  if (!newStats || newStats.mtimeMs === 0) {
@@ -2228,11 +2234,11 @@ var NodeFsHandler = class {
2228
2234
  this.fsw._emit(EV.CHANGE, file, newStats2);
2229
2235
  }
2230
2236
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
2231
- this.fsw._closeFile(path18);
2237
+ this.fsw._closeFile(path19);
2232
2238
  prevStats = newStats2;
2233
2239
  const closer2 = this._watchWithNodeFs(file, listener);
2234
2240
  if (closer2)
2235
- this.fsw._addPathCloser(path18, closer2);
2241
+ this.fsw._addPathCloser(path19, closer2);
2236
2242
  } else {
2237
2243
  prevStats = newStats2;
2238
2244
  }
@@ -2264,7 +2270,7 @@ var NodeFsHandler = class {
2264
2270
  * @param item basename of this item
2265
2271
  * @returns true if no more processing is needed for this entry.
2266
2272
  */
2267
- async _handleSymlink(entry, directory, path18, item) {
2273
+ async _handleSymlink(entry, directory, path19, item) {
2268
2274
  if (this.fsw.closed) {
2269
2275
  return;
2270
2276
  }
@@ -2274,7 +2280,7 @@ var NodeFsHandler = class {
2274
2280
  this.fsw._incrReadyCount();
2275
2281
  let linkPath;
2276
2282
  try {
2277
- linkPath = await (0, import_promises2.realpath)(path18);
2283
+ linkPath = await (0, import_promises2.realpath)(path19);
2278
2284
  } catch (e) {
2279
2285
  this.fsw._emitReady();
2280
2286
  return true;
@@ -2284,12 +2290,12 @@ var NodeFsHandler = class {
2284
2290
  if (dir.has(item)) {
2285
2291
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
2286
2292
  this.fsw._symlinkPaths.set(full, linkPath);
2287
- this.fsw._emit(EV.CHANGE, path18, entry.stats);
2293
+ this.fsw._emit(EV.CHANGE, path19, entry.stats);
2288
2294
  }
2289
2295
  } else {
2290
2296
  dir.add(item);
2291
2297
  this.fsw._symlinkPaths.set(full, linkPath);
2292
- this.fsw._emit(EV.ADD, path18, entry.stats);
2298
+ this.fsw._emit(EV.ADD, path19, entry.stats);
2293
2299
  }
2294
2300
  this.fsw._emitReady();
2295
2301
  return true;
@@ -2319,9 +2325,9 @@ var NodeFsHandler = class {
2319
2325
  return;
2320
2326
  }
2321
2327
  const item = entry.path;
2322
- let path18 = sp.join(directory, item);
2328
+ let path19 = sp.join(directory, item);
2323
2329
  current.add(item);
2324
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path18, item)) {
2330
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path19, item)) {
2325
2331
  return;
2326
2332
  }
2327
2333
  if (this.fsw.closed) {
@@ -2330,11 +2336,11 @@ var NodeFsHandler = class {
2330
2336
  }
2331
2337
  if (item === target || !target && !previous.has(item)) {
2332
2338
  this.fsw._incrReadyCount();
2333
- path18 = sp.join(dir, sp.relative(dir, path18));
2334
- this._addToNodeFs(path18, initialAdd, wh, depth + 1);
2339
+ path19 = sp.join(dir, sp.relative(dir, path19));
2340
+ this._addToNodeFs(path19, initialAdd, wh, depth + 1);
2335
2341
  }
2336
2342
  }).on(EV.ERROR, this._boundHandleError);
2337
- return new Promise((resolve12, reject) => {
2343
+ return new Promise((resolve13, reject) => {
2338
2344
  if (!stream)
2339
2345
  return reject();
2340
2346
  stream.once(STR_END, () => {
@@ -2343,7 +2349,7 @@ var NodeFsHandler = class {
2343
2349
  return;
2344
2350
  }
2345
2351
  const wasThrottled = throttler ? throttler.clear() : false;
2346
- resolve12(void 0);
2352
+ resolve13(void 0);
2347
2353
  previous.getChildren().filter((item) => {
2348
2354
  return item !== directory && !current.has(item);
2349
2355
  }).forEach((item) => {
@@ -2400,13 +2406,13 @@ var NodeFsHandler = class {
2400
2406
  * @param depth Child path actually targeted for watch
2401
2407
  * @param target Child path actually targeted for watch
2402
2408
  */
2403
- async _addToNodeFs(path18, initialAdd, priorWh, depth, target) {
2409
+ async _addToNodeFs(path19, initialAdd, priorWh, depth, target) {
2404
2410
  const ready = this.fsw._emitReady;
2405
- if (this.fsw._isIgnored(path18) || this.fsw.closed) {
2411
+ if (this.fsw._isIgnored(path19) || this.fsw.closed) {
2406
2412
  ready();
2407
2413
  return false;
2408
2414
  }
2409
- const wh = this.fsw._getWatchHelpers(path18);
2415
+ const wh = this.fsw._getWatchHelpers(path19);
2410
2416
  if (priorWh) {
2411
2417
  wh.filterPath = (entry) => priorWh.filterPath(entry);
2412
2418
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -2422,8 +2428,8 @@ var NodeFsHandler = class {
2422
2428
  const follow = this.fsw.options.followSymlinks;
2423
2429
  let closer;
2424
2430
  if (stats.isDirectory()) {
2425
- const absPath = sp.resolve(path18);
2426
- const targetPath = follow ? await (0, import_promises2.realpath)(path18) : path18;
2431
+ const absPath = sp.resolve(path19);
2432
+ const targetPath = follow ? await (0, import_promises2.realpath)(path19) : path19;
2427
2433
  if (this.fsw.closed)
2428
2434
  return;
2429
2435
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -2433,29 +2439,29 @@ var NodeFsHandler = class {
2433
2439
  this.fsw._symlinkPaths.set(absPath, targetPath);
2434
2440
  }
2435
2441
  } else if (stats.isSymbolicLink()) {
2436
- const targetPath = follow ? await (0, import_promises2.realpath)(path18) : path18;
2442
+ const targetPath = follow ? await (0, import_promises2.realpath)(path19) : path19;
2437
2443
  if (this.fsw.closed)
2438
2444
  return;
2439
2445
  const parent = sp.dirname(wh.watchPath);
2440
2446
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
2441
2447
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
2442
- closer = await this._handleDir(parent, stats, initialAdd, depth, path18, wh, targetPath);
2448
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path19, wh, targetPath);
2443
2449
  if (this.fsw.closed)
2444
2450
  return;
2445
2451
  if (targetPath !== void 0) {
2446
- this.fsw._symlinkPaths.set(sp.resolve(path18), targetPath);
2452
+ this.fsw._symlinkPaths.set(sp.resolve(path19), targetPath);
2447
2453
  }
2448
2454
  } else {
2449
2455
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
2450
2456
  }
2451
2457
  ready();
2452
2458
  if (closer)
2453
- this.fsw._addPathCloser(path18, closer);
2459
+ this.fsw._addPathCloser(path19, closer);
2454
2460
  return false;
2455
2461
  } catch (error) {
2456
2462
  if (this.fsw._handleError(error)) {
2457
2463
  ready();
2458
- return path18;
2464
+ return path19;
2459
2465
  }
2460
2466
  }
2461
2467
  }
@@ -2487,35 +2493,35 @@ function createPattern(matcher) {
2487
2493
  if (matcher.path === string)
2488
2494
  return true;
2489
2495
  if (matcher.recursive) {
2490
- const relative8 = sp2.relative(matcher.path, string);
2491
- if (!relative8) {
2496
+ const relative9 = sp2.relative(matcher.path, string);
2497
+ if (!relative9) {
2492
2498
  return false;
2493
2499
  }
2494
- return !relative8.startsWith("..") && !sp2.isAbsolute(relative8);
2500
+ return !relative9.startsWith("..") && !sp2.isAbsolute(relative9);
2495
2501
  }
2496
2502
  return false;
2497
2503
  };
2498
2504
  }
2499
2505
  return () => false;
2500
2506
  }
2501
- function normalizePath(path18) {
2502
- if (typeof path18 !== "string")
2507
+ function normalizePath(path19) {
2508
+ if (typeof path19 !== "string")
2503
2509
  throw new Error("string expected");
2504
- path18 = sp2.normalize(path18);
2505
- path18 = path18.replace(/\\/g, "/");
2510
+ path19 = sp2.normalize(path19);
2511
+ path19 = path19.replace(/\\/g, "/");
2506
2512
  let prepend = false;
2507
- if (path18.startsWith("//"))
2513
+ if (path19.startsWith("//"))
2508
2514
  prepend = true;
2509
- path18 = path18.replace(DOUBLE_SLASH_RE, "/");
2515
+ path19 = path19.replace(DOUBLE_SLASH_RE, "/");
2510
2516
  if (prepend)
2511
- path18 = "/" + path18;
2512
- return path18;
2517
+ path19 = "/" + path19;
2518
+ return path19;
2513
2519
  }
2514
2520
  function matchPatterns(patterns, testString, stats) {
2515
- const path18 = normalizePath(testString);
2521
+ const path19 = normalizePath(testString);
2516
2522
  for (let index = 0; index < patterns.length; index++) {
2517
2523
  const pattern = patterns[index];
2518
- if (pattern(path18, stats)) {
2524
+ if (pattern(path19, stats)) {
2519
2525
  return true;
2520
2526
  }
2521
2527
  }
@@ -2553,19 +2559,19 @@ var toUnix = (string) => {
2553
2559
  }
2554
2560
  return str;
2555
2561
  };
2556
- var normalizePathToUnix = (path18) => toUnix(sp2.normalize(toUnix(path18)));
2557
- var normalizeIgnored = (cwd = "") => (path18) => {
2558
- if (typeof path18 === "string") {
2559
- return normalizePathToUnix(sp2.isAbsolute(path18) ? path18 : sp2.join(cwd, path18));
2562
+ var normalizePathToUnix = (path19) => toUnix(sp2.normalize(toUnix(path19)));
2563
+ var normalizeIgnored = (cwd = "") => (path19) => {
2564
+ if (typeof path19 === "string") {
2565
+ return normalizePathToUnix(sp2.isAbsolute(path19) ? path19 : sp2.join(cwd, path19));
2560
2566
  } else {
2561
- return path18;
2567
+ return path19;
2562
2568
  }
2563
2569
  };
2564
- var getAbsolutePath = (path18, cwd) => {
2565
- if (sp2.isAbsolute(path18)) {
2566
- return path18;
2570
+ var getAbsolutePath = (path19, cwd) => {
2571
+ if (sp2.isAbsolute(path19)) {
2572
+ return path19;
2567
2573
  }
2568
- return sp2.join(cwd, path18);
2574
+ return sp2.join(cwd, path19);
2569
2575
  };
2570
2576
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
2571
2577
  var DirEntry = class {
@@ -2630,10 +2636,10 @@ var WatchHelper = class {
2630
2636
  dirParts;
2631
2637
  followSymlinks;
2632
2638
  statMethod;
2633
- constructor(path18, follow, fsw) {
2639
+ constructor(path19, follow, fsw) {
2634
2640
  this.fsw = fsw;
2635
- const watchPath = path18;
2636
- this.path = path18 = path18.replace(REPLACER_RE, "");
2641
+ const watchPath = path19;
2642
+ this.path = path19 = path19.replace(REPLACER_RE, "");
2637
2643
  this.watchPath = watchPath;
2638
2644
  this.fullWatchPath = sp2.resolve(watchPath);
2639
2645
  this.dirParts = [];
@@ -2773,20 +2779,20 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2773
2779
  this._closePromise = void 0;
2774
2780
  let paths = unifyPaths(paths_);
2775
2781
  if (cwd) {
2776
- paths = paths.map((path18) => {
2777
- const absPath = getAbsolutePath(path18, cwd);
2782
+ paths = paths.map((path19) => {
2783
+ const absPath = getAbsolutePath(path19, cwd);
2778
2784
  return absPath;
2779
2785
  });
2780
2786
  }
2781
- paths.forEach((path18) => {
2782
- this._removeIgnoredPath(path18);
2787
+ paths.forEach((path19) => {
2788
+ this._removeIgnoredPath(path19);
2783
2789
  });
2784
2790
  this._userIgnored = void 0;
2785
2791
  if (!this._readyCount)
2786
2792
  this._readyCount = 0;
2787
2793
  this._readyCount += paths.length;
2788
- Promise.all(paths.map(async (path18) => {
2789
- const res = await this._nodeFsHandler._addToNodeFs(path18, !_internal, void 0, 0, _origAdd);
2794
+ Promise.all(paths.map(async (path19) => {
2795
+ const res = await this._nodeFsHandler._addToNodeFs(path19, !_internal, void 0, 0, _origAdd);
2790
2796
  if (res)
2791
2797
  this._emitReady();
2792
2798
  return res;
@@ -2808,17 +2814,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2808
2814
  return this;
2809
2815
  const paths = unifyPaths(paths_);
2810
2816
  const { cwd } = this.options;
2811
- paths.forEach((path18) => {
2812
- if (!sp2.isAbsolute(path18) && !this._closers.has(path18)) {
2817
+ paths.forEach((path19) => {
2818
+ if (!sp2.isAbsolute(path19) && !this._closers.has(path19)) {
2813
2819
  if (cwd)
2814
- path18 = sp2.join(cwd, path18);
2815
- path18 = sp2.resolve(path18);
2820
+ path19 = sp2.join(cwd, path19);
2821
+ path19 = sp2.resolve(path19);
2816
2822
  }
2817
- this._closePath(path18);
2818
- this._addIgnoredPath(path18);
2819
- if (this._watched.has(path18)) {
2823
+ this._closePath(path19);
2824
+ this._addIgnoredPath(path19);
2825
+ if (this._watched.has(path19)) {
2820
2826
  this._addIgnoredPath({
2821
- path: path18,
2827
+ path: path19,
2822
2828
  recursive: true
2823
2829
  });
2824
2830
  }
@@ -2882,38 +2888,38 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2882
2888
  * @param stats arguments to be passed with event
2883
2889
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
2884
2890
  */
2885
- async _emit(event, path18, stats) {
2891
+ async _emit(event, path19, stats) {
2886
2892
  if (this.closed)
2887
2893
  return;
2888
2894
  const opts = this.options;
2889
2895
  if (isWindows)
2890
- path18 = sp2.normalize(path18);
2896
+ path19 = sp2.normalize(path19);
2891
2897
  if (opts.cwd)
2892
- path18 = sp2.relative(opts.cwd, path18);
2893
- const args = [path18];
2898
+ path19 = sp2.relative(opts.cwd, path19);
2899
+ const args = [path19];
2894
2900
  if (stats != null)
2895
2901
  args.push(stats);
2896
2902
  const awf = opts.awaitWriteFinish;
2897
2903
  let pw;
2898
- if (awf && (pw = this._pendingWrites.get(path18))) {
2904
+ if (awf && (pw = this._pendingWrites.get(path19))) {
2899
2905
  pw.lastChange = /* @__PURE__ */ new Date();
2900
2906
  return this;
2901
2907
  }
2902
2908
  if (opts.atomic) {
2903
2909
  if (event === EVENTS.UNLINK) {
2904
- this._pendingUnlinks.set(path18, [event, ...args]);
2910
+ this._pendingUnlinks.set(path19, [event, ...args]);
2905
2911
  setTimeout(() => {
2906
- this._pendingUnlinks.forEach((entry, path19) => {
2912
+ this._pendingUnlinks.forEach((entry, path20) => {
2907
2913
  this.emit(...entry);
2908
2914
  this.emit(EVENTS.ALL, ...entry);
2909
- this._pendingUnlinks.delete(path19);
2915
+ this._pendingUnlinks.delete(path20);
2910
2916
  });
2911
2917
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
2912
2918
  return this;
2913
2919
  }
2914
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path18)) {
2920
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path19)) {
2915
2921
  event = EVENTS.CHANGE;
2916
- this._pendingUnlinks.delete(path18);
2922
+ this._pendingUnlinks.delete(path19);
2917
2923
  }
2918
2924
  }
2919
2925
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -2931,16 +2937,16 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2931
2937
  this.emitWithAll(event, args);
2932
2938
  }
2933
2939
  };
2934
- this._awaitWriteFinish(path18, awf.stabilityThreshold, event, awfEmit);
2940
+ this._awaitWriteFinish(path19, awf.stabilityThreshold, event, awfEmit);
2935
2941
  return this;
2936
2942
  }
2937
2943
  if (event === EVENTS.CHANGE) {
2938
- const isThrottled = !this._throttle(EVENTS.CHANGE, path18, 50);
2944
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path19, 50);
2939
2945
  if (isThrottled)
2940
2946
  return this;
2941
2947
  }
2942
2948
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
2943
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path18) : path18;
2949
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path19) : path19;
2944
2950
  let stats2;
2945
2951
  try {
2946
2952
  stats2 = await (0, import_promises3.stat)(fullPath);
@@ -2971,23 +2977,23 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2971
2977
  * @param timeout duration of time to suppress duplicate actions
2972
2978
  * @returns tracking object or false if action should be suppressed
2973
2979
  */
2974
- _throttle(actionType, path18, timeout) {
2980
+ _throttle(actionType, path19, timeout) {
2975
2981
  if (!this._throttled.has(actionType)) {
2976
2982
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
2977
2983
  }
2978
2984
  const action = this._throttled.get(actionType);
2979
2985
  if (!action)
2980
2986
  throw new Error("invalid throttle");
2981
- const actionPath = action.get(path18);
2987
+ const actionPath = action.get(path19);
2982
2988
  if (actionPath) {
2983
2989
  actionPath.count++;
2984
2990
  return false;
2985
2991
  }
2986
2992
  let timeoutObject;
2987
2993
  const clear = () => {
2988
- const item = action.get(path18);
2994
+ const item = action.get(path19);
2989
2995
  const count = item ? item.count : 0;
2990
- action.delete(path18);
2996
+ action.delete(path19);
2991
2997
  clearTimeout(timeoutObject);
2992
2998
  if (item)
2993
2999
  clearTimeout(item.timeoutObject);
@@ -2995,7 +3001,7 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2995
3001
  };
2996
3002
  timeoutObject = setTimeout(clear, timeout);
2997
3003
  const thr = { timeoutObject, clear, count: 0 };
2998
- action.set(path18, thr);
3004
+ action.set(path19, thr);
2999
3005
  return thr;
3000
3006
  }
3001
3007
  _incrReadyCount() {
@@ -3009,44 +3015,44 @@ var FSWatcher = class extends import_node_events.EventEmitter {
3009
3015
  * @param event
3010
3016
  * @param awfEmit Callback to be called when ready for event to be emitted.
3011
3017
  */
3012
- _awaitWriteFinish(path18, threshold, event, awfEmit) {
3018
+ _awaitWriteFinish(path19, threshold, event, awfEmit) {
3013
3019
  const awf = this.options.awaitWriteFinish;
3014
3020
  if (typeof awf !== "object")
3015
3021
  return;
3016
3022
  const pollInterval = awf.pollInterval;
3017
3023
  let timeoutHandler;
3018
- let fullPath = path18;
3019
- if (this.options.cwd && !sp2.isAbsolute(path18)) {
3020
- fullPath = sp2.join(this.options.cwd, path18);
3024
+ let fullPath = path19;
3025
+ if (this.options.cwd && !sp2.isAbsolute(path19)) {
3026
+ fullPath = sp2.join(this.options.cwd, path19);
3021
3027
  }
3022
3028
  const now = /* @__PURE__ */ new Date();
3023
3029
  const writes = this._pendingWrites;
3024
3030
  function awaitWriteFinishFn(prevStat) {
3025
3031
  (0, import_node_fs2.stat)(fullPath, (err, curStat) => {
3026
- if (err || !writes.has(path18)) {
3032
+ if (err || !writes.has(path19)) {
3027
3033
  if (err && err.code !== "ENOENT")
3028
3034
  awfEmit(err);
3029
3035
  return;
3030
3036
  }
3031
3037
  const now2 = Number(/* @__PURE__ */ new Date());
3032
3038
  if (prevStat && curStat.size !== prevStat.size) {
3033
- writes.get(path18).lastChange = now2;
3039
+ writes.get(path19).lastChange = now2;
3034
3040
  }
3035
- const pw = writes.get(path18);
3041
+ const pw = writes.get(path19);
3036
3042
  const df = now2 - pw.lastChange;
3037
3043
  if (df >= threshold) {
3038
- writes.delete(path18);
3044
+ writes.delete(path19);
3039
3045
  awfEmit(void 0, curStat);
3040
3046
  } else {
3041
3047
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
3042
3048
  }
3043
3049
  });
3044
3050
  }
3045
- if (!writes.has(path18)) {
3046
- writes.set(path18, {
3051
+ if (!writes.has(path19)) {
3052
+ writes.set(path19, {
3047
3053
  lastChange: now,
3048
3054
  cancelWait: () => {
3049
- writes.delete(path18);
3055
+ writes.delete(path19);
3050
3056
  clearTimeout(timeoutHandler);
3051
3057
  return event;
3052
3058
  }
@@ -3057,8 +3063,8 @@ var FSWatcher = class extends import_node_events.EventEmitter {
3057
3063
  /**
3058
3064
  * Determines whether user has asked to ignore this path.
3059
3065
  */
3060
- _isIgnored(path18, stats) {
3061
- if (this.options.atomic && DOT_RE.test(path18))
3066
+ _isIgnored(path19, stats) {
3067
+ if (this.options.atomic && DOT_RE.test(path19))
3062
3068
  return true;
3063
3069
  if (!this._userIgnored) {
3064
3070
  const { cwd } = this.options;
@@ -3068,17 +3074,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
3068
3074
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
3069
3075
  this._userIgnored = anymatch(list, void 0);
3070
3076
  }
3071
- return this._userIgnored(path18, stats);
3077
+ return this._userIgnored(path19, stats);
3072
3078
  }
3073
- _isntIgnored(path18, stat4) {
3074
- return !this._isIgnored(path18, stat4);
3079
+ _isntIgnored(path19, stat4) {
3080
+ return !this._isIgnored(path19, stat4);
3075
3081
  }
3076
3082
  /**
3077
3083
  * Provides a set of common helpers and properties relating to symlink handling.
3078
3084
  * @param path file or directory pattern being watched
3079
3085
  */
3080
- _getWatchHelpers(path18) {
3081
- return new WatchHelper(path18, this.options.followSymlinks, this);
3086
+ _getWatchHelpers(path19) {
3087
+ return new WatchHelper(path19, this.options.followSymlinks, this);
3082
3088
  }
3083
3089
  // Directory helpers
3084
3090
  // -----------------
@@ -3110,63 +3116,63 @@ var FSWatcher = class extends import_node_events.EventEmitter {
3110
3116
  * @param item base path of item/directory
3111
3117
  */
3112
3118
  _remove(directory, item, isDirectory) {
3113
- const path18 = sp2.join(directory, item);
3114
- const fullPath = sp2.resolve(path18);
3115
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path18) || this._watched.has(fullPath);
3116
- if (!this._throttle("remove", path18, 100))
3119
+ const path19 = sp2.join(directory, item);
3120
+ const fullPath = sp2.resolve(path19);
3121
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path19) || this._watched.has(fullPath);
3122
+ if (!this._throttle("remove", path19, 100))
3117
3123
  return;
3118
3124
  if (!isDirectory && this._watched.size === 1) {
3119
3125
  this.add(directory, item, true);
3120
3126
  }
3121
- const wp = this._getWatchedDir(path18);
3127
+ const wp = this._getWatchedDir(path19);
3122
3128
  const nestedDirectoryChildren = wp.getChildren();
3123
- nestedDirectoryChildren.forEach((nested) => this._remove(path18, nested));
3129
+ nestedDirectoryChildren.forEach((nested) => this._remove(path19, nested));
3124
3130
  const parent = this._getWatchedDir(directory);
3125
3131
  const wasTracked = parent.has(item);
3126
3132
  parent.remove(item);
3127
3133
  if (this._symlinkPaths.has(fullPath)) {
3128
3134
  this._symlinkPaths.delete(fullPath);
3129
3135
  }
3130
- let relPath = path18;
3136
+ let relPath = path19;
3131
3137
  if (this.options.cwd)
3132
- relPath = sp2.relative(this.options.cwd, path18);
3138
+ relPath = sp2.relative(this.options.cwd, path19);
3133
3139
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
3134
3140
  const event = this._pendingWrites.get(relPath).cancelWait();
3135
3141
  if (event === EVENTS.ADD)
3136
3142
  return;
3137
3143
  }
3138
- this._watched.delete(path18);
3144
+ this._watched.delete(path19);
3139
3145
  this._watched.delete(fullPath);
3140
3146
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
3141
- if (wasTracked && !this._isIgnored(path18))
3142
- this._emit(eventName, path18);
3143
- this._closePath(path18);
3147
+ if (wasTracked && !this._isIgnored(path19))
3148
+ this._emit(eventName, path19);
3149
+ this._closePath(path19);
3144
3150
  }
3145
3151
  /**
3146
3152
  * Closes all watchers for a path
3147
3153
  */
3148
- _closePath(path18) {
3149
- this._closeFile(path18);
3150
- const dir = sp2.dirname(path18);
3151
- this._getWatchedDir(dir).remove(sp2.basename(path18));
3154
+ _closePath(path19) {
3155
+ this._closeFile(path19);
3156
+ const dir = sp2.dirname(path19);
3157
+ this._getWatchedDir(dir).remove(sp2.basename(path19));
3152
3158
  }
3153
3159
  /**
3154
3160
  * Closes only file-specific watchers
3155
3161
  */
3156
- _closeFile(path18) {
3157
- const closers = this._closers.get(path18);
3162
+ _closeFile(path19) {
3163
+ const closers = this._closers.get(path19);
3158
3164
  if (!closers)
3159
3165
  return;
3160
3166
  closers.forEach((closer) => closer());
3161
- this._closers.delete(path18);
3167
+ this._closers.delete(path19);
3162
3168
  }
3163
- _addPathCloser(path18, closer) {
3169
+ _addPathCloser(path19, closer) {
3164
3170
  if (!closer)
3165
3171
  return;
3166
- let list = this._closers.get(path18);
3172
+ let list = this._closers.get(path19);
3167
3173
  if (!list) {
3168
3174
  list = [];
3169
- this._closers.set(path18, list);
3175
+ this._closers.set(path19, list);
3170
3176
  }
3171
3177
  list.push(closer);
3172
3178
  }
@@ -3496,7 +3502,7 @@ var FileWatcher = class {
3496
3502
  return;
3497
3503
  }
3498
3504
  const changes = Array.from(this.pendingChanges.entries()).map(
3499
- ([path18, type]) => ({ path: path18, type })
3505
+ ([path19, type]) => ({ path: path19, type })
3500
3506
  );
3501
3507
  this.pendingChanges.clear();
3502
3508
  try {
@@ -3629,12 +3635,14 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config) {
3629
3635
  }
3630
3636
 
3631
3637
  // src/tools/index.ts
3632
- var import_plugin = require("@opencode-ai/plugin");
3638
+ var import_plugin2 = require("@opencode-ai/plugin");
3633
3639
 
3634
3640
  // src/indexer/index.ts
3635
3641
  var import_fs7 = require("fs");
3636
- var path12 = __toESM(require("path"), 1);
3642
+ var path13 = __toESM(require("path"), 1);
3637
3643
  var import_perf_hooks = require("perf_hooks");
3644
+ var import_child_process2 = require("child_process");
3645
+ var import_util2 = require("util");
3638
3646
 
3639
3647
  // node_modules/eventemitter3/index.mjs
3640
3648
  var import_index = __toESM(require_eventemitter3(), 1);
@@ -3658,7 +3666,7 @@ function pTimeout(promise, options) {
3658
3666
  } = options;
3659
3667
  let timer;
3660
3668
  let abortHandler;
3661
- const wrappedPromise = new Promise((resolve12, reject) => {
3669
+ const wrappedPromise = new Promise((resolve13, reject) => {
3662
3670
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
3663
3671
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
3664
3672
  }
@@ -3672,7 +3680,7 @@ function pTimeout(promise, options) {
3672
3680
  };
3673
3681
  signal.addEventListener("abort", abortHandler, { once: true });
3674
3682
  }
3675
- promise.then(resolve12, reject);
3683
+ promise.then(resolve13, reject);
3676
3684
  if (milliseconds === Number.POSITIVE_INFINITY) {
3677
3685
  return;
3678
3686
  }
@@ -3680,7 +3688,7 @@ function pTimeout(promise, options) {
3680
3688
  timer = customTimers.setTimeout.call(void 0, () => {
3681
3689
  if (fallback) {
3682
3690
  try {
3683
- resolve12(fallback());
3691
+ resolve13(fallback());
3684
3692
  } catch (error) {
3685
3693
  reject(error);
3686
3694
  }
@@ -3690,7 +3698,7 @@ function pTimeout(promise, options) {
3690
3698
  promise.cancel();
3691
3699
  }
3692
3700
  if (message === false) {
3693
- resolve12();
3701
+ resolve13();
3694
3702
  } else if (message instanceof Error) {
3695
3703
  reject(message);
3696
3704
  } else {
@@ -4092,7 +4100,7 @@ var PQueue = class extends import_index.default {
4092
4100
  // Assign unique ID if not provided
4093
4101
  id: options.id ?? (this.#idAssigner++).toString()
4094
4102
  };
4095
- return new Promise((resolve12, reject) => {
4103
+ return new Promise((resolve13, reject) => {
4096
4104
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
4097
4105
  let cleanupQueueAbortHandler = () => void 0;
4098
4106
  const run = async () => {
@@ -4132,7 +4140,7 @@ var PQueue = class extends import_index.default {
4132
4140
  })]);
4133
4141
  }
4134
4142
  const result = await operation;
4135
- resolve12(result);
4143
+ resolve13(result);
4136
4144
  this.emit("completed", result);
4137
4145
  } catch (error) {
4138
4146
  reject(error);
@@ -4320,13 +4328,13 @@ var PQueue = class extends import_index.default {
4320
4328
  });
4321
4329
  }
4322
4330
  async #onEvent(event, filter) {
4323
- return new Promise((resolve12) => {
4331
+ return new Promise((resolve13) => {
4324
4332
  const listener = () => {
4325
4333
  if (filter && !filter()) {
4326
4334
  return;
4327
4335
  }
4328
4336
  this.off(event, listener);
4329
- resolve12();
4337
+ resolve13();
4330
4338
  };
4331
4339
  this.on(event, listener);
4332
4340
  });
@@ -4612,7 +4620,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4612
4620
  const finalDelay = Math.min(delayTime, remainingTime);
4613
4621
  options.signal?.throwIfAborted();
4614
4622
  if (finalDelay > 0) {
4615
- await new Promise((resolve12, reject) => {
4623
+ await new Promise((resolve13, reject) => {
4616
4624
  const onAbort = () => {
4617
4625
  clearTimeout(timeoutToken);
4618
4626
  options.signal?.removeEventListener("abort", onAbort);
@@ -4620,7 +4628,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4620
4628
  };
4621
4629
  const timeoutToken = setTimeout(() => {
4622
4630
  options.signal?.removeEventListener("abort", onAbort);
4623
- resolve12();
4631
+ resolve13();
4624
4632
  }, finalDelay);
4625
4633
  if (options.unref) {
4626
4634
  timeoutToken.unref?.();
@@ -4861,6 +4869,8 @@ var BaseEmbeddingProvider = class {
4861
4869
  this.credentials = credentials;
4862
4870
  this.modelInfo = modelInfo;
4863
4871
  }
4872
+ credentials;
4873
+ modelInfo;
4864
4874
  async embedQuery(query) {
4865
4875
  const result = await this.embedBatch([query]);
4866
4876
  return {
@@ -5846,6 +5856,15 @@ function createMockNativeBinding() {
5846
5856
  close() {
5847
5857
  throw error;
5848
5858
  }
5859
+ getTransitiveReachability() {
5860
+ throw error;
5861
+ }
5862
+ detectCommunities() {
5863
+ throw error;
5864
+ }
5865
+ computeCentrality() {
5866
+ throw error;
5867
+ }
5849
5868
  }
5850
5869
  };
5851
5870
  }
@@ -6396,6 +6415,14 @@ var Database = class {
6396
6415
  this.throwIfClosed();
6397
6416
  return this.inner.getSymbolsByNameCi(name);
6398
6417
  }
6418
+ getSymbolsForBranch(branch) {
6419
+ this.throwIfClosed();
6420
+ return this.inner.getSymbolsForBranch(branch);
6421
+ }
6422
+ getSymbolsForFiles(filePaths, branch) {
6423
+ this.throwIfClosed();
6424
+ return this.inner.getSymbolsForFiles(filePaths, branch);
6425
+ }
6399
6426
  deleteSymbolsByFile(filePath) {
6400
6427
  this.throwIfClosed();
6401
6428
  return this.inner.deleteSymbolsByFile(filePath);
@@ -6409,17 +6436,17 @@ var Database = class {
6409
6436
  if (edges.length === 0) return;
6410
6437
  this.inner.upsertCallEdgesBatch(edges);
6411
6438
  }
6412
- getCallers(targetName, branch) {
6439
+ getCallers(targetName, branch, callTypeFilter) {
6413
6440
  this.throwIfClosed();
6414
- return this.inner.getCallers(targetName, branch);
6441
+ return this.inner.getCallers(targetName, branch, callTypeFilter ?? null);
6415
6442
  }
6416
- getCallersWithContext(targetName, branch) {
6443
+ getCallersWithContext(targetName, branch, callTypeFilter) {
6417
6444
  this.throwIfClosed();
6418
- return this.inner.getCallersWithContext(targetName, branch);
6445
+ return this.inner.getCallersWithContext(targetName, branch, callTypeFilter ?? null);
6419
6446
  }
6420
- getCallees(symbolId, branch) {
6447
+ getCallees(symbolId, branch, callTypeFilter) {
6421
6448
  this.throwIfClosed();
6422
- return this.inner.getCallees(symbolId, branch);
6449
+ return this.inner.getCallees(symbolId, branch, callTypeFilter ?? null);
6423
6450
  }
6424
6451
  deleteCallEdgesByFile(filePath) {
6425
6452
  this.throwIfClosed();
@@ -6429,6 +6456,10 @@ var Database = class {
6429
6456
  this.throwIfClosed();
6430
6457
  this.inner.resolveCallEdge(edgeId, toSymbolId);
6431
6458
  }
6459
+ findShortestPath(fromName, toName, branch, maxDepth) {
6460
+ this.throwIfClosed();
6461
+ return this.inner.findShortestPath(fromName, toName, branch, maxDepth ?? null);
6462
+ }
6432
6463
  addSymbolsToBranch(branch, symbolIds) {
6433
6464
  this.throwIfClosed();
6434
6465
  this.inner.addSymbolsToBranch(branch, symbolIds);
@@ -6469,8 +6500,125 @@ var Database = class {
6469
6500
  this.throwIfClosed();
6470
6501
  return this.inner.gcOrphanCallEdges();
6471
6502
  }
6503
+ getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth) {
6504
+ this.throwIfClosed();
6505
+ return this.inner.getTransitiveReachability(
6506
+ rootSymbolIds,
6507
+ branch,
6508
+ direction,
6509
+ maxDepth ?? null
6510
+ );
6511
+ }
6512
+ detectCommunities(branch, symbolIds) {
6513
+ this.throwIfClosed();
6514
+ return this.inner.detectCommunities(branch, symbolIds ?? null);
6515
+ }
6516
+ computeCentrality(branch) {
6517
+ this.throwIfClosed();
6518
+ return this.inner.computeCentrality(branch);
6519
+ }
6472
6520
  };
6473
6521
 
6522
+ // src/tools/changed-files.ts
6523
+ var path12 = __toESM(require("path"), 1);
6524
+ var import_child_process = require("child_process");
6525
+ var import_util = require("util");
6526
+ var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
6527
+ function getErrorMessage(error) {
6528
+ if (error instanceof Error) return error.message;
6529
+ return String(error);
6530
+ }
6531
+ async function getChangedFiles(opts) {
6532
+ const { pr, branch, projectRoot, baseBranch = "main" } = opts;
6533
+ if (pr !== void 0) {
6534
+ return getChangedFilesForPr(pr, projectRoot, baseBranch);
6535
+ }
6536
+ return getChangedFilesForBranch(branch, projectRoot, baseBranch);
6537
+ }
6538
+ async function getChangedFilesForPr(pr, projectRoot, baseBranch) {
6539
+ let headRefName;
6540
+ let actualBaseBranch = baseBranch;
6541
+ try {
6542
+ const { stdout } = await execFileAsync(
6543
+ "gh",
6544
+ ["pr", "view", String(pr), "--json", "headRefName,baseRefName,files"],
6545
+ { cwd: projectRoot, timeout: 3e4 }
6546
+ );
6547
+ const data = JSON.parse(stdout);
6548
+ headRefName = data.headRefName;
6549
+ actualBaseBranch = data.baseRefName || baseBranch;
6550
+ if (data.files && data.files.length > 0) {
6551
+ return {
6552
+ files: normalizeFiles(
6553
+ data.files.map((f) => f.path),
6554
+ projectRoot
6555
+ ),
6556
+ baseBranch: actualBaseBranch,
6557
+ source: "gh",
6558
+ headRefName
6559
+ };
6560
+ }
6561
+ } catch (error) {
6562
+ throw new Error(
6563
+ `Failed to retrieve PR #${pr} via gh CLI: ${getErrorMessage(error)}`
6564
+ );
6565
+ }
6566
+ if (headRefName === void 0) {
6567
+ throw new Error(
6568
+ `PR #${pr} returned no usable branch or file information.`
6569
+ );
6570
+ }
6571
+ const result = await getChangedFilesForBranch(headRefName, projectRoot, actualBaseBranch);
6572
+ return { ...result, headRefName };
6573
+ }
6574
+ async function getChangedFilesForBranch(branch, projectRoot, baseBranch) {
6575
+ const targetBranch = branch || await getCurrentBranch2(projectRoot);
6576
+ const mergeBase = await getMergeBase(projectRoot, baseBranch, targetBranch);
6577
+ const { stdout } = await execFileAsync(
6578
+ "git",
6579
+ ["diff", "--name-only", `${mergeBase}...${targetBranch}`],
6580
+ { cwd: projectRoot, timeout: 3e4 }
6581
+ );
6582
+ return {
6583
+ files: normalizeFiles(stdout.split("\n"), projectRoot),
6584
+ baseBranch,
6585
+ source: "git",
6586
+ headRefName: targetBranch
6587
+ };
6588
+ }
6589
+ async function getCurrentBranch2(projectRoot) {
6590
+ const { stdout } = await execFileAsync(
6591
+ "git",
6592
+ ["branch", "--show-current"],
6593
+ { cwd: projectRoot, timeout: 3e4 }
6594
+ );
6595
+ return stdout.trim() || "HEAD";
6596
+ }
6597
+ async function getMergeBase(projectRoot, baseBranch, branch) {
6598
+ const { stdout } = await execFileAsync(
6599
+ "git",
6600
+ ["merge-base", baseBranch, branch],
6601
+ { cwd: projectRoot, timeout: 3e4 }
6602
+ );
6603
+ return stdout.trim();
6604
+ }
6605
+ function normalizeFiles(rawFiles, projectRoot) {
6606
+ const seen = /* @__PURE__ */ new Set();
6607
+ const result = [];
6608
+ for (const raw of rawFiles) {
6609
+ const trimmed = raw.trim();
6610
+ if (!trimmed) continue;
6611
+ const absolute = path12.resolve(projectRoot, trimmed);
6612
+ const relative9 = path12.relative(projectRoot, absolute);
6613
+ const cleaned = relative9.startsWith("./") ? relative9.slice(2) : relative9;
6614
+ if (!seen.has(cleaned)) {
6615
+ seen.add(cleaned);
6616
+ result.push(cleaned);
6617
+ }
6618
+ }
6619
+ return result;
6620
+ }
6621
+
6474
6622
  // src/indexer/index.ts
6475
6623
  var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
6476
6624
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
@@ -6478,6 +6626,7 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
6478
6626
  "function_declaration",
6479
6627
  "function",
6480
6628
  "arrow_function",
6629
+ "export_statement",
6481
6630
  "method_definition",
6482
6631
  "class_declaration",
6483
6632
  "interface_declaration",
@@ -6516,7 +6665,7 @@ function float32ArrayToBuffer(arr) {
6516
6665
  function bufferToFloat32Array(buf) {
6517
6666
  return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
6518
6667
  }
6519
- function getErrorMessage(error) {
6668
+ function getErrorMessage2(error) {
6520
6669
  if (error instanceof Error) {
6521
6670
  return error.message;
6522
6671
  }
@@ -6529,7 +6678,7 @@ function getErrorMessage(error) {
6529
6678
  return String(error);
6530
6679
  }
6531
6680
  function isRateLimitError(error) {
6532
- const message = getErrorMessage(error);
6681
+ const message = getErrorMessage2(error);
6533
6682
  return message.includes("429") || message.toLowerCase().includes("rate limit") || message.toLowerCase().includes("too many requests");
6534
6683
  }
6535
6684
  function getSafeEmbeddingChunkTokenLimit(provider) {
@@ -6547,7 +6696,7 @@ function getDynamicBatchOptions(provider) {
6547
6696
  return {};
6548
6697
  }
6549
6698
  function isSqliteCorruptionError(error) {
6550
- const message = getErrorMessage(error).toLowerCase();
6699
+ const message = getErrorMessage2(error).toLowerCase();
6551
6700
  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");
6552
6701
  }
6553
6702
  var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
@@ -6709,9 +6858,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
6709
6858
  return true;
6710
6859
  }
6711
6860
  function isPathWithinRoot(filePath, rootPath) {
6712
- const normalizedFilePath = path12.resolve(filePath);
6713
- const normalizedRoot = path12.resolve(rootPath);
6714
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path12.sep}`);
6861
+ const normalizedFilePath = path13.resolve(filePath);
6862
+ const normalizedRoot = path13.resolve(rootPath);
6863
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path13.sep}`);
6715
6864
  }
6716
6865
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
6717
6866
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -7764,9 +7913,9 @@ var Indexer = class {
7764
7913
  this.projectRoot = projectRoot;
7765
7914
  this.config = config;
7766
7915
  this.indexPath = this.getIndexPath();
7767
- this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
7768
- this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
7769
- this.indexingLockPath = path12.join(this.indexPath, "indexing.lock");
7916
+ this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
7917
+ this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
7918
+ this.indexingLockPath = path13.join(this.indexPath, "indexing.lock");
7770
7919
  this.logger = initializeLogger(config.debug);
7771
7920
  }
7772
7921
  getIndexPath() {
@@ -7798,14 +7947,14 @@ var Indexer = class {
7798
7947
  }
7799
7948
  atomicWriteSync(targetPath, data) {
7800
7949
  const tempPath = `${targetPath}.tmp`;
7801
- (0, import_fs7.mkdirSync)(path12.dirname(targetPath), { recursive: true });
7950
+ (0, import_fs7.mkdirSync)(path13.dirname(targetPath), { recursive: true });
7802
7951
  (0, import_fs7.writeFileSync)(tempPath, data);
7803
7952
  (0, import_fs7.renameSync)(tempPath, targetPath);
7804
7953
  }
7805
7954
  getScopedRoots() {
7806
- const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
7955
+ const roots = /* @__PURE__ */ new Set([path13.resolve(this.projectRoot)]);
7807
7956
  for (const kbRoot of this.config.knowledgeBases) {
7808
- roots.add(path12.resolve(this.projectRoot, kbRoot));
7957
+ roots.add(path13.resolve(this.projectRoot, kbRoot));
7809
7958
  }
7810
7959
  return Array.from(roots);
7811
7960
  }
@@ -7814,22 +7963,29 @@ var Indexer = class {
7814
7963
  if (this.config.scope !== "global") {
7815
7964
  return branchName;
7816
7965
  }
7817
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7966
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7967
+ return `${projectHash}:${branchName}`;
7968
+ }
7969
+ getBranchCatalogKeyFor(branchName) {
7970
+ if (this.config.scope !== "global") {
7971
+ return branchName;
7972
+ }
7973
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7818
7974
  return `${projectHash}:${branchName}`;
7819
7975
  }
7820
7976
  getLegacyBranchCatalogKey() {
7821
7977
  return this.currentBranch || "default";
7822
7978
  }
7823
7979
  getLegacyMigrationMetadataKey() {
7824
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7980
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7825
7981
  return `index.globalBranchMigration.${projectHash}`;
7826
7982
  }
7827
7983
  getProjectEmbeddingStrategyMetadataKey() {
7828
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7984
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7829
7985
  return `index.embeddingStrategyVersion.${projectHash}`;
7830
7986
  }
7831
7987
  getProjectForceReembedMetadataKey() {
7832
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7988
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7833
7989
  return `index.forceReembed.${projectHash}`;
7834
7990
  }
7835
7991
  hasProjectForceReembedPending() {
@@ -7923,7 +8079,7 @@ var Indexer = class {
7923
8079
  if (!this.database) {
7924
8080
  return { chunkIds, symbolIds };
7925
8081
  }
7926
- const projectRootPath = path12.resolve(this.projectRoot);
8082
+ const projectRootPath = path13.resolve(this.projectRoot);
7927
8083
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
7928
8084
  ...Array.from(this.fileHashCache.keys()).filter(
7929
8085
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -7946,7 +8102,7 @@ var Indexer = class {
7946
8102
  if (this.config.scope !== "global") {
7947
8103
  return this.getBranchCatalogCleanupKeys();
7948
8104
  }
7949
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
8105
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7950
8106
  const keys = /* @__PURE__ */ new Set();
7951
8107
  const projectChunkIdSet = new Set(projectChunkIds);
7952
8108
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -8030,7 +8186,7 @@ var Indexer = class {
8030
8186
  if (!this.database || this.config.scope !== "global") {
8031
8187
  return false;
8032
8188
  }
8033
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
8189
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
8034
8190
  const roots = this.getScopedRoots();
8035
8191
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
8036
8192
  return this.database.getAllBranches().some(
@@ -8064,7 +8220,7 @@ var Indexer = class {
8064
8220
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
8065
8221
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
8066
8222
  ]);
8067
- const projectRootPath = path12.resolve(this.projectRoot);
8223
+ const projectRootPath = path13.resolve(this.projectRoot);
8068
8224
  const projectLocalFilePaths = new Set(
8069
8225
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
8070
8226
  );
@@ -8317,7 +8473,7 @@ var Indexer = class {
8317
8473
  this.logger.search("warn", "External reranker failed; using deterministic order", {
8318
8474
  provider: reranker.provider,
8319
8475
  model: reranker.model,
8320
- error: getErrorMessage(error)
8476
+ error: getErrorMessage2(error)
8321
8477
  });
8322
8478
  return candidates;
8323
8479
  }
@@ -8424,13 +8580,13 @@ var Indexer = class {
8424
8580
  }
8425
8581
  await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
8426
8582
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
8427
- const storePath = path12.join(this.indexPath, "vectors");
8583
+ const storePath = path13.join(this.indexPath, "vectors");
8428
8584
  this.store = new VectorStore(storePath, dimensions);
8429
- const indexFilePath = path12.join(this.indexPath, "vectors.usearch");
8585
+ const indexFilePath = path13.join(this.indexPath, "vectors.usearch");
8430
8586
  if ((0, import_fs7.existsSync)(indexFilePath)) {
8431
8587
  this.store.load();
8432
8588
  }
8433
- const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
8589
+ const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
8434
8590
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8435
8591
  try {
8436
8592
  this.invertedIndex.load();
@@ -8440,7 +8596,7 @@ var Indexer = class {
8440
8596
  }
8441
8597
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8442
8598
  }
8443
- const dbPath = path12.join(this.indexPath, "codebase.db");
8599
+ const dbPath = path13.join(this.indexPath, "codebase.db");
8444
8600
  let dbIsNew = !(0, import_fs7.existsSync)(dbPath);
8445
8601
  try {
8446
8602
  this.database = new Database(dbPath);
@@ -8521,7 +8677,7 @@ var Indexer = class {
8521
8677
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
8522
8678
  return {
8523
8679
  resetCorruptedIndex: true,
8524
- warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
8680
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
8525
8681
  };
8526
8682
  }
8527
8683
  throw error;
@@ -8536,7 +8692,7 @@ var Indexer = class {
8536
8692
  return;
8537
8693
  }
8538
8694
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
8539
- const storeBasePath = path12.join(this.indexPath, "vectors");
8695
+ const storeBasePath = path13.join(this.indexPath, "vectors");
8540
8696
  const storeIndexPath = `${storeBasePath}.usearch`;
8541
8697
  const storeMetadataPath = `${storeBasePath}.meta.json`;
8542
8698
  const backupIndexPath = `${storeIndexPath}.bak`;
@@ -8621,9 +8777,9 @@ var Indexer = class {
8621
8777
  if (!isSqliteCorruptionError(error)) {
8622
8778
  return false;
8623
8779
  }
8624
- const dbPath = path12.join(this.indexPath, "codebase.db");
8780
+ const dbPath = path13.join(this.indexPath, "codebase.db");
8625
8781
  const warning = this.getCorruptedIndexWarning(dbPath);
8626
- const errorMessage = getErrorMessage(error);
8782
+ const errorMessage = getErrorMessage2(error);
8627
8783
  if (this.config.scope === "global") {
8628
8784
  this.logger.error("Detected corrupted shared global index database", {
8629
8785
  stage,
@@ -8644,15 +8800,15 @@ var Indexer = class {
8644
8800
  this.indexCompatibility = null;
8645
8801
  this.fileHashCache.clear();
8646
8802
  const resetPaths = [
8647
- path12.join(this.indexPath, "codebase.db"),
8648
- path12.join(this.indexPath, "codebase.db-shm"),
8649
- path12.join(this.indexPath, "codebase.db-wal"),
8650
- path12.join(this.indexPath, "vectors.usearch"),
8651
- path12.join(this.indexPath, "inverted-index.json"),
8652
- path12.join(this.indexPath, "file-hashes.json"),
8653
- path12.join(this.indexPath, "failed-batches.json"),
8654
- path12.join(this.indexPath, "indexing.lock"),
8655
- path12.join(this.indexPath, "vectors")
8803
+ path13.join(this.indexPath, "codebase.db"),
8804
+ path13.join(this.indexPath, "codebase.db-shm"),
8805
+ path13.join(this.indexPath, "codebase.db-wal"),
8806
+ path13.join(this.indexPath, "vectors.usearch"),
8807
+ path13.join(this.indexPath, "inverted-index.json"),
8808
+ path13.join(this.indexPath, "file-hashes.json"),
8809
+ path13.join(this.indexPath, "failed-batches.json"),
8810
+ path13.join(this.indexPath, "indexing.lock"),
8811
+ path13.join(this.indexPath, "vectors")
8656
8812
  ];
8657
8813
  await Promise.all(resetPaths.map(async (targetPath) => {
8658
8814
  try {
@@ -8917,7 +9073,7 @@ var Indexer = class {
8917
9073
  for (const parsed of parsedFiles) {
8918
9074
  currentFilePaths.add(parsed.path);
8919
9075
  if (parsed.chunks.length === 0) {
8920
- const relativePath = path12.relative(this.projectRoot, parsed.path);
9076
+ const relativePath = path13.relative(this.projectRoot, parsed.path);
8921
9077
  stats.parseFailures.push(relativePath);
8922
9078
  }
8923
9079
  let fileChunkCount = 0;
@@ -9056,6 +9212,7 @@ var Indexer = class {
9056
9212
  targetName: site.calleeName,
9057
9213
  toSymbolId: void 0,
9058
9214
  callType: site.callType,
9215
+ confidence: site.confidence,
9059
9216
  line: site.line,
9060
9217
  col: site.column,
9061
9218
  isResolved: false
@@ -9200,7 +9357,7 @@ var Indexer = class {
9200
9357
  for (const requestBatch of requestBatches) {
9201
9358
  queue.add(async () => {
9202
9359
  if (rateLimitBackoffMs > 0) {
9203
- await new Promise((resolve12) => setTimeout(resolve12, rateLimitBackoffMs));
9360
+ await new Promise((resolve13) => setTimeout(resolve13, rateLimitBackoffMs));
9204
9361
  }
9205
9362
  try {
9206
9363
  const result = await pRetry(
@@ -9215,7 +9372,7 @@ var Indexer = class {
9215
9372
  factor: 2,
9216
9373
  shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError),
9217
9374
  onFailedAttempt: (error) => {
9218
- const message = getErrorMessage(error);
9375
+ const message = getErrorMessage2(error);
9219
9376
  if (isRateLimitError(error)) {
9220
9377
  rateLimitBackoffMs = Math.min(providerRateLimits.maxRetryMs, (rateLimitBackoffMs || providerRateLimits.minRetryMs) * 2);
9221
9378
  this.logger.embedding("warn", `Rate limited, backing off`, {
@@ -9322,7 +9479,7 @@ var Indexer = class {
9322
9479
  });
9323
9480
  } catch (error) {
9324
9481
  const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id));
9325
- const failureMessage = getErrorMessage(error);
9482
+ const failureMessage = getErrorMessage2(error);
9326
9483
  const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9327
9484
  for (const chunk of failedChunks) {
9328
9485
  if (!failedChunkIds.has(chunk.id)) {
@@ -9761,8 +9918,8 @@ var Indexer = class {
9761
9918
  this.indexCompatibility = compatibility;
9762
9919
  return;
9763
9920
  }
9764
- const localProjectIndexPath = path12.join(this.projectRoot, ".opencode", "index");
9765
- if (path12.resolve(this.indexPath) !== path12.resolve(localProjectIndexPath)) {
9921
+ const localProjectIndexPath = path13.join(this.projectRoot, ".opencode", "index");
9922
+ if (path13.resolve(this.indexPath) !== path13.resolve(localProjectIndexPath)) {
9766
9923
  throw new Error(
9767
9924
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
9768
9925
  );
@@ -9850,7 +10007,7 @@ var Indexer = class {
9850
10007
  gcOrphanSymbols: 0,
9851
10008
  gcOrphanCallEdges: 0,
9852
10009
  resetCorruptedIndex: true,
9853
- warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
10010
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
9854
10011
  };
9855
10012
  }
9856
10013
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -10001,7 +10158,7 @@ var Indexer = class {
10001
10158
  succeeded += successfulResults.length;
10002
10159
  stillFailing.push(...failedChunksForBatch.values());
10003
10160
  } catch (error) {
10004
- const failureMessage = getErrorMessage(error);
10161
+ const failureMessage = getErrorMessage2(error);
10005
10162
  const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
10006
10163
  const unaccountedChunks = batch.chunks.filter(
10007
10164
  (chunk) => !failedChunksForBatch.has(chunk.id) && !completedChunkIds.has(chunk.id)
@@ -10175,12 +10332,12 @@ var Indexer = class {
10175
10332
  })
10176
10333
  );
10177
10334
  }
10178
- async getCallers(targetName) {
10335
+ async getCallers(targetName, callTypeFilter) {
10179
10336
  const { database } = await this.ensureInitialized();
10180
10337
  const seen = /* @__PURE__ */ new Set();
10181
10338
  const results = [];
10182
10339
  for (const branchKey of this.getBranchCatalogKeys()) {
10183
- for (const edge of database.getCallersWithContext(targetName, branchKey)) {
10340
+ for (const edge of database.getCallersWithContext(targetName, branchKey, callTypeFilter)) {
10184
10341
  if (!seen.has(edge.id)) {
10185
10342
  seen.add(edge.id);
10186
10343
  results.push(edge);
@@ -10189,12 +10346,12 @@ var Indexer = class {
10189
10346
  }
10190
10347
  return results;
10191
10348
  }
10192
- async getCallees(symbolId) {
10349
+ async getCallees(symbolId, callTypeFilter) {
10193
10350
  const { database } = await this.ensureInitialized();
10194
10351
  const seen = /* @__PURE__ */ new Set();
10195
10352
  const results = [];
10196
10353
  for (const branchKey of this.getBranchCatalogKeys()) {
10197
- for (const edge of database.getCallees(symbolId, branchKey)) {
10354
+ for (const edge of database.getCallees(symbolId, branchKey, callTypeFilter)) {
10198
10355
  if (!seen.has(edge.id)) {
10199
10356
  seen.add(edge.id);
10200
10357
  results.push(edge);
@@ -10203,6 +10360,198 @@ var Indexer = class {
10203
10360
  }
10204
10361
  return results;
10205
10362
  }
10363
+ async findCallPath(fromName, toName, maxDepth) {
10364
+ const { database } = await this.ensureInitialized();
10365
+ let shortest = [];
10366
+ for (const branchKey of this.getBranchCatalogKeys()) {
10367
+ const path19 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
10368
+ if (path19.length > 0 && (shortest.length === 0 || path19.length < shortest.length)) {
10369
+ shortest = path19;
10370
+ }
10371
+ }
10372
+ return shortest;
10373
+ }
10374
+ async getSymbolsForBranch(branch) {
10375
+ const { database } = await this.ensureInitialized();
10376
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
10377
+ return database.getSymbolsForBranch(resolvedBranch);
10378
+ }
10379
+ async getSymbolsForFiles(filePaths, branch) {
10380
+ const { database } = await this.ensureInitialized();
10381
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
10382
+ return database.getSymbolsForFiles(filePaths, resolvedBranch);
10383
+ }
10384
+ async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
10385
+ const { database } = await this.ensureInitialized();
10386
+ const branch = this.getBranchCatalogKey();
10387
+ return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
10388
+ }
10389
+ async detectCommunities(branch, symbolIds) {
10390
+ const { database } = await this.ensureInitialized();
10391
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
10392
+ return database.detectCommunities(resolvedBranch, symbolIds);
10393
+ }
10394
+ async computeCentrality(branch) {
10395
+ const { database } = await this.ensureInitialized();
10396
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
10397
+ return database.computeCentrality(resolvedBranch);
10398
+ }
10399
+ async getPrImpact(opts) {
10400
+ const { database } = await this.ensureInitialized();
10401
+ const execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
10402
+ const changedFilesResult = await getChangedFiles({
10403
+ pr: opts.pr,
10404
+ branch: opts.branch,
10405
+ projectRoot: this.projectRoot,
10406
+ baseBranch: this.baseBranch
10407
+ });
10408
+ const changedFiles = changedFilesResult.files;
10409
+ const headRefName = changedFilesResult.headRefName;
10410
+ if (opts.pr !== void 0 && headRefName === void 0) {
10411
+ throw new Error(
10412
+ `Could not resolve head branch for PR #${opts.pr}. Run index_codebase on the PR branch first.`
10413
+ );
10414
+ }
10415
+ const resolvedBranch = opts.pr !== void 0 ? headRefName : opts.branch || this.currentBranch;
10416
+ const branchKey = this.getBranchCatalogKeyFor(resolvedBranch || "default");
10417
+ const branchSymbols = database.getSymbolsForBranch(branchKey);
10418
+ if (branchSymbols.length === 0) {
10419
+ throw new Error(
10420
+ "Run index_codebase first to build the call graph and symbol index for this branch."
10421
+ );
10422
+ }
10423
+ const absoluteChangedFiles = changedFiles.map((f) => path13.resolve(this.projectRoot, f));
10424
+ const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
10425
+ const directIds = directSymbols.map((s) => s.id);
10426
+ const direction = opts.direction ?? "both";
10427
+ const maxDepth = opts.maxDepth ?? 5;
10428
+ const transitiveCallers = database.getTransitiveReachability(
10429
+ directIds,
10430
+ branchKey,
10431
+ direction,
10432
+ maxDepth
10433
+ );
10434
+ const affectedIdsSet = new Set(directIds);
10435
+ for (const caller of transitiveCallers) {
10436
+ affectedIdsSet.add(caller.symbolId);
10437
+ }
10438
+ const allAffectedIds = Array.from(affectedIdsSet);
10439
+ const communitiesData = database.detectCommunities(branchKey, allAffectedIds);
10440
+ const communityMap = /* @__PURE__ */ new Map();
10441
+ for (const c of communitiesData) {
10442
+ if (!communityMap.has(c.communityLabel)) {
10443
+ communityMap.set(c.communityLabel, {
10444
+ label: c.communityLabel,
10445
+ symbolCount: 0,
10446
+ directSymbols: /* @__PURE__ */ new Set()
10447
+ });
10448
+ }
10449
+ const entry = communityMap.get(c.communityLabel);
10450
+ entry.symbolCount++;
10451
+ if (directIds.includes(c.symbolId)) {
10452
+ entry.directSymbols.add(c.symbolId);
10453
+ }
10454
+ }
10455
+ const communities = Array.from(communityMap.values()).map((c) => ({
10456
+ label: c.label,
10457
+ symbolCount: c.symbolCount,
10458
+ directSymbols: Array.from(c.directSymbols)
10459
+ }));
10460
+ const centralityData = database.computeCentrality(branchKey);
10461
+ const hubThreshold = opts.hubThreshold ?? 10;
10462
+ const hubNodes = centralityData.filter((c) => directIds.includes(c.symbolId) && c.callerCount >= hubThreshold).map((c) => ({
10463
+ id: c.symbolId,
10464
+ name: c.symbolName,
10465
+ callerCount: c.callerCount,
10466
+ filePath: c.filePath
10467
+ }));
10468
+ const totalAffected = allAffectedIds.length;
10469
+ let riskLevel;
10470
+ let riskReason;
10471
+ if (totalAffected < 5 && hubNodes.length === 0) {
10472
+ riskLevel = "LOW";
10473
+ riskReason = `Small impact: ${totalAffected} affected symbols, no hub nodes touched.`;
10474
+ } else if (totalAffected > 20 || hubNodes.length > 1) {
10475
+ riskLevel = "HIGH";
10476
+ riskReason = `Large impact: ${totalAffected} affected symbols${hubNodes.length > 0 ? `, ${hubNodes.length} hub nodes touched` : ""}.`;
10477
+ } else {
10478
+ riskLevel = "MEDIUM";
10479
+ riskReason = `Moderate impact: ${totalAffected} affected symbols${hubNodes.length === 1 ? ", 1 hub node touched" : ""}.`;
10480
+ }
10481
+ let conflictingPRs;
10482
+ if (opts.checkConflicts) {
10483
+ conflictingPRs = [];
10484
+ try {
10485
+ const { stdout } = await execFileAsync2(
10486
+ "gh",
10487
+ ["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
10488
+ { cwd: this.projectRoot, timeout: 3e4 }
10489
+ );
10490
+ const openPRs = JSON.parse(stdout);
10491
+ const currentCommunityLabels = new Set(communities.map((c) => c.label));
10492
+ const allCommunitiesData = database.detectCommunities(branchKey);
10493
+ const symbolToCommunity = /* @__PURE__ */ new Map();
10494
+ const structuralKey = (filePath, name) => `${filePath.toLowerCase()}:${name.toLowerCase()}`;
10495
+ for (const c of allCommunitiesData) {
10496
+ symbolToCommunity.set(structuralKey(c.filePath, c.symbolName), c.communityLabel);
10497
+ }
10498
+ for (const openPr of openPRs) {
10499
+ if (openPr.number === opts.pr) continue;
10500
+ try {
10501
+ const otherChanged = await getChangedFiles({
10502
+ pr: openPr.number,
10503
+ projectRoot: this.projectRoot,
10504
+ baseBranch: this.baseBranch
10505
+ });
10506
+ const otherAbsolute = otherChanged.files.map((f) => path13.resolve(this.projectRoot, f));
10507
+ const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
10508
+ const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
10509
+ const otherLabels = /* @__PURE__ */ new Set();
10510
+ for (const sym of otherSymbols) {
10511
+ const label = symbolToCommunity.get(structuralKey(sym.filePath, sym.name));
10512
+ if (label) {
10513
+ otherLabels.add(label);
10514
+ }
10515
+ }
10516
+ const overlapping = Array.from(otherLabels).filter(
10517
+ (l) => currentCommunityLabels.has(l)
10518
+ );
10519
+ if (overlapping.length > 0) {
10520
+ conflictingPRs.push({
10521
+ pr: openPr.number,
10522
+ branch: openPr.headRefName,
10523
+ overlappingCommunities: overlapping
10524
+ });
10525
+ }
10526
+ } catch {
10527
+ }
10528
+ }
10529
+ } catch {
10530
+ }
10531
+ }
10532
+ return {
10533
+ changedFiles,
10534
+ directSymbols: directSymbols.map((s) => ({
10535
+ id: s.id,
10536
+ name: s.name,
10537
+ kind: s.kind,
10538
+ filePath: s.filePath
10539
+ })),
10540
+ transitiveCallers: transitiveCallers.map((c) => ({
10541
+ id: c.symbolId,
10542
+ name: c.symbolName,
10543
+ filePath: c.filePath,
10544
+ depth: c.depth
10545
+ })),
10546
+ totalAffected,
10547
+ communities,
10548
+ hubNodes,
10549
+ riskLevel,
10550
+ riskReason,
10551
+ direction,
10552
+ conflictingPRs
10553
+ };
10554
+ }
10206
10555
  async close() {
10207
10556
  await this.database?.close();
10208
10557
  this.database = null;
@@ -10430,54 +10779,139 @@ ${truncateContent(r.content)}
10430
10779
  return formatted.join("\n\n");
10431
10780
  }
10432
10781
 
10782
+ // src/tools/pr-impact.ts
10783
+ var import_plugin = require("@opencode-ai/plugin");
10784
+
10785
+ // src/tools/format-pr-impact.ts
10786
+ function formatPrImpact(result) {
10787
+ const lines = [];
10788
+ lines.push(`\u2192 Files changed: ${result.changedFiles.length}`);
10789
+ for (const file of result.changedFiles) {
10790
+ lines.push(` - ${file}`);
10791
+ }
10792
+ const directCount = result.directSymbols.length;
10793
+ const transitiveCount = result.transitiveCallers.length;
10794
+ const directionLabel = result.direction === "callees" ? "callees" : result.direction === "both" ? "reachable (callers + callees)" : "callers";
10795
+ lines.push(
10796
+ `\u2192 Symbols affected: ${result.totalAffected} (${directCount} direct, ${transitiveCount} transitive ${directionLabel})`
10797
+ );
10798
+ if (directCount > 0) {
10799
+ lines.push(
10800
+ ` Direct: ${result.directSymbols.map((s) => `${s.name} (${s.kind})`).join(", ")}`
10801
+ );
10802
+ }
10803
+ if (transitiveCount > 0) {
10804
+ lines.push(
10805
+ ` Transitive ${directionLabel}: ${result.transitiveCallers.map((s) => s.name).join(", ")}`
10806
+ );
10807
+ }
10808
+ if (result.communities.length > 0) {
10809
+ lines.push(
10810
+ `\u2192 Communities touched: ${result.communities.map((c) => c.label).join(", ")}`
10811
+ );
10812
+ for (const community of result.communities) {
10813
+ lines.push(
10814
+ ` - ${community.label}: ${community.symbolCount} symbols`
10815
+ );
10816
+ }
10817
+ } else {
10818
+ lines.push("\u2192 Communities touched: none");
10819
+ }
10820
+ lines.push(`\u2192 Risk: ${result.riskLevel} \u2014 ${result.riskReason}`);
10821
+ if (result.hubNodes.length > 0) {
10822
+ lines.push(" Hub nodes in change scope:");
10823
+ for (const hub of result.hubNodes) {
10824
+ lines.push(` - ${hub.name} (${hub.callerCount} callers) at ${hub.filePath}`);
10825
+ }
10826
+ }
10827
+ if (result.conflictingPRs && result.conflictingPRs.length > 0) {
10828
+ const conflictList = result.conflictingPRs.map(
10829
+ (c) => `PR #${c.pr} (also touches ${c.overlappingCommunities.join(", ")} community)`
10830
+ );
10831
+ lines.push(`\u2192 Potential conflicts with: ${conflictList.join(", ")}`);
10832
+ }
10833
+ return lines.join("\n");
10834
+ }
10835
+
10836
+ // src/tools/pr-impact.ts
10837
+ var z = import_plugin.tool.schema;
10838
+ var pr_impact = (0, import_plugin.tool)({
10839
+ description: "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.",
10840
+ args: {
10841
+ pr: z.number().optional().describe("Pull request number to analyze"),
10842
+ branch: z.string().optional().describe("Branch name to analyze (defaults to current branch)"),
10843
+ maxDepth: z.number().optional().default(5).describe("Maximum traversal depth for transitive callers (default: 5)"),
10844
+ hubThreshold: z.number().optional().default(10).describe("Minimum caller count to flag a symbol as a hub node (default: 10)"),
10845
+ checkConflicts: z.boolean().optional().default(false).describe("Check for conflicting open PRs touching the same communities (default: false)"),
10846
+ direction: z.enum(["callers", "callees", "both"]).optional().default("both").describe("Call-graph traversal direction: 'callers' for upstream, 'callees' for downstream, 'both' for union (default: both)")
10847
+ },
10848
+ async execute(args, context) {
10849
+ const indexer = getIndexerForProject(context?.worktree);
10850
+ try {
10851
+ const result = await indexer.getPrImpact({
10852
+ pr: args.pr,
10853
+ branch: args.branch,
10854
+ maxDepth: args.maxDepth,
10855
+ hubThreshold: args.hubThreshold,
10856
+ checkConflicts: args.checkConflicts,
10857
+ direction: args.direction
10858
+ });
10859
+ return formatPrImpact(result);
10860
+ } catch (error) {
10861
+ const message = error instanceof Error ? error.message : String(error);
10862
+ return `Error analyzing PR impact: ${message}`;
10863
+ }
10864
+ }
10865
+ });
10866
+
10433
10867
  // src/tools/knowledge-base-paths.ts
10434
- var path13 = __toESM(require("path"), 1);
10868
+ var path14 = __toESM(require("path"), 1);
10435
10869
  function resolveConfigPathValue(value, baseDir) {
10436
10870
  const trimmed = value.trim();
10437
10871
  if (!trimmed) {
10438
10872
  return trimmed;
10439
10873
  }
10440
- const absolutePath = path13.isAbsolute(trimmed) ? trimmed : path13.resolve(baseDir, trimmed);
10441
- return path13.normalize(absolutePath);
10874
+ const absolutePath = path14.isAbsolute(trimmed) ? trimmed : path14.resolve(baseDir, trimmed);
10875
+ return path14.normalize(absolutePath);
10442
10876
  }
10443
10877
  function serializeConfigPathValue(value, baseDir) {
10444
10878
  const trimmed = value.trim();
10445
10879
  if (!trimmed) {
10446
10880
  return trimmed;
10447
10881
  }
10448
- if (!path13.isAbsolute(trimmed)) {
10449
- return normalizePathSeparators(path13.normalize(trimmed));
10882
+ if (!path14.isAbsolute(trimmed)) {
10883
+ return normalizePathSeparators(path14.normalize(trimmed));
10450
10884
  }
10451
- const relativePath = path13.relative(baseDir, trimmed);
10452
- if (!relativePath || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath)) {
10453
- return normalizePathSeparators(path13.normalize(relativePath || "."));
10885
+ const relativePath = path14.relative(baseDir, trimmed);
10886
+ if (!relativePath || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath)) {
10887
+ return normalizePathSeparators(path14.normalize(relativePath || "."));
10454
10888
  }
10455
- return path13.normalize(trimmed);
10889
+ return path14.normalize(trimmed);
10456
10890
  }
10457
10891
  function resolveKnowledgeBasePath(value, projectRoot) {
10458
- return path13.isAbsolute(value) ? value : path13.resolve(projectRoot, value);
10892
+ return path14.isAbsolute(value) ? value : path14.resolve(projectRoot, value);
10459
10893
  }
10460
10894
  function normalizeKnowledgeBasePath2(value, projectRoot) {
10461
- return path13.normalize(resolveKnowledgeBasePath(value, projectRoot));
10895
+ return path14.normalize(resolveKnowledgeBasePath(value, projectRoot));
10462
10896
  }
10463
10897
  function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot) {
10464
- const normalizedInput = path13.normalize(inputPath);
10898
+ const normalizedInput = path14.normalize(inputPath);
10465
10899
  return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput);
10466
10900
  }
10467
10901
  function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
10468
- const normalizedInput = path13.normalize(inputPath);
10902
+ const normalizedInput = path14.normalize(inputPath);
10469
10903
  return knowledgeBases.findIndex(
10470
- (kb) => path13.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
10904
+ (kb) => path14.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
10471
10905
  );
10472
10906
  }
10473
10907
 
10474
10908
  // src/tools/index.ts
10475
10909
  var import_fs9 = require("fs");
10476
- var path15 = __toESM(require("path"), 1);
10910
+ var path16 = __toESM(require("path"), 1);
10477
10911
 
10478
10912
  // src/tools/config-state.ts
10479
10913
  var import_fs8 = require("fs");
10480
- var path14 = __toESM(require("path"), 1);
10914
+ var path15 = __toESM(require("path"), 1);
10481
10915
  function normalizeKnowledgeBasePaths(config, projectRoot) {
10482
10916
  const normalized = { ...config };
10483
10917
  if (Array.isArray(normalized.knowledgeBases)) {
@@ -10504,8 +10938,8 @@ function loadEditableConfig(projectRoot) {
10504
10938
  }
10505
10939
  function saveConfig(projectRoot, config) {
10506
10940
  const configPath = getConfigPath(projectRoot);
10507
- const configDir = path14.dirname(configPath);
10508
- const configBaseDir = path14.dirname(configDir);
10941
+ const configDir = path15.dirname(configPath);
10942
+ const configBaseDir = path15.dirname(configDir);
10509
10943
  if (!(0, import_fs8.existsSync)(configDir)) {
10510
10944
  (0, import_fs8.mkdirSync)(configDir, { recursive: true });
10511
10945
  }
@@ -10523,7 +10957,7 @@ var os5 = __toESM(require("os"), 1);
10523
10957
  function ensureStringArray(value) {
10524
10958
  return Array.isArray(value) ? value : [];
10525
10959
  }
10526
- var z = import_plugin.tool.schema;
10960
+ var z2 = import_plugin2.tool.schema;
10527
10961
  var indexerMap = /* @__PURE__ */ new Map();
10528
10962
  var defaultProjectRoot = "";
10529
10963
  function initializeTools(projectRoot, config) {
@@ -10543,12 +10977,12 @@ function shouldForceLocalizeProjectIndex(projectRoot) {
10543
10977
  if (currentConfig.scope !== "project") {
10544
10978
  return false;
10545
10979
  }
10546
- const localIndexPath = path15.join(projectRoot, ".opencode", "index");
10980
+ const localIndexPath = path16.join(projectRoot, ".opencode", "index");
10547
10981
  const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
10548
10982
  if (!mainRepoRoot) {
10549
10983
  return false;
10550
10984
  }
10551
- const inheritedIndexPath = path15.join(mainRepoRoot, ".opencode", "index");
10985
+ const inheritedIndexPath = path16.join(mainRepoRoot, ".opencode", "index");
10552
10986
  return !(0, import_fs9.existsSync)(localIndexPath) && (0, import_fs9.existsSync)(inheritedIndexPath);
10553
10987
  }
10554
10988
  function getIndexerForProject(directory) {
@@ -10564,14 +10998,14 @@ function getIndexerForProject(directory) {
10564
10998
  }
10565
10999
  return indexer;
10566
11000
  }
10567
- var codebase_peek = (0, import_plugin.tool)({
11001
+ var codebase_peek = (0, import_plugin2.tool)({
10568
11002
  description: "Quick lookup of code locations by meaning. Returns only metadata (file, line, name, type) WITHOUT code content. Use this first to find WHERE code is, then use Read tool to examine specific files. Saves tokens by not returning full code blocks. Best for: discovery, navigation, finding multiple related locations.",
10569
11003
  args: {
10570
- query: z.string().describe("Natural language description of what code you're looking for."),
10571
- limit: z.number().optional().default(10).describe("Maximum number of results to return"),
10572
- fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
10573
- directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
10574
- chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type")
11004
+ query: z2.string().describe("Natural language description of what code you're looking for."),
11005
+ limit: z2.number().optional().default(10).describe("Maximum number of results to return"),
11006
+ fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
11007
+ directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
11008
+ chunkType: z2.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type")
10575
11009
  },
10576
11010
  async execute(args, context) {
10577
11011
  const indexer = getIndexerForProject(context?.worktree);
@@ -10584,12 +11018,12 @@ var codebase_peek = (0, import_plugin.tool)({
10584
11018
  return formatCodebasePeek(results);
10585
11019
  }
10586
11020
  });
10587
- var index_codebase = (0, import_plugin.tool)({
11021
+ var index_codebase = (0, import_plugin2.tool)({
10588
11022
  description: "Index the codebase for semantic search. Creates vector embeddings of code chunks. Incremental - only re-indexes changed files (~50ms when nothing changed). Run before first codebase_search.",
10589
11023
  args: {
10590
- force: z.boolean().optional().default(false).describe("Force reindex even if already indexed"),
10591
- estimateOnly: z.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
10592
- verbose: z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
11024
+ force: z2.boolean().optional().default(false).describe("Force reindex even if already indexed"),
11025
+ estimateOnly: z2.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
11026
+ verbose: z2.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
10593
11027
  },
10594
11028
  async execute(args, context) {
10595
11029
  const projectRoot = context?.worktree || defaultProjectRoot;
@@ -10622,7 +11056,7 @@ var index_codebase = (0, import_plugin.tool)({
10622
11056
  return formatIndexStats(stats, args.verbose ?? false);
10623
11057
  }
10624
11058
  });
10625
- var index_status = (0, import_plugin.tool)({
11059
+ var index_status = (0, import_plugin2.tool)({
10626
11060
  description: "Check the status of the codebase index. Shows whether the codebase is indexed, how many chunks are stored, and the embedding provider being used.",
10627
11061
  args: {},
10628
11062
  async execute(_args, context) {
@@ -10631,7 +11065,7 @@ var index_status = (0, import_plugin.tool)({
10631
11065
  return formatStatus(status);
10632
11066
  }
10633
11067
  });
10634
- var index_health_check = (0, import_plugin.tool)({
11068
+ var index_health_check = (0, import_plugin2.tool)({
10635
11069
  description: "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
10636
11070
  args: {},
10637
11071
  async execute(_args, context) {
@@ -10640,7 +11074,7 @@ var index_health_check = (0, import_plugin.tool)({
10640
11074
  return formatHealthCheck(result);
10641
11075
  }
10642
11076
  });
10643
- var index_metrics = (0, import_plugin.tool)({
11077
+ var index_metrics = (0, import_plugin2.tool)({
10644
11078
  description: "Get metrics and performance statistics for the codebase index. Shows indexing stats, search timings, cache hit rates, and API usage. Requires debug.enabled=true and debug.metrics=true in config.",
10645
11079
  args: {},
10646
11080
  async execute(_args, context) {
@@ -10655,12 +11089,12 @@ var index_metrics = (0, import_plugin.tool)({
10655
11089
  return logger.formatMetrics();
10656
11090
  }
10657
11091
  });
10658
- var index_logs = (0, import_plugin.tool)({
11092
+ var index_logs = (0, import_plugin2.tool)({
10659
11093
  description: "Get recent debug logs from the codebase indexer. Shows timestamped log entries with level and category. Requires debug.enabled=true in config.",
10660
11094
  args: {
10661
- limit: z.number().optional().default(20).describe("Maximum number of log entries to return"),
10662
- category: z.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional().describe("Filter by log category"),
10663
- level: z.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
11095
+ limit: z2.number().optional().default(20).describe("Maximum number of log entries to return"),
11096
+ category: z2.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional().describe("Filter by log category"),
11097
+ level: z2.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
10664
11098
  },
10665
11099
  async execute(args, context) {
10666
11100
  const indexer = getIndexerForProject(context?.worktree);
@@ -10679,15 +11113,15 @@ var index_logs = (0, import_plugin.tool)({
10679
11113
  return formatLogs(logs);
10680
11114
  }
10681
11115
  });
10682
- var find_similar = (0, import_plugin.tool)({
11116
+ var find_similar = (0, import_plugin2.tool)({
10683
11117
  description: "Find code similar to a given snippet. Use for duplicate detection, pattern discovery, or refactoring prep. Paste code and find semantically similar implementations elsewhere in the codebase.",
10684
11118
  args: {
10685
- code: z.string().describe("The code snippet to find similar code for"),
10686
- limit: z.number().optional().default(10).describe("Maximum number of results to return"),
10687
- fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
10688
- directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
10689
- chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10690
- excludeFile: z.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)")
11119
+ code: z2.string().describe("The code snippet to find similar code for"),
11120
+ limit: z2.number().optional().default(10).describe("Maximum number of results to return"),
11121
+ fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
11122
+ directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
11123
+ chunkType: z2.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
11124
+ excludeFile: z2.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)")
10691
11125
  },
10692
11126
  async execute(args, context) {
10693
11127
  const indexer = getIndexerForProject(context?.worktree);
@@ -10703,15 +11137,15 @@ var find_similar = (0, import_plugin.tool)({
10703
11137
  return formatSearchResults(results);
10704
11138
  }
10705
11139
  });
10706
- var codebase_search = (0, import_plugin.tool)({
11140
+ var codebase_search = (0, import_plugin2.tool)({
10707
11141
  description: "Search codebase by MEANING, not keywords. Returns full code content. Use when you need to see actual implementation. For just finding WHERE code is (saves ~90% tokens), use codebase_peek instead. For known identifiers like 'validateToken', use grep - it's faster.",
10708
11142
  args: {
10709
- query: z.string().describe("Natural language description of what code you're looking for. Describe behavior, not syntax."),
10710
- limit: z.number().optional().default(5).describe("Maximum number of results to return"),
10711
- fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
10712
- directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
10713
- chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10714
- contextLines: z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
11143
+ query: z2.string().describe("Natural language description of what code you're looking for. Describe behavior, not syntax."),
11144
+ limit: z2.number().optional().default(5).describe("Maximum number of results to return"),
11145
+ fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
11146
+ directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
11147
+ chunkType: z2.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
11148
+ contextLines: z2.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
10715
11149
  },
10716
11150
  async execute(args, context) {
10717
11151
  const indexer = getIndexerForProject(context?.worktree);
@@ -10727,13 +11161,13 @@ var codebase_search = (0, import_plugin.tool)({
10727
11161
  return formatSearchResults(results, "score");
10728
11162
  }
10729
11163
  });
10730
- var implementation_lookup = (0, import_plugin.tool)({
11164
+ var implementation_lookup = (0, import_plugin2.tool)({
10731
11165
  description: "Jump to symbol definition. Find WHERE something is defined. Returns the authoritative source location(s) for a function, class, method, type, or variable. Prefers real implementation files over tests, docs, examples, and fixtures. Use when you need the definition site, not all usages.",
10732
11166
  args: {
10733
- query: z.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
10734
- limit: z.number().optional().default(5).describe("Maximum number of results"),
10735
- fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
10736
- directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
11167
+ query: z2.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
11168
+ limit: z2.number().optional().default(5).describe("Maximum number of results"),
11169
+ fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
11170
+ directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
10737
11171
  },
10738
11172
  async execute(args, context) {
10739
11173
  const indexer = getIndexerForProject(context?.worktree);
@@ -10745,12 +11179,13 @@ var implementation_lookup = (0, import_plugin.tool)({
10745
11179
  return formatDefinitionLookup(results, args.query);
10746
11180
  }
10747
11181
  });
10748
- var call_graph = (0, import_plugin.tool)({
10749
- description: "Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies between functions.",
11182
+ var call_graph = (0, import_plugin2.tool)({
11183
+ description: "Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies between functions. Supports relationship types: Call, MethodCall, Constructor, Import, Inherits, Implements.",
10750
11184
  args: {
10751
- name: z.string().describe("Function or method name to query"),
10752
- direction: z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
10753
- symbolId: z.string().optional().describe("Symbol ID (required for 'callees' direction, returned by previous call_graph queries)")
11185
+ name: z2.string().describe("Function or method name to query"),
11186
+ direction: z2.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
11187
+ symbolId: z2.string().optional().describe("Symbol ID (required for 'callees' direction, returned by previous call_graph queries)"),
11188
+ relationshipType: z2.enum(["Call", "MethodCall", "Constructor", "Import", "Inherits", "Implements"]).optional().describe("Filter by relationship type. Omit to show all.")
10754
11189
  },
10755
11190
  async execute(args, context) {
10756
11191
  const indexer = getIndexerForProject(context?.worktree);
@@ -10758,35 +11193,59 @@ var call_graph = (0, import_plugin.tool)({
10758
11193
  if (!args.symbolId) {
10759
11194
  return "Error: 'symbolId' is required when direction is 'callees'. First use direction='callers' to find the symbol ID.";
10760
11195
  }
10761
- const callees = await indexer.getCallees(args.symbolId);
11196
+ const callees = await indexer.getCallees(args.symbolId, args.relationshipType);
10762
11197
  if (callees.length === 0) {
10763
- return `No callees found for symbol ${args.symbolId}. The function may not call any other tracked functions.`;
11198
+ return `No callees found for symbol ${args.symbolId}${args.relationshipType ? ` with type ${args.relationshipType}` : ""}. The function may not call any other tracked functions.`;
10764
11199
  }
10765
- const formatted2 = callees.map(
10766
- (e, i) => `[${i + 1}] \u2192 ${e.targetName} (${e.callType}) at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`
10767
- );
11200
+ const formatted2 = callees.map((e, i) => {
11201
+ const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
11202
+ return `[${i + 1}] \u2192 ${e.targetName} (${e.callType})${conf} at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`;
11203
+ });
10768
11204
  return formatted2.join("\n");
10769
11205
  }
10770
- const callers = await indexer.getCallers(args.name);
11206
+ const callers = await indexer.getCallers(args.name, args.relationshipType);
10771
11207
  if (callers.length === 0) {
10772
- return `No callers found for "${args.name}". It may not be called by any tracked function, or the index needs updating.`;
11208
+ return `No callers found for "${args.name}"${args.relationshipType ? ` with type ${args.relationshipType}` : ""}. It may not be called by any tracked function, or the index needs updating.`;
10773
11209
  }
10774
- const formatted = callers.map(
10775
- (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]"}`
10776
- );
11210
+ const formatted = callers.map((e, i) => {
11211
+ const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
11212
+ 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]"}`;
11213
+ });
10777
11214
  return formatted.join("\n");
10778
11215
  }
10779
11216
  });
10780
- var add_knowledge_base = (0, import_plugin.tool)({
11217
+ var call_graph_path = (0, import_plugin2.tool)({
11218
+ description: "Find the shortest connection path between two symbols in the call graph. Given a source and target function/method name, returns the chain of calls connecting them.",
11219
+ args: {
11220
+ from: z2.string().describe("Source function/method name (starting point)"),
11221
+ to: z2.string().describe("Target function/method name (destination)"),
11222
+ maxDepth: z2.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
11223
+ },
11224
+ async execute(args, context) {
11225
+ const indexer = getIndexerForProject(context?.worktree);
11226
+ const path19 = await indexer.findCallPath(args.from, args.to, args.maxDepth);
11227
+ if (path19.length === 0) {
11228
+ return `No path found between "${args.from}" and "${args.to}". They may be in disconnected components, or the call graph index needs updating.`;
11229
+ }
11230
+ const formatted = path19.map((hop, i) => {
11231
+ const prefix = i === 0 ? "[start]" : `--${hop.callType}-->`;
11232
+ const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
11233
+ return `${prefix} ${hop.symbolName}${location}`;
11234
+ });
11235
+ return `Path (${path19.length} hops):
11236
+ ${formatted.join("\n")}`;
11237
+ }
11238
+ });
11239
+ var add_knowledge_base = (0, import_plugin2.tool)({
10781
11240
  description: "Add a folder as a knowledge base to the semantic search index. The folder will be indexed alongside the main project code. Supports absolute paths or relative paths (relative to the project root).",
10782
11241
  args: {
10783
- path: z.string().describe("Path to the folder to add as a knowledge base (absolute or relative to project root)")
11242
+ path: z2.string().describe("Path to the folder to add as a knowledge base (absolute or relative to project root)")
10784
11243
  },
10785
11244
  async execute(args, context) {
10786
11245
  const projectRoot = context?.worktree || defaultProjectRoot;
10787
11246
  const inputPath = args.path.trim();
10788
- const normalizedPath = path15.resolve(
10789
- path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, projectRoot)
11247
+ const normalizedPath = path16.resolve(
11248
+ path16.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, projectRoot)
10790
11249
  );
10791
11250
  if (!(0, import_fs9.existsSync)(normalizedPath)) {
10792
11251
  return `Error: Directory does not exist: ${normalizedPath}`;
@@ -10815,7 +11274,7 @@ var add_knowledge_base = (0, import_plugin.tool)({
10815
11274
  }
10816
11275
  }
10817
11276
  for (const dotDir of sensitiveDotDirs) {
10818
- const sensitiveDir = path15.join(homeDir, dotDir);
11277
+ const sensitiveDir = path16.join(homeDir, dotDir);
10819
11278
  if (realPath === sensitiveDir || realPath.startsWith(sensitiveDir + "/")) {
10820
11279
  return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
10821
11280
  }
@@ -10849,7 +11308,7 @@ Run /index to rebuild the index with the new knowledge base.`;
10849
11308
  return result;
10850
11309
  }
10851
11310
  });
10852
- var list_knowledge_bases = (0, import_plugin.tool)({
11311
+ var list_knowledge_bases = (0, import_plugin2.tool)({
10853
11312
  description: "List all configured knowledge base folders that are indexed alongside the main project.",
10854
11313
  args: {},
10855
11314
  async execute(_args, context) {
@@ -10886,10 +11345,10 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10886
11345
  return result;
10887
11346
  }
10888
11347
  });
10889
- var remove_knowledge_base = (0, import_plugin.tool)({
11348
+ var remove_knowledge_base = (0, import_plugin2.tool)({
10890
11349
  description: "Remove a knowledge base folder from the semantic search index.",
10891
11350
  args: {
10892
- path: z.string().describe("Path of the knowledge base to remove (must match the configured path exactly)")
11351
+ path: z2.string().describe("Path of the knowledge base to remove (must match the configured path exactly)")
10893
11352
  },
10894
11353
  async execute(args, context) {
10895
11354
  const projectRoot = context?.worktree || defaultProjectRoot;
@@ -10931,7 +11390,7 @@ Run /index to rebuild the index without the removed knowledge base.`;
10931
11390
 
10932
11391
  // src/commands/loader.ts
10933
11392
  var import_fs10 = require("fs");
10934
- var path16 = __toESM(require("path"), 1);
11393
+ var path17 = __toESM(require("path"), 1);
10935
11394
  function parseFrontmatter(content) {
10936
11395
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
10937
11396
  const match = content.match(frontmatterRegex);
@@ -10957,7 +11416,7 @@ function loadCommandsFromDirectory(commandsDir) {
10957
11416
  }
10958
11417
  const files = (0, import_fs10.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
10959
11418
  for (const file of files) {
10960
- const filePath = path16.join(commandsDir, file);
11419
+ const filePath = path17.join(commandsDir, file);
10961
11420
  let content;
10962
11421
  try {
10963
11422
  content = (0, import_fs10.readFileSync)(filePath, "utf-8");
@@ -10966,7 +11425,7 @@ function loadCommandsFromDirectory(commandsDir) {
10966
11425
  throw new Error(`Failed to load command file ${filePath}: ${message}`);
10967
11426
  }
10968
11427
  const { frontmatter, body } = parseFrontmatter(content);
10969
- const name = path16.basename(file, ".md");
11428
+ const name = path17.basename(file, ".md");
10970
11429
  const description = frontmatter.description || `Run the ${name} command`;
10971
11430
  commands.set(name, {
10972
11431
  description,
@@ -11188,7 +11647,7 @@ function assessRoutingIntent(text) {
11188
11647
  reason: "no_local_discovery_signal"
11189
11648
  };
11190
11649
  }
11191
- function buildRoutingHint(assessment, status) {
11650
+ function buildRoutingHint(assessment, status, includeGraphHandoff = false) {
11192
11651
  if (assessment.intent === "definition_lookup") {
11193
11652
  if (!status || !status.indexed || status.compatibility?.compatible === false) {
11194
11653
  return "For this turn, if you need a symbol definition, check `index_status` first and run `index_codebase` if the index is missing or incompatible. Then use `implementation_lookup` for the definition site. Use `grep` for exhaustive literal matches.";
@@ -11199,15 +11658,21 @@ function buildRoutingHint(assessment, status) {
11199
11658
  return null;
11200
11659
  }
11201
11660
  if (!status || !status.indexed || status.compatibility?.compatible === false) {
11202
- return "For this turn, if local code discovery by behavior is needed, check `index_status` first and run `index_codebase` if the index is missing or incompatible. Use `grep` for exact identifiers or exhaustive matches.";
11661
+ const graphHandoff2 = includeGraphHandoff ? " Use graph tools after semantic discovery identifies relevant symbols." : "";
11662
+ return `For this turn, if local code discovery by behavior is needed, check \`index_status\` first and run \`index_codebase\` if the index is missing or incompatible.${graphHandoff2} Use \`grep\` for exact identifiers or exhaustive matches.`;
11203
11663
  }
11204
- return "For this turn, prefer `codebase_peek` for local code discovery by behavior or likely location, then use `codebase_search` when you need implementation content. Use `grep` for exact identifiers or exhaustive matches.";
11664
+ const graphHandoff = includeGraphHandoff ? " before graph tools such as `call_graph`, `call_graph_path`, `pr_impact`, or OMO CodeGraph" : "";
11665
+ return `For this turn, prefer \`codebase_peek\` for local code discovery by behavior or likely location${graphHandoff}. Then use \`codebase_search\` when you need implementation content. Use \`grep\` for exact identifiers or exhaustive matches.`;
11205
11666
  }
11206
11667
  var RoutingHintController = class {
11207
- constructor(getStatus, maxSessions = 200) {
11668
+ constructor(getStatus, maxSessions = 200, includeGraphHandoff = false) {
11208
11669
  this.getStatus = getStatus;
11209
11670
  this.maxSessions = maxSessions;
11671
+ this.includeGraphHandoff = includeGraphHandoff;
11210
11672
  }
11673
+ getStatus;
11674
+ maxSessions;
11675
+ includeGraphHandoff;
11211
11676
  sessionState = /* @__PURE__ */ new Map();
11212
11677
  observeUserMessage(sessionID, parts) {
11213
11678
  const assessment = assessRoutingIntent(extractUserText(parts));
@@ -11228,7 +11693,7 @@ var RoutingHintController = class {
11228
11693
  return [];
11229
11694
  }
11230
11695
  const status = await this.safeGetStatus();
11231
- const hint = buildRoutingHint(state.assessment, status);
11696
+ const hint = buildRoutingHint(state.assessment, status, this.includeGraphHandoff);
11232
11697
  return hint ? [hint] : [];
11233
11698
  }
11234
11699
  markToolUsed(sessionID, toolName) {
@@ -11279,9 +11744,9 @@ function replaceActiveWatcher(projectRoot, nextWatcher) {
11279
11744
  function getCommandsDir() {
11280
11745
  let currentDir = process.cwd();
11281
11746
  if (typeof import_meta2 !== "undefined" && import_meta2.url) {
11282
- currentDir = path17.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
11747
+ currentDir = path18.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
11283
11748
  }
11284
- return path17.join(currentDir, "..", "commands");
11749
+ return path18.join(currentDir, "..", "commands");
11285
11750
  }
11286
11751
  function appendRoutingHints(output, hints, preferredRole) {
11287
11752
  const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
@@ -11300,8 +11765,8 @@ var plugin = async ({ directory, worktree }) => {
11300
11765
  const config = parseConfig(rawConfig);
11301
11766
  initializeTools(projectRoot, config);
11302
11767
  const getProjectIndexer = () => getIndexerForProject(projectRoot);
11303
- const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus()) : null;
11304
- const isHomeDir = path17.resolve(projectRoot) === path17.resolve(os6.homedir());
11768
+ const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus(), 200, config.search.routingGraphHandoffHints) : null;
11769
+ const isHomeDir = path18.resolve(projectRoot) === path18.resolve(os6.homedir());
11305
11770
  const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
11306
11771
  if (isHomeDir) {
11307
11772
  console.warn(
@@ -11336,10 +11801,12 @@ var plugin = async ({ directory, worktree }) => {
11336
11801
  index_logs,
11337
11802
  find_similar,
11338
11803
  call_graph,
11804
+ call_graph_path,
11339
11805
  implementation_lookup,
11340
11806
  add_knowledge_base,
11341
11807
  list_knowledge_bases,
11342
- remove_knowledge_base
11808
+ remove_knowledge_base,
11809
+ pr_impact
11343
11810
  },
11344
11811
  async "chat.message"(input, output) {
11345
11812
  routingHints?.observeUserMessage(input.sessionID, output.parts);