opencode-codebase-index 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -7,7 +7,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
9
  var __commonJS = (cb, mod) => function __require() {
10
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ try {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ } catch (e) {
13
+ throw mod = 0, e;
14
+ }
11
15
  };
12
16
  var __export = (target, all) => {
13
17
  for (var name in all)
@@ -657,6 +661,7 @@ __export(index_exports, {
657
661
  default: () => index_default
658
662
  });
659
663
  module.exports = __toCommonJS(index_exports);
664
+ var os6 = __toESM(require("os"), 1);
660
665
  var path17 = __toESM(require("path"), 1);
661
666
  var import_url2 = require("url");
662
667
 
@@ -803,7 +808,8 @@ function getDefaultSearchConfig() {
803
808
  rrfK: 60,
804
809
  rerankTopN: 20,
805
810
  contextLines: 0,
806
- routingHints: true
811
+ routingHints: true,
812
+ routingHintRole: "system"
807
813
  };
808
814
  }
809
815
  function getDefaultRerankerBaseUrl(provider) {
@@ -924,7 +930,8 @@ function parseConfig(raw) {
924
930
  rrfK: typeof rawSearch.rrfK === "number" ? Math.max(1, Math.floor(rawSearch.rrfK)) : defaultSearch.rrfK,
925
931
  rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
926
932
  contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines,
927
- routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints
933
+ routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints,
934
+ routingHintRole: rawSearch.routingHintRole === "developer" || rawSearch.routingHintRole === "system" ? rawSearch.routingHintRole : defaultSearch.routingHintRole
928
935
  };
929
936
  const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
930
937
  const debug = {
@@ -1246,6 +1253,32 @@ function hasFilteredPathSegment(relativePath, separator = path4.sep) {
1246
1253
  (part) => isHiddenPathSegment(part) || isBuildPathSegment(part)
1247
1254
  );
1248
1255
  }
1256
+ var RESTRICTED_DIRECTORIES = /* @__PURE__ */ new Set([
1257
+ // macOS
1258
+ "library",
1259
+ "applications",
1260
+ "system",
1261
+ "volumes",
1262
+ "private",
1263
+ "cores",
1264
+ // Linux
1265
+ "proc",
1266
+ "sys",
1267
+ "dev",
1268
+ "run",
1269
+ "snap",
1270
+ // Windows
1271
+ "windows",
1272
+ "programdata",
1273
+ "program files",
1274
+ "program files (x86)",
1275
+ "$recycle.bin"
1276
+ ]);
1277
+ function isRestrictedDirectory(relativePath, separator = path4.sep) {
1278
+ const firstSegment = relativePath.split(separator)[0];
1279
+ if (!firstSegment) return false;
1280
+ return RESTRICTED_DIRECTORIES.has(firstSegment.toLowerCase());
1281
+ }
1249
1282
 
1250
1283
  // src/config/rebase.ts
1251
1284
  function isWithinRoot(rootDir, targetPath) {
@@ -2305,7 +2338,7 @@ var NodeFsHandler = class {
2305
2338
  this._addToNodeFs(path18, initialAdd, wh, depth + 1);
2306
2339
  }
2307
2340
  }).on(EV.ERROR, this._boundHandleError);
