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.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") {
@@ -647,6 +651,7 @@ var require_eventemitter3 = __commonJS({
647
651
  });
648
652
 
649
653
  // src/index.ts
654
+ import * as os6 from "os";
650
655
  import * as path17 from "path";
651
656
  import { fileURLToPath as fileURLToPath2 } from "url";
652
657
 
@@ -793,7 +798,8 @@ function getDefaultSearchConfig() {
793
798
  rrfK: 60,
794
799
  rerankTopN: 20,
795
800
  contextLines: 0,
796
- routingHints: true
801
+ routingHints: true,
802
+ routingHintRole: "system"
797
803
  };
798
804
  }
799
805
  function getDefaultRerankerBaseUrl(provider) {
@@ -914,7 +920,8 @@ function parseConfig(raw) {
914
920
  rrfK: typeof rawSearch.rrfK === "number" ? Math.max(1, Math.floor(rawSearch.rrfK)) : defaultSearch.rrfK,
915
921
  rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
916
922
  contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines,
917
- routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints
923
+ routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints,
924
+ routingHintRole: rawSearch.routingHintRole === "developer" || rawSearch.routingHintRole === "system" ? rawSearch.routingHintRole : defaultSearch.routingHintRole
918
925
  };
919
926
  const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
920
927
  const debug = {
@@ -1236,6 +1243,32 @@ function hasFilteredPathSegment(relativePath, separator = path4.sep) {
1236
1243
  (part) => isHiddenPathSegment(part) || isBuildPathSegment(part)
1237
1244
  );
1238
1245
  }
1246
+ var RESTRICTED_DIRECTORIES = /* @__PURE__ */ new Set([
1247
+ // macOS
1248
+ "library",
1249
+ "applications",
1250
+ "system",
1251
+ "volumes",
1252
+ "private",
1253
+ "cores",
1254
+ // Linux
1255
+ "proc",
1256
+ "sys",
1257
+ "dev",
1258
+ "run",
1259
+ "snap",
1260
+ // Windows
1261
+ "windows",
1262
+ "programdata",
1263
+ "program files",
1264
+ "program files (x86)",
1265
+ "$recycle.bin"
1266
+ ]);
1267
+ function isRestrictedDirectory(relativePath, separator = path4.sep) {
1268
+ const firstSegment = relativePath.split(separator)[0];
1269
+ if (!firstSegment) return false;
1270
+ return RESTRICTED_DIRECTORIES.has(firstSegment.toLowerCase());
1271
+ }
1239
1272
 
1240
1273
  // src/config/rebase.ts
1241
1274
  function isWithinRoot(rootDir, targetPath) {
@@ -2295,7 +2328,7 @@ var NodeFsHandler = class {
2295
2328
  this._addToNodeFs(path18, initialAdd, wh, depth + 1);
2296
2329
  }
2297
2330
  }).on(EV.ERROR, this._boundHandleError);
