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.js 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 __copyProps = (to, from, except, desc) => {
13
17
  if (from && typeof from === "object" || typeof from === "function") {
@@ -324,7 +328,7 @@ var require_ignore = __commonJS({
324
328
  // path matching.
325
329
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
326
330
  // @returns {TestResult} true if a file is ignored
327
- test(path18, checkUnignored, mode) {
331
+ test(path19, checkUnignored, mode) {
328
332
  let ignored = false;
329
333
  let unignored = false;
330
334
  let matchedRule;
@@ -333,7 +337,7 @@ var require_ignore = __commonJS({
333
337
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
334
338
  return;
335
339
  }
336
- const matched = rule[mode].test(path18);
340
+ const matched = rule[mode].test(path19);
337
341
  if (!matched) {
338
342
  return;
339
343
  }
@@ -354,17 +358,17 @@ var require_ignore = __commonJS({
354
358
  var throwError = (message, Ctor) => {
355
359
  throw new Ctor(message);
356
360
  };
357
- var checkPath = (path18, originalPath, doThrow) => {
358
- if (!isString(path18)) {
361
+ var checkPath = (path19, originalPath, doThrow) => {
362
+ if (!isString(path19)) {
359
363
  return doThrow(
360
364
  `path must be a string, but got \`${originalPath}\``,
361
365
  TypeError
362
366
  );
363
367
  }
364
- if (!path18) {
368
+ if (!path19) {
365
369
  return doThrow(`path must not be empty`, TypeError);
366
370
  }
367
- if (checkPath.isNotRelative(path18)) {
371
+ if (checkPath.isNotRelative(path19)) {
368
372
  const r = "`path.relative()`d";
369
373
  return doThrow(
370
374
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -373,7 +377,7 @@ var require_ignore = __commonJS({
373
377
  }
374
378
  return true;
375
379
  };
376
- var isNotRelative = (path18) => REGEX_TEST_INVALID_PATH.test(path18);
380
+ var isNotRelative = (path19) => REGEX_TEST_INVALID_PATH.test(path19);
377
381
  checkPath.isNotRelative = isNotRelative;
378
382
  checkPath.convert = (p) => p;
379
383
  var Ignore2 = class {
@@ -403,19 +407,19 @@ var require_ignore = __commonJS({
403
407
  }
404
408
  // @returns {TestResult}
405
409
  _test(originalPath, cache, checkUnignored, slices) {
406
- const path18 = originalPath && checkPath.convert(originalPath);
410
+ const path19 = originalPath && checkPath.convert(originalPath);
407
411
  checkPath(
408
- path18,
412
+ path19,
409
413
  originalPath,
410
414
  this._strictPathCheck ? throwError : RETURN_FALSE
411
415
  );
412
- return this._t(path18, cache, checkUnignored, slices);
416
+ return this._t(path19, cache, checkUnignored, slices);
413
417
  }
414
- checkIgnore(path18) {
415
- if (!REGEX_TEST_TRAILING_SLASH.test(path18)) {
416
- return this.test(path18);
418
+ checkIgnore(path19) {
419
+ if (!REGEX_TEST_TRAILING_SLASH.test(path19)) {
420
+ return this.test(path19);
417
421
  }
418
- const slices = path18.split(SLASH2).filter(Boolean);
422
+ const slices = path19.split(SLASH2).filter(Boolean);
419
423
  slices.pop();
420
424
  if (slices.length) {
421
425
  const parent = this._t(
@@ -428,18 +432,18 @@ var require_ignore = __commonJS({
428
432
  return parent;
429
433
  }
430
434
  }
431
- return this._rules.test(path18, false, MODE_CHECK_IGNORE);
435
+ return this._rules.test(path19, false, MODE_CHECK_IGNORE);
432
436
  }
433
- _t(path18, cache, checkUnignored, slices) {
434
- if (path18 in cache) {
435
- return cache[path18];
437
+ _t(path19, cache, checkUnignored, slices) {
438
+ if (path19 in cache) {
439
+ return cache[path19];
436
440
  }
437
441
  if (!slices) {
438
- slices = path18.split(SLASH2).filter(Boolean);
442
+ slices = path19.split(SLASH2).filter(Boolean);
439
443
  }
440
444
  slices.pop();
441
445
  if (!slices.length) {
442
- return cache[path18] = this._rules.test(path18, checkUnignored, MODE_IGNORE);
446
+ return cache[path19] = this._rules.test(path19, checkUnignored, MODE_IGNORE);
443
447
  }
444
448
  const parent = this._t(
445
449
  slices.join(SLASH2) + SLASH2,
@@ -447,29 +451,29 @@ var require_ignore = __commonJS({
447
451
  checkUnignored,
448
452
  slices
449
453
  );
450
- return cache[path18] = parent.ignored ? parent : this._rules.test(path18, checkUnignored, MODE_IGNORE);
454
+ return cache[path19] = parent.ignored ? parent : this._rules.test(path19, checkUnignored, MODE_IGNORE);
451
455
  }
452
- ignores(path18) {
453
- return this._test(path18, this._ignoreCache, false).ignored;
456
+ ignores(path19) {
457
+ return this._test(path19, this._ignoreCache, false).ignored;
454
458
  }
455
459
  createFilter() {
456
- return (path18) => !this.ignores(path18);
460
+ return (path19) => !this.ignores(path19);
457
461
  }
458
462
  filter(paths) {
459
463
  return makeArray(paths).filter(this.createFilter());
460
464
  }
461
465
  // @returns {TestResult}
462
- test(path18) {
463
- return this._test(path18, this._testCache, true);
466
+ test(path19) {
467
+ return this._test(path19, this._testCache, true);
464
468
  }
465
469
  };
466
470
  var factory = (options) => new Ignore2(options);
467
- var isPathValid = (path18) => checkPath(path18 && checkPath.convert(path18), path18, RETURN_FALSE);
471
+ var isPathValid = (path19) => checkPath(path19 && checkPath.convert(path19), path19, RETURN_FALSE);
468
472
  var setupWindows = () => {
469
473
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
470
474
  checkPath.convert = makePosix;
471
475
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
472
- checkPath.isNotRelative = (path18) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path18) || isNotRelative(path18);
476
+ checkPath.isNotRelative = (path19) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path19) || isNotRelative(path19);
473
477
  };
474
478
  if (
475
479
  // Detect `process` so that it can run in browsers.
@@ -648,7 +652,7 @@ var require_eventemitter3 = __commonJS({
648
652
 
649
653
  // src/index.ts
650
654
  import * as os6 from "os";
651
- import * as path17 from "path";
655
+ import * as path18 from "path";
652
656
  import { fileURLToPath as fileURLToPath2 } from "url";
653
657
 
654
658
  // src/config/constants.ts
@@ -795,6 +799,7 @@ function getDefaultSearchConfig() {
795
799
  rerankTopN: 20,
796
800
  contextLines: 0,
797
801
  routingHints: true,
802
+ routingGraphHandoffHints: false,
798
803
  routingHintRole: "system"
799
804
  };
800
805
  }
@@ -917,6 +922,7 @@ function parseConfig(raw) {
917
922
  rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
918
923
  contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines,
919
924
  routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints,
925
+ routingGraphHandoffHints: typeof rawSearch.routingGraphHandoffHints === "boolean" ? rawSearch.routingGraphHandoffHints : defaultSearch.routingGraphHandoffHints,
920
926
  routingHintRole: rawSearch.routingHintRole === "developer" || rawSearch.routingHintRole === "system" ? rawSearch.routingHintRole : defaultSearch.routingHintRole
921
927
  };
922
928
  const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
@@ -1543,7 +1549,7 @@ var ReaddirpStream = class extends Readable {
1543
1549
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
1544
1550
  const statMethod = opts.lstat ? lstat : stat;
1545
1551
  if (wantBigintFsStats) {
1546
- this._stat = (path18) => statMethod(path18, { bigint: true });
1552
+ this._stat = (path19) => statMethod(path19, { bigint: true });
1547
1553
  } else {
1548
1554
  this._stat = statMethod;
1549
1555
  }
@@ -1568,8 +1574,8 @@ var ReaddirpStream = class extends Readable {
1568
1574
  const par = this.parent;
1569
1575
  const fil = par && par.files;
1570
1576
  if (fil && fil.length > 0) {
1571
- const { path: path18, depth } = par;
1572
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path18));
1577
+ const { path: path19, depth } = par;
1578
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path19));
1573
1579
  const awaited = await Promise.all(slice);
1574
1580
  for (const entry of awaited) {
1575
1581
  if (!entry)
@@ -1609,20 +1615,20 @@ var ReaddirpStream = class extends Readable {
1609
1615
  this.reading = false;
1610
1616
  }
1611
1617
  }
1612
- async _exploreDir(path18, depth) {
1618
+ async _exploreDir(path19, depth) {
1613
1619
  let files;
1614
1620
  try {
1615
- files = await readdir(path18, this._rdOptions);
1621
+ files = await readdir(path19, this._rdOptions);
1616
1622
  } catch (error) {
1617
1623
  this._onError(error);
1618
1624
  }
1619
- return { files, depth, path: path18 };
1625
+ return { files, depth, path: path19 };
1620
1626
  }
1621
- async _formatEntry(dirent, path18) {
1627
+ async _formatEntry(dirent, path19) {
1622
1628
  let entry;
1623
1629
  const basename5 = this._isDirent ? dirent.name : dirent;
1624
1630
  try {
1625
- const fullPath = presolve(pjoin(path18, basename5));
1631
+ const fullPath = presolve(pjoin(path19, basename5));
1626
1632
  entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
1627
1633
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
1628
1634
  } catch (err) {
@@ -2022,16 +2028,16 @@ var delFromSet = (main, prop, item) => {
2022
2028
  };
2023
2029
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
2024
2030
  var FsWatchInstances = /* @__PURE__ */ new Map();
2025
- function createFsWatchInstance(path18, options, listener, errHandler, emitRaw) {
2031
+ function createFsWatchInstance(path19, options, listener, errHandler, emitRaw) {
2026
2032
  const handleEvent = (rawEvent, evPath) => {
2027
- listener(path18);
2028
- emitRaw(rawEvent, evPath, { watchedPath: path18 });
2029
- if (evPath && path18 !== evPath) {
2030
- fsWatchBroadcast(sp.resolve(path18, evPath), KEY_LISTENERS, sp.join(path18, evPath));
2033
+ listener(path19);
2034
+ emitRaw(rawEvent, evPath, { watchedPath: path19 });
2035
+ if (evPath && path19 !== evPath) {
2036
+ fsWatchBroadcast(sp.resolve(path19, evPath), KEY_LISTENERS, sp.join(path19, evPath));
2031
2037
  }
2032
2038
  };
2033
2039
  try {
2034
- return fs_watch(path18, {
2040
+ return fs_watch(path19, {
2035
2041
  persistent: options.persistent
2036
2042
  }, handleEvent);
2037
2043
  } catch (error) {
@@ -2047,12 +2053,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
2047
2053
  listener(val1, val2, val3);
2048
2054
  });
2049
2055
  };
2050
- var setFsWatchListener = (path18, fullPath, options, handlers) => {
2056
+ var setFsWatchListener = (path19, fullPath, options, handlers) => {
2051
2057
  const { listener, errHandler, rawEmitter } = handlers;
2052
2058
  let cont = FsWatchInstances.get(fullPath);
2053
2059
  let watcher;
2054
2060
  if (!options.persistent) {
2055
- watcher = createFsWatchInstance(path18, options, listener, errHandler, rawEmitter);
2061
+ watcher = createFsWatchInstance(path19, options, listener, errHandler, rawEmitter);
2056
2062
  if (!watcher)
2057
2063
  return;
2058
2064
  return watcher.close.bind(watcher);
@@ -2063,7 +2069,7 @@ var setFsWatchListener = (path18, fullPath, options, handlers) => {
2063
2069
  addAndConvert(cont, KEY_RAW, rawEmitter);
2064
2070
  } else {
2065
2071
  watcher = createFsWatchInstance(
2066
- path18,
2072
+ path19,
2067
2073
  options,
2068
2074
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
2069
2075
  errHandler,
@@ -2078,7 +2084,7 @@ var setFsWatchListener = (path18, fullPath, options, handlers) => {
2078
2084
  cont.watcherUnusable = true;
2079
2085
  if (isWindows && error.code === "EPERM") {
2080
2086
  try {
2081
- const fd = await open(path18, "r");
2087
+ const fd = await open(path19, "r");
2082
2088
  await fd.close();
2083
2089
  broadcastErr(error);
2084
2090
  } catch (err) {
@@ -2109,7 +2115,7 @@ var setFsWatchListener = (path18, fullPath, options, handlers) => {
2109
2115
  };
2110
2116
  };
2111
2117
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
2112
- var setFsWatchFileListener = (path18, fullPath, options, handlers) => {
2118
+ var setFsWatchFileListener = (path19, fullPath, options, handlers) => {
2113
2119
  const { listener, rawEmitter } = handlers;
2114
2120
  let cont = FsWatchFileInstances.get(fullPath);
2115
2121
  const copts = cont && cont.options;
@@ -2131,7 +2137,7 @@ var setFsWatchFileListener = (path18, fullPath, options, handlers) => {
2131
2137
  });
2132
2138
  const currmtime = curr.mtimeMs;
2133
2139
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
2134
- foreach(cont.listeners, (listener2) => listener2(path18, curr));
2140
+ foreach(cont.listeners, (listener2) => listener2(path19, curr));
2135
2141
  }
2136
2142
  })
2137
2143
  };
@@ -2161,13 +2167,13 @@ var NodeFsHandler = class {
2161
2167
  * @param listener on fs change
2162
2168
  * @returns closer for the watcher instance
2163
2169
  */
2164
- _watchWithNodeFs(path18, listener) {
2170
+ _watchWithNodeFs(path19, listener) {
2165
2171
  const opts = this.fsw.options;
2166
- const directory = sp.dirname(path18);
2167
- const basename5 = sp.basename(path18);
2172
+ const directory = sp.dirname(path19);
2173
+ const basename5 = sp.basename(path19);
2168
2174
  const parent = this.fsw._getWatchedDir(directory);
2169
2175
  parent.add(basename5);
2170
- const absolutePath = sp.resolve(path18);
2176
+ const absolutePath = sp.resolve(path19);
2171
2177
  const options = {
2172
2178
  persistent: opts.persistent
2173
2179
  };
@@ -2177,12 +2183,12 @@ var NodeFsHandler = class {
2177
2183
  if (opts.usePolling) {
2178
2184
  const enableBin = opts.interval !== opts.binaryInterval;
2179
2185
  options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
2180
- closer = setFsWatchFileListener(path18, absolutePath, options, {
2186
+ closer = setFsWatchFileListener(path19, absolutePath, options, {
2181
2187
  listener,
2182
2188
  rawEmitter: this.fsw._emitRaw
2183
2189
  });
2184
2190
  } else {
2185
- closer = setFsWatchListener(path18, absolutePath, options, {
2191
+ closer = setFsWatchListener(path19, absolutePath, options, {
2186
2192
  listener,
2187
2193
  errHandler: this._boundHandleError,
2188
2194
  rawEmitter: this.fsw._emitRaw
@@ -2204,7 +2210,7 @@ var NodeFsHandler = class {
2204
2210
  let prevStats = stats;
2205
2211
  if (parent.has(basename5))
2206
2212
  return;
2207
- const listener = async (path18, newStats) => {
2213
+ const listener = async (path19, newStats) => {
2208
2214
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
2209
2215
  return;
2210
2216
  if (!newStats || newStats.mtimeMs === 0) {
@@ -2218,11 +2224,11 @@ var NodeFsHandler = class {
2218
2224
  this.fsw._emit(EV.CHANGE, file, newStats2);
2219
2225
  }
2220
2226
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
2221
- this.fsw._closeFile(path18);
2227
+ this.fsw._closeFile(path19);
2222
2228
  prevStats = newStats2;
2223
2229
  const closer2 = this._watchWithNodeFs(file, listener);
2224
2230
  if (closer2)
2225
- this.fsw._addPathCloser(path18, closer2);
2231
+ this.fsw._addPathCloser(path19, closer2);
2226
2232
  } else {
2227
2233
  prevStats = newStats2;
2228
2234
  }
@@ -2254,7 +2260,7 @@ var NodeFsHandler = class {
2254
2260
  * @param item basename of this item
2255
2261
  * @returns true if no more processing is needed for this entry.
2256
2262
  */
2257
- async _handleSymlink(entry, directory, path18, item) {
2263
+ async _handleSymlink(entry, directory, path19, item) {
2258
2264
  if (this.fsw.closed) {
2259
2265
  return;
2260
2266
  }
@@ -2264,7 +2270,7 @@ var NodeFsHandler = class {
2264
2270
  this.fsw._incrReadyCount();
2265
2271
  let linkPath;
2266
2272
  try {
2267
- linkPath = await fsrealpath(path18);
2273
+ linkPath = await fsrealpath(path19);
2268
2274
  } catch (e) {
2269
2275
  this.fsw._emitReady();
2270
2276
  return true;
@@ -2274,12 +2280,12 @@ var NodeFsHandler = class {
2274
2280
  if (dir.has(item)) {
2275
2281
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
2276
2282
  this.fsw._symlinkPaths.set(full, linkPath);
2277
- this.fsw._emit(EV.CHANGE, path18, entry.stats);
2283
+ this.fsw._emit(EV.CHANGE, path19, entry.stats);
2278
2284
  }
2279
2285
  } else {
2280
2286
  dir.add(item);
2281
2287
  this.fsw._symlinkPaths.set(full, linkPath);
2282
- this.fsw._emit(EV.ADD, path18, entry.stats);
2288
+ this.fsw._emit(EV.ADD, path19, entry.stats);
2283
2289
  }
2284
2290
  this.fsw._emitReady();
2285
2291
  return true;
@@ -2309,9 +2315,9 @@ var NodeFsHandler = class {
2309
2315
  return;
2310
2316
  }
2311
2317
  const item = entry.path;
2312
- let path18 = sp.join(directory, item);
2318
+ let path19 = sp.join(directory, item);
2313
2319
  current.add(item);
2314
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path18, item)) {
2320
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path19, item)) {
2315
2321
  return;
2316
2322
  }
2317
2323
  if (this.fsw.closed) {
@@ -2320,11 +2326,11 @@ var NodeFsHandler = class {
2320
2326
  }
2321
2327
  if (item === target || !target && !previous.has(item)) {
2322
2328
  this.fsw._incrReadyCount();
2323
- path18 = sp.join(dir, sp.relative(dir, path18));
2324
- this._addToNodeFs(path18, initialAdd, wh, depth + 1);
2329
+ path19 = sp.join(dir, sp.relative(dir, path19));
2330
+ this._addToNodeFs(path19, initialAdd, wh, depth + 1);
2325
2331
  }
2326
2332
  }).on(EV.ERROR, this._boundHandleError);
2327
- return new Promise((resolve12, reject) => {
2333
+ return new Promise((resolve13, reject) => {
2328
2334
  if (!stream)
2329
2335
  return reject();
2330
2336
  stream.once(STR_END, () => {
@@ -2333,7 +2339,7 @@ var NodeFsHandler = class {
2333
2339
  return;
2334
2340
  }
2335
2341
  const wasThrottled = throttler ? throttler.clear() : false;
2336
- resolve12(void 0);
2342
+ resolve13(void 0);
2337
2343
  previous.getChildren().filter((item) => {
2338
2344
  return item !== directory && !current.has(item);
2339
2345
  }).forEach((item) => {
@@ -2390,13 +2396,13 @@ var NodeFsHandler = class {
2390
2396
  * @param depth Child path actually targeted for watch
2391
2397
  * @param target Child path actually targeted for watch
2392
2398
  */
2393
- async _addToNodeFs(path18, initialAdd, priorWh, depth, target) {
2399
+ async _addToNodeFs(path19, initialAdd, priorWh, depth, target) {
2394
2400
  const ready = this.fsw._emitReady;
2395
- if (this.fsw._isIgnored(path18) || this.fsw.closed) {
2401
+ if (this.fsw._isIgnored(path19) || this.fsw.closed) {
2396
2402
  ready();
2397
2403
  return false;
2398
2404
  }
2399
- const wh = this.fsw._getWatchHelpers(path18);
2405
+ const wh = this.fsw._getWatchHelpers(path19);
2400
2406
  if (priorWh) {
2401
2407
  wh.filterPath = (entry) => priorWh.filterPath(entry);
2402
2408
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -2412,8 +2418,8 @@ var NodeFsHandler = class {
2412
2418
  const follow = this.fsw.options.followSymlinks;
2413
2419
  let closer;
2414
2420
  if (stats.isDirectory()) {
2415
- const absPath = sp.resolve(path18);
2416
- const targetPath = follow ? await fsrealpath(path18) : path18;
2421
+ const absPath = sp.resolve(path19);
2422
+ const targetPath = follow ? await fsrealpath(path19) : path19;
2417
2423
  if (this.fsw.closed)
2418
2424
  return;
2419
2425
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -2423,29 +2429,29 @@ var NodeFsHandler = class {
2423
2429
  this.fsw._symlinkPaths.set(absPath, targetPath);
2424
2430
  }
2425
2431
  } else if (stats.isSymbolicLink()) {
2426
- const targetPath = follow ? await fsrealpath(path18) : path18;
2432
+ const targetPath = follow ? await fsrealpath(path19) : path19;
2427
2433
  if (this.fsw.closed)
2428
2434
  return;
2429
2435
  const parent = sp.dirname(wh.watchPath);
2430
2436
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
2431
2437
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
2432
- closer = await this._handleDir(parent, stats, initialAdd, depth, path18, wh, targetPath);
2438
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path19, wh, targetPath);
2433
2439
  if (this.fsw.closed)
2434
2440
  return;
2435
2441
  if (targetPath !== void 0) {
2436
- this.fsw._symlinkPaths.set(sp.resolve(path18), targetPath);
2442
+ this.fsw._symlinkPaths.set(sp.resolve(path19), targetPath);
2437
2443
  }
2438
2444
  } else {
2439
2445
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
2440
2446
  }
2441
2447
  ready();
2442
2448
  if (closer)
2443
- this.fsw._addPathCloser(path18, closer);
2449
+ this.fsw._addPathCloser(path19, closer);
2444
2450
  return false;
2445
2451
  } catch (error) {
2446
2452
  if (this.fsw._handleError(error)) {
2447
2453
  ready();
2448
- return path18;
2454
+ return path19;
2449
2455
  }
2450
2456
  }
2451
2457
  }
@@ -2477,35 +2483,35 @@ function createPattern(matcher) {
2477
2483
  if (matcher.path === string)
2478
2484
  return true;
2479
2485
  if (matcher.recursive) {
2480
- const relative8 = sp2.relative(matcher.path, string);
2481
- if (!relative8) {
2486
+ const relative9 = sp2.relative(matcher.path, string);
2487
+ if (!relative9) {
2482
2488
  return false;
2483
2489
  }
2484
- return !relative8.startsWith("..") && !sp2.isAbsolute(relative8);
2490
+ return !relative9.startsWith("..") && !sp2.isAbsolute(relative9);
2485
2491
  }
2486
2492
  return false;
2487
2493
  };
2488
2494
  }
2489
2495
  return () => false;
2490
2496
  }
2491
- function normalizePath(path18) {
2492
- if (typeof path18 !== "string")
2497
+ function normalizePath(path19) {
2498
+ if (typeof path19 !== "string")
2493
2499
  throw new Error("string expected");
2494
- path18 = sp2.normalize(path18);
2495
- path18 = path18.replace(/\\/g, "/");
2500
+ path19 = sp2.normalize(path19);
2501
+ path19 = path19.replace(/\\/g, "/");
2496
2502
  let prepend = false;
2497
- if (path18.startsWith("//"))
2503
+ if (path19.startsWith("//"))
2498
2504
  prepend = true;
2499
- path18 = path18.replace(DOUBLE_SLASH_RE, "/");
2505
+ path19 = path19.replace(DOUBLE_SLASH_RE, "/");
2500
2506
  if (prepend)
2501
- path18 = "/" + path18;
2502
- return path18;
2507
+ path19 = "/" + path19;
2508
+ return path19;
2503
2509
  }
2504
2510
  function matchPatterns(patterns, testString, stats) {
2505
- const path18 = normalizePath(testString);
2511
+ const path19 = normalizePath(testString);
2506
2512
  for (let index = 0; index < patterns.length; index++) {
2507
2513
  const pattern = patterns[index];
2508
- if (pattern(path18, stats)) {
2514
+ if (pattern(path19, stats)) {
2509
2515
  return true;
2510
2516
  }
2511
2517
  }
@@ -2543,19 +2549,19 @@ var toUnix = (string) => {
2543
2549
  }
2544
2550
  return str;
2545
2551
  };
2546
- var normalizePathToUnix = (path18) => toUnix(sp2.normalize(toUnix(path18)));
2547
- var normalizeIgnored = (cwd = "") => (path18) => {
2548
- if (typeof path18 === "string") {
2549
- return normalizePathToUnix(sp2.isAbsolute(path18) ? path18 : sp2.join(cwd, path18));
2552
+ var normalizePathToUnix = (path19) => toUnix(sp2.normalize(toUnix(path19)));
2553
+ var normalizeIgnored = (cwd = "") => (path19) => {
2554
+ if (typeof path19 === "string") {
2555
+ return normalizePathToUnix(sp2.isAbsolute(path19) ? path19 : sp2.join(cwd, path19));
2550
2556
  } else {
2551
- return path18;
2557
+ return path19;
2552
2558
  }
2553
2559
  };
2554
- var getAbsolutePath = (path18, cwd) => {
2555
- if (sp2.isAbsolute(path18)) {
2556
- return path18;
2560
+ var getAbsolutePath = (path19, cwd) => {
2561
+ if (sp2.isAbsolute(path19)) {
2562
+ return path19;
2557
2563
  }
2558
- return sp2.join(cwd, path18);
2564
+ return sp2.join(cwd, path19);
2559
2565
  };
2560
2566
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
2561
2567
  var DirEntry = class {
@@ -2620,10 +2626,10 @@ var WatchHelper = class {
2620
2626
  dirParts;
2621
2627
  followSymlinks;
2622
2628
  statMethod;
2623
- constructor(path18, follow, fsw) {
2629
+ constructor(path19, follow, fsw) {
2624
2630
  this.fsw = fsw;
2625
- const watchPath = path18;
2626
- this.path = path18 = path18.replace(REPLACER_RE, "");
2631
+ const watchPath = path19;
2632
+ this.path = path19 = path19.replace(REPLACER_RE, "");
2627
2633
  this.watchPath = watchPath;
2628
2634
  this.fullWatchPath = sp2.resolve(watchPath);
2629
2635
  this.dirParts = [];
@@ -2763,20 +2769,20 @@ var FSWatcher = class extends EventEmitter {
2763
2769
  this._closePromise = void 0;
2764
2770
  let paths = unifyPaths(paths_);
2765
2771
  if (cwd) {
2766
- paths = paths.map((path18) => {
2767
- const absPath = getAbsolutePath(path18, cwd);
2772
+ paths = paths.map((path19) => {
2773
+ const absPath = getAbsolutePath(path19, cwd);
2768
2774
  return absPath;
2769
2775
  });
2770
2776
  }
2771
- paths.forEach((path18) => {
2772
- this._removeIgnoredPath(path18);
2777
+ paths.forEach((path19) => {
2778
+ this._removeIgnoredPath(path19);
2773
2779
  });
2774
2780
  this._userIgnored = void 0;
2775
2781
  if (!this._readyCount)
2776
2782
  this._readyCount = 0;
2777
2783
  this._readyCount += paths.length;
2778
- Promise.all(paths.map(async (path18) => {
2779
- const res = await this._nodeFsHandler._addToNodeFs(path18, !_internal, void 0, 0, _origAdd);
2784
+ Promise.all(paths.map(async (path19) => {
2785
+ const res = await this._nodeFsHandler._addToNodeFs(path19, !_internal, void 0, 0, _origAdd);
2780
2786
  if (res)
2781
2787
  this._emitReady();
2782
2788
  return res;
@@ -2798,17 +2804,17 @@ var FSWatcher = class extends EventEmitter {
2798
2804
  return this;
2799
2805
  const paths = unifyPaths(paths_);
2800
2806
  const { cwd } = this.options;
2801
- paths.forEach((path18) => {
2802
- if (!sp2.isAbsolute(path18) && !this._closers.has(path18)) {
2807
+ paths.forEach((path19) => {
2808
+ if (!sp2.isAbsolute(path19) && !this._closers.has(path19)) {
2803
2809
  if (cwd)
2804
- path18 = sp2.join(cwd, path18);
2805
- path18 = sp2.resolve(path18);
2810
+ path19 = sp2.join(cwd, path19);
2811
+ path19 = sp2.resolve(path19);
2806
2812
  }
2807
- this._closePath(path18);
2808
- this._addIgnoredPath(path18);
2809
- if (this._watched.has(path18)) {
2813
+ this._closePath(path19);
2814
+ this._addIgnoredPath(path19);
2815
+ if (this._watched.has(path19)) {
2810
2816
  this._addIgnoredPath({
2811
- path: path18,
2817
+ path: path19,
2812
2818
  recursive: true
2813
2819
  });
2814
2820
  }
@@ -2872,38 +2878,38 @@ var FSWatcher = class extends EventEmitter {
2872
2878
  * @param stats arguments to be passed with event
2873
2879
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
2874
2880
  */
2875
- async _emit(event, path18, stats) {
2881
+ async _emit(event, path19, stats) {
2876
2882
  if (this.closed)
2877
2883
  return;
2878
2884
  const opts = this.options;
2879
2885
  if (isWindows)
2880
- path18 = sp2.normalize(path18);
2886
+ path19 = sp2.normalize(path19);
2881
2887
  if (opts.cwd)
2882
- path18 = sp2.relative(opts.cwd, path18);
2883
- const args = [path18];
2888
+ path19 = sp2.relative(opts.cwd, path19);
2889
+ const args = [path19];
2884
2890
  if (stats != null)
2885
2891
  args.push(stats);
2886
2892
  const awf = opts.awaitWriteFinish;
2887
2893
  let pw;
2888
- if (awf && (pw = this._pendingWrites.get(path18))) {
2894
+ if (awf && (pw = this._pendingWrites.get(path19))) {
2889
2895
  pw.lastChange = /* @__PURE__ */ new Date();
2890
2896
  return this;
2891
2897
  }
2892
2898
  if (opts.atomic) {
2893
2899
  if (event === EVENTS.UNLINK) {
2894
- this._pendingUnlinks.set(path18, [event, ...args]);
2900
+ this._pendingUnlinks.set(path19, [event, ...args]);
2895
2901
  setTimeout(() => {
2896
- this._pendingUnlinks.forEach((entry, path19) => {
2902
+ this._pendingUnlinks.forEach((entry, path20) => {
2897
2903
  this.emit(...entry);
2898
2904
  this.emit(EVENTS.ALL, ...entry);
2899
- this._pendingUnlinks.delete(path19);
2905
+ this._pendingUnlinks.delete(path20);
2900
2906
  });
2901
2907
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
2902
2908
  return this;
2903
2909
  }
2904
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path18)) {
2910
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path19)) {
2905
2911
  event = EVENTS.CHANGE;
2906
- this._pendingUnlinks.delete(path18);
2912
+ this._pendingUnlinks.delete(path19);
2907
2913
  }
2908
2914
  }
2909
2915
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -2921,16 +2927,16 @@ var FSWatcher = class extends EventEmitter {
2921
2927
  this.emitWithAll(event, args);
2922
2928
  }
2923
2929
  };
2924
- this._awaitWriteFinish(path18, awf.stabilityThreshold, event, awfEmit);
2930
+ this._awaitWriteFinish(path19, awf.stabilityThreshold, event, awfEmit);
2925
2931
  return this;
2926
2932
  }
2927
2933
  if (event === EVENTS.CHANGE) {
2928
- const isThrottled = !this._throttle(EVENTS.CHANGE, path18, 50);
2934
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path19, 50);
2929
2935
  if (isThrottled)
2930
2936
  return this;
2931
2937
  }
2932
2938
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
2933
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path18) : path18;
2939
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path19) : path19;
2934
2940
  let stats2;
2935
2941
  try {
2936
2942
  stats2 = await stat3(fullPath);
@@ -2961,23 +2967,23 @@ var FSWatcher = class extends EventEmitter {
2961
2967
  * @param timeout duration of time to suppress duplicate actions
2962
2968
  * @returns tracking object or false if action should be suppressed
2963
2969
  */
2964
- _throttle(actionType, path18, timeout) {
2970
+ _throttle(actionType, path19, timeout) {
2965
2971
  if (!this._throttled.has(actionType)) {
2966
2972
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
2967
2973
  }
2968
2974
  const action = this._throttled.get(actionType);
2969
2975
  if (!action)
2970
2976
  throw new Error("invalid throttle");
2971
- const actionPath = action.get(path18);
2977
+ const actionPath = action.get(path19);
2972
2978
  if (actionPath) {
2973
2979
  actionPath.count++;
2974
2980
  return false;
2975
2981
  }
2976
2982
  let timeoutObject;
2977
2983
  const clear = () => {
2978
- const item = action.get(path18);
2984
+ const item = action.get(path19);
2979
2985
  const count = item ? item.count : 0;
2980
- action.delete(path18);
2986
+ action.delete(path19);
2981
2987
  clearTimeout(timeoutObject);
2982
2988
  if (item)
2983
2989
  clearTimeout(item.timeoutObject);
@@ -2985,7 +2991,7 @@ var FSWatcher = class extends EventEmitter {
2985
2991
  };
2986
2992
  timeoutObject = setTimeout(clear, timeout);
2987
2993
  const thr = { timeoutObject, clear, count: 0 };
2988
- action.set(path18, thr);
2994
+ action.set(path19, thr);
2989
2995
  return thr;
2990
2996
  }
2991
2997
  _incrReadyCount() {
@@ -2999,44 +3005,44 @@ var FSWatcher = class extends EventEmitter {
2999
3005
  * @param event
3000
3006
  * @param awfEmit Callback to be called when ready for event to be emitted.
3001
3007
  */
3002
- _awaitWriteFinish(path18, threshold, event, awfEmit) {
3008
+ _awaitWriteFinish(path19, threshold, event, awfEmit) {
3003
3009
  const awf = this.options.awaitWriteFinish;
3004
3010
  if (typeof awf !== "object")
3005
3011
  return;
3006
3012
  const pollInterval = awf.pollInterval;
3007
3013
  let timeoutHandler;
3008
- let fullPath = path18;
3009
- if (this.options.cwd && !sp2.isAbsolute(path18)) {
3010
- fullPath = sp2.join(this.options.cwd, path18);
3014
+ let fullPath = path19;
3015
+ if (this.options.cwd && !sp2.isAbsolute(path19)) {
3016
+ fullPath = sp2.join(this.options.cwd, path19);
3011
3017
  }
3012
3018
  const now = /* @__PURE__ */ new Date();
3013
3019
  const writes = this._pendingWrites;
3014
3020
  function awaitWriteFinishFn(prevStat) {
3015
3021
  statcb(fullPath, (err, curStat) => {
3016
- if (err || !writes.has(path18)) {
3022
+ if (err || !writes.has(path19)) {
3017
3023
  if (err && err.code !== "ENOENT")
3018
3024
  awfEmit(err);
3019
3025
  return;
3020
3026
  }
3021
3027
  const now2 = Number(/* @__PURE__ */ new Date());
3022
3028
  if (prevStat && curStat.size !== prevStat.size) {
3023
- writes.get(path18).lastChange = now2;
3029
+ writes.get(path19).lastChange = now2;
3024
3030
  }
3025
- const pw = writes.get(path18);
3031
+ const pw = writes.get(path19);
3026
3032
  const df = now2 - pw.lastChange;
3027
3033
  if (df >= threshold) {
3028
- writes.delete(path18);
3034
+ writes.delete(path19);
3029
3035
  awfEmit(void 0, curStat);
3030
3036
  } else {
3031
3037
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
3032
3038
  }
3033
3039
  });
3034
3040
  }
3035
- if (!writes.has(path18)) {
3036
- writes.set(path18, {
3041
+ if (!writes.has(path19)) {
3042
+ writes.set(path19, {
3037
3043
  lastChange: now,
3038
3044
  cancelWait: () => {
3039
- writes.delete(path18);
3045
+ writes.delete(path19);
3040
3046
  clearTimeout(timeoutHandler);
3041
3047
  return event;
3042
3048
  }
@@ -3047,8 +3053,8 @@ var FSWatcher = class extends EventEmitter {
3047
3053
  /**
3048
3054
  * Determines whether user has asked to ignore this path.
3049
3055
  */
3050
- _isIgnored(path18, stats) {
3051
- if (this.options.atomic && DOT_RE.test(path18))
3056
+ _isIgnored(path19, stats) {
3057
+ if (this.options.atomic && DOT_RE.test(path19))
3052
3058
  return true;
3053
3059
  if (!this._userIgnored) {
3054
3060
  const { cwd } = this.options;
@@ -3058,17 +3064,17 @@ var FSWatcher = class extends EventEmitter {
3058
3064
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
3059
3065
  this._userIgnored = anymatch(list, void 0);
3060
3066
  }
3061
- return this._userIgnored(path18, stats);
3067
+ return this._userIgnored(path19, stats);
3062
3068
  }
3063
- _isntIgnored(path18, stat4) {
3064
- return !this._isIgnored(path18, stat4);
3069
+ _isntIgnored(path19, stat4) {
3070
+ return !this._isIgnored(path19, stat4);
3065
3071
  }
3066
3072
  /**
3067
3073
  * Provides a set of common helpers and properties relating to symlink handling.
3068
3074
  * @param path file or directory pattern being watched
3069
3075
  */
3070
- _getWatchHelpers(path18) {
3071
- return new WatchHelper(path18, this.options.followSymlinks, this);
3076
+ _getWatchHelpers(path19) {
3077
+ return new WatchHelper(path19, this.options.followSymlinks, this);
3072
3078
  }
3073
3079
  // Directory helpers
3074
3080
  // -----------------
@@ -3100,63 +3106,63 @@ var FSWatcher = class extends EventEmitter {
3100
3106
  * @param item base path of item/directory
3101
3107
  */
3102
3108
  _remove(directory, item, isDirectory) {
3103
- const path18 = sp2.join(directory, item);
3104
- const fullPath = sp2.resolve(path18);
3105
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path18) || this._watched.has(fullPath);
3106
- if (!this._throttle("remove", path18, 100))
3109
+ const path19 = sp2.join(directory, item);
3110
+ const fullPath = sp2.resolve(path19);
3111
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path19) || this._watched.has(fullPath);
3112
+ if (!this._throttle("remove", path19, 100))
3107
3113
  return;
3108
3114
  if (!isDirectory && this._watched.size === 1) {
3109
3115
  this.add(directory, item, true);
3110
3116
  }
3111
- const wp = this._getWatchedDir(path18);
3117
+ const wp = this._getWatchedDir(path19);
3112
3118
  const nestedDirectoryChildren = wp.getChildren();
3113
- nestedDirectoryChildren.forEach((nested) => this._remove(path18, nested));
3119
+ nestedDirectoryChildren.forEach((nested) => this._remove(path19, nested));
3114
3120
  const parent = this._getWatchedDir(directory);
3115
3121
  const wasTracked = parent.has(item);
3116
3122
  parent.remove(item);
3117
3123
  if (this._symlinkPaths.has(fullPath)) {
3118
3124
  this._symlinkPaths.delete(fullPath);
3119
3125
  }
3120
- let relPath = path18;
3126
+ let relPath = path19;
3121
3127
  if (this.options.cwd)
3122
- relPath = sp2.relative(this.options.cwd, path18);
3128
+ relPath = sp2.relative(this.options.cwd, path19);
3123
3129
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
3124
3130
  const event = this._pendingWrites.get(relPath).cancelWait();
3125
3131
  if (event === EVENTS.ADD)
3126
3132
  return;
3127
3133
  }
3128
- this._watched.delete(path18);
3134
+ this._watched.delete(path19);
3129
3135
  this._watched.delete(fullPath);
3130
3136
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
3131
- if (wasTracked && !this._isIgnored(path18))
3132
- this._emit(eventName, path18);
3133
- this._closePath(path18);
3137
+ if (wasTracked && !this._isIgnored(path19))
3138
+ this._emit(eventName, path19);
3139
+ this._closePath(path19);
3134
3140
  }
3135
3141
  /**
3136
3142
  * Closes all watchers for a path
3137
3143
  */
3138
- _closePath(path18) {
3139
- this._closeFile(path18);
3140
- const dir = sp2.dirname(path18);
3141
- this._getWatchedDir(dir).remove(sp2.basename(path18));
3144
+ _closePath(path19) {
3145
+ this._closeFile(path19);
3146
+ const dir = sp2.dirname(path19);
3147
+ this._getWatchedDir(dir).remove(sp2.basename(path19));
3142
3148
  }
3143
3149
  /**
3144
3150
  * Closes only file-specific watchers
3145
3151
  */
3146
- _closeFile(path18) {
3147
- const closers = this._closers.get(path18);
3152
+ _closeFile(path19) {
3153
+ const closers = this._closers.get(path19);
3148
3154
  if (!closers)
3149
3155
  return;
3150
3156
  closers.forEach((closer) => closer());
3151
- this._closers.delete(path18);
3157
+ this._closers.delete(path19);
3152
3158
  }
3153
- _addPathCloser(path18, closer) {
3159
+ _addPathCloser(path19, closer) {
3154
3160
  if (!closer)
3155
3161
  return;
3156
- let list = this._closers.get(path18);
3162
+ let list = this._closers.get(path19);
3157
3163
  if (!list) {
3158
3164
  list = [];
3159
- this._closers.set(path18, list);
3165
+ this._closers.set(path19, list);
3160
3166
  }
3161
3167
  list.push(closer);
3162
3168
  }
@@ -3486,7 +3492,7 @@ var FileWatcher = class {
3486
3492
  return;
3487
3493
  }
3488
3494
  const changes = Array.from(this.pendingChanges.entries()).map(
3489
- ([path18, type]) => ({ path: path18, type })
3495
+ ([path19, type]) => ({ path: path19, type })
3490
3496
  );
3491
3497
  this.pendingChanges.clear();
3492
3498
  try {
@@ -3619,12 +3625,14 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config) {
3619
3625
  }
3620
3626
 
3621
3627
  // src/tools/index.ts
3622
- import { tool } from "@opencode-ai/plugin";
3628
+ import { tool as tool2 } from "@opencode-ai/plugin";
3623
3629
 
3624
3630
  // src/indexer/index.ts
3625
3631
  import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync2, renameSync, unlinkSync, mkdirSync as mkdirSync2, promises as fsPromises2 } from "fs";
3626
- import * as path12 from "path";
3632
+ import * as path13 from "path";
3627
3633
  import { performance as performance2 } from "perf_hooks";
3634
+ import { execFile as execFile2 } from "child_process";
3635
+ import { promisify as promisify2 } from "util";
3628
3636
 
3629
3637
  // node_modules/eventemitter3/index.mjs
3630
3638
  var import_index = __toESM(require_eventemitter3(), 1);
@@ -3648,7 +3656,7 @@ function pTimeout(promise, options) {
3648
3656
  } = options;
3649
3657
  let timer;
3650
3658
  let abortHandler;
3651
- const wrappedPromise = new Promise((resolve12, reject) => {
3659
+ const wrappedPromise = new Promise((resolve13, reject) => {
3652
3660
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
3653
3661
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
3654
3662
  }
@@ -3662,7 +3670,7 @@ function pTimeout(promise, options) {
3662
3670
  };
3663
3671
  signal.addEventListener("abort", abortHandler, { once: true });
3664
3672
  }
3665
- promise.then(resolve12, reject);
3673
+ promise.then(resolve13, reject);
3666
3674
  if (milliseconds === Number.POSITIVE_INFINITY) {
3667
3675
  return;
3668
3676
  }
@@ -3670,7 +3678,7 @@ function pTimeout(promise, options) {
3670
3678
  timer = customTimers.setTimeout.call(void 0, () => {
3671
3679
  if (fallback) {
3672
3680
  try {
3673
- resolve12(fallback());
3681
+ resolve13(fallback());
3674
3682
  } catch (error) {
3675
3683
  reject(error);
3676
3684
  }
@@ -3680,7 +3688,7 @@ function pTimeout(promise, options) {
3680
3688
  promise.cancel();
3681
3689
  }
3682
3690
  if (message === false) {
3683
- resolve12();
3691
+ resolve13();
3684
3692
  } else if (message instanceof Error) {
3685
3693
  reject(message);
3686
3694
  } else {
@@ -4082,7 +4090,7 @@ var PQueue = class extends import_index.default {
4082
4090
  // Assign unique ID if not provided
4083
4091
  id: options.id ?? (this.#idAssigner++).toString()
4084
4092
  };
4085
- return new Promise((resolve12, reject) => {
4093
+ return new Promise((resolve13, reject) => {
4086
4094
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
4087
4095
  let cleanupQueueAbortHandler = () => void 0;
4088
4096
  const run = async () => {
@@ -4122,7 +4130,7 @@ var PQueue = class extends import_index.default {
4122
4130
  })]);
4123
4131
  }
4124
4132
  const result = await operation;
4125
- resolve12(result);
4133
+ resolve13(result);
4126
4134
  this.emit("completed", result);
4127
4135
  } catch (error) {
4128
4136
  reject(error);
@@ -4310,13 +4318,13 @@ var PQueue = class extends import_index.default {
4310
4318
  });
4311
4319
  }
4312
4320
  async #onEvent(event, filter) {
4313
- return new Promise((resolve12) => {
4321
+ return new Promise((resolve13) => {
4314
4322
  const listener = () => {
4315
4323
  if (filter && !filter()) {
4316
4324
  return;
4317
4325
  }
4318
4326
  this.off(event, listener);
4319
- resolve12();
4327
+ resolve13();
4320
4328
  };
4321
4329
  this.on(event, listener);
4322
4330
  });
@@ -4602,7 +4610,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4602
4610
  const finalDelay = Math.min(delayTime, remainingTime);
4603
4611
  options.signal?.throwIfAborted();
4604
4612
  if (finalDelay > 0) {
4605
- await new Promise((resolve12, reject) => {
4613
+ await new Promise((resolve13, reject) => {
4606
4614
  const onAbort = () => {
4607
4615
  clearTimeout(timeoutToken);
4608
4616
  options.signal?.removeEventListener("abort", onAbort);
@@ -4610,7 +4618,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4610
4618
  };
4611
4619
  const timeoutToken = setTimeout(() => {
4612
4620
  options.signal?.removeEventListener("abort", onAbort);
4613
- resolve12();
4621
+ resolve13();
4614
4622
  }, finalDelay);
4615
4623
  if (options.unref) {
4616
4624
  timeoutToken.unref?.();
@@ -4851,6 +4859,8 @@ var BaseEmbeddingProvider = class {
4851
4859
  this.credentials = credentials;
4852
4860
  this.modelInfo = modelInfo;
4853
4861
  }
4862
+ credentials;
4863
+ modelInfo;
4854
4864
  async embedQuery(query) {
4855
4865
  const result = await this.embedBatch([query]);
4856
4866
  return {
@@ -5835,6 +5845,15 @@ function createMockNativeBinding() {
5835
5845
  close() {
5836
5846
  throw error;
5837
5847
  }
5848
+ getTransitiveReachability() {
5849
+ throw error;
5850
+ }
5851
+ detectCommunities() {
5852
+ throw error;
5853
+ }
5854
+ computeCentrality() {
5855
+ throw error;
5856
+ }
5838
5857
  }
5839
5858
  };
5840
5859
  }
@@ -6385,6 +6404,14 @@ var Database = class {
6385
6404
  this.throwIfClosed();
6386
6405
  return this.inner.getSymbolsByNameCi(name);
6387
6406
  }
6407
+ getSymbolsForBranch(branch) {
6408
+ this.throwIfClosed();
6409
+ return this.inner.getSymbolsForBranch(branch);
6410
+ }
6411
+ getSymbolsForFiles(filePaths, branch) {
6412
+ this.throwIfClosed();
6413
+ return this.inner.getSymbolsForFiles(filePaths, branch);
6414
+ }
6388
6415
  deleteSymbolsByFile(filePath) {
6389
6416
  this.throwIfClosed();
6390
6417
  return this.inner.deleteSymbolsByFile(filePath);
@@ -6398,17 +6425,17 @@ var Database = class {
6398
6425
  if (edges.length === 0) return;
6399
6426
  this.inner.upsertCallEdgesBatch(edges);
6400
6427
  }
6401
- getCallers(targetName, branch) {
6428
+ getCallers(targetName, branch, callTypeFilter) {
6402
6429
  this.throwIfClosed();
6403
- return this.inner.getCallers(targetName, branch);
6430
+ return this.inner.getCallers(targetName, branch, callTypeFilter ?? null);
6404
6431
  }
6405
- getCallersWithContext(targetName, branch) {
6432
+ getCallersWithContext(targetName, branch, callTypeFilter) {
6406
6433
  this.throwIfClosed();
6407
- return this.inner.getCallersWithContext(targetName, branch);
6434
+ return this.inner.getCallersWithContext(targetName, branch, callTypeFilter ?? null);
6408
6435
  }
6409
- getCallees(symbolId, branch) {
6436
+ getCallees(symbolId, branch, callTypeFilter) {
6410
6437
  this.throwIfClosed();
6411
- return this.inner.getCallees(symbolId, branch);
6438
+ return this.inner.getCallees(symbolId, branch, callTypeFilter ?? null);
6412
6439
  }
6413
6440
  deleteCallEdgesByFile(filePath) {
6414
6441
  this.throwIfClosed();
@@ -6418,6 +6445,10 @@ var Database = class {
6418
6445
  this.throwIfClosed();
6419
6446
  this.inner.resolveCallEdge(edgeId, toSymbolId);
6420
6447
  }
6448
+ findShortestPath(fromName, toName, branch, maxDepth) {
6449
+ this.throwIfClosed();
6450
+ return this.inner.findShortestPath(fromName, toName, branch, maxDepth ?? null);
6451
+ }
6421
6452
  addSymbolsToBranch(branch, symbolIds) {
6422
6453
  this.throwIfClosed();
6423
6454
  this.inner.addSymbolsToBranch(branch, symbolIds);
@@ -6458,8 +6489,125 @@ var Database = class {
6458
6489
  this.throwIfClosed();
6459
6490
  return this.inner.gcOrphanCallEdges();
6460
6491
  }
6492
+ getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth) {
6493
+ this.throwIfClosed();
6494
+ return this.inner.getTransitiveReachability(
6495
+ rootSymbolIds,
6496
+ branch,
6497
+ direction,
6498
+ maxDepth ?? null
6499
+ );
6500
+ }
6501
+ detectCommunities(branch, symbolIds) {
6502
+ this.throwIfClosed();
6503
+ return this.inner.detectCommunities(branch, symbolIds ?? null);
6504
+ }
6505
+ computeCentrality(branch) {
6506
+ this.throwIfClosed();
6507
+ return this.inner.computeCentrality(branch);
6508
+ }
6461
6509
  };
6462
6510
 
6511
+ // src/tools/changed-files.ts
6512
+ import * as path12 from "path";
6513
+ import { execFile } from "child_process";
6514
+ import { promisify } from "util";
6515
+ var execFileAsync = promisify(execFile);
6516
+ function getErrorMessage(error) {
6517
+ if (error instanceof Error) return error.message;
6518
+ return String(error);
6519
+ }
6520
+ async function getChangedFiles(opts) {
6521
+ const { pr, branch, projectRoot, baseBranch = "main" } = opts;
6522
+ if (pr !== void 0) {
6523
+ return getChangedFilesForPr(pr, projectRoot, baseBranch);
6524
+ }
6525
+ return getChangedFilesForBranch(branch, projectRoot, baseBranch);
6526
+ }
6527
+ async function getChangedFilesForPr(pr, projectRoot, baseBranch) {
6528
+ let headRefName;
6529
+ let actualBaseBranch = baseBranch;
6530
+ try {
6531
+ const { stdout } = await execFileAsync(
6532
+ "gh",
6533
+ ["pr", "view", String(pr), "--json", "headRefName,baseRefName,files"],
6534
+ { cwd: projectRoot, timeout: 3e4 }
6535
+ );
6536
+ const data = JSON.parse(stdout);
6537
+ headRefName = data.headRefName;
6538
+ actualBaseBranch = data.baseRefName || baseBranch;
6539
+ if (data.files && data.files.length > 0) {
6540
+ return {
6541
+ files: normalizeFiles(
6542
+ data.files.map((f) => f.path),
6543
+ projectRoot
6544
+ ),
6545
+ baseBranch: actualBaseBranch,
6546
+ source: "gh",
6547
+ headRefName
6548
+ };
6549
+ }
6550
+ } catch (error) {
6551
+ throw new Error(
6552
+ `Failed to retrieve PR #${pr} via gh CLI: ${getErrorMessage(error)}`
6553
+ );
6554
+ }
6555
+ if (headRefName === void 0) {
6556
+ throw new Error(
6557
+ `PR #${pr} returned no usable branch or file information.`
6558
+ );
6559
+ }
6560
+ const result = await getChangedFilesForBranch(headRefName, projectRoot, actualBaseBranch);
6561
+ return { ...result, headRefName };
6562
+ }
6563
+ async function getChangedFilesForBranch(branch, projectRoot, baseBranch) {
6564
+ const targetBranch = branch || await getCurrentBranch2(projectRoot);
6565
+ const mergeBase = await getMergeBase(projectRoot, baseBranch, targetBranch);
6566
+ const { stdout } = await execFileAsync(
6567
+ "git",
6568
+ ["diff", "--name-only", `${mergeBase}...${targetBranch}`],
6569
+ { cwd: projectRoot, timeout: 3e4 }
6570
+ );
6571
+ return {
6572
+ files: normalizeFiles(stdout.split("\n"), projectRoot),
6573
+ baseBranch,
6574
+ source: "git",
6575
+ headRefName: targetBranch
6576
+ };
6577
+ }
6578
+ async function getCurrentBranch2(projectRoot) {
6579
+ const { stdout } = await execFileAsync(
6580
+ "git",
6581
+ ["branch", "--show-current"],
6582
+ { cwd: projectRoot, timeout: 3e4 }
6583
+ );
6584
+ return stdout.trim() || "HEAD";
6585
+ }
6586
+ async function getMergeBase(projectRoot, baseBranch, branch) {
6587
+ const { stdout } = await execFileAsync(
6588
+ "git",
6589
+ ["merge-base", baseBranch, branch],
6590
+ { cwd: projectRoot, timeout: 3e4 }
6591
+ );
6592
+ return stdout.trim();
6593
+ }
6594
+ function normalizeFiles(rawFiles, projectRoot) {
6595
+ const seen = /* @__PURE__ */ new Set();
6596
+ const result = [];
6597
+ for (const raw of rawFiles) {
6598
+ const trimmed = raw.trim();
6599
+ if (!trimmed) continue;
6600
+ const absolute = path12.resolve(projectRoot, trimmed);
6601
+ const relative9 = path12.relative(projectRoot, absolute);
6602
+ const cleaned = relative9.startsWith("./") ? relative9.slice(2) : relative9;
6603
+ if (!seen.has(cleaned)) {
6604
+ seen.add(cleaned);
6605
+ result.push(cleaned);
6606
+ }
6607
+ }
6608
+ return result;
6609
+ }
6610
+
6463
6611
  // src/indexer/index.ts
6464
6612
  var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
6465
6613
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
@@ -6467,6 +6615,7 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
6467
6615
  "function_declaration",
6468
6616
  "function",
6469
6617
  "arrow_function",
6618
+ "export_statement",
6470
6619
  "method_definition",
6471
6620
  "class_declaration",
6472
6621
  "interface_declaration",
@@ -6505,7 +6654,7 @@ function float32ArrayToBuffer(arr) {
6505
6654
  function bufferToFloat32Array(buf) {
6506
6655
  return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
6507
6656
  }
6508
- function getErrorMessage(error) {
6657
+ function getErrorMessage2(error) {
6509
6658
  if (error instanceof Error) {
6510
6659
  return error.message;
6511
6660
  }
@@ -6518,7 +6667,7 @@ function getErrorMessage(error) {
6518
6667
  return String(error);
6519
6668
  }
6520
6669
  function isRateLimitError(error) {
6521
- const message = getErrorMessage(error);
6670
+ const message = getErrorMessage2(error);
6522
6671
  return message.includes("429") || message.toLowerCase().includes("rate limit") || message.toLowerCase().includes("too many requests");
6523
6672
  }
6524
6673
  function getSafeEmbeddingChunkTokenLimit(provider) {
@@ -6536,7 +6685,7 @@ function getDynamicBatchOptions(provider) {
6536
6685
  return {};
6537
6686
  }
6538
6687
  function isSqliteCorruptionError(error) {
6539
- const message = getErrorMessage(error).toLowerCase();
6688
+ const message = getErrorMessage2(error).toLowerCase();
6540
6689
  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");
6541
6690
  }
6542
6691
  var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
@@ -6698,9 +6847,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
6698
6847
  return true;
6699
6848
  }
6700
6849
  function isPathWithinRoot(filePath, rootPath) {
6701
- const normalizedFilePath = path12.resolve(filePath);
6702
- const normalizedRoot = path12.resolve(rootPath);
6703
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path12.sep}`);
6850
+ const normalizedFilePath = path13.resolve(filePath);
6851
+ const normalizedRoot = path13.resolve(rootPath);
6852
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path13.sep}`);
6704
6853
  }
6705
6854
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
6706
6855
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -7753,9 +7902,9 @@ var Indexer = class {
7753
7902
  this.projectRoot = projectRoot;
7754
7903
  this.config = config;
7755
7904
  this.indexPath = this.getIndexPath();
7756
- this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
7757
- this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
7758
- this.indexingLockPath = path12.join(this.indexPath, "indexing.lock");
7905
+ this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
7906
+ this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
7907
+ this.indexingLockPath = path13.join(this.indexPath, "indexing.lock");
7759
7908
  this.logger = initializeLogger(config.debug);
7760
7909
  }
7761
7910
  getIndexPath() {
@@ -7787,14 +7936,14 @@ var Indexer = class {
7787
7936
  }
7788
7937
  atomicWriteSync(targetPath, data) {
7789
7938
  const tempPath = `${targetPath}.tmp`;
7790
- mkdirSync2(path12.dirname(targetPath), { recursive: true });
7939
+ mkdirSync2(path13.dirname(targetPath), { recursive: true });
7791
7940
  writeFileSync2(tempPath, data);
7792
7941
  renameSync(tempPath, targetPath);
7793
7942
  }
7794
7943
  getScopedRoots() {
7795
- const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
7944
+ const roots = /* @__PURE__ */ new Set([path13.resolve(this.projectRoot)]);
7796
7945
  for (const kbRoot of this.config.knowledgeBases) {
7797
- roots.add(path12.resolve(this.projectRoot, kbRoot));
7946
+ roots.add(path13.resolve(this.projectRoot, kbRoot));
7798
7947
  }
7799
7948
  return Array.from(roots);
7800
7949
  }
@@ -7803,22 +7952,29 @@ var Indexer = class {
7803
7952
  if (this.config.scope !== "global") {
7804
7953
  return branchName;
7805
7954
  }
7806
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7955
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7956
+ return `${projectHash}:${branchName}`;
7957
+ }
7958
+ getBranchCatalogKeyFor(branchName) {
7959
+ if (this.config.scope !== "global") {
7960
+ return branchName;
7961
+ }
7962
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7807
7963
  return `${projectHash}:${branchName}`;
7808
7964
  }
7809
7965
  getLegacyBranchCatalogKey() {
7810
7966
  return this.currentBranch || "default";
7811
7967
  }
7812
7968
  getLegacyMigrationMetadataKey() {
7813
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7969
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7814
7970
  return `index.globalBranchMigration.${projectHash}`;
7815
7971
  }
7816
7972
  getProjectEmbeddingStrategyMetadataKey() {
7817
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7973
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7818
7974
  return `index.embeddingStrategyVersion.${projectHash}`;
7819
7975
  }
7820
7976
  getProjectForceReembedMetadataKey() {
7821
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7977
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7822
7978
  return `index.forceReembed.${projectHash}`;
7823
7979
  }
7824
7980
  hasProjectForceReembedPending() {
@@ -7912,7 +8068,7 @@ var Indexer = class {
7912
8068
  if (!this.database) {
7913
8069
  return { chunkIds, symbolIds };
7914
8070
  }
7915
- const projectRootPath = path12.resolve(this.projectRoot);
8071
+ const projectRootPath = path13.resolve(this.projectRoot);
7916
8072
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
7917
8073
  ...Array.from(this.fileHashCache.keys()).filter(
7918
8074
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -7935,7 +8091,7 @@ var Indexer = class {
7935
8091
  if (this.config.scope !== "global") {
7936
8092
  return this.getBranchCatalogCleanupKeys();
7937
8093
  }
7938
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
8094
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7939
8095
  const keys = /* @__PURE__ */ new Set();
7940
8096
  const projectChunkIdSet = new Set(projectChunkIds);
7941
8097
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -8019,7 +8175,7 @@ var Indexer = class {
8019
8175
  if (!this.database || this.config.scope !== "global") {
8020
8176
  return false;
8021
8177
  }
8022
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
8178
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
8023
8179
  const roots = this.getScopedRoots();
8024
8180
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
8025
8181
  return this.database.getAllBranches().some(
@@ -8053,7 +8209,7 @@ var Indexer = class {
8053
8209
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
8054
8210
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
8055
8211
  ]);
8056
- const projectRootPath = path12.resolve(this.projectRoot);
8212
+ const projectRootPath = path13.resolve(this.projectRoot);
8057
8213
  const projectLocalFilePaths = new Set(
8058
8214
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
8059
8215
  );
@@ -8306,7 +8462,7 @@ var Indexer = class {
8306
8462
  this.logger.search("warn", "External reranker failed; using deterministic order", {
8307
8463
  provider: reranker.provider,
8308
8464
  model: reranker.model,
8309
- error: getErrorMessage(error)
8465
+ error: getErrorMessage2(error)
8310
8466
  });
8311
8467
  return candidates;
8312
8468
  }
@@ -8413,13 +8569,13 @@ var Indexer = class {
8413
8569
  }
8414
8570
  await fsPromises2.mkdir(this.indexPath, { recursive: true });
8415
8571
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
8416
- const storePath = path12.join(this.indexPath, "vectors");
8572
+ const storePath = path13.join(this.indexPath, "vectors");
8417
8573
  this.store = new VectorStore(storePath, dimensions);
8418
- const indexFilePath = path12.join(this.indexPath, "vectors.usearch");
8574
+ const indexFilePath = path13.join(this.indexPath, "vectors.usearch");
8419
8575
  if (existsSync7(indexFilePath)) {
8420
8576
  this.store.load();
8421
8577
  }
8422
- const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
8578
+ const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
8423
8579
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8424
8580
  try {
8425
8581
  this.invertedIndex.load();
@@ -8429,7 +8585,7 @@ var Indexer = class {
8429
8585
  }
8430
8586
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8431
8587
  }
8432
- const dbPath = path12.join(this.indexPath, "codebase.db");
8588
+ const dbPath = path13.join(this.indexPath, "codebase.db");
8433
8589
  let dbIsNew = !existsSync7(dbPath);
8434
8590
  try {
8435
8591
  this.database = new Database(dbPath);
@@ -8510,7 +8666,7 @@ var Indexer = class {
8510
8666
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
8511
8667
  return {
8512
8668
  resetCorruptedIndex: true,
8513
- warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
8669
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
8514
8670
  };
8515
8671
  }
8516
8672
  throw error;
@@ -8525,7 +8681,7 @@ var Indexer = class {
8525
8681
  return;
8526
8682
  }
8527
8683
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
8528
- const storeBasePath = path12.join(this.indexPath, "vectors");
8684
+ const storeBasePath = path13.join(this.indexPath, "vectors");
8529
8685
  const storeIndexPath = `${storeBasePath}.usearch`;
8530
8686
  const storeMetadataPath = `${storeBasePath}.meta.json`;
8531
8687
  const backupIndexPath = `${storeIndexPath}.bak`;
@@ -8610,9 +8766,9 @@ var Indexer = class {
8610
8766
  if (!isSqliteCorruptionError(error)) {
8611
8767
  return false;
8612
8768
  }
8613
- const dbPath = path12.join(this.indexPath, "codebase.db");
8769
+ const dbPath = path13.join(this.indexPath, "codebase.db");
8614
8770
  const warning = this.getCorruptedIndexWarning(dbPath);
8615
- const errorMessage = getErrorMessage(error);
8771
+ const errorMessage = getErrorMessage2(error);
8616
8772
  if (this.config.scope === "global") {
8617
8773
  this.logger.error("Detected corrupted shared global index database", {
8618
8774
  stage,
@@ -8633,15 +8789,15 @@ var Indexer = class {
8633
8789
  this.indexCompatibility = null;
8634
8790
  this.fileHashCache.clear();
8635
8791
  const resetPaths = [
8636
- path12.join(this.indexPath, "codebase.db"),
8637
- path12.join(this.indexPath, "codebase.db-shm"),
8638
- path12.join(this.indexPath, "codebase.db-wal"),
8639
- path12.join(this.indexPath, "vectors.usearch"),
8640
- path12.join(this.indexPath, "inverted-index.json"),
8641
- path12.join(this.indexPath, "file-hashes.json"),
8642
- path12.join(this.indexPath, "failed-batches.json"),
8643
- path12.join(this.indexPath, "indexing.lock"),
8644
- path12.join(this.indexPath, "vectors")
8792
+ path13.join(this.indexPath, "codebase.db"),
8793
+ path13.join(this.indexPath, "codebase.db-shm"),
8794
+ path13.join(this.indexPath, "codebase.db-wal"),
8795
+ path13.join(this.indexPath, "vectors.usearch"),
8796
+ path13.join(this.indexPath, "inverted-index.json"),
8797
+ path13.join(this.indexPath, "file-hashes.json"),
8798
+ path13.join(this.indexPath, "failed-batches.json"),
8799
+ path13.join(this.indexPath, "indexing.lock"),
8800
+ path13.join(this.indexPath, "vectors")
8645
8801
  ];
8646
8802
  await Promise.all(resetPaths.map(async (targetPath) => {
8647
8803
  try {
@@ -8906,7 +9062,7 @@ var Indexer = class {
8906
9062
  for (const parsed of parsedFiles) {
8907
9063
  currentFilePaths.add(parsed.path);
8908
9064
  if (parsed.chunks.length === 0) {
8909
- const relativePath = path12.relative(this.projectRoot, parsed.path);
9065
+ const relativePath = path13.relative(this.projectRoot, parsed.path);
8910
9066
  stats.parseFailures.push(relativePath);
8911
9067
  }
8912
9068
  let fileChunkCount = 0;
@@ -9045,6 +9201,7 @@ var Indexer = class {
9045
9201
  targetName: site.calleeName,
9046
9202
  toSymbolId: void 0,
9047
9203
  callType: site.callType,
9204
+ confidence: site.confidence,
9048
9205
  line: site.line,
9049
9206
  col: site.column,
9050
9207
  isResolved: false
@@ -9189,7 +9346,7 @@ var Indexer = class {
9189
9346
  for (const requestBatch of requestBatches) {
9190
9347
  queue.add(async () => {
9191
9348
  if (rateLimitBackoffMs > 0) {
9192
- await new Promise((resolve12) => setTimeout(resolve12, rateLimitBackoffMs));
9349
+ await new Promise((resolve13) => setTimeout(resolve13, rateLimitBackoffMs));
9193
9350
  }
9194
9351
  try {
9195
9352
  const result = await pRetry(
@@ -9204,7 +9361,7 @@ var Indexer = class {
9204
9361
  factor: 2,
9205
9362
  shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError),
9206
9363
  onFailedAttempt: (error) => {
9207
- const message = getErrorMessage(error);
9364
+ const message = getErrorMessage2(error);
9208
9365
  if (isRateLimitError(error)) {
9209
9366
  rateLimitBackoffMs = Math.min(providerRateLimits.maxRetryMs, (rateLimitBackoffMs || providerRateLimits.minRetryMs) * 2);
9210
9367
  this.logger.embedding("warn", `Rate limited, backing off`, {
@@ -9311,7 +9468,7 @@ var Indexer = class {
9311
9468
  });
9312
9469
  } catch (error) {
9313
9470
  const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id));
9314
- const failureMessage = getErrorMessage(error);
9471
+ const failureMessage = getErrorMessage2(error);
9315
9472
  const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9316
9473
  for (const chunk of failedChunks) {
9317
9474
  if (!failedChunkIds.has(chunk.id)) {
@@ -9750,8 +9907,8 @@ var Indexer = class {
9750
9907
  this.indexCompatibility = compatibility;
9751
9908
  return;
9752
9909
  }
9753
- const localProjectIndexPath = path12.join(this.projectRoot, ".opencode", "index");
9754
- if (path12.resolve(this.indexPath) !== path12.resolve(localProjectIndexPath)) {
9910
+ const localProjectIndexPath = path13.join(this.projectRoot, ".opencode", "index");
9911
+ if (path13.resolve(this.indexPath) !== path13.resolve(localProjectIndexPath)) {
9755
9912
  throw new Error(
9756
9913
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
9757
9914
  );
@@ -9839,7 +9996,7 @@ var Indexer = class {
9839
9996
  gcOrphanSymbols: 0,
9840
9997
  gcOrphanCallEdges: 0,
9841
9998
  resetCorruptedIndex: true,
9842
- warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
9999
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
9843
10000
  };
9844
10001
  }
9845
10002
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -9990,7 +10147,7 @@ var Indexer = class {
9990
10147
  succeeded += successfulResults.length;
9991
10148
  stillFailing.push(...failedChunksForBatch.values());
9992
10149
  } catch (error) {
9993
- const failureMessage = getErrorMessage(error);
10150
+ const failureMessage = getErrorMessage2(error);
9994
10151
  const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9995
10152
  const unaccountedChunks = batch.chunks.filter(
9996
10153
  (chunk) => !failedChunksForBatch.has(chunk.id) && !completedChunkIds.has(chunk.id)
@@ -10164,12 +10321,12 @@ var Indexer = class {
10164
10321
  })
10165
10322
  );
10166
10323
  }
10167
- async getCallers(targetName) {
10324
+ async getCallers(targetName, callTypeFilter) {
10168
10325
  const { database } = await this.ensureInitialized();
10169
10326
  const seen = /* @__PURE__ */ new Set();
10170
10327
  const results = [];
10171
10328
  for (const branchKey of this.getBranchCatalogKeys()) {
10172
- for (const edge of database.getCallersWithContext(targetName, branchKey)) {
10329
+ for (const edge of database.getCallersWithContext(targetName, branchKey, callTypeFilter)) {
10173
10330
  if (!seen.has(edge.id)) {
10174
10331
  seen.add(edge.id);
10175
10332
  results.push(edge);
@@ -10178,12 +10335,12 @@ var Indexer = class {
10178
10335
  }
10179
10336
  return results;
10180
10337
  }
10181
- async getCallees(symbolId) {
10338
+ async getCallees(symbolId, callTypeFilter) {
10182
10339
  const { database } = await this.ensureInitialized();
10183
10340
  const seen = /* @__PURE__ */ new Set();
10184
10341
  const results = [];
10185
10342
  for (const branchKey of this.getBranchCatalogKeys()) {
10186
- for (const edge of database.getCallees(symbolId, branchKey)) {
10343
+ for (const edge of database.getCallees(symbolId, branchKey, callTypeFilter)) {
10187
10344
  if (!seen.has(edge.id)) {
10188
10345
  seen.add(edge.id);
10189
10346
  results.push(edge);
@@ -10192,6 +10349,198 @@ var Indexer = class {
10192
10349
  }
10193
10350
  return results;
10194
10351
  }
10352
+ async findCallPath(fromName, toName, maxDepth) {
10353
+ const { database } = await this.ensureInitialized();
10354
+ let shortest = [];
10355
+ for (const branchKey of this.getBranchCatalogKeys()) {
10356
+ const path19 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
10357
+ if (path19.length > 0 && (shortest.length === 0 || path19.length < shortest.length)) {
10358
+ shortest = path19;
10359
+ }
10360
+ }
10361
+ return shortest;
10362
+ }
10363
+ async getSymbolsForBranch(branch) {
10364
+ const { database } = await this.ensureInitialized();
10365
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
10366
+ return database.getSymbolsForBranch(resolvedBranch);
10367
+ }
10368
+ async getSymbolsForFiles(filePaths, branch) {
10369
+ const { database } = await this.ensureInitialized();
10370
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
10371
+ return database.getSymbolsForFiles(filePaths, resolvedBranch);
10372
+ }
10373
+ async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
10374
+ const { database } = await this.ensureInitialized();
10375
+ const branch = this.getBranchCatalogKey();
10376
+ return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
10377
+ }
10378
+ async detectCommunities(branch, symbolIds) {
10379
+ const { database } = await this.ensureInitialized();
10380
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
10381
+ return database.detectCommunities(resolvedBranch, symbolIds);
10382
+ }
10383
+ async computeCentrality(branch) {
10384
+ const { database } = await this.ensureInitialized();
10385
+ const resolvedBranch = branch ?? this.getBranchCatalogKey();
10386
+ return database.computeCentrality(resolvedBranch);
10387
+ }
10388
+ async getPrImpact(opts) {
10389
+ const { database } = await this.ensureInitialized();
10390
+ const execFileAsync2 = promisify2(execFile2);
10391
+ const changedFilesResult = await getChangedFiles({
10392
+ pr: opts.pr,
10393
+ branch: opts.branch,
10394
+ projectRoot: this.projectRoot,
10395
+ baseBranch: this.baseBranch
10396
+ });
10397
+ const changedFiles = changedFilesResult.files;
10398
+ const headRefName = changedFilesResult.headRefName;
10399
+ if (opts.pr !== void 0 && headRefName === void 0) {
10400
+ throw new Error(
10401
+ `Could not resolve head branch for PR #${opts.pr}. Run index_codebase on the PR branch first.`
10402
+ );
10403
+ }
10404
+ const resolvedBranch = opts.pr !== void 0 ? headRefName : opts.branch || this.currentBranch;
10405
+ const branchKey = this.getBranchCatalogKeyFor(resolvedBranch || "default");
10406
+ const branchSymbols = database.getSymbolsForBranch(branchKey);
10407
+ if (branchSymbols.length === 0) {
10408
+ throw new Error(
10409
+ "Run index_codebase first to build the call graph and symbol index for this branch."
10410
+ );
10411
+ }
10412
+ const absoluteChangedFiles = changedFiles.map((f) => path13.resolve(this.projectRoot, f));
10413
+ const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
10414
+ const directIds = directSymbols.map((s) => s.id);
10415
+ const direction = opts.direction ?? "both";
10416
+ const maxDepth = opts.maxDepth ?? 5;
10417
+ const transitiveCallers = database.getTransitiveReachability(
10418
+ directIds,
10419
+ branchKey,
10420
+ direction,
10421
+ maxDepth
10422
+ );
10423
+ const affectedIdsSet = new Set(directIds);
10424
+ for (const caller of transitiveCallers) {
10425
+ affectedIdsSet.add(caller.symbolId);
10426
+ }
10427
+ const allAffectedIds = Array.from(affectedIdsSet);
10428
+ const communitiesData = database.detectCommunities(branchKey, allAffectedIds);
10429
+ const communityMap = /* @__PURE__ */ new Map();
10430
+ for (const c of communitiesData) {
10431
+ if (!communityMap.has(c.communityLabel)) {
10432
+ communityMap.set(c.communityLabel, {
10433
+ label: c.communityLabel,
10434
+ symbolCount: 0,
10435
+ directSymbols: /* @__PURE__ */ new Set()
10436
+ });
10437
+ }
10438
+ const entry = communityMap.get(c.communityLabel);
10439
+ entry.symbolCount++;
10440
+ if (directIds.includes(c.symbolId)) {
10441
+ entry.directSymbols.add(c.symbolId);
10442
+ }
10443
+ }
10444
+ const communities = Array.from(communityMap.values()).map((c) => ({
10445
+ label: c.label,
10446
+ symbolCount: c.symbolCount,
10447
+ directSymbols: Array.from(c.directSymbols)
10448
+ }));
10449
+ const centralityData = database.computeCentrality(branchKey);
10450
+ const hubThreshold = opts.hubThreshold ?? 10;
10451
+ const hubNodes = centralityData.filter((c) => directIds.includes(c.symbolId) && c.callerCount >= hubThreshold).map((c) => ({
10452
+ id: c.symbolId,
10453
+ name: c.symbolName,
10454
+ callerCount: c.callerCount,
10455
+ filePath: c.filePath
10456
+ }));
10457
+ const totalAffected = allAffectedIds.length;
10458
+ let riskLevel;
10459
+ let riskReason;
10460
+ if (totalAffected < 5 && hubNodes.length === 0) {
10461
+ riskLevel = "LOW";
10462
+ riskReason = `Small impact: ${totalAffected} affected symbols, no hub nodes touched.`;
10463
+ } else if (totalAffected > 20 || hubNodes.length > 1) {
10464
+ riskLevel = "HIGH";
10465
+ riskReason = `Large impact: ${totalAffected} affected symbols${hubNodes.length > 0 ? `, ${hubNodes.length} hub nodes touched` : ""}.`;
10466
+ } else {
10467
+ riskLevel = "MEDIUM";
10468
+ riskReason = `Moderate impact: ${totalAffected} affected symbols${hubNodes.length === 1 ? ", 1 hub node touched" : ""}.`;
10469
+ }
10470
+ let conflictingPRs;
10471
+ if (opts.checkConflicts) {
10472
+ conflictingPRs = [];
10473
+ try {
10474
+ const { stdout } = await execFileAsync2(
10475
+ "gh",
10476
+ ["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
10477
+ { cwd: this.projectRoot, timeout: 3e4 }
10478
+ );
10479
+ const openPRs = JSON.parse(stdout);
10480
+ const currentCommunityLabels = new Set(communities.map((c) => c.label));
10481
+ const allCommunitiesData = database.detectCommunities(branchKey);
10482
+ const symbolToCommunity = /* @__PURE__ */ new Map();
10483
+ const structuralKey = (filePath, name) => `${filePath.toLowerCase()}:${name.toLowerCase()}`;
10484
+ for (const c of allCommunitiesData) {
10485
+ symbolToCommunity.set(structuralKey(c.filePath, c.symbolName), c.communityLabel);
10486
+ }
10487
+ for (const openPr of openPRs) {
10488
+ if (openPr.number === opts.pr) continue;
10489
+ try {
10490
+ const otherChanged = await getChangedFiles({
10491
+ pr: openPr.number,
10492
+ projectRoot: this.projectRoot,
10493
+ baseBranch: this.baseBranch
10494
+ });
10495
+ const otherAbsolute = otherChanged.files.map((f) => path13.resolve(this.projectRoot, f));
10496
+ const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
10497
+ const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
10498
+ const otherLabels = /* @__PURE__ */ new Set();
10499
+ for (const sym of otherSymbols) {
10500
+ const label = symbolToCommunity.get(structuralKey(sym.filePath, sym.name));
10501
+ if (label) {
10502
+ otherLabels.add(label);
10503
+ }
10504
+ }
10505
+ const overlapping = Array.from(otherLabels).filter(
10506
+ (l) => currentCommunityLabels.has(l)
10507
+ );
10508
+ if (overlapping.length > 0) {
10509
+ conflictingPRs.push({
10510
+ pr: openPr.number,
10511
+ branch: openPr.headRefName,
10512
+ overlappingCommunities: overlapping
10513
+ });
10514
+ }
10515
+ } catch {
10516
+ }
10517
+ }
10518
+ } catch {
10519
+ }
10520
+ }
10521
+ return {
10522
+ changedFiles,
10523
+ directSymbols: directSymbols.map((s) => ({
10524
+ id: s.id,
10525
+ name: s.name,
10526
+ kind: s.kind,
10527
+ filePath: s.filePath
10528
+ })),
10529
+ transitiveCallers: transitiveCallers.map((c) => ({
10530
+ id: c.symbolId,
10531
+ name: c.symbolName,
10532
+ filePath: c.filePath,
10533
+ depth: c.depth
10534
+ })),
10535
+ totalAffected,
10536
+ communities,
10537
+ hubNodes,
10538
+ riskLevel,
10539
+ riskReason,
10540
+ direction,
10541
+ conflictingPRs
10542
+ };
10543
+ }
10195
10544
  async close() {
10196
10545
  await this.database?.close();
10197
10546
  this.database = null;
@@ -10419,54 +10768,139 @@ ${truncateContent(r.content)}
10419
10768
  return formatted.join("\n\n");
10420
10769
  }
10421
10770
 
10771
+ // src/tools/pr-impact.ts
10772
+ import { tool } from "@opencode-ai/plugin";
10773
+
10774
+ // src/tools/format-pr-impact.ts
10775
+ function formatPrImpact(result) {
10776
+ const lines = [];
10777
+ lines.push(`\u2192 Files changed: ${result.changedFiles.length}`);
10778
+ for (const file of result.changedFiles) {
10779
+ lines.push(` - ${file}`);
10780
+ }
10781
+ const directCount = result.directSymbols.length;
10782
+ const transitiveCount = result.transitiveCallers.length;
10783
+ const directionLabel = result.direction === "callees" ? "callees" : result.direction === "both" ? "reachable (callers + callees)" : "callers";
10784
+ lines.push(
10785
+ `\u2192 Symbols affected: ${result.totalAffected} (${directCount} direct, ${transitiveCount} transitive ${directionLabel})`
10786
+ );
10787
+ if (directCount > 0) {
10788
+ lines.push(
10789
+ ` Direct: ${result.directSymbols.map((s) => `${s.name} (${s.kind})`).join(", ")}`
10790
+ );
10791
+ }
10792
+ if (transitiveCount > 0) {
10793
+ lines.push(
10794
+ ` Transitive ${directionLabel}: ${result.transitiveCallers.map((s) => s.name).join(", ")}`
10795
+ );
10796
+ }
10797
+ if (result.communities.length > 0) {
10798
+ lines.push(
10799
+ `\u2192 Communities touched: ${result.communities.map((c) => c.label).join(", ")}`
10800
+ );
10801
+ for (const community of result.communities) {
10802
+ lines.push(
10803
+ ` - ${community.label}: ${community.symbolCount} symbols`
10804
+ );
10805
+ }
10806
+ } else {
10807
+ lines.push("\u2192 Communities touched: none");
10808
+ }
10809
+ lines.push(`\u2192 Risk: ${result.riskLevel} \u2014 ${result.riskReason}`);
10810
+ if (result.hubNodes.length > 0) {
10811
+ lines.push(" Hub nodes in change scope:");
10812
+ for (const hub of result.hubNodes) {
10813
+ lines.push(` - ${hub.name} (${hub.callerCount} callers) at ${hub.filePath}`);
10814
+ }
10815
+ }
10816
+ if (result.conflictingPRs && result.conflictingPRs.length > 0) {
10817
+ const conflictList = result.conflictingPRs.map(
10818
+ (c) => `PR #${c.pr} (also touches ${c.overlappingCommunities.join(", ")} community)`
10819
+ );
10820
+ lines.push(`\u2192 Potential conflicts with: ${conflictList.join(", ")}`);
10821
+ }
10822
+ return lines.join("\n");
10823
+ }
10824
+
10825
+ // src/tools/pr-impact.ts
10826
+ var z = tool.schema;
10827
+ var pr_impact = tool({
10828
+ 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.",
10829
+ args: {
10830
+ pr: z.number().optional().describe("Pull request number to analyze"),
10831
+ branch: z.string().optional().describe("Branch name to analyze (defaults to current branch)"),
10832
+ maxDepth: z.number().optional().default(5).describe("Maximum traversal depth for transitive callers (default: 5)"),
10833
+ hubThreshold: z.number().optional().default(10).describe("Minimum caller count to flag a symbol as a hub node (default: 10)"),
10834
+ checkConflicts: z.boolean().optional().default(false).describe("Check for conflicting open PRs touching the same communities (default: false)"),
10835
+ 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)")
10836
+ },
10837
+ async execute(args, context) {
10838
+ const indexer = getIndexerForProject(context?.worktree);
10839
+ try {
10840
+ const result = await indexer.getPrImpact({
10841
+ pr: args.pr,
10842
+ branch: args.branch,
10843
+ maxDepth: args.maxDepth,
10844
+ hubThreshold: args.hubThreshold,
10845
+ checkConflicts: args.checkConflicts,
10846
+ direction: args.direction
10847
+ });
10848
+ return formatPrImpact(result);
10849
+ } catch (error) {
10850
+ const message = error instanceof Error ? error.message : String(error);
10851
+ return `Error analyzing PR impact: ${message}`;
10852
+ }
10853
+ }
10854
+ });
10855
+
10422
10856
  // src/tools/knowledge-base-paths.ts
10423
- import * as path13 from "path";
10857
+ import * as path14 from "path";
10424
10858
  function resolveConfigPathValue(value, baseDir) {
10425
10859
  const trimmed = value.trim();
10426
10860
  if (!trimmed) {
10427
10861
  return trimmed;
10428
10862
  }
10429
- const absolutePath = path13.isAbsolute(trimmed) ? trimmed : path13.resolve(baseDir, trimmed);
10430
- return path13.normalize(absolutePath);
10863
+ const absolutePath = path14.isAbsolute(trimmed) ? trimmed : path14.resolve(baseDir, trimmed);
10864
+ return path14.normalize(absolutePath);
10431
10865
  }
10432
10866
  function serializeConfigPathValue(value, baseDir) {
10433
10867
  const trimmed = value.trim();
10434
10868
  if (!trimmed) {
10435
10869
  return trimmed;
10436
10870
  }
10437
- if (!path13.isAbsolute(trimmed)) {
10438
- return normalizePathSeparators(path13.normalize(trimmed));
10871
+ if (!path14.isAbsolute(trimmed)) {
10872
+ return normalizePathSeparators(path14.normalize(trimmed));
10439
10873
  }
10440
- const relativePath = path13.relative(baseDir, trimmed);
10441
- if (!relativePath || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath)) {
10442
- return normalizePathSeparators(path13.normalize(relativePath || "."));
10874
+ const relativePath = path14.relative(baseDir, trimmed);
10875
+ if (!relativePath || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath)) {
10876
+ return normalizePathSeparators(path14.normalize(relativePath || "."));
10443
10877
  }
10444
- return path13.normalize(trimmed);
10878
+ return path14.normalize(trimmed);
10445
10879
  }
10446
10880
  function resolveKnowledgeBasePath(value, projectRoot) {
10447
- return path13.isAbsolute(value) ? value : path13.resolve(projectRoot, value);
10881
+ return path14.isAbsolute(value) ? value : path14.resolve(projectRoot, value);
10448
10882
  }
10449
10883
  function normalizeKnowledgeBasePath2(value, projectRoot) {
10450
- return path13.normalize(resolveKnowledgeBasePath(value, projectRoot));
10884
+ return path14.normalize(resolveKnowledgeBasePath(value, projectRoot));
10451
10885
  }
10452
10886
  function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot) {
10453
- const normalizedInput = path13.normalize(inputPath);
10887
+ const normalizedInput = path14.normalize(inputPath);
10454
10888
  return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput);
10455
10889
  }
10456
10890
  function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
10457
- const normalizedInput = path13.normalize(inputPath);
10891
+ const normalizedInput = path14.normalize(inputPath);
10458
10892
  return knowledgeBases.findIndex(
10459
- (kb) => path13.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
10893
+ (kb) => path14.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
10460
10894
  );
10461
10895
  }
10462
10896
 
10463
10897
  // src/tools/index.ts
10464
10898
  import { existsSync as existsSync9, realpathSync, statSync as statSync3 } from "fs";
10465
- import * as path15 from "path";
10899
+ import * as path16 from "path";
10466
10900
 
10467
10901
  // src/tools/config-state.ts
10468
10902
  import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
10469
- import * as path14 from "path";
10903
+ import * as path15 from "path";
10470
10904
  function normalizeKnowledgeBasePaths(config, projectRoot) {
10471
10905
  const normalized = { ...config };
10472
10906
  if (Array.isArray(normalized.knowledgeBases)) {
@@ -10493,8 +10927,8 @@ function loadEditableConfig(projectRoot) {
10493
10927
  }
10494
10928
  function saveConfig(projectRoot, config) {
10495
10929
  const configPath = getConfigPath(projectRoot);
10496
- const configDir = path14.dirname(configPath);
10497
- const configBaseDir = path14.dirname(configDir);
10930
+ const configDir = path15.dirname(configPath);
10931
+ const configBaseDir = path15.dirname(configDir);
10498
10932
  if (!existsSync8(configDir)) {
10499
10933
  mkdirSync3(configDir, { recursive: true });
10500
10934
  }
@@ -10512,7 +10946,7 @@ import * as os5 from "os";
10512
10946
  function ensureStringArray(value) {
10513
10947
  return Array.isArray(value) ? value : [];
10514
10948
  }
10515
- var z = tool.schema;
10949
+ var z2 = tool2.schema;
10516
10950
  var indexerMap = /* @__PURE__ */ new Map();
10517
10951
  var defaultProjectRoot = "";
10518
10952
  function initializeTools(projectRoot, config) {
@@ -10532,12 +10966,12 @@ function shouldForceLocalizeProjectIndex(projectRoot) {
10532
10966
  if (currentConfig.scope !== "project") {
10533
10967
  return false;
10534
10968
  }
10535
- const localIndexPath = path15.join(projectRoot, ".opencode", "index");
10969
+ const localIndexPath = path16.join(projectRoot, ".opencode", "index");
10536
10970
  const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
10537
10971
  if (!mainRepoRoot) {
10538
10972
  return false;
10539
10973
  }
10540
- const inheritedIndexPath = path15.join(mainRepoRoot, ".opencode", "index");
10974
+ const inheritedIndexPath = path16.join(mainRepoRoot, ".opencode", "index");
10541
10975
  return !existsSync9(localIndexPath) && existsSync9(inheritedIndexPath);
10542
10976
  }
10543
10977
  function getIndexerForProject(directory) {
@@ -10553,14 +10987,14 @@ function getIndexerForProject(directory) {
10553
10987
  }
10554
10988
  return indexer;
10555
10989
  }
10556
- var codebase_peek = tool({
10990
+ var codebase_peek = tool2({
10557
10991
  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.",
10558
10992
  args: {
10559
- query: z.string().describe("Natural language description of what code you're looking for."),
10560
- limit: z.number().optional().default(10).describe("Maximum number of results to return"),
10561
- fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
10562
- directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
10563
- chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type")
10993
+ query: z2.string().describe("Natural language description of what code you're looking for."),
10994
+ limit: z2.number().optional().default(10).describe("Maximum number of results to return"),
10995
+ fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
10996
+ directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
10997
+ chunkType: z2.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type")
10564
10998
  },
10565
10999
  async execute(args, context) {
10566
11000
  const indexer = getIndexerForProject(context?.worktree);
@@ -10573,12 +11007,12 @@ var codebase_peek = tool({
10573
11007
  return formatCodebasePeek(results);
10574
11008
  }
10575
11009
  });
10576
- var index_codebase = tool({
11010
+ var index_codebase = tool2({
10577
11011
  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.",
10578
11012
  args: {
10579
- force: z.boolean().optional().default(false).describe("Force reindex even if already indexed"),
10580
- estimateOnly: z.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
10581
- verbose: z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
11013
+ force: z2.boolean().optional().default(false).describe("Force reindex even if already indexed"),
11014
+ estimateOnly: z2.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
11015
+ verbose: z2.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
10582
11016
  },
10583
11017
  async execute(args, context) {
10584
11018
  const projectRoot = context?.worktree || defaultProjectRoot;
@@ -10611,7 +11045,7 @@ var index_codebase = tool({
10611
11045
  return formatIndexStats(stats, args.verbose ?? false);
10612
11046
  }
10613
11047
  });
10614
- var index_status = tool({
11048
+ var index_status = tool2({
10615
11049
  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.",
10616
11050
  args: {},
10617
11051
  async execute(_args, context) {
@@ -10620,7 +11054,7 @@ var index_status = tool({
10620
11054
  return formatStatus(status);
10621
11055
  }
10622
11056
  });
10623
- var index_health_check = tool({
11057
+ var index_health_check = tool2({
10624
11058
  description: "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
10625
11059
  args: {},
10626
11060
  async execute(_args, context) {
@@ -10629,7 +11063,7 @@ var index_health_check = tool({
10629
11063
  return formatHealthCheck(result);
10630
11064
  }
10631
11065
  });
10632
- var index_metrics = tool({
11066
+ var index_metrics = tool2({
10633
11067
  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.",
10634
11068
  args: {},
10635
11069
  async execute(_args, context) {
@@ -10644,12 +11078,12 @@ var index_metrics = tool({
10644
11078
  return logger.formatMetrics();
10645
11079
  }
10646
11080
  });
10647
- var index_logs = tool({
11081
+ var index_logs = tool2({
10648
11082
  description: "Get recent debug logs from the codebase indexer. Shows timestamped log entries with level and category. Requires debug.enabled=true in config.",
10649
11083
  args: {
10650
- limit: z.number().optional().default(20).describe("Maximum number of log entries to return"),
10651
- category: z.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional().describe("Filter by log category"),
10652
- level: z.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
11084
+ limit: z2.number().optional().default(20).describe("Maximum number of log entries to return"),
11085
+ category: z2.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional().describe("Filter by log category"),
11086
+ level: z2.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
10653
11087
  },
10654
11088
  async execute(args, context) {
10655
11089
  const indexer = getIndexerForProject(context?.worktree);
@@ -10668,15 +11102,15 @@ var index_logs = tool({
10668
11102
  return formatLogs(logs);
10669
11103
  }
10670
11104
  });
10671
- var find_similar = tool({
11105
+ var find_similar = tool2({
10672
11106
  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.",
10673
11107
  args: {
10674
- code: z.string().describe("The code snippet to find similar code for"),
10675
- limit: z.number().optional().default(10).describe("Maximum number of results to return"),
10676
- fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
10677
- directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
10678
- chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10679
- excludeFile: z.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)")
11108
+ code: z2.string().describe("The code snippet to find similar code for"),
11109
+ limit: z2.number().optional().default(10).describe("Maximum number of results to return"),
11110
+ fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
11111
+ directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
11112
+ chunkType: z2.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
11113
+ excludeFile: z2.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)")
10680
11114
  },
10681
11115
  async execute(args, context) {
10682
11116
  const indexer = getIndexerForProject(context?.worktree);
@@ -10692,15 +11126,15 @@ var find_similar = tool({
10692
11126
  return formatSearchResults(results);
10693
11127
  }
10694
11128
  });
10695
- var codebase_search = tool({
11129
+ var codebase_search = tool2({
10696
11130
  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.",
10697
11131
  args: {
10698
- query: z.string().describe("Natural language description of what code you're looking for. Describe behavior, not syntax."),
10699
- limit: z.number().optional().default(5).describe("Maximum number of results to return"),
10700
- fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
10701
- directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
10702
- chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10703
- contextLines: z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
11132
+ query: z2.string().describe("Natural language description of what code you're looking for. Describe behavior, not syntax."),
11133
+ limit: z2.number().optional().default(5).describe("Maximum number of results to return"),
11134
+ fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
11135
+ directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
11136
+ chunkType: z2.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
11137
+ contextLines: z2.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
10704
11138
  },
10705
11139
  async execute(args, context) {
10706
11140
  const indexer = getIndexerForProject(context?.worktree);
@@ -10716,13 +11150,13 @@ var codebase_search = tool({
10716
11150
  return formatSearchResults(results, "score");
10717
11151
  }
10718
11152
  });
10719
- var implementation_lookup = tool({
11153
+ var implementation_lookup = tool2({
10720
11154
  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.",
10721
11155
  args: {
10722
- query: z.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
10723
- limit: z.number().optional().default(5).describe("Maximum number of results"),
10724
- fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
10725
- directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
11156
+ query: z2.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
11157
+ limit: z2.number().optional().default(5).describe("Maximum number of results"),
11158
+ fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
11159
+ directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
10726
11160
  },
10727
11161
  async execute(args, context) {
10728
11162
  const indexer = getIndexerForProject(context?.worktree);
@@ -10734,12 +11168,13 @@ var implementation_lookup = tool({
10734
11168
  return formatDefinitionLookup(results, args.query);
10735
11169
  }
10736
11170
  });
10737
- var call_graph = tool({
10738
- description: "Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies between functions.",
11171
+ var call_graph = tool2({
11172
+ 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.",
10739
11173
  args: {
10740
- name: z.string().describe("Function or method name to query"),
10741
- direction: z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
10742
- symbolId: z.string().optional().describe("Symbol ID (required for 'callees' direction, returned by previous call_graph queries)")
11174
+ name: z2.string().describe("Function or method name to query"),
11175
+ direction: z2.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
11176
+ symbolId: z2.string().optional().describe("Symbol ID (required for 'callees' direction, returned by previous call_graph queries)"),
11177
+ relationshipType: z2.enum(["Call", "MethodCall", "Constructor", "Import", "Inherits", "Implements"]).optional().describe("Filter by relationship type. Omit to show all.")
10743
11178
  },
10744
11179
  async execute(args, context) {
10745
11180
  const indexer = getIndexerForProject(context?.worktree);
@@ -10747,35 +11182,59 @@ var call_graph = tool({
10747
11182
  if (!args.symbolId) {
10748
11183
  return "Error: 'symbolId' is required when direction is 'callees'. First use direction='callers' to find the symbol ID.";
10749
11184
  }
10750
- const callees = await indexer.getCallees(args.symbolId);
11185
+ const callees = await indexer.getCallees(args.symbolId, args.relationshipType);
10751
11186
  if (callees.length === 0) {
10752
- return `No callees found for symbol ${args.symbolId}. The function may not call any other tracked functions.`;
11187
+ return `No callees found for symbol ${args.symbolId}${args.relationshipType ? ` with type ${args.relationshipType}` : ""}. The function may not call any other tracked functions.`;
10753
11188
  }
10754
- const formatted2 = callees.map(
10755
- (e, i) => `[${i + 1}] \u2192 ${e.targetName} (${e.callType}) at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`
10756
- );
11189
+ const formatted2 = callees.map((e, i) => {
11190
+ const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
11191
+ return `[${i + 1}] \u2192 ${e.targetName} (${e.callType})${conf} at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`;
11192
+ });
10757
11193
  return formatted2.join("\n");
10758
11194
  }
10759
- const callers = await indexer.getCallers(args.name);
11195
+ const callers = await indexer.getCallers(args.name, args.relationshipType);
10760
11196
  if (callers.length === 0) {
10761
- return `No callers found for "${args.name}". It may not be called by any tracked function, or the index needs updating.`;
11197
+ 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.`;
10762
11198
  }
10763
- const formatted = callers.map(
10764
- (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]"}`
10765
- );
11199
+ const formatted = callers.map((e, i) => {
11200
+ const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
11201
+ 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]"}`;
11202
+ });
10766
11203
  return formatted.join("\n");
10767
11204
  }
10768
11205
  });
10769
- var add_knowledge_base = tool({
11206
+ var call_graph_path = tool2({
11207
+ 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.",
11208
+ args: {
11209
+ from: z2.string().describe("Source function/method name (starting point)"),
11210
+ to: z2.string().describe("Target function/method name (destination)"),
11211
+ maxDepth: z2.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
11212
+ },
11213
+ async execute(args, context) {
11214
+ const indexer = getIndexerForProject(context?.worktree);
11215
+ const path19 = await indexer.findCallPath(args.from, args.to, args.maxDepth);
11216
+ if (path19.length === 0) {
11217
+ return `No path found between "${args.from}" and "${args.to}". They may be in disconnected components, or the call graph index needs updating.`;
11218
+ }
11219
+ const formatted = path19.map((hop, i) => {
11220
+ const prefix = i === 0 ? "[start]" : `--${hop.callType}-->`;
11221
+ const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
11222
+ return `${prefix} ${hop.symbolName}${location}`;
11223
+ });
11224
+ return `Path (${path19.length} hops):
11225
+ ${formatted.join("\n")}`;
11226
+ }
11227
+ });
11228
+ var add_knowledge_base = tool2({
10770
11229
  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).",
10771
11230
  args: {
10772
- path: z.string().describe("Path to the folder to add as a knowledge base (absolute or relative to project root)")
11231
+ path: z2.string().describe("Path to the folder to add as a knowledge base (absolute or relative to project root)")
10773
11232
  },
10774
11233
  async execute(args, context) {
10775
11234
  const projectRoot = context?.worktree || defaultProjectRoot;
10776
11235
  const inputPath = args.path.trim();
10777
- const normalizedPath = path15.resolve(
10778
- path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, projectRoot)
11236
+ const normalizedPath = path16.resolve(
11237
+ path16.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, projectRoot)
10779
11238
  );
10780
11239
  if (!existsSync9(normalizedPath)) {
10781
11240
  return `Error: Directory does not exist: ${normalizedPath}`;
@@ -10804,7 +11263,7 @@ var add_knowledge_base = tool({
10804
11263
  }
10805
11264
  }
10806
11265
  for (const dotDir of sensitiveDotDirs) {
10807
- const sensitiveDir = path15.join(homeDir, dotDir);
11266
+ const sensitiveDir = path16.join(homeDir, dotDir);
10808
11267
  if (realPath === sensitiveDir || realPath.startsWith(sensitiveDir + "/")) {
10809
11268
  return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
10810
11269
  }
@@ -10838,7 +11297,7 @@ Run /index to rebuild the index with the new knowledge base.`;
10838
11297
  return result;
10839
11298
  }
10840
11299
  });
10841
- var list_knowledge_bases = tool({
11300
+ var list_knowledge_bases = tool2({
10842
11301
  description: "List all configured knowledge base folders that are indexed alongside the main project.",
10843
11302
  args: {},
10844
11303
  async execute(_args, context) {
@@ -10875,10 +11334,10 @@ var list_knowledge_bases = tool({
10875
11334
  return result;
10876
11335
  }
10877
11336
  });
10878
- var remove_knowledge_base = tool({
11337
+ var remove_knowledge_base = tool2({
10879
11338
  description: "Remove a knowledge base folder from the semantic search index.",
10880
11339
  args: {
10881
- path: z.string().describe("Path of the knowledge base to remove (must match the configured path exactly)")
11340
+ path: z2.string().describe("Path of the knowledge base to remove (must match the configured path exactly)")
10882
11341
  },
10883
11342
  async execute(args, context) {
10884
11343
  const projectRoot = context?.worktree || defaultProjectRoot;
@@ -10920,7 +11379,7 @@ Run /index to rebuild the index without the removed knowledge base.`;
10920
11379
 
10921
11380
  // src/commands/loader.ts
10922
11381
  import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync7 } from "fs";
10923
- import * as path16 from "path";
11382
+ import * as path17 from "path";
10924
11383
  function parseFrontmatter(content) {
10925
11384
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
10926
11385
  const match = content.match(frontmatterRegex);
@@ -10946,7 +11405,7 @@ function loadCommandsFromDirectory(commandsDir) {
10946
11405
  }
10947
11406
  const files = readdirSync2(commandsDir).filter((f) => f.endsWith(".md"));
10948
11407
  for (const file of files) {
10949
- const filePath = path16.join(commandsDir, file);
11408
+ const filePath = path17.join(commandsDir, file);
10950
11409
  let content;
10951
11410
  try {
10952
11411
  content = readFileSync7(filePath, "utf-8");
@@ -10955,7 +11414,7 @@ function loadCommandsFromDirectory(commandsDir) {
10955
11414
  throw new Error(`Failed to load command file ${filePath}: ${message}`);
10956
11415
  }
10957
11416
  const { frontmatter, body } = parseFrontmatter(content);
10958
- const name = path16.basename(file, ".md");
11417
+ const name = path17.basename(file, ".md");
10959
11418
  const description = frontmatter.description || `Run the ${name} command`;
10960
11419
  commands.set(name, {
10961
11420
  description,
@@ -11177,7 +11636,7 @@ function assessRoutingIntent(text) {
11177
11636
  reason: "no_local_discovery_signal"
11178
11637
  };
11179
11638
  }
11180
- function buildRoutingHint(assessment, status) {
11639
+ function buildRoutingHint(assessment, status, includeGraphHandoff = false) {
11181
11640
  if (assessment.intent === "definition_lookup") {
11182
11641
  if (!status || !status.indexed || status.compatibility?.compatible === false) {
11183
11642
  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.";
@@ -11188,15 +11647,21 @@ function buildRoutingHint(assessment, status) {
11188
11647
  return null;
11189
11648
  }
11190
11649
  if (!status || !status.indexed || status.compatibility?.compatible === false) {
11191
- 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.";
11650
+ const graphHandoff2 = includeGraphHandoff ? " Use graph tools after semantic discovery identifies relevant symbols." : "";
11651
+ 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.`;
11192
11652
  }
11193
- 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.";
11653
+ const graphHandoff = includeGraphHandoff ? " before graph tools such as `call_graph`, `call_graph_path`, `pr_impact`, or OMO CodeGraph" : "";
11654
+ 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.`;
11194
11655
  }
11195
11656
  var RoutingHintController = class {
11196
- constructor(getStatus, maxSessions = 200) {
11657
+ constructor(getStatus, maxSessions = 200, includeGraphHandoff = false) {
11197
11658
  this.getStatus = getStatus;
11198
11659
  this.maxSessions = maxSessions;
11660
+ this.includeGraphHandoff = includeGraphHandoff;
11199
11661
  }
11662
+ getStatus;
11663
+ maxSessions;
11664
+ includeGraphHandoff;
11200
11665
  sessionState = /* @__PURE__ */ new Map();
11201
11666
  observeUserMessage(sessionID, parts) {
11202
11667
  const assessment = assessRoutingIntent(extractUserText(parts));
@@ -11217,7 +11682,7 @@ var RoutingHintController = class {
11217
11682
  return [];
11218
11683
  }
11219
11684
  const status = await this.safeGetStatus();
11220
- const hint = buildRoutingHint(state.assessment, status);
11685
+ const hint = buildRoutingHint(state.assessment, status, this.includeGraphHandoff);
11221
11686
  return hint ? [hint] : [];
11222
11687
  }
11223
11688
  markToolUsed(sessionID, toolName) {
@@ -11267,9 +11732,9 @@ function replaceActiveWatcher(projectRoot, nextWatcher) {
11267
11732
  function getCommandsDir() {
11268
11733
  let currentDir = process.cwd();
11269
11734
  if (typeof import.meta !== "undefined" && import.meta.url) {
11270
- currentDir = path17.dirname(fileURLToPath2(import.meta.url));
11735
+ currentDir = path18.dirname(fileURLToPath2(import.meta.url));
11271
11736
  }
11272
- return path17.join(currentDir, "..", "commands");
11737
+ return path18.join(currentDir, "..", "commands");
11273
11738
  }
11274
11739
  function appendRoutingHints(output, hints, preferredRole) {
11275
11740
  const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
@@ -11288,8 +11753,8 @@ var plugin = async ({ directory, worktree }) => {
11288
11753
  const config = parseConfig(rawConfig);
11289
11754
  initializeTools(projectRoot, config);
11290
11755
  const getProjectIndexer = () => getIndexerForProject(projectRoot);
11291
- const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus()) : null;
11292
- const isHomeDir = path17.resolve(projectRoot) === path17.resolve(os6.homedir());
11756
+ const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus(), 200, config.search.routingGraphHandoffHints) : null;
11757
+ const isHomeDir = path18.resolve(projectRoot) === path18.resolve(os6.homedir());
11293
11758
  const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
11294
11759
  if (isHomeDir) {
11295
11760
  console.warn(
@@ -11324,10 +11789,12 @@ var plugin = async ({ directory, worktree }) => {
11324
11789
  index_logs,
11325
11790
  find_similar,
11326
11791
  call_graph,
11792
+ call_graph_path,
11327
11793
  implementation_lookup,
11328
11794
  add_knowledge_base,
11329
11795
  list_knowledge_bases,
11330
- remove_knowledge_base
11796
+ remove_knowledge_base,
11797
+ pr_impact
11331
11798
  },
11332
11799
  async "chat.message"(input, output) {
11333
11800
  routingHints?.observeUserMessage(input.sessionID, output.parts);