opencode-codebase-index 0.9.0 → 0.10.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
@@ -657,6 +657,7 @@ __export(index_exports, {
657
657
  default: () => index_default
658
658
  });
659
659
  module.exports = __toCommonJS(index_exports);
660
+ var os6 = __toESM(require("os"), 1);
660
661
  var path17 = __toESM(require("path"), 1);
661
662
  var import_url2 = require("url");
662
663
 
@@ -803,7 +804,8 @@ function getDefaultSearchConfig() {
803
804
  rrfK: 60,
804
805
  rerankTopN: 20,
805
806
  contextLines: 0,
806
- routingHints: true
807
+ routingHints: true,
808
+ routingHintRole: "system"
807
809
  };
808
810
  }
809
811
  function getDefaultRerankerBaseUrl(provider) {
@@ -924,7 +926,8 @@ function parseConfig(raw) {
924
926
  rrfK: typeof rawSearch.rrfK === "number" ? Math.max(1, Math.floor(rawSearch.rrfK)) : defaultSearch.rrfK,
925
927
  rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
926
928
  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
929
+ routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints,
930
+ routingHintRole: rawSearch.routingHintRole === "developer" || rawSearch.routingHintRole === "system" ? rawSearch.routingHintRole : defaultSearch.routingHintRole
928
931
  };
929
932
  const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
930
933
  const debug = {
@@ -1246,6 +1249,32 @@ function hasFilteredPathSegment(relativePath, separator = path4.sep) {
1246
1249
  (part) => isHiddenPathSegment(part) || isBuildPathSegment(part)
1247
1250
  );
1248
1251
  }
1252
+ var RESTRICTED_DIRECTORIES = /* @__PURE__ */ new Set([
1253
+ // macOS
1254
+ "library",
1255
+ "applications",
1256
+ "system",
1257
+ "volumes",
1258
+ "private",
1259
+ "cores",
1260
+ // Linux
1261
+ "proc",
1262
+ "sys",
1263
+ "dev",
1264
+ "run",
1265
+ "snap",
1266
+ // Windows
1267
+ "windows",
1268
+ "programdata",
1269
+ "program files",
1270
+ "program files (x86)",
1271
+ "$recycle.bin"
1272
+ ]);
1273
+ function isRestrictedDirectory(relativePath, separator = path4.sep) {
1274
+ const firstSegment = relativePath.split(separator)[0];
1275
+ if (!firstSegment) return false;
1276
+ return RESTRICTED_DIRECTORIES.has(firstSegment.toLowerCase());
1277
+ }
1249
1278
 
1250
1279
  // src/config/rebase.ts
1251
1280
  function isWithinRoot(rootDir, targetPath) {
@@ -2305,7 +2334,7 @@ var NodeFsHandler = class {
2305
2334
  this._addToNodeFs(path18, initialAdd, wh, depth + 1);
2306
2335
  }
2307
2336
  }).on(EV.ERROR, this._boundHandleError);