2298
- return new Promise((resolve11, reject) => {
2331
+ return new Promise((resolve12, reject) => {
2299
2332
  if (!stream)
2300
2333
  return reject();
2301
2334
  stream.once(STR_END, () => {
@@ -2304,7 +2337,7 @@ var NodeFsHandler = class {
2304
2337
  return;
2305
2338
  }
2306
2339
  const wasThrottled = throttler ? throttler.clear() : false;
2307
- resolve11(void 0);
2340
+ resolve12(void 0);
2308
2341
  previous.getChildren().filter((item) => {
2309
2342
  return item !== directory && !current.has(item);
2310
2343
  }).forEach((item) => {
@@ -3404,6 +3437,9 @@ var FileWatcher = class {
3404
3437
  if (hasFilteredPathSegment(relativePath, path8.sep)) {
3405
3438
  return true;
3406
3439
  }
3440
+ if (isRestrictedDirectory(relativePath, path8.sep)) {
3441
+ return true;
3442
+ }
3407
3443
  if (ignoreFilter.ignores(relativePath)) {
3408
3444
  return true;
3409
3445
  }
@@ -3416,6 +3452,13 @@ var FileWatcher = class {
3416
3452
  pollInterval: 100
3417
3453
  }
3418
3454
  });
3455
+ this.watcher.on("error", (error) => {
3456
+ const err = error instanceof Error ? error : null;
3457
+ if (err?.code === "EPERM" || err?.code === "EACCES") {
3458
+ return;
3459
+ }
3460
+ console.error("[codebase-index] Watcher error:", err?.message ?? error);
3461
+ });
3419
3462
  this.watcher.on("add", (filePath) => this.handleChange("add", filePath));
3420
3463
  this.watcher.on("change", (filePath) => this.handleChange("change", filePath));
3421
3464
  this.watcher.on("unlink", (filePath) => this.handleChange("unlink", filePath));
@@ -3550,7 +3593,7 @@ var GitHeadWatcher = class {
3550
3593
  };
3551
3594
 
3552
3595
  // src/watcher/index.ts
3553
- function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3596
+ function createWatcherWithIndexer(getIndexer, projectRoot, config) {
3554
3597
  const fileWatcher = new FileWatcher(projectRoot, config);
3555
3598
  fileWatcher.start(async (changes) => {
3556
3599
  const hasAddOrChange = changes.some(
@@ -3558,7 +3601,7 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3558
3601
  );
3559
3602
  const hasDelete = changes.some((c) => c.type === "unlink");
3560
3603
  if (hasAddOrChange || hasDelete) {
3561
- await getIndexer2().index();
3604
+ await getIndexer().index();
3562
3605
  }
3563
3606
  });
3564
3607
  let gitWatcher = null;
@@ -3566,7 +3609,7 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3566
3609
  gitWatcher = new GitHeadWatcher(projectRoot);
3567
3610
  gitWatcher.start(async (oldBranch, newBranch) => {
3568
3611
  console.log(`Branch changed: ${oldBranch ?? "(none)"} -> ${newBranch}`);
3569
- await getIndexer2().index();
3612
+ await getIndexer().index();
3570
3613
  });
3571
3614
  }
3572
3615
  return {
@@ -3609,7 +3652,7 @@ function pTimeout(promise, options) {
3609
3652
  } = options;
3610
3653
  let timer;
3611
3654
  let abortHandler;
3612
- const wrappedPromise = new Promise((resolve11, reject) => {
3655
+ const wrappedPromise = new Promise((resolve12, reject) => {
3613
3656
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
3614
3657
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
3615
3658
  }
@@ -3623,7 +3666,7 @@ function pTimeout(promise, options) {
3623
3666
  };
3624
3667
  signal.addEventListener("abort", abortHandler, { once: true });
3625
3668
  }
3626
- promise.then(resolve11, reject);
3669
+ promise.then(resolve12, reject);
3627
3670
  if (milliseconds === Number.POSITIVE_INFINITY) {
3628
3671
  return;
3629
3672
  }
@@ -3631,7 +3674,7 @@ function pTimeout(promise, options) {
3631
3674
  timer = customTimers.setTimeout.call(void 0, () => {
3632
3675
  if (fallback) {
3633
3676
  try {
3634
- resolve11(fallback());
3677
+ resolve12(fallback());
3635
3678
  } catch (error) {
3636
3679
  reject(error);
3637
3680
  }
@@ -3641,7 +3684,7 @@ function pTimeout(promise, options) {
3641
3684
  promise.cancel();
3642
3685
  }
3643
3686
  if (message === false) {
3644
- resolve11();
3687
+ resolve12();
3645
3688
  } else if (message instanceof Error) {
3646
3689
  reject(message);
3647
3690
  } else {
@@ -4043,7 +4086,7 @@ var PQueue = class extends import_index.default {
4043
4086
  // Assign unique ID if not provided
4044
4087
  id: options.id ?? (this.#idAssigner++).toString()
4045
4088
  };
4046
- return new Promise((resolve11, reject) => {
4089
+ return new Promise((resolve12, reject) => {
4047
4090
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
4048
4091
  let cleanupQueueAbortHandler = () => void 0;
4049
4092
  const run = async () => {
@@ -4083,7 +4126,7 @@ var PQueue = class extends import_index.default {
4083
4126
  })]);
4084
4127
  }
4085
4128
  const result = await operation;
4086
- resolve11(result);
4129
+ resolve12(result);
4087
4130
  this.emit("completed", result);
4088
4131
  } catch (error) {
4089
4132
  reject(error);
@@ -4271,13 +4314,13 @@ var PQueue = class extends import_index.default {
4271
4314
  });
4272
4315
  }
4273
4316
  async #onEvent(event, filter) {
4274
- return new Promise((resolve11) => {
4317
+ return new Promise((resolve12) => {
4275
4318
  const listener = () => {
4276
4319
  if (filter && !filter()) {
4277
4320
  return;
4278
4321
  }
4279
4322
  this.off(event, listener);
4280
- resolve11();
4323
+ resolve12();
4281
4324
  };
4282
4325
  this.on(event, listener);
4283
4326
  });
@@ -4563,7 +4606,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4563
4606
  const finalDelay = Math.min(delayTime, remainingTime);
4564
4607
  options.signal?.throwIfAborted();
4565
4608
  if (finalDelay > 0) {
4566
- await new Promise((resolve11, reject) => {
4609
+ await new Promise((resolve12, reject) => {
4567
4610
  const onAbort = () => {
4568
4611
  clearTimeout(timeoutToken);
4569
4612
  options.signal?.removeEventListener("abort", onAbort);
@@ -4571,7 +4614,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4571
4614
  };
4572
4615
  const timeoutToken = setTimeout(() => {
4573
4616
  options.signal?.removeEventListener("abort", onAbort);
4574
- resolve11();
4617
+ resolve12();
4575
4618
  }, finalDelay);
4576
4619
  if (options.unref) {
4577
4620
  timeoutToken.unref?.();
@@ -4812,6 +4855,8 @@ var BaseEmbeddingProvider = class {
4812
4855
  this.credentials = credentials;
4813
4856
  this.modelInfo = modelInfo;
4814
4857
  }
4858
+ credentials;
4859
+ modelInfo;
4815
4860
  async embedQuery(query) {
4816
4861
  const result = await this.embedBatch([query]);
4817
4862
  return {
@@ -6359,17 +6404,17 @@ var Database = class {
6359
6404
  if (edges.length === 0) return;
6360
6405
  this.inner.upsertCallEdgesBatch(edges);
6361
6406
  }
6362
- getCallers(targetName, branch) {
6407
+ getCallers(targetName, branch, callTypeFilter) {
6363
6408
  this.throwIfClosed();
6364
- return this.inner.getCallers(targetName, branch);
6409
+ return this.inner.getCallers(targetName, branch, callTypeFilter ?? null);
6365
6410
  }
6366
- getCallersWithContext(targetName, branch) {
6411
+ getCallersWithContext(targetName, branch, callTypeFilter) {
6367
6412
  this.throwIfClosed();
6368
- return this.inner.getCallersWithContext(targetName, branch);
6413
+ return this.inner.getCallersWithContext(targetName, branch, callTypeFilter ?? null);
6369
6414
  }
6370
- getCallees(symbolId, branch) {
6415
+ getCallees(symbolId, branch, callTypeFilter) {
6371
6416
  this.throwIfClosed();
6372
- return this.inner.getCallees(symbolId, branch);
6417
+ return this.inner.getCallees(symbolId, branch, callTypeFilter ?? null);
6373
6418
  }
6374
6419
  deleteCallEdgesByFile(filePath) {
6375
6420
  this.throwIfClosed();
@@ -6379,6 +6424,10 @@ var Database = class {
6379
6424
  this.throwIfClosed();
6380
6425
  this.inner.resolveCallEdge(edgeId, toSymbolId);
6381
6426
  }
6427
+ findShortestPath(fromName, toName, branch, maxDepth) {
6428
+ this.throwIfClosed();
6429
+ return this.inner.findShortestPath(fromName, toName, branch, maxDepth ?? null);
6430
+ }
6382
6431
  addSymbolsToBranch(branch, symbolIds) {
6383
6432
  this.throwIfClosed();
6384
6433
  this.inner.addSymbolsToBranch(branch, symbolIds);
@@ -9006,6 +9055,7 @@ var Indexer = class {
9006
9055
  targetName: site.calleeName,
9007
9056
  toSymbolId: void 0,
9008
9057
  callType: site.callType,
9058
+ confidence: site.confidence,
9009
9059
  line: site.line,
9010
9060
  col: site.column,
9011
9061
  isResolved: false
@@ -9150,7 +9200,7 @@ var Indexer = class {
9150
9200
  for (const requestBatch of requestBatches) {
9151
9201
  queue.add(async () => {
9152
9202
  if (rateLimitBackoffMs > 0) {
9153
- await new Promise((resolve11) => setTimeout(resolve11, rateLimitBackoffMs));
9203
+ await new Promise((resolve12) => setTimeout(resolve12, rateLimitBackoffMs));
9154
9204
  }
9155
9205
  try {
9156
9206
  const result = await pRetry(
@@ -10125,12 +10175,12 @@ var Indexer = class {
10125
10175
  })
10126
10176
  );
10127
10177
  }
10128
- async getCallers(targetName) {
10178
+ async getCallers(targetName, callTypeFilter) {
10129
10179
  const { database } = await this.ensureInitialized();
10130
10180
  const seen = /* @__PURE__ */ new Set();
10131
10181
  const results = [];
10132
10182
  for (const branchKey of this.getBranchCatalogKeys()) {
10133
- for (const edge of database.getCallersWithContext(targetName, branchKey)) {
10183
+ for (const edge of database.getCallersWithContext(targetName, branchKey, callTypeFilter)) {
10134
10184
  if (!seen.has(edge.id)) {
10135
10185
  seen.add(edge.id);
10136
10186
  results.push(edge);
@@ -10139,12 +10189,12 @@ var Indexer = class {
10139
10189
  }
10140
10190
  return results;
10141
10191
  }
10142
- async getCallees(symbolId) {
10192
+ async getCallees(symbolId, callTypeFilter) {
10143
10193
  const { database } = await this.ensureInitialized();
10144
10194
  const seen = /* @__PURE__ */ new Set();
10145
10195
  const results = [];
10146
10196
  for (const branchKey of this.getBranchCatalogKeys()) {
10147
- for (const edge of database.getCallees(symbolId, branchKey)) {
10197
+ for (const edge of database.getCallees(symbolId, branchKey, callTypeFilter)) {
10148
10198
  if (!seen.has(edge.id)) {
10149
10199
  seen.add(edge.id);
10150
10200
  results.push(edge);
@@ -10153,6 +10203,17 @@ var Indexer = class {
10153
10203
  }
10154
10204
  return results;
10155
10205
  }
10206
+ async findCallPath(fromName, toName, maxDepth) {
10207
+ const { database } = await this.ensureInitialized();
10208
+ let shortest = [];
10209
+ for (const branchKey of this.getBranchCatalogKeys()) {
10210
+ const path18 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
10211
+ if (path18.length > 0 && (shortest.length === 0 || path18.length < shortest.length)) {
10212
+ shortest = path18;
10213
+ }
10214
+ }
10215
+ return shortest;
10216
+ }
10156
10217
  async close() {
10157
10218
  await this.database?.close();
10158
10219
  this.database = null;
@@ -10474,39 +10535,45 @@ function ensureStringArray(value) {
10474
10535
  return Array.isArray(value) ? value : [];
10475
10536
  }
10476
10537
  var z = tool.schema;
10477
- var sharedIndexer = null;
10478
- var sharedProjectRoot = "";
10538
+ var indexerMap = /* @__PURE__ */ new Map();
10539
+ var defaultProjectRoot = "";
10479
10540
  function initializeTools(projectRoot, config) {
10480
- sharedProjectRoot = projectRoot;
10481
- sharedIndexer = new Indexer(projectRoot, config);
10482
- }
10483
- function getSharedIndexer() {
10484
- return getIndexer();
10541
+ defaultProjectRoot = projectRoot;
10542
+ const indexer = new Indexer(projectRoot, config);
10543
+ indexerMap.set(projectRoot, indexer);
10485
10544
  }
10486
- function refreshIndexerFromConfig() {
10487
- if (!sharedProjectRoot) {
10545
+ function refreshIndexerForDirectory(projectRoot) {
10546
+ if (!projectRoot) {
10488
10547
  throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10489
10548
  }
10490
- sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig(sharedProjectRoot)));
10549
+ const indexer = new Indexer(projectRoot, parseConfig(loadRuntimeConfig(projectRoot)));
10550
+ indexerMap.set(projectRoot, indexer);
10491
10551
  }
10492
- function shouldForceLocalizeProjectIndex() {
10493
- const currentConfig = parseConfig(loadRuntimeConfig(sharedProjectRoot));
10552
+ function shouldForceLocalizeProjectIndex(projectRoot) {
10553
+ const currentConfig = parseConfig(loadRuntimeConfig(projectRoot));
10494
10554
  if (currentConfig.scope !== "project") {
10495
10555
  return false;
10496
10556
  }
10497
- const localIndexPath = path15.join(sharedProjectRoot, ".opencode", "index");
10498
- const mainRepoRoot = resolveWorktreeMainRepoRoot(sharedProjectRoot);
10557
+ const localIndexPath = path15.join(projectRoot, ".opencode", "index");
10558
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
10499
10559
  if (!mainRepoRoot) {
10500
10560
  return false;
10501
10561
  }
10502
10562
  const inheritedIndexPath = path15.join(mainRepoRoot, ".opencode", "index");
10503
10563
  return !existsSync9(localIndexPath) && existsSync9(inheritedIndexPath);
10504
10564
  }
10505
- function getIndexer() {
10506
- if (!sharedIndexer) {
10565
+ function getIndexerForProject(directory) {
10566
+ const projectRoot = directory || defaultProjectRoot;
10567
+ if (!projectRoot) {
10507
10568
  throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10508
10569
  }
10509
- return sharedIndexer;
10570
+ let indexer = indexerMap.get(projectRoot);
10571
+ if (!indexer) {
10572
+ const config = parseConfig(loadRuntimeConfig(projectRoot));
10573
+ indexer = new Indexer(projectRoot, config);
10574
+ indexerMap.set(projectRoot, indexer);
10575
+ }
10576
+ return indexer;
10510
10577
  }
10511
10578
  var codebase_peek = tool({
10512
10579
  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.",
@@ -10517,8 +10584,8 @@ var codebase_peek = tool({
10517
10584
  directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
10518
10585
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type")
10519
10586
  },
10520
- async execute(args) {
10521
- const indexer = getIndexer();
10587
+ async execute(args, context) {
10588
+ const indexer = getIndexerForProject(context?.worktree);
10522
10589
  const results = await indexer.search(args.query, args.limit ?? 10, {
10523
10590
  fileType: args.fileType,
10524
10591
  directory: args.directory,
@@ -10536,16 +10603,17 @@ var index_codebase = tool({
10536
10603
  verbose: z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
10537
10604
  },
10538
10605
  async execute(args, context) {
10539
- let indexer = getIndexer();
10606
+ const projectRoot = context?.worktree || defaultProjectRoot;
10607
+ let indexer = getIndexerForProject(projectRoot);
10540
10608
  if (args.estimateOnly) {
10541
10609
  const estimate = await indexer.estimateCost();
10542
10610
  return formatCostEstimate(estimate);
10543
10611
  }
10544
10612
  if (args.force) {
10545
- if (shouldForceLocalizeProjectIndex()) {
10546
- materializeLocalProjectConfig(sharedProjectRoot, loadProjectConfigLayer(sharedProjectRoot));
10547
- refreshIndexerFromConfig();
10548
- indexer = getIndexer();
10613
+ if (shouldForceLocalizeProjectIndex(projectRoot)) {
10614
+ materializeLocalProjectConfig(projectRoot, loadProjectConfigLayer(projectRoot));
10615
+ refreshIndexerForDirectory(projectRoot);
10616
+ indexer = getIndexerForProject(projectRoot);
10549
10617
  }
10550
10618
  await indexer.clearIndex();
10551
10619
  }
@@ -10568,8 +10636,8 @@ var index_codebase = tool({
10568
10636
  var index_status = tool({
10569
10637
  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.",
10570
10638
  args: {},
10571
- async execute() {
10572
- const indexer = getIndexer();
10639
+ async execute(_args, context) {
10640
+ const indexer = getIndexerForProject(context?.worktree);
10573
10641
  const status = await indexer.getStatus();
10574
10642
  return formatStatus(status);
10575
10643
  }
@@ -10577,8 +10645,8 @@ var index_status = tool({
10577
10645
  var index_health_check = tool({
10578
10646
  description: "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
10579
10647
  args: {},
10580
- async execute() {
10581
- const indexer = getIndexer();
10648
+ async execute(_args, context) {
10649
+ const indexer = getIndexerForProject(context?.worktree);
10582
10650
  const result = await indexer.healthCheck();
10583
10651
  return formatHealthCheck(result);
10584
10652
  }
@@ -10586,8 +10654,8 @@ var index_health_check = tool({
10586
10654
  var index_metrics = tool({
10587
10655
  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.",
10588
10656
  args: {},
10589
- async execute() {
10590
- const indexer = getIndexer();
10657
+ async execute(_args, context) {
10658
+ const indexer = getIndexerForProject(context?.worktree);
10591
10659
  const logger = indexer.getLogger();
10592
10660
  if (!logger.isEnabled()) {
10593
10661
  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```';
@@ -10605,8 +10673,8 @@ var index_logs = tool({
10605
10673
  category: z.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional().describe("Filter by log category"),
10606
10674
  level: z.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
10607
10675
  },
10608
- async execute(args) {
10609
- const indexer = getIndexer();
10676
+ async execute(args, context) {
10677
+ const indexer = getIndexerForProject(context?.worktree);
10610
10678
  const logger = indexer.getLogger();
10611
10679
  if (!logger.isEnabled()) {
10612
10680
  return 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true\n }\n}\n```';
@@ -10632,8 +10700,8 @@ var find_similar = tool({
10632
10700
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10633
10701
  excludeFile: z.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)")
10634
10702
  },
10635
- async execute(args) {
10636
- const indexer = getIndexer();
10703
+ async execute(args, context) {
10704
+ const indexer = getIndexerForProject(context?.worktree);
10637
10705
  const results = await indexer.findSimilar(args.code, args.limit, {
10638
10706
  fileType: args.fileType,
10639
10707
  directory: args.directory,
@@ -10656,8 +10724,8 @@ var codebase_search = tool({
10656
10724
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10657
10725
  contextLines: z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
10658
10726
  },
10659
- async execute(args) {
10660
- const indexer = getIndexer();
10727
+ async execute(args, context) {
10728
+ const indexer = getIndexerForProject(context?.worktree);
10661
10729
  const results = await indexer.search(args.query, args.limit ?? 5, {
10662
10730
  fileType: args.fileType,
10663
10731
  directory: args.directory,
@@ -10678,8 +10746,8 @@ var implementation_lookup = tool({
10678
10746
  fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
10679
10747
  directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
10680
10748
  },
10681
- async execute(args) {
10682
- const indexer = getIndexer();
10749
+ async execute(args, context) {
10750
+ const indexer = getIndexerForProject(context?.worktree);
10683
10751
  const results = await indexer.search(args.query, args.limit ?? 5, {
10684
10752
  fileType: args.fileType,
10685
10753
  directory: args.directory,
@@ -10689,46 +10757,72 @@ var implementation_lookup = tool({
10689
10757
  }
10690
10758
  });
10691
10759
  var call_graph = tool({
10692
- description: "Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies between functions.",
10760
+ 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.",
10693
10761
  args: {
10694
10762
  name: z.string().describe("Function or method name to query"),
10695
10763
  direction: z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
10696
- symbolId: z.string().optional().describe("Symbol ID (required for 'callees' direction, returned by previous call_graph queries)")
10764
+ symbolId: z.string().optional().describe("Symbol ID (required for 'callees' direction, returned by previous call_graph queries)"),
10765
+ relationshipType: z.enum(["Call", "MethodCall", "Constructor", "Import", "Inherits", "Implements"]).optional().describe("Filter by relationship type. Omit to show all.")
10697
10766
  },
10698
- async execute(args) {
10699
- const indexer = getIndexer();
10767
+ async execute(args, context) {
10768
+ const indexer = getIndexerForProject(context?.worktree);
10700
10769
  if (args.direction === "callees") {
10701
10770
  if (!args.symbolId) {
10702
10771
  return "Error: 'symbolId' is required when direction is 'callees'. First use direction='callers' to find the symbol ID.";
10703
10772
  }
10704
- const callees = await indexer.getCallees(args.symbolId);
10773
+ const callees = await indexer.getCallees(args.symbolId, args.relationshipType);
10705
10774
  if (callees.length === 0) {
10706
- return `No callees found for symbol ${args.symbolId}. The function may not call any other tracked functions.`;
10775
+ return `No callees found for symbol ${args.symbolId}${args.relationshipType ? ` with type ${args.relationshipType}` : ""}. The function may not call any other tracked functions.`;
10707
10776
  }
10708
- const formatted2 = callees.map(
10709
- (e, i) => `[${i + 1}] \u2192 ${e.targetName} (${e.callType}) at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`
10710
- );
10777
+ const formatted2 = callees.map((e, i) => {
10778
+ const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
10779
+ return `[${i + 1}] \u2192 ${e.targetName} (${e.callType})${conf} at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`;
10780
+ });
10711
10781
  return formatted2.join("\n");
10712
10782
  }
10713
- const callers = await indexer.getCallers(args.name);
10783
+ const callers = await indexer.getCallers(args.name, args.relationshipType);
10714
10784
  if (callers.length === 0) {
10715
- return `No callers found for "${args.name}". It may not be called by any tracked function, or the index needs updating.`;
10785
+ 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.`;
10716
10786
  }
10717
- const formatted = callers.map(
10718
- (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]"}`
10719
- );
10787
+ const formatted = callers.map((e, i) => {
10788
+ const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
10789
+ 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]"}`;
10790
+ });
10720
10791
  return formatted.join("\n");
10721
10792
  }
10722
10793
  });
10794
+ var call_graph_path = tool({
10795
+ 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.",
10796
+ args: {
10797
+ from: z.string().describe("Source function/method name (starting point)"),
10798
+ to: z.string().describe("Target function/method name (destination)"),
10799
+ maxDepth: z.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
10800
+ },
10801
+ async execute(args, context) {
10802
+ const indexer = getIndexerForProject(context?.worktree);
10803
+ const path18 = await indexer.findCallPath(args.from, args.to, args.maxDepth);
10804
+ if (path18.length === 0) {
10805
+ return `No path found between "${args.from}" and "${args.to}". They may be in disconnected components, or the call graph index needs updating.`;
10806
+ }
10807
+ const formatted = path18.map((hop, i) => {
10808
+ const prefix = i === 0 ? "[start]" : `--${hop.callType}-->`;
10809
+ const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
10810
+ return `${prefix} ${hop.symbolName}${location}`;
10811
+ });
10812
+ return `Path (${path18.length} hops):
10813
+ ${formatted.join("\n")}`;
10814
+ }
10815
+ });
10723
10816
  var add_knowledge_base = tool({
10724
10817
  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).",
10725
10818
  args: {
10726
10819
  path: z.string().describe("Path to the folder to add as a knowledge base (absolute or relative to project root)")
10727
10820
  },
10728
- async execute(args) {
10821
+ async execute(args, context) {
10822
+ const projectRoot = context?.worktree || defaultProjectRoot;
10729
10823
  const inputPath = args.path.trim();
10730
10824
  const normalizedPath = path15.resolve(
10731
- path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, sharedProjectRoot)
10825
+ path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, projectRoot)
10732
10826
  );
10733
10827
  if (!existsSync9(normalizedPath)) {
10734
10828
  return `Error: Directory does not exist: ${normalizedPath}`;
@@ -10770,21 +10864,21 @@ var add_knowledge_base = tool({
10770
10864
  } catch (error) {
10771
10865
  return `Error: Cannot access directory: ${normalizedPath} - ${error instanceof Error ? error.message : String(error)}`;
10772
10866
  }
10773
- const config = loadEditableConfig(sharedProjectRoot);
10867
+ const config = loadEditableConfig(projectRoot);
10774
10868
  const knowledgeBases = ensureStringArray(config.knowledgeBases);
10775
- const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, sharedProjectRoot);
10869
+ const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, projectRoot);
10776
10870
  if (alreadyExists) {
10777
10871
  return `Knowledge base already configured: ${normalizedPath}`;
10778
10872
  }
10779
10873
  knowledgeBases.push(normalizedPath);
10780
10874
  config.knowledgeBases = knowledgeBases;
10781
- saveConfig(sharedProjectRoot, config);
10782
- refreshIndexerFromConfig();
10875
+ saveConfig(projectRoot, config);
10876
+ refreshIndexerForDirectory(projectRoot);
10783
10877
  let result = `${normalizedPath}
10784
10878
  `;
10785
10879
  result += `Total knowledge bases: ${knowledgeBases.length}
10786
10880
  `;
10787
- result += `Config saved to: ${getConfigPath(sharedProjectRoot)}
10881
+ result += `Config saved to: ${getConfigPath(projectRoot)}
10788
10882
  `;
10789
10883
  result += `
10790
10884
  Run /index to rebuild the index with the new knowledge base.`;
@@ -10794,8 +10888,9 @@ Run /index to rebuild the index with the new knowledge base.`;
10794
10888
  var list_knowledge_bases = tool({
10795
10889
  description: "List all configured knowledge base folders that are indexed alongside the main project.",
10796
10890
  args: {},
10797
- async execute() {
10798
- const config = loadRuntimeConfig(sharedProjectRoot);
10891
+ async execute(_args, context) {
10892
+ const projectRoot = context?.worktree || defaultProjectRoot;
10893
+ const config = loadRuntimeConfig(projectRoot);
10799
10894
  const knowledgeBases = ensureStringArray(config.knowledgeBases);
10800
10895
  if (knowledgeBases.length === 0) {
10801
10896
  return "No knowledge bases configured. Use add_knowledge_base to add folders.";
@@ -10805,7 +10900,7 @@ var list_knowledge_bases = tool({
10805
10900
  `;
10806
10901
  for (let i = 0; i < knowledgeBases.length; i++) {
10807
10902
  const kb = knowledgeBases[i];
10808
- const resolvedPath = resolveKnowledgeBasePath(kb, sharedProjectRoot);
10903
+ const resolvedPath = resolveKnowledgeBasePath(kb, projectRoot);
10809
10904
  const exists = existsSync9(resolvedPath);
10810
10905
  result += `[${i + 1}] ${kb}
10811
10906
  `;
@@ -10823,7 +10918,7 @@ var list_knowledge_bases = tool({
10823
10918
  }
10824
10919
  result += "\n";
10825
10920
  }
10826
- result += `Config file: ${getConfigPath(sharedProjectRoot)}`;
10921
+ result += `Config file: ${getConfigPath(projectRoot)}`;
10827
10922
  return result;
10828
10923
  }
10829
10924
  });
@@ -10832,14 +10927,15 @@ var remove_knowledge_base = tool({
10832
10927
  args: {
10833
10928
  path: z.string().describe("Path of the knowledge base to remove (must match the configured path exactly)")
10834
10929
  },
10835
- async execute(args) {
10930
+ async execute(args, context) {
10931
+ const projectRoot = context?.worktree || defaultProjectRoot;
10836
10932
  const inputPath = args.path.trim();
10837
- const config = loadEditableConfig(sharedProjectRoot);
10933
+ const config = loadEditableConfig(projectRoot);
10838
10934
  const knowledgeBases = ensureStringArray(config.knowledgeBases);
10839
10935
  if (knowledgeBases.length === 0) {
10840
10936
  return "No knowledge bases configured.";
10841
10937
  }
10842
- const index = findKnowledgeBasePathIndex(knowledgeBases, inputPath, sharedProjectRoot);
10938
+ const index = findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot);
10843
10939
  if (index === -1) {
10844
10940
  let result2 = `Knowledge base not found: ${inputPath}
10845
10941
 
@@ -10854,14 +10950,14 @@ var remove_knowledge_base = tool({
10854
10950
  }
10855
10951
  const removed = knowledgeBases.splice(index, 1)[0];
10856
10952
  config.knowledgeBases = knowledgeBases;
10857
- saveConfig(sharedProjectRoot, config);
10858
- refreshIndexerFromConfig();
10953
+ saveConfig(projectRoot, config);
10954
+ refreshIndexerForDirectory(projectRoot);
10859
10955
  let result = `Removed: ${removed}
10860
10956
 
10861
10957
  `;
10862
10958
  result += `Remaining knowledge bases: ${knowledgeBases.length}
10863
10959
  `;
10864
- result += `Config saved to: ${getConfigPath(sharedProjectRoot)}
10960
+ result += `Config saved to: ${getConfigPath(projectRoot)}
10865
10961
  `;
10866
10962
  result += `
10867
10963
  Run /index to rebuild the index without the removed knowledge base.`;
@@ -11148,6 +11244,8 @@ var RoutingHintController = class {
11148
11244
  this.getStatus = getStatus;
11149
11245
  this.maxSessions = maxSessions;
11150
11246
  }
11247
+ getStatus;
11248
+ maxSessions;
11151
11249
  sessionState = /* @__PURE__ */ new Map();
11152
11250
  observeUserMessage(sessionID, parts) {
11153
11251
  const assessment = assessRoutingIntent(extractUserText(parts));
@@ -11204,10 +11302,16 @@ var RoutingHintController = class {
11204
11302
  };
11205
11303
 
11206
11304
  // src/index.ts
11207
- var activeWatcher = null;
11208
- function replaceActiveWatcher(nextWatcher) {
11209
- activeWatcher?.stop();
11210
- activeWatcher = nextWatcher;
11305
+ var activeWatchers = /* @__PURE__ */ new Map();
11306
+ function replaceActiveWatcher(projectRoot, nextWatcher) {
11307
+ const existing = activeWatchers.get(projectRoot);
11308
+ if (existing) {
11309
+ existing.stop();
11310
+ activeWatchers.delete(projectRoot);
11311
+ }
11312
+ if (nextWatcher) {
11313
+ activeWatchers.set(projectRoot, nextWatcher);
11314
+ }
11211
11315
  }
11212
11316
  function getCommandsDir() {
11213
11317
  let currentDir = process.cwd();
@@ -11216,21 +11320,37 @@ function getCommandsDir() {
11216
11320
  }
11217
11321
  return path17.join(currentDir, "..", "commands");
11218
11322
  }
11219
- var plugin = async ({ directory }) => {
11323
+ function appendRoutingHints(output, hints, preferredRole) {
11324
+ const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
11325
+ if (Array.isArray(preferredBucket)) {
11326
+ preferredBucket.push(...hints);
11327
+ return;
11328
+ }
11329
+ if (Array.isArray(output.system)) {
11330
+ output.system.push(...hints);
11331
+ }
11332
+ }
11333
+ var plugin = async ({ directory, worktree }) => {
11220
11334
  try {
11221
- const projectRoot = directory;
11335
+ const projectRoot = worktree || directory;
11222
11336
  const rawConfig = loadMergedConfig(projectRoot);
11223
11337
  const config = parseConfig(rawConfig);
11224
11338
  initializeTools(projectRoot, config);
11225
- const indexer = getSharedIndexer();
11226
- const routingHints = config.search.routingHints ? new RoutingHintController(() => indexer.getStatus()) : null;
11227
- const isValidProject = !config.indexing.requireProjectMarker || hasProjectMarker(projectRoot);
11228
- if (!isValidProject) {
11339
+ const getProjectIndexer = () => getIndexerForProject(projectRoot);
11340
+ const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus()) : null;
11341
+ const isHomeDir = path17.resolve(projectRoot) === path17.resolve(os6.homedir());
11342
+ const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
11343
+ if (isHomeDir) {
11344
+ console.warn(
11345
+ `[codebase-index] Refusing to watch or index home directory "${projectRoot}". Open a specific project directory instead.`
11346
+ );
11347
+ } else if (!isValidProject) {
11229
11348
  console.warn(
11230
11349
  `[codebase-index] Skipping file watching and auto-indexing: no project marker found in "${projectRoot}". Set "indexing.requireProjectMarker": false in config to override.`
11231
11350
  );
11232
11351
  }
11233
11352
  if (config.indexing.autoIndex && isValidProject) {
11353
+ const indexer = getProjectIndexer();
11234
11354
  indexer.initialize().then(() => {
11235
11355
  indexer.index().catch(() => {
11236
11356
  });
@@ -11238,9 +11358,9 @@ var plugin = async ({ directory }) => {
11238
11358
  });
11239
11359
  }
11240
11360
  if (config.indexing.watchFiles && isValidProject) {
11241
- replaceActiveWatcher(createWatcherWithIndexer(getSharedIndexer, projectRoot, config));
11361
+ replaceActiveWatcher(projectRoot, createWatcherWithIndexer(getProjectIndexer, projectRoot, config));
11242
11362
  } else {
11243
- replaceActiveWatcher(null);
11363
+ replaceActiveWatcher(projectRoot, null);
11244
11364
  }
11245
11365
  return {
11246
11366
  tool: {
@@ -11253,6 +11373,7 @@ var plugin = async ({ directory }) => {
11253
11373
  index_logs,
11254
11374
  find_similar,
11255
11375
  call_graph,
11376
+ call_graph_path,
11256
11377
  implementation_lookup,
11257
11378
  add_knowledge_base,
11258
11379
  list_knowledge_bases,
@@ -11262,8 +11383,18 @@ var plugin = async ({ directory }) => {
11262
11383
  routingHints?.observeUserMessage(input.sessionID, output.parts);
11263
11384
  },
11264
11385
  async "experimental.chat.system.transform"(input, output) {
11386
+ if (config.search.routingHintRole !== "system") {
11387
+ return;
11388
+ }
11265
11389
  const hints = await routingHints?.getSystemHints(input.sessionID) ?? [];
11266
- output.system.push(...hints);
11390
+ appendRoutingHints(output, hints, "system");
11391
+ },
11392
+ async "experimental.chat.developer.transform"(input, output) {
11393
+ if (config.search.routingHintRole !== "developer") {
11394
+ return;
11395
+ }
11396
+ const hints = await routingHints?.getSystemHints(input.sessionID) ?? [];
11397
+ appendRoutingHints(output, hints, "developer");
11267
11398
  },
11268
11399
  async "tool.execute.after"(input) {
11269
11400
  routingHints?.markToolUsed(input.sessionID, input.tool);
@@ -11277,8 +11408,8 @@ var plugin = async ({ directory }) => {
11277
11408
  }
11278
11409
  }
11279
11410
  };
11280
- } catch (error) {
11281
- console.error("[codebase-index] Failed to initialize plugin:", error);
11411
+ } catch {
11412
+ console.error("[codebase-index] Failed to initialize plugin (check config and network)");
11282
11413
  return {
11283
11414
  tool: void 0,
11284
11415
  async config() {