2308
- return new Promise((resolve11, reject) => {
2341
+ return new Promise((resolve12, reject) => {
2309
2342
  if (!stream)
2310
2343
  return reject();
2311
2344
  stream.once(STR_END, () => {
@@ -2314,7 +2347,7 @@ var NodeFsHandler = class {
2314
2347
  return;
2315
2348
  }
2316
2349
  const wasThrottled = throttler ? throttler.clear() : false;
2317
- resolve11(void 0);
2350
+ resolve12(void 0);
2318
2351
  previous.getChildren().filter((item) => {
2319
2352
  return item !== directory && !current.has(item);
2320
2353
  }).forEach((item) => {
@@ -3414,6 +3447,9 @@ var FileWatcher = class {
3414
3447
  if (hasFilteredPathSegment(relativePath, path8.sep)) {
3415
3448
  return true;
3416
3449
  }
3450
+ if (isRestrictedDirectory(relativePath, path8.sep)) {
3451
+ return true;
3452
+ }
3417
3453
  if (ignoreFilter.ignores(relativePath)) {
3418
3454
  return true;
3419
3455
  }
@@ -3426,6 +3462,13 @@ var FileWatcher = class {
3426
3462
  pollInterval: 100
3427
3463
  }
3428
3464
  });
3465
+ this.watcher.on("error", (error) => {
3466
+ const err = error instanceof Error ? error : null;
3467
+ if (err?.code === "EPERM" || err?.code === "EACCES") {
3468
+ return;
3469
+ }
3470
+ console.error("[codebase-index] Watcher error:", err?.message ?? error);
3471
+ });
3429
3472
  this.watcher.on("add", (filePath) => this.handleChange("add", filePath));
3430
3473
  this.watcher.on("change", (filePath) => this.handleChange("change", filePath));
3431
3474
  this.watcher.on("unlink", (filePath) => this.handleChange("unlink", filePath));
@@ -3560,7 +3603,7 @@ var GitHeadWatcher = class {
3560
3603
  };
3561
3604
 
3562
3605
  // src/watcher/index.ts
3563
- function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3606
+ function createWatcherWithIndexer(getIndexer, projectRoot, config) {
3564
3607
  const fileWatcher = new FileWatcher(projectRoot, config);
3565
3608
  fileWatcher.start(async (changes) => {
3566
3609
  const hasAddOrChange = changes.some(
@@ -3568,7 +3611,7 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3568
3611
  );
3569
3612
  const hasDelete = changes.some((c) => c.type === "unlink");
3570
3613
  if (hasAddOrChange || hasDelete) {
3571
- await getIndexer2().index();
3614
+ await getIndexer().index();
3572
3615
  }
3573
3616
  });
3574
3617
  let gitWatcher = null;
@@ -3576,7 +3619,7 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3576
3619
  gitWatcher = new GitHeadWatcher(projectRoot);
3577
3620
  gitWatcher.start(async (oldBranch, newBranch) => {
3578
3621
  console.log(`Branch changed: ${oldBranch ?? "(none)"} -> ${newBranch}`);
3579
- await getIndexer2().index();
3622
+ await getIndexer().index();
3580
3623
  });
3581
3624
  }
3582
3625
  return {
@@ -3619,7 +3662,7 @@ function pTimeout(promise, options) {
3619
3662
  } = options;
3620
3663
  let timer;
3621
3664
  let abortHandler;
3622
- const wrappedPromise = new Promise((resolve11, reject) => {
3665
+ const wrappedPromise = new Promise((resolve12, reject) => {
3623
3666
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
3624
3667
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
3625
3668
  }
@@ -3633,7 +3676,7 @@ function pTimeout(promise, options) {
3633
3676
  };
3634
3677
  signal.addEventListener("abort", abortHandler, { once: true });
3635
3678
  }
3636
- promise.then(resolve11, reject);
3679
+ promise.then(resolve12, reject);
3637
3680
  if (milliseconds === Number.POSITIVE_INFINITY) {
3638
3681
  return;
3639
3682
  }
@@ -3641,7 +3684,7 @@ function pTimeout(promise, options) {
3641
3684
  timer = customTimers.setTimeout.call(void 0, () => {
3642
3685
  if (fallback) {
3643
3686
  try {
3644
- resolve11(fallback());
3687
+ resolve12(fallback());
3645
3688
  } catch (error) {
3646
3689
  reject(error);
3647
3690
  }
@@ -3651,7 +3694,7 @@ function pTimeout(promise, options) {
3651
3694
  promise.cancel();
3652
3695
  }
3653
3696
  if (message === false) {
3654
- resolve11();
3697
+ resolve12();
3655
3698
  } else if (message instanceof Error) {
3656
3699
  reject(message);
3657
3700
  } else {
@@ -4053,7 +4096,7 @@ var PQueue = class extends import_index.default {
4053
4096
  // Assign unique ID if not provided
4054
4097
  id: options.id ?? (this.#idAssigner++).toString()
4055
4098
  };
4056
- return new Promise((resolve11, reject) => {
4099
+ return new Promise((resolve12, reject) => {
4057
4100
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
4058
4101
  let cleanupQueueAbortHandler = () => void 0;
4059
4102
  const run = async () => {
@@ -4093,7 +4136,7 @@ var PQueue = class extends import_index.default {
4093
4136
  })]);
4094
4137
  }
4095
4138
  const result = await operation;
4096
- resolve11(result);
4139
+ resolve12(result);
4097
4140
  this.emit("completed", result);
4098
4141
  } catch (error) {
4099
4142
  reject(error);
@@ -4281,13 +4324,13 @@ var PQueue = class extends import_index.default {
4281
4324
  });
4282
4325
  }
4283
4326
  async #onEvent(event, filter) {
4284
- return new Promise((resolve11) => {
4327
+ return new Promise((resolve12) => {
4285
4328
  const listener = () => {
4286
4329
  if (filter && !filter()) {
4287
4330
  return;
4288
4331
  }
4289
4332
  this.off(event, listener);
4290
- resolve11();
4333
+ resolve12();
4291
4334
  };
4292
4335
  this.on(event, listener);
4293
4336
  });
@@ -4573,7 +4616,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4573
4616
  const finalDelay = Math.min(delayTime, remainingTime);
4574
4617
  options.signal?.throwIfAborted();
4575
4618
  if (finalDelay > 0) {
4576
- await new Promise((resolve11, reject) => {
4619
+ await new Promise((resolve12, reject) => {
4577
4620
  const onAbort = () => {
4578
4621
  clearTimeout(timeoutToken);
4579
4622
  options.signal?.removeEventListener("abort", onAbort);
@@ -4581,7 +4624,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4581
4624
  };
4582
4625
  const timeoutToken = setTimeout(() => {
4583
4626
  options.signal?.removeEventListener("abort", onAbort);
4584
- resolve11();
4627
+ resolve12();
4585
4628
  }, finalDelay);
4586
4629
  if (options.unref) {
4587
4630
  timeoutToken.unref?.();
@@ -4822,6 +4865,8 @@ var BaseEmbeddingProvider = class {
4822
4865
  this.credentials = credentials;
4823
4866
  this.modelInfo = modelInfo;
4824
4867
  }
4868
+ credentials;
4869
+ modelInfo;
4825
4870
  async embedQuery(query) {
4826
4871
  const result = await this.embedBatch([query]);
4827
4872
  return {
@@ -6370,17 +6415,17 @@ var Database = class {
6370
6415
  if (edges.length === 0) return;
6371
6416
  this.inner.upsertCallEdgesBatch(edges);
6372
6417
  }
6373
- getCallers(targetName, branch) {
6418
+ getCallers(targetName, branch, callTypeFilter) {
6374
6419
  this.throwIfClosed();
6375
- return this.inner.getCallers(targetName, branch);
6420
+ return this.inner.getCallers(targetName, branch, callTypeFilter ?? null);
6376
6421
  }
6377
- getCallersWithContext(targetName, branch) {
6422
+ getCallersWithContext(targetName, branch, callTypeFilter) {
6378
6423
  this.throwIfClosed();
6379
- return this.inner.getCallersWithContext(targetName, branch);
6424
+ return this.inner.getCallersWithContext(targetName, branch, callTypeFilter ?? null);
6380
6425
  }
6381
- getCallees(symbolId, branch) {
6426
+ getCallees(symbolId, branch, callTypeFilter) {
6382
6427
  this.throwIfClosed();
6383
- return this.inner.getCallees(symbolId, branch);
6428
+ return this.inner.getCallees(symbolId, branch, callTypeFilter ?? null);
6384
6429
  }
6385
6430
  deleteCallEdgesByFile(filePath) {
6386
6431
  this.throwIfClosed();
@@ -6390,6 +6435,10 @@ var Database = class {
6390
6435
  this.throwIfClosed();
6391
6436
  this.inner.resolveCallEdge(edgeId, toSymbolId);
6392
6437
  }
6438
+ findShortestPath(fromName, toName, branch, maxDepth) {
6439
+ this.throwIfClosed();
6440
+ return this.inner.findShortestPath(fromName, toName, branch, maxDepth ?? null);
6441
+ }
6393
6442
  addSymbolsToBranch(branch, symbolIds) {
6394
6443
  this.throwIfClosed();
6395
6444
  this.inner.addSymbolsToBranch(branch, symbolIds);
@@ -9017,6 +9066,7 @@ var Indexer = class {
9017
9066
  targetName: site.calleeName,
9018
9067
  toSymbolId: void 0,
9019
9068
  callType: site.callType,
9069
+ confidence: site.confidence,
9020
9070
  line: site.line,
9021
9071
  col: site.column,
9022
9072
  isResolved: false
@@ -9161,7 +9211,7 @@ var Indexer = class {
9161
9211
  for (const requestBatch of requestBatches) {
9162
9212
  queue.add(async () => {
9163
9213
  if (rateLimitBackoffMs > 0) {
9164
- await new Promise((resolve11) => setTimeout(resolve11, rateLimitBackoffMs));
9214
+ await new Promise((resolve12) => setTimeout(resolve12, rateLimitBackoffMs));
9165
9215
  }
9166
9216
  try {
9167
9217
  const result = await pRetry(
@@ -10136,12 +10186,12 @@ var Indexer = class {
10136
10186
  })
10137
10187
  );
10138
10188
  }
10139
- async getCallers(targetName) {
10189
+ async getCallers(targetName, callTypeFilter) {
10140
10190
  const { database } = await this.ensureInitialized();
10141
10191
  const seen = /* @__PURE__ */ new Set();
10142
10192
  const results = [];
10143
10193
  for (const branchKey of this.getBranchCatalogKeys()) {
10144
- for (const edge of database.getCallersWithContext(targetName, branchKey)) {
10194
+ for (const edge of database.getCallersWithContext(targetName, branchKey, callTypeFilter)) {
10145
10195
  if (!seen.has(edge.id)) {
10146
10196
  seen.add(edge.id);
10147
10197
  results.push(edge);
@@ -10150,12 +10200,12 @@ var Indexer = class {
10150
10200
  }
10151
10201
  return results;
10152
10202
  }
10153
- async getCallees(symbolId) {
10203
+ async getCallees(symbolId, callTypeFilter) {
10154
10204
  const { database } = await this.ensureInitialized();
10155
10205
  const seen = /* @__PURE__ */ new Set();
10156
10206
  const results = [];
10157
10207
  for (const branchKey of this.getBranchCatalogKeys()) {
10158
- for (const edge of database.getCallees(symbolId, branchKey)) {
10208
+ for (const edge of database.getCallees(symbolId, branchKey, callTypeFilter)) {
10159
10209
  if (!seen.has(edge.id)) {
10160
10210
  seen.add(edge.id);
10161
10211
  results.push(edge);
@@ -10164,6 +10214,17 @@ var Indexer = class {
10164
10214
  }
10165
10215
  return results;
10166
10216
  }
10217
+ async findCallPath(fromName, toName, maxDepth) {
10218
+ const { database } = await this.ensureInitialized();
10219
+ let shortest = [];
10220
+ for (const branchKey of this.getBranchCatalogKeys()) {
10221
+ const path18 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
10222
+ if (path18.length > 0 && (shortest.length === 0 || path18.length < shortest.length)) {
10223
+ shortest = path18;
10224
+ }
10225
+ }
10226
+ return shortest;
10227
+ }
10167
10228
  async close() {
10168
10229
  await this.database?.close();
10169
10230
  this.database = null;
@@ -10485,39 +10546,45 @@ function ensureStringArray(value) {
10485
10546
  return Array.isArray(value) ? value : [];
10486
10547
  }
10487
10548
  var z = import_plugin.tool.schema;
10488
- var sharedIndexer = null;
10489
- var sharedProjectRoot = "";
10549
+ var indexerMap = /* @__PURE__ */ new Map();
10550
+ var defaultProjectRoot = "";
10490
10551
  function initializeTools(projectRoot, config) {
10491
- sharedProjectRoot = projectRoot;
10492
- sharedIndexer = new Indexer(projectRoot, config);
10493
- }
10494
- function getSharedIndexer() {
10495
- return getIndexer();
10552
+ defaultProjectRoot = projectRoot;
10553
+ const indexer = new Indexer(projectRoot, config);
10554
+ indexerMap.set(projectRoot, indexer);
10496
10555
  }
10497
- function refreshIndexerFromConfig() {
10498
- if (!sharedProjectRoot) {
10556
+ function refreshIndexerForDirectory(projectRoot) {
10557
+ if (!projectRoot) {
10499
10558
  throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10500
10559
  }
10501
- sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig(sharedProjectRoot)));
10560
+ const indexer = new Indexer(projectRoot, parseConfig(loadRuntimeConfig(projectRoot)));
10561
+ indexerMap.set(projectRoot, indexer);
10502
10562
  }
10503
- function shouldForceLocalizeProjectIndex() {
10504
- const currentConfig = parseConfig(loadRuntimeConfig(sharedProjectRoot));
10563
+ function shouldForceLocalizeProjectIndex(projectRoot) {
10564
+ const currentConfig = parseConfig(loadRuntimeConfig(projectRoot));
10505
10565
  if (currentConfig.scope !== "project") {
10506
10566
  return false;
10507
10567
  }
10508
- const localIndexPath = path15.join(sharedProjectRoot, ".opencode", "index");
10509
- const mainRepoRoot = resolveWorktreeMainRepoRoot(sharedProjectRoot);
10568
+ const localIndexPath = path15.join(projectRoot, ".opencode", "index");
10569
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
10510
10570
  if (!mainRepoRoot) {
10511
10571
  return false;
10512
10572
  }
10513
10573
  const inheritedIndexPath = path15.join(mainRepoRoot, ".opencode", "index");
10514
10574
  return !(0, import_fs9.existsSync)(localIndexPath) && (0, import_fs9.existsSync)(inheritedIndexPath);
10515
10575
  }
10516
- function getIndexer() {
10517
- if (!sharedIndexer) {
10576
+ function getIndexerForProject(directory) {
10577
+ const projectRoot = directory || defaultProjectRoot;
10578
+ if (!projectRoot) {
10518
10579
  throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10519
10580
  }
10520
- return sharedIndexer;
10581
+ let indexer = indexerMap.get(projectRoot);
10582
+ if (!indexer) {
10583
+ const config = parseConfig(loadRuntimeConfig(projectRoot));
10584
+ indexer = new Indexer(projectRoot, config);
10585
+ indexerMap.set(projectRoot, indexer);
10586
+ }
10587
+ return indexer;
10521
10588
  }
10522
10589
  var codebase_peek = (0, import_plugin.tool)({
10523
10590
  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.",
@@ -10528,8 +10595,8 @@ var codebase_peek = (0, import_plugin.tool)({
10528
10595
  directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
10529
10596
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type")
10530
10597
  },
10531
- async execute(args) {
10532
- const indexer = getIndexer();
10598
+ async execute(args, context) {
10599
+ const indexer = getIndexerForProject(context?.worktree);
10533
10600
  const results = await indexer.search(args.query, args.limit ?? 10, {
10534
10601
  fileType: args.fileType,
10535
10602
  directory: args.directory,
@@ -10547,16 +10614,17 @@ var index_codebase = (0, import_plugin.tool)({
10547
10614
  verbose: z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
10548
10615
  },
10549
10616
  async execute(args, context) {
10550
- let indexer = getIndexer();
10617
+ const projectRoot = context?.worktree || defaultProjectRoot;
10618
+ let indexer = getIndexerForProject(projectRoot);
10551
10619
  if (args.estimateOnly) {
10552
10620
  const estimate = await indexer.estimateCost();
10553
10621
  return formatCostEstimate(estimate);
10554
10622
  }
10555
10623
  if (args.force) {
10556
- if (shouldForceLocalizeProjectIndex()) {
10557
- materializeLocalProjectConfig(sharedProjectRoot, loadProjectConfigLayer(sharedProjectRoot));
10558
- refreshIndexerFromConfig();
10559
- indexer = getIndexer();
10624
+ if (shouldForceLocalizeProjectIndex(projectRoot)) {
10625
+ materializeLocalProjectConfig(projectRoot, loadProjectConfigLayer(projectRoot));
10626
+ refreshIndexerForDirectory(projectRoot);
10627
+ indexer = getIndexerForProject(projectRoot);
10560
10628
  }
10561
10629
  await indexer.clearIndex();
10562
10630
  }
@@ -10579,8 +10647,8 @@ var index_codebase = (0, import_plugin.tool)({
10579
10647
  var index_status = (0, import_plugin.tool)({
10580
10648
  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.",
10581
10649
  args: {},
10582
- async execute() {
10583
- const indexer = getIndexer();
10650
+ async execute(_args, context) {
10651
+ const indexer = getIndexerForProject(context?.worktree);
10584
10652
  const status = await indexer.getStatus();
10585
10653
  return formatStatus(status);
10586
10654
  }
@@ -10588,8 +10656,8 @@ var index_status = (0, import_plugin.tool)({
10588
10656
  var index_health_check = (0, import_plugin.tool)({
10589
10657
  description: "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
10590
10658
  args: {},
10591
- async execute() {
10592
- const indexer = getIndexer();
10659
+ async execute(_args, context) {
10660
+ const indexer = getIndexerForProject(context?.worktree);
10593
10661
  const result = await indexer.healthCheck();
10594
10662
  return formatHealthCheck(result);
10595
10663
  }
@@ -10597,8 +10665,8 @@ var index_health_check = (0, import_plugin.tool)({
10597
10665
  var index_metrics = (0, import_plugin.tool)({
10598
10666
  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.",
10599
10667
  args: {},
10600
- async execute() {
10601
- const indexer = getIndexer();
10668
+ async execute(_args, context) {
10669
+ const indexer = getIndexerForProject(context?.worktree);
10602
10670
  const logger = indexer.getLogger();
10603
10671
  if (!logger.isEnabled()) {
10604
10672
  return 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true,\n "metrics": true\n }\n}\n```';
@@ -10616,8 +10684,8 @@ var index_logs = (0, import_plugin.tool)({
10616
10684
  category: z.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional().describe("Filter by log category"),
10617
10685
  level: z.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
10618
10686
  },
10619
- async execute(args) {
10620
- const indexer = getIndexer();
10687
+ async execute(args, context) {
10688
+ const indexer = getIndexerForProject(context?.worktree);
10621
10689
  const logger = indexer.getLogger();
10622
10690
  if (!logger.isEnabled()) {
10623
10691
  return 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true\n }\n}\n```';
@@ -10643,8 +10711,8 @@ var find_similar = (0, import_plugin.tool)({
10643
10711
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10644
10712
  excludeFile: z.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)")
10645
10713
  },
10646
- async execute(args) {
10647
- const indexer = getIndexer();
10714
+ async execute(args, context) {
10715
+ const indexer = getIndexerForProject(context?.worktree);
10648
10716
  const results = await indexer.findSimilar(args.code, args.limit, {
10649
10717
  fileType: args.fileType,
10650
10718
  directory: args.directory,
@@ -10667,8 +10735,8 @@ var codebase_search = (0, import_plugin.tool)({
10667
10735
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10668
10736
  contextLines: z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
10669
10737
  },
10670
- async execute(args) {
10671
- const indexer = getIndexer();
10738
+ async execute(args, context) {
10739
+ const indexer = getIndexerForProject(context?.worktree);
10672
10740
  const results = await indexer.search(args.query, args.limit ?? 5, {
10673
10741
  fileType: args.fileType,
10674
10742
  directory: args.directory,
@@ -10689,8 +10757,8 @@ var implementation_lookup = (0, import_plugin.tool)({
10689
10757
  fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
10690
10758
  directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
10691
10759
  },
10692
- async execute(args) {
10693
- const indexer = getIndexer();
10760
+ async execute(args, context) {
10761
+ const indexer = getIndexerForProject(context?.worktree);
10694
10762
  const results = await indexer.search(args.query, args.limit ?? 5, {
10695
10763
  fileType: args.fileType,
10696
10764
  directory: args.directory,
@@ -10700,46 +10768,72 @@ var implementation_lookup = (0, import_plugin.tool)({
10700
10768
  }
10701
10769
  });
10702
10770
  var call_graph = (0, import_plugin.tool)({
10703
- description: "Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies between functions.",
10771
+ 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.",
10704
10772
  args: {
10705
10773
  name: z.string().describe("Function or method name to query"),
10706
10774
  direction: z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
10707
- symbolId: z.string().optional().describe("Symbol ID (required for 'callees' direction, returned by previous call_graph queries)")
10775
+ symbolId: z.string().optional().describe("Symbol ID (required for 'callees' direction, returned by previous call_graph queries)"),
10776
+ relationshipType: z.enum(["Call", "MethodCall", "Constructor", "Import", "Inherits", "Implements"]).optional().describe("Filter by relationship type. Omit to show all.")
10708
10777
  },
10709
- async execute(args) {
10710
- const indexer = getIndexer();
10778
+ async execute(args, context) {
10779
+ const indexer = getIndexerForProject(context?.worktree);
10711
10780
  if (args.direction === "callees") {
10712
10781
  if (!args.symbolId) {
10713
10782
  return "Error: 'symbolId' is required when direction is 'callees'. First use direction='callers' to find the symbol ID.";
10714
10783
  }
10715
- const callees = await indexer.getCallees(args.symbolId);
10784
+ const callees = await indexer.getCallees(args.symbolId, args.relationshipType);
10716
10785
  if (callees.length === 0) {
10717
- return `No callees found for symbol ${args.symbolId}. The function may not call any other tracked functions.`;
10786
+ return `No callees found for symbol ${args.symbolId}${args.relationshipType ? ` with type ${args.relationshipType}` : ""}. The function may not call any other tracked functions.`;
10718
10787
  }
10719
- const formatted2 = callees.map(
10720
- (e, i) => `[${i + 1}] \u2192 ${e.targetName} (${e.callType}) at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`
10721
- );
10788
+ const formatted2 = callees.map((e, i) => {
10789
+ const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
10790
+ return `[${i + 1}] \u2192 ${e.targetName} (${e.callType})${conf} at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`;
10791
+ });
10722
10792
  return formatted2.join("\n");
10723
10793
  }
10724
- const callers = await indexer.getCallers(args.name);
10794
+ const callers = await indexer.getCallers(args.name, args.relationshipType);
10725
10795
  if (callers.length === 0) {
10726
- return `No callers found for "${args.name}". It may not be called by any tracked function, or the index needs updating.`;
10796
+ 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.`;
10727
10797
  }
10728
- const formatted = callers.map(
10729
- (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]"}`
10730
- );
10798
+ const formatted = callers.map((e, i) => {
10799
+ const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
10800
+ 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]"}`;
10801
+ });
10731
10802
  return formatted.join("\n");
10732
10803
  }
10733
10804
  });
10805
+ var call_graph_path = (0, import_plugin.tool)({
10806
+ 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.",
10807
+ args: {
10808
+ from: z.string().describe("Source function/method name (starting point)"),
10809
+ to: z.string().describe("Target function/method name (destination)"),
10810
+ maxDepth: z.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
10811
+ },
10812
+ async execute(args, context) {
10813
+ const indexer = getIndexerForProject(context?.worktree);
10814
+ const path18 = await indexer.findCallPath(args.from, args.to, args.maxDepth);
10815
+ if (path18.length === 0) {
10816
+ return `No path found between "${args.from}" and "${args.to}". They may be in disconnected components, or the call graph index needs updating.`;
10817
+ }
10818
+ const formatted = path18.map((hop, i) => {
10819
+ const prefix = i === 0 ? "[start]" : `--${hop.callType}-->`;
10820
+ const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
10821
+ return `${prefix} ${hop.symbolName}${location}`;
10822
+ });
10823
+ return `Path (${path18.length} hops):
10824
+ ${formatted.join("\n")}`;
10825
+ }
10826
+ });
10734
10827
  var add_knowledge_base = (0, import_plugin.tool)({
10735
10828
  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).",
10736
10829
  args: {
10737
10830
  path: z.string().describe("Path to the folder to add as a knowledge base (absolute or relative to project root)")
10738
10831
  },
10739
- async execute(args) {
10832
+ async execute(args, context) {
10833
+ const projectRoot = context?.worktree || defaultProjectRoot;
10740
10834
  const inputPath = args.path.trim();
10741
10835
  const normalizedPath = path15.resolve(
10742
- path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, sharedProjectRoot)
10836
+ path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, projectRoot)
10743
10837
  );
10744
10838
  if (!(0, import_fs9.existsSync)(normalizedPath)) {
10745
10839
  return `Error: Directory does not exist: ${normalizedPath}`;
@@ -10781,21 +10875,21 @@ var add_knowledge_base = (0, import_plugin.tool)({
10781
10875
  } catch (error) {
10782
10876
  return `Error: Cannot access directory: ${normalizedPath} - ${error instanceof Error ? error.message : String(error)}`;
10783
10877
  }
10784
- const config = loadEditableConfig(sharedProjectRoot);
10878
+ const config = loadEditableConfig(projectRoot);
10785
10879
  const knowledgeBases = ensureStringArray(config.knowledgeBases);
10786
- const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, sharedProjectRoot);
10880
+ const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, projectRoot);
10787
10881
  if (alreadyExists) {
10788
10882
  return `Knowledge base already configured: ${normalizedPath}`;
10789
10883
  }
10790
10884
  knowledgeBases.push(normalizedPath);
10791
10885
  config.knowledgeBases = knowledgeBases;
10792
- saveConfig(sharedProjectRoot, config);
10793
- refreshIndexerFromConfig();
10886
+ saveConfig(projectRoot, config);
10887
+ refreshIndexerForDirectory(projectRoot);
10794
10888
  let result = `${normalizedPath}
10795
10889
  `;
10796
10890
  result += `Total knowledge bases: ${knowledgeBases.length}
10797
10891
  `;
10798
- result += `Config saved to: ${getConfigPath(sharedProjectRoot)}
10892
+ result += `Config saved to: ${getConfigPath(projectRoot)}
10799
10893
  `;
10800
10894
  result += `
10801
10895
  Run /index to rebuild the index with the new knowledge base.`;
@@ -10805,8 +10899,9 @@ Run /index to rebuild the index with the new knowledge base.`;
10805
10899
  var list_knowledge_bases = (0, import_plugin.tool)({
10806
10900
  description: "List all configured knowledge base folders that are indexed alongside the main project.",
10807
10901
  args: {},
10808
- async execute() {
10809
- const config = loadRuntimeConfig(sharedProjectRoot);
10902
+ async execute(_args, context) {
10903
+ const projectRoot = context?.worktree || defaultProjectRoot;
10904
+ const config = loadRuntimeConfig(projectRoot);
10810
10905
  const knowledgeBases = ensureStringArray(config.knowledgeBases);
10811
10906
  if (knowledgeBases.length === 0) {
10812
10907
  return "No knowledge bases configured. Use add_knowledge_base to add folders.";
@@ -10816,7 +10911,7 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10816
10911
  `;
10817
10912
  for (let i = 0; i < knowledgeBases.length; i++) {
10818
10913
  const kb = knowledgeBases[i];
10819
- const resolvedPath = resolveKnowledgeBasePath(kb, sharedProjectRoot);
10914
+ const resolvedPath = resolveKnowledgeBasePath(kb, projectRoot);
10820
10915
  const exists = (0, import_fs9.existsSync)(resolvedPath);
10821
10916
  result += `[${i + 1}] ${kb}
10822
10917
  `;
@@ -10834,7 +10929,7 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10834
10929
  }
10835
10930
  result += "\n";
10836
10931
  }
10837
- result += `Config file: ${getConfigPath(sharedProjectRoot)}`;
10932
+ result += `Config file: ${getConfigPath(projectRoot)}`;
10838
10933
  return result;
10839
10934
  }
10840
10935
  });
@@ -10843,14 +10938,15 @@ var remove_knowledge_base = (0, import_plugin.tool)({
10843
10938
  args: {
10844
10939
  path: z.string().describe("Path of the knowledge base to remove (must match the configured path exactly)")
10845
10940
  },
10846
- async execute(args) {
10941
+ async execute(args, context) {
10942
+ const projectRoot = context?.worktree || defaultProjectRoot;
10847
10943
  const inputPath = args.path.trim();
10848
- const config = loadEditableConfig(sharedProjectRoot);
10944
+ const config = loadEditableConfig(projectRoot);
10849
10945
  const knowledgeBases = ensureStringArray(config.knowledgeBases);
10850
10946
  if (knowledgeBases.length === 0) {
10851
10947
  return "No knowledge bases configured.";
10852
10948
  }
10853
- const index = findKnowledgeBasePathIndex(knowledgeBases, inputPath, sharedProjectRoot);
10949
+ const index = findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot);
10854
10950
  if (index === -1) {
10855
10951
  let result2 = `Knowledge base not found: ${inputPath}
10856
10952
 
@@ -10865,14 +10961,14 @@ var remove_knowledge_base = (0, import_plugin.tool)({
10865
10961
  }
10866
10962
  const removed = knowledgeBases.splice(index, 1)[0];
10867
10963
  config.knowledgeBases = knowledgeBases;
10868
- saveConfig(sharedProjectRoot, config);
10869
- refreshIndexerFromConfig();
10964
+ saveConfig(projectRoot, config);
10965
+ refreshIndexerForDirectory(projectRoot);
10870
10966
  let result = `Removed: ${removed}
10871
10967
 
10872
10968
  `;
10873
10969
  result += `Remaining knowledge bases: ${knowledgeBases.length}
10874
10970
  `;
10875
- result += `Config saved to: ${getConfigPath(sharedProjectRoot)}
10971
+ result += `Config saved to: ${getConfigPath(projectRoot)}
10876
10972
  `;
10877
10973
  result += `
10878
10974
  Run /index to rebuild the index without the removed knowledge base.`;
@@ -11159,6 +11255,8 @@ var RoutingHintController = class {
11159
11255
  this.getStatus = getStatus;
11160
11256
  this.maxSessions = maxSessions;
11161
11257
  }
11258
+ getStatus;
11259
+ maxSessions;
11162
11260
  sessionState = /* @__PURE__ */ new Map();
11163
11261
  observeUserMessage(sessionID, parts) {
11164
11262
  const assessment = assessRoutingIntent(extractUserText(parts));
@@ -11216,10 +11314,16 @@ var RoutingHintController = class {
11216
11314
 
11217
11315
  // src/index.ts
11218
11316
  var import_meta2 = {};
11219
- var activeWatcher = null;
11220
- function replaceActiveWatcher(nextWatcher) {
11221
- activeWatcher?.stop();
11222
- activeWatcher = nextWatcher;
11317
+ var activeWatchers = /* @__PURE__ */ new Map();
11318
+ function replaceActiveWatcher(projectRoot, nextWatcher) {
11319
+ const existing = activeWatchers.get(projectRoot);
11320
+ if (existing) {
11321
+ existing.stop();
11322
+ activeWatchers.delete(projectRoot);
11323
+ }
11324
+ if (nextWatcher) {
11325
+ activeWatchers.set(projectRoot, nextWatcher);
11326
+ }
11223
11327
  }
11224
11328
  function getCommandsDir() {
11225
11329
  let currentDir = process.cwd();
@@ -11228,21 +11332,37 @@ function getCommandsDir() {
11228
11332
  }
11229
11333
  return path17.join(currentDir, "..", "commands");
11230
11334
  }
11231
- var plugin = async ({ directory }) => {
11335
+ function appendRoutingHints(output, hints, preferredRole) {
11336
+ const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
11337
+ if (Array.isArray(preferredBucket)) {
11338
+ preferredBucket.push(...hints);
11339
+ return;
11340
+ }
11341
+ if (Array.isArray(output.system)) {
11342
+ output.system.push(...hints);
11343
+ }
11344
+ }
11345
+ var plugin = async ({ directory, worktree }) => {
11232
11346
  try {
11233
- const projectRoot = directory;
11347
+ const projectRoot = worktree || directory;
11234
11348
  const rawConfig = loadMergedConfig(projectRoot);
11235
11349
  const config = parseConfig(rawConfig);
11236
11350
  initializeTools(projectRoot, config);
11237
- const indexer = getSharedIndexer();
11238
- const routingHints = config.search.routingHints ? new RoutingHintController(() => indexer.getStatus()) : null;
11239
- const isValidProject = !config.indexing.requireProjectMarker || hasProjectMarker(projectRoot);
11240
- if (!isValidProject) {
11351
+ const getProjectIndexer = () => getIndexerForProject(projectRoot);
11352
+ const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus()) : null;
11353
+ const isHomeDir = path17.resolve(projectRoot) === path17.resolve(os6.homedir());
11354
+ const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
11355
+ if (isHomeDir) {
11356
+ console.warn(
11357
+ `[codebase-index] Refusing to watch or index home directory "${projectRoot}". Open a specific project directory instead.`
11358
+ );
11359
+ } else if (!isValidProject) {
11241
11360
  console.warn(
11242
11361
  `[codebase-index] Skipping file watching and auto-indexing: no project marker found in "${projectRoot}". Set "indexing.requireProjectMarker": false in config to override.`
11243
11362
  );
11244
11363
  }
11245
11364
  if (config.indexing.autoIndex && isValidProject) {
11365
+ const indexer = getProjectIndexer();
11246
11366
  indexer.initialize().then(() => {
11247
11367
  indexer.index().catch(() => {
11248
11368
  });
@@ -11250,9 +11370,9 @@ var plugin = async ({ directory }) => {
11250
11370
  });
11251
11371
  }
11252
11372
  if (config.indexing.watchFiles && isValidProject) {
11253
- replaceActiveWatcher(createWatcherWithIndexer(getSharedIndexer, projectRoot, config));
11373
+ replaceActiveWatcher(projectRoot, createWatcherWithIndexer(getProjectIndexer, projectRoot, config));
11254
11374
  } else {
11255
- replaceActiveWatcher(null);
11375
+ replaceActiveWatcher(projectRoot, null);
11256
11376
  }
11257
11377
  return {
11258
11378
  tool: {
@@ -11265,6 +11385,7 @@ var plugin = async ({ directory }) => {
11265
11385
  index_logs,
11266
11386
  find_similar,
11267
11387
  call_graph,
11388
+ call_graph_path,
11268
11389
  implementation_lookup,
11269
11390
  add_knowledge_base,
11270
11391
  list_knowledge_bases,
@@ -11274,8 +11395,18 @@ var plugin = async ({ directory }) => {
11274
11395
  routingHints?.observeUserMessage(input.sessionID, output.parts);
11275
11396
  },
11276
11397
  async "experimental.chat.system.transform"(input, output) {
11398
+ if (config.search.routingHintRole !== "system") {
11399
+ return;
11400
+ }
11277
11401
  const hints = await routingHints?.getSystemHints(input.sessionID) ?? [];
11278
- output.system.push(...hints);
11402
+ appendRoutingHints(output, hints, "system");
11403
+ },
11404
+ async "experimental.chat.developer.transform"(input, output) {
11405
+ if (config.search.routingHintRole !== "developer") {
11406
+ return;
11407
+ }
11408
+ const hints = await routingHints?.getSystemHints(input.sessionID) ?? [];
11409
+ appendRoutingHints(output, hints, "developer");
11279
11410
  },
11280
11411
  async "tool.execute.after"(input) {
11281
11412
  routingHints?.markToolUsed(input.sessionID, input.tool);
@@ -11289,8 +11420,8 @@ var plugin = async ({ directory }) => {
11289
11420
  }
11290
11421
  }
11291
11422
  };
11292
- } catch (error) {
11293
- console.error("[codebase-index] Failed to initialize plugin:", error);
11423
+ } catch {
11424
+ console.error("[codebase-index] Failed to initialize plugin (check config and network)");
11294
11425
  return {
11295
11426
  tool: void 0,
11296
11427
  async config() {