2308
- return new Promise((resolve11, reject) => {
2337
+ return new Promise((resolve12, reject) => {
2309
2338
  if (!stream)
2310
2339
  return reject();
2311
2340
  stream.once(STR_END, () => {
@@ -2314,7 +2343,7 @@ var NodeFsHandler = class {
2314
2343
  return;
2315
2344
  }
2316
2345
  const wasThrottled = throttler ? throttler.clear() : false;
2317
- resolve11(void 0);
2346
+ resolve12(void 0);
2318
2347
  previous.getChildren().filter((item) => {
2319
2348
  return item !== directory && !current.has(item);
2320
2349
  }).forEach((item) => {
@@ -3414,6 +3443,9 @@ var FileWatcher = class {
3414
3443
  if (hasFilteredPathSegment(relativePath, path8.sep)) {
3415
3444
  return true;
3416
3445
  }
3446
+ if (isRestrictedDirectory(relativePath, path8.sep)) {
3447
+ return true;
3448
+ }
3417
3449
  if (ignoreFilter.ignores(relativePath)) {
3418
3450
  return true;
3419
3451
  }
@@ -3426,6 +3458,13 @@ var FileWatcher = class {
3426
3458
  pollInterval: 100
3427
3459
  }
3428
3460
  });
3461
+ this.watcher.on("error", (error) => {
3462
+ const err = error instanceof Error ? error : null;
3463
+ if (err?.code === "EPERM" || err?.code === "EACCES") {
3464
+ return;
3465
+ }
3466
+ console.error("[codebase-index] Watcher error:", err?.message ?? error);
3467
+ });
3429
3468
  this.watcher.on("add", (filePath) => this.handleChange("add", filePath));
3430
3469
  this.watcher.on("change", (filePath) => this.handleChange("change", filePath));
3431
3470
  this.watcher.on("unlink", (filePath) => this.handleChange("unlink", filePath));
@@ -3560,7 +3599,7 @@ var GitHeadWatcher = class {
3560
3599
  };
3561
3600
 
3562
3601
  // src/watcher/index.ts
3563
- function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3602
+ function createWatcherWithIndexer(getIndexer, projectRoot, config) {
3564
3603
  const fileWatcher = new FileWatcher(projectRoot, config);
3565
3604
  fileWatcher.start(async (changes) => {
3566
3605
  const hasAddOrChange = changes.some(
@@ -3568,7 +3607,7 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3568
3607
  );
3569
3608
  const hasDelete = changes.some((c) => c.type === "unlink");
3570
3609
  if (hasAddOrChange || hasDelete) {
3571
- await getIndexer2().index();
3610
+ await getIndexer().index();
3572
3611
  }
3573
3612
  });
3574
3613
  let gitWatcher = null;
@@ -3576,7 +3615,7 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3576
3615
  gitWatcher = new GitHeadWatcher(projectRoot);
3577
3616
  gitWatcher.start(async (oldBranch, newBranch) => {
3578
3617
  console.log(`Branch changed: ${oldBranch ?? "(none)"} -> ${newBranch}`);
3579
- await getIndexer2().index();
3618
+ await getIndexer().index();
3580
3619
  });
3581
3620
  }
3582
3621
  return {
@@ -3619,7 +3658,7 @@ function pTimeout(promise, options) {
3619
3658
  } = options;
3620
3659
  let timer;
3621
3660
  let abortHandler;
3622
- const wrappedPromise = new Promise((resolve11, reject) => {
3661
+ const wrappedPromise = new Promise((resolve12, reject) => {
3623
3662
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
3624
3663
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
3625
3664
  }
@@ -3633,7 +3672,7 @@ function pTimeout(promise, options) {
3633
3672
  };
3634
3673
  signal.addEventListener("abort", abortHandler, { once: true });
3635
3674
  }
3636
- promise.then(resolve11, reject);
3675
+ promise.then(resolve12, reject);
3637
3676
  if (milliseconds === Number.POSITIVE_INFINITY) {
3638
3677
  return;
3639
3678
  }
@@ -3641,7 +3680,7 @@ function pTimeout(promise, options) {
3641
3680
  timer = customTimers.setTimeout.call(void 0, () => {
3642
3681
  if (fallback) {
3643
3682
  try {
3644
- resolve11(fallback());
3683
+ resolve12(fallback());
3645
3684
  } catch (error) {
3646
3685
  reject(error);
3647
3686
  }
@@ -3651,7 +3690,7 @@ function pTimeout(promise, options) {
3651
3690
  promise.cancel();
3652
3691
  }
3653
3692
  if (message === false) {
3654
- resolve11();
3693
+ resolve12();
3655
3694
  } else if (message instanceof Error) {
3656
3695
  reject(message);
3657
3696
  } else {
@@ -4053,7 +4092,7 @@ var PQueue = class extends import_index.default {
4053
4092
  // Assign unique ID if not provided
4054
4093
  id: options.id ?? (this.#idAssigner++).toString()
4055
4094
  };
4056
- return new Promise((resolve11, reject) => {
4095
+ return new Promise((resolve12, reject) => {
4057
4096
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
4058
4097
  let cleanupQueueAbortHandler = () => void 0;
4059
4098
  const run = async () => {
@@ -4093,7 +4132,7 @@ var PQueue = class extends import_index.default {
4093
4132
  })]);
4094
4133
  }
4095
4134
  const result = await operation;
4096
- resolve11(result);
4135
+ resolve12(result);
4097
4136
  this.emit("completed", result);
4098
4137
  } catch (error) {
4099
4138
  reject(error);
@@ -4281,13 +4320,13 @@ var PQueue = class extends import_index.default {
4281
4320
  });
4282
4321
  }
4283
4322
  async #onEvent(event, filter) {
4284
- return new Promise((resolve11) => {
4323
+ return new Promise((resolve12) => {
4285
4324
  const listener = () => {
4286
4325
  if (filter && !filter()) {
4287
4326
  return;
4288
4327
  }
4289
4328
  this.off(event, listener);
4290
- resolve11();
4329
+ resolve12();
4291
4330
  };
4292
4331
  this.on(event, listener);
4293
4332
  });
@@ -4573,7 +4612,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4573
4612
  const finalDelay = Math.min(delayTime, remainingTime);
4574
4613
  options.signal?.throwIfAborted();
4575
4614
  if (finalDelay > 0) {
4576
- await new Promise((resolve11, reject) => {
4615
+ await new Promise((resolve12, reject) => {
4577
4616
  const onAbort = () => {
4578
4617
  clearTimeout(timeoutToken);
4579
4618
  options.signal?.removeEventListener("abort", onAbort);
@@ -4581,7 +4620,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4581
4620
  };
4582
4621
  const timeoutToken = setTimeout(() => {
4583
4622
  options.signal?.removeEventListener("abort", onAbort);
4584
- resolve11();
4623
+ resolve12();
4585
4624
  }, finalDelay);
4586
4625
  if (options.unref) {
4587
4626
  timeoutToken.unref?.();
@@ -9161,7 +9200,7 @@ var Indexer = class {
9161
9200
  for (const requestBatch of requestBatches) {
9162
9201
  queue.add(async () => {
9163
9202
  if (rateLimitBackoffMs > 0) {
9164
- await new Promise((resolve11) => setTimeout(resolve11, rateLimitBackoffMs));
9203
+ await new Promise((resolve12) => setTimeout(resolve12, rateLimitBackoffMs));
9165
9204
  }
9166
9205
  try {
9167
9206
  const result = await pRetry(
@@ -10485,39 +10524,45 @@ function ensureStringArray(value) {
10485
10524
  return Array.isArray(value) ? value : [];
10486
10525
  }
10487
10526
  var z = import_plugin.tool.schema;
10488
- var sharedIndexer = null;
10489
- var sharedProjectRoot = "";
10527
+ var indexerMap = /* @__PURE__ */ new Map();
10528
+ var defaultProjectRoot = "";
10490
10529
  function initializeTools(projectRoot, config) {
10491
- sharedProjectRoot = projectRoot;
10492
- sharedIndexer = new Indexer(projectRoot, config);
10530
+ defaultProjectRoot = projectRoot;
10531
+ const indexer = new Indexer(projectRoot, config);
10532
+ indexerMap.set(projectRoot, indexer);
10493
10533
  }
10494
- function getSharedIndexer() {
10495
- return getIndexer();
10496
- }
10497
- function refreshIndexerFromConfig() {
10498
- if (!sharedProjectRoot) {
10534
+ function refreshIndexerForDirectory(projectRoot) {
10535
+ if (!projectRoot) {
10499
10536
  throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10500
10537
  }
10501
- sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig(sharedProjectRoot)));
10538
+ const indexer = new Indexer(projectRoot, parseConfig(loadRuntimeConfig(projectRoot)));
10539
+ indexerMap.set(projectRoot, indexer);
10502
10540
  }
10503
- function shouldForceLocalizeProjectIndex() {
10504
- const currentConfig = parseConfig(loadRuntimeConfig(sharedProjectRoot));
10541
+ function shouldForceLocalizeProjectIndex(projectRoot) {
10542
+ const currentConfig = parseConfig(loadRuntimeConfig(projectRoot));
10505
10543
  if (currentConfig.scope !== "project") {
10506
10544
  return false;
10507
10545
  }
10508
- const localIndexPath = path15.join(sharedProjectRoot, ".opencode", "index");
10509
- const mainRepoRoot = resolveWorktreeMainRepoRoot(sharedProjectRoot);
10546
+ const localIndexPath = path15.join(projectRoot, ".opencode", "index");
10547
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
10510
10548
  if (!mainRepoRoot) {
10511
10549
  return false;
10512
10550
  }
10513
10551
  const inheritedIndexPath = path15.join(mainRepoRoot, ".opencode", "index");
10514
10552
  return !(0, import_fs9.existsSync)(localIndexPath) && (0, import_fs9.existsSync)(inheritedIndexPath);
10515
10553
  }
10516
- function getIndexer() {
10517
- if (!sharedIndexer) {
10554
+ function getIndexerForProject(directory) {
10555
+ const projectRoot = directory || defaultProjectRoot;
10556
+ if (!projectRoot) {
10518
10557
  throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10519
10558
  }
10520
- return sharedIndexer;
10559
+ let indexer = indexerMap.get(projectRoot);
10560
+ if (!indexer) {
10561
+ const config = parseConfig(loadRuntimeConfig(projectRoot));
10562
+ indexer = new Indexer(projectRoot, config);
10563
+ indexerMap.set(projectRoot, indexer);
10564
+ }
10565
+ return indexer;
10521
10566
  }
10522
10567
  var codebase_peek = (0, import_plugin.tool)({
10523
10568
  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 +10573,8 @@ var codebase_peek = (0, import_plugin.tool)({
10528
10573
  directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
10529
10574
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type")
10530
10575
  },
10531
- async execute(args) {
10532
- const indexer = getIndexer();
10576
+ async execute(args, context) {
10577
+ const indexer = getIndexerForProject(context?.worktree);
10533
10578
  const results = await indexer.search(args.query, args.limit ?? 10, {
10534
10579
  fileType: args.fileType,
10535
10580
  directory: args.directory,
@@ -10547,16 +10592,17 @@ var index_codebase = (0, import_plugin.tool)({
10547
10592
  verbose: z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
10548
10593
  },
10549
10594
  async execute(args, context) {
10550
- let indexer = getIndexer();
10595
+ const projectRoot = context?.worktree || defaultProjectRoot;
10596
+ let indexer = getIndexerForProject(projectRoot);
10551
10597
  if (args.estimateOnly) {
10552
10598
  const estimate = await indexer.estimateCost();
10553
10599
  return formatCostEstimate(estimate);
10554
10600
  }
10555
10601
  if (args.force) {
10556
- if (shouldForceLocalizeProjectIndex()) {
10557
- materializeLocalProjectConfig(sharedProjectRoot, loadProjectConfigLayer(sharedProjectRoot));
10558
- refreshIndexerFromConfig();
10559
- indexer = getIndexer();
10602
+ if (shouldForceLocalizeProjectIndex(projectRoot)) {
10603
+ materializeLocalProjectConfig(projectRoot, loadProjectConfigLayer(projectRoot));
10604
+ refreshIndexerForDirectory(projectRoot);
10605
+ indexer = getIndexerForProject(projectRoot);
10560
10606
  }
10561
10607
  await indexer.clearIndex();
10562
10608
  }
@@ -10579,8 +10625,8 @@ var index_codebase = (0, import_plugin.tool)({
10579
10625
  var index_status = (0, import_plugin.tool)({
10580
10626
  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
10627
  args: {},
10582
- async execute() {
10583
- const indexer = getIndexer();
10628
+ async execute(_args, context) {
10629
+ const indexer = getIndexerForProject(context?.worktree);
10584
10630
  const status = await indexer.getStatus();
10585
10631
  return formatStatus(status);
10586
10632
  }
@@ -10588,8 +10634,8 @@ var index_status = (0, import_plugin.tool)({
10588
10634
  var index_health_check = (0, import_plugin.tool)({
10589
10635
  description: "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
10590
10636
  args: {},
10591
- async execute() {
10592
- const indexer = getIndexer();
10637
+ async execute(_args, context) {
10638
+ const indexer = getIndexerForProject(context?.worktree);
10593
10639
  const result = await indexer.healthCheck();
10594
10640
  return formatHealthCheck(result);
10595
10641
  }
@@ -10597,8 +10643,8 @@ var index_health_check = (0, import_plugin.tool)({
10597
10643
  var index_metrics = (0, import_plugin.tool)({
10598
10644
  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
10645
  args: {},
10600
- async execute() {
10601
- const indexer = getIndexer();
10646
+ async execute(_args, context) {
10647
+ const indexer = getIndexerForProject(context?.worktree);
10602
10648
  const logger = indexer.getLogger();
10603
10649
  if (!logger.isEnabled()) {
10604
10650
  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 +10662,8 @@ var index_logs = (0, import_plugin.tool)({
10616
10662
  category: z.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional().describe("Filter by log category"),
10617
10663
  level: z.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
10618
10664
  },
10619
- async execute(args) {
10620
- const indexer = getIndexer();
10665
+ async execute(args, context) {
10666
+ const indexer = getIndexerForProject(context?.worktree);
10621
10667
  const logger = indexer.getLogger();
10622
10668
  if (!logger.isEnabled()) {
10623
10669
  return 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true\n }\n}\n```';
@@ -10643,8 +10689,8 @@ var find_similar = (0, import_plugin.tool)({
10643
10689
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10644
10690
  excludeFile: z.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)")
10645
10691
  },
10646
- async execute(args) {
10647
- const indexer = getIndexer();
10692
+ async execute(args, context) {
10693
+ const indexer = getIndexerForProject(context?.worktree);
10648
10694
  const results = await indexer.findSimilar(args.code, args.limit, {
10649
10695
  fileType: args.fileType,
10650
10696
  directory: args.directory,
@@ -10667,8 +10713,8 @@ var codebase_search = (0, import_plugin.tool)({
10667
10713
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10668
10714
  contextLines: z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
10669
10715
  },
10670
- async execute(args) {
10671
- const indexer = getIndexer();
10716
+ async execute(args, context) {
10717
+ const indexer = getIndexerForProject(context?.worktree);
10672
10718
  const results = await indexer.search(args.query, args.limit ?? 5, {
10673
10719
  fileType: args.fileType,
10674
10720
  directory: args.directory,
@@ -10689,8 +10735,8 @@ var implementation_lookup = (0, import_plugin.tool)({
10689
10735
  fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
10690
10736
  directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
10691
10737
  },
10692
- async execute(args) {
10693
- const indexer = getIndexer();
10738
+ async execute(args, context) {
10739
+ const indexer = getIndexerForProject(context?.worktree);
10694
10740
  const results = await indexer.search(args.query, args.limit ?? 5, {
10695
10741
  fileType: args.fileType,
10696
10742
  directory: args.directory,
@@ -10706,8 +10752,8 @@ var call_graph = (0, import_plugin.tool)({
10706
10752
  direction: z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
10707
10753
  symbolId: z.string().optional().describe("Symbol ID (required for 'callees' direction, returned by previous call_graph queries)")
10708
10754
  },
10709
- async execute(args) {
10710
- const indexer = getIndexer();
10755
+ async execute(args, context) {
10756
+ const indexer = getIndexerForProject(context?.worktree);
10711
10757
  if (args.direction === "callees") {
10712
10758
  if (!args.symbolId) {
10713
10759
  return "Error: 'symbolId' is required when direction is 'callees'. First use direction='callers' to find the symbol ID.";
@@ -10736,10 +10782,11 @@ var add_knowledge_base = (0, import_plugin.tool)({
10736
10782
  args: {
10737
10783
  path: z.string().describe("Path to the folder to add as a knowledge base (absolute or relative to project root)")
10738
10784
  },
10739
- async execute(args) {
10785
+ async execute(args, context) {
10786
+ const projectRoot = context?.worktree || defaultProjectRoot;
10740
10787
  const inputPath = args.path.trim();
10741
10788
  const normalizedPath = path15.resolve(
10742
- path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, sharedProjectRoot)
10789
+ path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, projectRoot)
10743
10790
  );
10744
10791
  if (!(0, import_fs9.existsSync)(normalizedPath)) {
10745
10792
  return `Error: Directory does not exist: ${normalizedPath}`;
@@ -10781,21 +10828,21 @@ var add_knowledge_base = (0, import_plugin.tool)({
10781
10828
  } catch (error) {
10782
10829
  return `Error: Cannot access directory: ${normalizedPath} - ${error instanceof Error ? error.message : String(error)}`;
10783
10830
  }
10784
- const config = loadEditableConfig(sharedProjectRoot);
10831
+ const config = loadEditableConfig(projectRoot);
10785
10832
  const knowledgeBases = ensureStringArray(config.knowledgeBases);
10786
- const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, sharedProjectRoot);
10833
+ const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, projectRoot);
10787
10834
  if (alreadyExists) {
10788
10835
  return `Knowledge base already configured: ${normalizedPath}`;
10789
10836
  }
10790
10837
  knowledgeBases.push(normalizedPath);
10791
10838
  config.knowledgeBases = knowledgeBases;
10792
- saveConfig(sharedProjectRoot, config);
10793
- refreshIndexerFromConfig();
10839
+ saveConfig(projectRoot, config);
10840
+ refreshIndexerForDirectory(projectRoot);
10794
10841
  let result = `${normalizedPath}
10795
10842
  `;
10796
10843
  result += `Total knowledge bases: ${knowledgeBases.length}
10797
10844
  `;
10798
- result += `Config saved to: ${getConfigPath(sharedProjectRoot)}
10845
+ result += `Config saved to: ${getConfigPath(projectRoot)}
10799
10846
  `;
10800
10847
  result += `
10801
10848
  Run /index to rebuild the index with the new knowledge base.`;
@@ -10805,8 +10852,9 @@ Run /index to rebuild the index with the new knowledge base.`;
10805
10852
  var list_knowledge_bases = (0, import_plugin.tool)({
10806
10853
  description: "List all configured knowledge base folders that are indexed alongside the main project.",
10807
10854
  args: {},
10808
- async execute() {
10809
- const config = loadRuntimeConfig(sharedProjectRoot);
10855
+ async execute(_args, context) {
10856
+ const projectRoot = context?.worktree || defaultProjectRoot;
10857
+ const config = loadRuntimeConfig(projectRoot);
10810
10858
  const knowledgeBases = ensureStringArray(config.knowledgeBases);
10811
10859
  if (knowledgeBases.length === 0) {
10812
10860
  return "No knowledge bases configured. Use add_knowledge_base to add folders.";
@@ -10816,7 +10864,7 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10816
10864
  `;
10817
10865
  for (let i = 0; i < knowledgeBases.length; i++) {
10818
10866
  const kb = knowledgeBases[i];
10819
- const resolvedPath = resolveKnowledgeBasePath(kb, sharedProjectRoot);
10867
+ const resolvedPath = resolveKnowledgeBasePath(kb, projectRoot);
10820
10868
  const exists = (0, import_fs9.existsSync)(resolvedPath);
10821
10869
  result += `[${i + 1}] ${kb}
10822
10870
  `;
@@ -10834,7 +10882,7 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10834
10882
  }
10835
10883
  result += "\n";
10836
10884
  }
10837
- result += `Config file: ${getConfigPath(sharedProjectRoot)}`;
10885
+ result += `Config file: ${getConfigPath(projectRoot)}`;
10838
10886
  return result;
10839
10887
  }
10840
10888
  });
@@ -10843,14 +10891,15 @@ var remove_knowledge_base = (0, import_plugin.tool)({
10843
10891
  args: {
10844
10892
  path: z.string().describe("Path of the knowledge base to remove (must match the configured path exactly)")
10845
10893
  },
10846
- async execute(args) {
10894
+ async execute(args, context) {
10895
+ const projectRoot = context?.worktree || defaultProjectRoot;
10847
10896
  const inputPath = args.path.trim();
10848
- const config = loadEditableConfig(sharedProjectRoot);
10897
+ const config = loadEditableConfig(projectRoot);
10849
10898
  const knowledgeBases = ensureStringArray(config.knowledgeBases);
10850
10899
  if (knowledgeBases.length === 0) {
10851
10900
  return "No knowledge bases configured.";
10852
10901
  }
10853
- const index = findKnowledgeBasePathIndex(knowledgeBases, inputPath, sharedProjectRoot);
10902
+ const index = findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot);
10854
10903
  if (index === -1) {
10855
10904
  let result2 = `Knowledge base not found: ${inputPath}
10856
10905
 
@@ -10865,14 +10914,14 @@ var remove_knowledge_base = (0, import_plugin.tool)({
10865
10914
  }
10866
10915
  const removed = knowledgeBases.splice(index, 1)[0];
10867
10916
  config.knowledgeBases = knowledgeBases;
10868
- saveConfig(sharedProjectRoot, config);
10869
- refreshIndexerFromConfig();
10917
+ saveConfig(projectRoot, config);
10918
+ refreshIndexerForDirectory(projectRoot);
10870
10919
  let result = `Removed: ${removed}
10871
10920
 
10872
10921
  `;
10873
10922
  result += `Remaining knowledge bases: ${knowledgeBases.length}
10874
10923
  `;
10875
- result += `Config saved to: ${getConfigPath(sharedProjectRoot)}
10924
+ result += `Config saved to: ${getConfigPath(projectRoot)}
10876
10925
  `;
10877
10926
  result += `
10878
10927
  Run /index to rebuild the index without the removed knowledge base.`;
@@ -11216,10 +11265,16 @@ var RoutingHintController = class {
11216
11265
 
11217
11266
  // src/index.ts
11218
11267
  var import_meta2 = {};
11219
- var activeWatcher = null;
11220
- function replaceActiveWatcher(nextWatcher) {
11221
- activeWatcher?.stop();
11222
- activeWatcher = nextWatcher;
11268
+ var activeWatchers = /* @__PURE__ */ new Map();
11269
+ function replaceActiveWatcher(projectRoot, nextWatcher) {
11270
+ const existing = activeWatchers.get(projectRoot);
11271
+ if (existing) {
11272
+ existing.stop();
11273
+ activeWatchers.delete(projectRoot);
11274
+ }
11275
+ if (nextWatcher) {
11276
+ activeWatchers.set(projectRoot, nextWatcher);
11277
+ }
11223
11278
  }
11224
11279
  function getCommandsDir() {
11225
11280
  let currentDir = process.cwd();
@@ -11228,21 +11283,37 @@ function getCommandsDir() {
11228
11283
  }
11229
11284
  return path17.join(currentDir, "..", "commands");
11230
11285
  }
11231
- var plugin = async ({ directory }) => {
11286
+ function appendRoutingHints(output, hints, preferredRole) {
11287
+ const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
11288
+ if (Array.isArray(preferredBucket)) {
11289
+ preferredBucket.push(...hints);
11290
+ return;
11291
+ }
11292
+ if (Array.isArray(output.system)) {
11293
+ output.system.push(...hints);
11294
+ }
11295
+ }
11296
+ var plugin = async ({ directory, worktree }) => {
11232
11297
  try {
11233
- const projectRoot = directory;
11298
+ const projectRoot = worktree || directory;
11234
11299
  const rawConfig = loadMergedConfig(projectRoot);
11235
11300
  const config = parseConfig(rawConfig);
11236
11301
  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) {
11302
+ const getProjectIndexer = () => getIndexerForProject(projectRoot);
11303
+ const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus()) : null;
11304
+ const isHomeDir = path17.resolve(projectRoot) === path17.resolve(os6.homedir());
11305
+ const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
11306
+ if (isHomeDir) {
11307
+ console.warn(
11308
+ `[codebase-index] Refusing to watch or index home directory "${projectRoot}". Open a specific project directory instead.`
11309
+ );
11310
+ } else if (!isValidProject) {
11241
11311
  console.warn(
11242
11312
  `[codebase-index] Skipping file watching and auto-indexing: no project marker found in "${projectRoot}". Set "indexing.requireProjectMarker": false in config to override.`
11243
11313
  );
11244
11314
  }
11245
11315
  if (config.indexing.autoIndex && isValidProject) {
11316
+ const indexer = getProjectIndexer();
11246
11317
  indexer.initialize().then(() => {
11247
11318
  indexer.index().catch(() => {
11248
11319
  });
@@ -11250,9 +11321,9 @@ var plugin = async ({ directory }) => {
11250
11321
  });
11251
11322
  }
11252
11323
  if (config.indexing.watchFiles && isValidProject) {
11253
- replaceActiveWatcher(createWatcherWithIndexer(getSharedIndexer, projectRoot, config));
11324
+ replaceActiveWatcher(projectRoot, createWatcherWithIndexer(getProjectIndexer, projectRoot, config));
11254
11325
  } else {
11255
- replaceActiveWatcher(null);
11326
+ replaceActiveWatcher(projectRoot, null);
11256
11327
  }
11257
11328
  return {
11258
11329
  tool: {
@@ -11274,8 +11345,18 @@ var plugin = async ({ directory }) => {
11274
11345
  routingHints?.observeUserMessage(input.sessionID, output.parts);
11275
11346
  },
11276
11347
  async "experimental.chat.system.transform"(input, output) {
11348
+ if (config.search.routingHintRole !== "system") {
11349
+ return;
11350
+ }
11277
11351
  const hints = await routingHints?.getSystemHints(input.sessionID) ?? [];
11278
- output.system.push(...hints);
11352
+ appendRoutingHints(output, hints, "system");
11353
+ },
11354
+ async "experimental.chat.developer.transform"(input, output) {
11355
+ if (config.search.routingHintRole !== "developer") {
11356
+ return;
11357
+ }
11358
+ const hints = await routingHints?.getSystemHints(input.sessionID) ?? [];
11359
+ appendRoutingHints(output, hints, "developer");
11279
11360
  },
11280
11361
  async "tool.execute.after"(input) {
11281
11362
  routingHints?.markToolUsed(input.sessionID, input.tool);
@@ -11289,8 +11370,8 @@ var plugin = async ({ directory }) => {
11289
11370
  }
11290
11371
  }
11291
11372
  };
11292
- } catch (error) {
11293
- console.error("[codebase-index] Failed to initialize plugin:", error);
11373
+ } catch {
11374
+ console.error("[codebase-index] Failed to initialize plugin (check config and network)");
11294
11375
  return {
11295
11376
  tool: void 0,
11296
11377
  async config() {