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.js CHANGED
@@ -647,6 +647,7 @@ var require_eventemitter3 = __commonJS({
647
647
  });
648
648
 
649
649
  // src/index.ts
650
+ import * as os6 from "os";
650
651
  import * as path17 from "path";
651
652
  import { fileURLToPath as fileURLToPath2 } from "url";
652
653
 
@@ -793,7 +794,8 @@ function getDefaultSearchConfig() {
793
794
  rrfK: 60,
794
795
  rerankTopN: 20,
795
796
  contextLines: 0,
796
- routingHints: true
797
+ routingHints: true,
798
+ routingHintRole: "system"
797
799
  };
798
800
  }
799
801
  function getDefaultRerankerBaseUrl(provider) {
@@ -914,7 +916,8 @@ function parseConfig(raw) {
914
916
  rrfK: typeof rawSearch.rrfK === "number" ? Math.max(1, Math.floor(rawSearch.rrfK)) : defaultSearch.rrfK,
915
917
  rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
916
918
  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
919
+ routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints,
920
+ routingHintRole: rawSearch.routingHintRole === "developer" || rawSearch.routingHintRole === "system" ? rawSearch.routingHintRole : defaultSearch.routingHintRole
918
921
  };
919
922
  const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
920
923
  const debug = {
@@ -1236,6 +1239,32 @@ function hasFilteredPathSegment(relativePath, separator = path4.sep) {
1236
1239
  (part) => isHiddenPathSegment(part) || isBuildPathSegment(part)
1237
1240
  );
1238
1241
  }
1242
+ var RESTRICTED_DIRECTORIES = /* @__PURE__ */ new Set([
1243
+ // macOS
1244
+ "library",
1245
+ "applications",
1246
+ "system",
1247
+ "volumes",
1248
+ "private",
1249
+ "cores",
1250
+ // Linux
1251
+ "proc",
1252
+ "sys",
1253
+ "dev",
1254
+ "run",
1255
+ "snap",
1256
+ // Windows
1257
+ "windows",
1258
+ "programdata",
1259
+ "program files",
1260
+ "program files (x86)",
1261
+ "$recycle.bin"
1262
+ ]);
1263
+ function isRestrictedDirectory(relativePath, separator = path4.sep) {
1264
+ const firstSegment = relativePath.split(separator)[0];
1265
+ if (!firstSegment) return false;
1266
+ return RESTRICTED_DIRECTORIES.has(firstSegment.toLowerCase());
1267
+ }
1239
1268
 
1240
1269
  // src/config/rebase.ts
1241
1270
  function isWithinRoot(rootDir, targetPath) {
@@ -2295,7 +2324,7 @@ var NodeFsHandler = class {
2295
2324
  this._addToNodeFs(path18, initialAdd, wh, depth + 1);
2296
2325
  }
2297
2326
  }).on(EV.ERROR, this._boundHandleError);
2298
- return new Promise((resolve11, reject) => {
2327
+ return new Promise((resolve12, reject) => {
2299
2328
  if (!stream)
2300
2329
  return reject();
2301
2330
  stream.once(STR_END, () => {
@@ -2304,7 +2333,7 @@ var NodeFsHandler = class {
2304
2333
  return;
2305
2334
  }
2306
2335
  const wasThrottled = throttler ? throttler.clear() : false;
2307
- resolve11(void 0);
2336
+ resolve12(void 0);
2308
2337
  previous.getChildren().filter((item) => {
2309
2338
  return item !== directory && !current.has(item);
2310
2339
  }).forEach((item) => {
@@ -3404,6 +3433,9 @@ var FileWatcher = class {
3404
3433
  if (hasFilteredPathSegment(relativePath, path8.sep)) {
3405
3434
  return true;
3406
3435
  }
3436
+ if (isRestrictedDirectory(relativePath, path8.sep)) {
3437
+ return true;
3438
+ }
3407
3439
  if (ignoreFilter.ignores(relativePath)) {
3408
3440
  return true;
3409
3441
  }
@@ -3416,6 +3448,13 @@ var FileWatcher = class {
3416
3448
  pollInterval: 100
3417
3449
  }
3418
3450
  });
3451
+ this.watcher.on("error", (error) => {
3452
+ const err = error instanceof Error ? error : null;
3453
+ if (err?.code === "EPERM" || err?.code === "EACCES") {
3454
+ return;
3455
+ }
3456
+ console.error("[codebase-index] Watcher error:", err?.message ?? error);
3457
+ });
3419
3458
  this.watcher.on("add", (filePath) => this.handleChange("add", filePath));
3420
3459
  this.watcher.on("change", (filePath) => this.handleChange("change", filePath));
3421
3460
  this.watcher.on("unlink", (filePath) => this.handleChange("unlink", filePath));
@@ -3550,7 +3589,7 @@ var GitHeadWatcher = class {
3550
3589
  };
3551
3590
 
3552
3591
  // src/watcher/index.ts
3553
- function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3592
+ function createWatcherWithIndexer(getIndexer, projectRoot, config) {
3554
3593
  const fileWatcher = new FileWatcher(projectRoot, config);
3555
3594
  fileWatcher.start(async (changes) => {
3556
3595
  const hasAddOrChange = changes.some(
@@ -3558,7 +3597,7 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3558
3597
  );
3559
3598
  const hasDelete = changes.some((c) => c.type === "unlink");
3560
3599
  if (hasAddOrChange || hasDelete) {
3561
- await getIndexer2().index();
3600
+ await getIndexer().index();
3562
3601
  }
3563
3602
  });
3564
3603
  let gitWatcher = null;
@@ -3566,7 +3605,7 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3566
3605
  gitWatcher = new GitHeadWatcher(projectRoot);
3567
3606
  gitWatcher.start(async (oldBranch, newBranch) => {
3568
3607
  console.log(`Branch changed: ${oldBranch ?? "(none)"} -> ${newBranch}`);
3569
- await getIndexer2().index();
3608
+ await getIndexer().index();
3570
3609
  });
3571
3610
  }
3572
3611
  return {
@@ -3609,7 +3648,7 @@ function pTimeout(promise, options) {
3609
3648
  } = options;
3610
3649
  let timer;
3611
3650
  let abortHandler;
3612
- const wrappedPromise = new Promise((resolve11, reject) => {
3651
+ const wrappedPromise = new Promise((resolve12, reject) => {
3613
3652
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
3614
3653
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
3615
3654
  }
@@ -3623,7 +3662,7 @@ function pTimeout(promise, options) {
3623
3662
  };
3624
3663
  signal.addEventListener("abort", abortHandler, { once: true });
3625
3664
  }
3626
- promise.then(resolve11, reject);
3665
+ promise.then(resolve12, reject);
3627
3666
  if (milliseconds === Number.POSITIVE_INFINITY) {
3628
3667
  return;
3629
3668
  }
@@ -3631,7 +3670,7 @@ function pTimeout(promise, options) {
3631
3670
  timer = customTimers.setTimeout.call(void 0, () => {
3632
3671
  if (fallback) {
3633
3672
  try {
3634
- resolve11(fallback());
3673
+ resolve12(fallback());
3635
3674
  } catch (error) {
3636
3675
  reject(error);
3637
3676
  }
@@ -3641,7 +3680,7 @@ function pTimeout(promise, options) {
3641
3680
  promise.cancel();
3642
3681
  }
3643
3682
  if (message === false) {
3644
- resolve11();
3683
+ resolve12();
3645
3684
  } else if (message instanceof Error) {
3646
3685
  reject(message);
3647
3686
  } else {
@@ -4043,7 +4082,7 @@ var PQueue = class extends import_index.default {
4043
4082
  // Assign unique ID if not provided
4044
4083
  id: options.id ?? (this.#idAssigner++).toString()
4045
4084
  };
4046
- return new Promise((resolve11, reject) => {
4085
+ return new Promise((resolve12, reject) => {
4047
4086
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
4048
4087
  let cleanupQueueAbortHandler = () => void 0;
4049
4088
  const run = async () => {
@@ -4083,7 +4122,7 @@ var PQueue = class extends import_index.default {
4083
4122
  })]);
4084
4123
  }
4085
4124
  const result = await operation;
4086
- resolve11(result);
4125
+ resolve12(result);
4087
4126
  this.emit("completed", result);
4088
4127
  } catch (error) {
4089
4128
  reject(error);
@@ -4271,13 +4310,13 @@ var PQueue = class extends import_index.default {
4271
4310
  });
4272
4311
  }
4273
4312
  async #onEvent(event, filter) {
4274
- return new Promise((resolve11) => {
4313
+ return new Promise((resolve12) => {
4275
4314
  const listener = () => {
4276
4315
  if (filter && !filter()) {
4277
4316
  return;
4278
4317
  }
4279
4318
  this.off(event, listener);
4280
- resolve11();
4319
+ resolve12();
4281
4320
  };
4282
4321
  this.on(event, listener);
4283
4322
  });
@@ -4563,7 +4602,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4563
4602
  const finalDelay = Math.min(delayTime, remainingTime);
4564
4603
  options.signal?.throwIfAborted();
4565
4604
  if (finalDelay > 0) {
4566
- await new Promise((resolve11, reject) => {
4605
+ await new Promise((resolve12, reject) => {
4567
4606
  const onAbort = () => {
4568
4607
  clearTimeout(timeoutToken);
4569
4608
  options.signal?.removeEventListener("abort", onAbort);
@@ -4571,7 +4610,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4571
4610
  };
4572
4611
  const timeoutToken = setTimeout(() => {
4573
4612
  options.signal?.removeEventListener("abort", onAbort);
4574
- resolve11();
4613
+ resolve12();
4575
4614
  }, finalDelay);
4576
4615
  if (options.unref) {
4577
4616
  timeoutToken.unref?.();
@@ -9150,7 +9189,7 @@ var Indexer = class {
9150
9189
  for (const requestBatch of requestBatches) {
9151
9190
  queue.add(async () => {
9152
9191
  if (rateLimitBackoffMs > 0) {
9153
- await new Promise((resolve11) => setTimeout(resolve11, rateLimitBackoffMs));
9192
+ await new Promise((resolve12) => setTimeout(resolve12, rateLimitBackoffMs));
9154
9193
  }
9155
9194
  try {
9156
9195
  const result = await pRetry(
@@ -10474,39 +10513,45 @@ function ensureStringArray(value) {
10474
10513
  return Array.isArray(value) ? value : [];
10475
10514
  }
10476
10515
  var z = tool.schema;
10477
- var sharedIndexer = null;
10478
- var sharedProjectRoot = "";
10516
+ var indexerMap = /* @__PURE__ */ new Map();
10517
+ var defaultProjectRoot = "";
10479
10518
  function initializeTools(projectRoot, config) {
10480
- sharedProjectRoot = projectRoot;
10481
- sharedIndexer = new Indexer(projectRoot, config);
10519
+ defaultProjectRoot = projectRoot;
10520
+ const indexer = new Indexer(projectRoot, config);
10521
+ indexerMap.set(projectRoot, indexer);
10482
10522
  }
10483
- function getSharedIndexer() {
10484
- return getIndexer();
10485
- }
10486
- function refreshIndexerFromConfig() {
10487
- if (!sharedProjectRoot) {
10523
+ function refreshIndexerForDirectory(projectRoot) {
10524
+ if (!projectRoot) {
10488
10525
  throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10489
10526
  }
10490
- sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig(sharedProjectRoot)));
10527
+ const indexer = new Indexer(projectRoot, parseConfig(loadRuntimeConfig(projectRoot)));
10528
+ indexerMap.set(projectRoot, indexer);
10491
10529
  }
10492
- function shouldForceLocalizeProjectIndex() {
10493
- const currentConfig = parseConfig(loadRuntimeConfig(sharedProjectRoot));
10530
+ function shouldForceLocalizeProjectIndex(projectRoot) {
10531
+ const currentConfig = parseConfig(loadRuntimeConfig(projectRoot));
10494
10532
  if (currentConfig.scope !== "project") {
10495
10533
  return false;
10496
10534
  }
10497
- const localIndexPath = path15.join(sharedProjectRoot, ".opencode", "index");
10498
- const mainRepoRoot = resolveWorktreeMainRepoRoot(sharedProjectRoot);
10535
+ const localIndexPath = path15.join(projectRoot, ".opencode", "index");
10536
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
10499
10537
  if (!mainRepoRoot) {
10500
10538
  return false;
10501
10539
  }
10502
10540
  const inheritedIndexPath = path15.join(mainRepoRoot, ".opencode", "index");
10503
10541
  return !existsSync9(localIndexPath) && existsSync9(inheritedIndexPath);
10504
10542
  }
10505
- function getIndexer() {
10506
- if (!sharedIndexer) {
10543
+ function getIndexerForProject(directory) {
10544
+ const projectRoot = directory || defaultProjectRoot;
10545
+ if (!projectRoot) {
10507
10546
  throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10508
10547
  }
10509
- return sharedIndexer;
10548
+ let indexer = indexerMap.get(projectRoot);
10549
+ if (!indexer) {
10550
+ const config = parseConfig(loadRuntimeConfig(projectRoot));
10551
+ indexer = new Indexer(projectRoot, config);
10552
+ indexerMap.set(projectRoot, indexer);
10553
+ }
10554
+ return indexer;
10510
10555
  }
10511
10556
  var codebase_peek = tool({
10512
10557
  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 +10562,8 @@ var codebase_peek = tool({
10517
10562
  directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
10518
10563
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type")
10519
10564
  },
10520
- async execute(args) {
10521
- const indexer = getIndexer();
10565
+ async execute(args, context) {
10566
+ const indexer = getIndexerForProject(context?.worktree);
10522
10567
  const results = await indexer.search(args.query, args.limit ?? 10, {
10523
10568
  fileType: args.fileType,
10524
10569
  directory: args.directory,
@@ -10536,16 +10581,17 @@ var index_codebase = tool({
10536
10581
  verbose: z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
10537
10582
  },
10538
10583
  async execute(args, context) {
10539
- let indexer = getIndexer();
10584
+ const projectRoot = context?.worktree || defaultProjectRoot;
10585
+ let indexer = getIndexerForProject(projectRoot);
10540
10586
  if (args.estimateOnly) {
10541
10587
  const estimate = await indexer.estimateCost();
10542
10588
  return formatCostEstimate(estimate);
10543
10589
  }
10544
10590
  if (args.force) {
10545
- if (shouldForceLocalizeProjectIndex()) {
10546
- materializeLocalProjectConfig(sharedProjectRoot, loadProjectConfigLayer(sharedProjectRoot));
10547
- refreshIndexerFromConfig();
10548
- indexer = getIndexer();
10591
+ if (shouldForceLocalizeProjectIndex(projectRoot)) {
10592
+ materializeLocalProjectConfig(projectRoot, loadProjectConfigLayer(projectRoot));
10593
+ refreshIndexerForDirectory(projectRoot);
10594
+ indexer = getIndexerForProject(projectRoot);
10549
10595
  }
10550
10596
  await indexer.clearIndex();
10551
10597
  }
@@ -10568,8 +10614,8 @@ var index_codebase = tool({
10568
10614
  var index_status = tool({
10569
10615
  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
10616
  args: {},
10571
- async execute() {
10572
- const indexer = getIndexer();
10617
+ async execute(_args, context) {
10618
+ const indexer = getIndexerForProject(context?.worktree);
10573
10619
  const status = await indexer.getStatus();
10574
10620
  return formatStatus(status);
10575
10621
  }
@@ -10577,8 +10623,8 @@ var index_status = tool({
10577
10623
  var index_health_check = tool({
10578
10624
  description: "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
10579
10625
  args: {},
10580
- async execute() {
10581
- const indexer = getIndexer();
10626
+ async execute(_args, context) {
10627
+ const indexer = getIndexerForProject(context?.worktree);
10582
10628
  const result = await indexer.healthCheck();
10583
10629
  return formatHealthCheck(result);
10584
10630
  }
@@ -10586,8 +10632,8 @@ var index_health_check = tool({
10586
10632
  var index_metrics = tool({
10587
10633
  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
10634
  args: {},
10589
- async execute() {
10590
- const indexer = getIndexer();
10635
+ async execute(_args, context) {
10636
+ const indexer = getIndexerForProject(context?.worktree);
10591
10637
  const logger = indexer.getLogger();
10592
10638
  if (!logger.isEnabled()) {
10593
10639
  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 +10651,8 @@ var index_logs = tool({
10605
10651
  category: z.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional().describe("Filter by log category"),
10606
10652
  level: z.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
10607
10653
  },
10608
- async execute(args) {
10609
- const indexer = getIndexer();
10654
+ async execute(args, context) {
10655
+ const indexer = getIndexerForProject(context?.worktree);
10610
10656
  const logger = indexer.getLogger();
10611
10657
  if (!logger.isEnabled()) {
10612
10658
  return 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true\n }\n}\n```';
@@ -10632,8 +10678,8 @@ var find_similar = tool({
10632
10678
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10633
10679
  excludeFile: z.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)")
10634
10680
  },
10635
- async execute(args) {
10636
- const indexer = getIndexer();
10681
+ async execute(args, context) {
10682
+ const indexer = getIndexerForProject(context?.worktree);
10637
10683
  const results = await indexer.findSimilar(args.code, args.limit, {
10638
10684
  fileType: args.fileType,
10639
10685
  directory: args.directory,
@@ -10656,8 +10702,8 @@ var codebase_search = tool({
10656
10702
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10657
10703
  contextLines: z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
10658
10704
  },
10659
- async execute(args) {
10660
- const indexer = getIndexer();
10705
+ async execute(args, context) {
10706
+ const indexer = getIndexerForProject(context?.worktree);
10661
10707
  const results = await indexer.search(args.query, args.limit ?? 5, {
10662
10708
  fileType: args.fileType,
10663
10709
  directory: args.directory,
@@ -10678,8 +10724,8 @@ var implementation_lookup = tool({
10678
10724
  fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
10679
10725
  directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
10680
10726
  },
10681
- async execute(args) {
10682
- const indexer = getIndexer();
10727
+ async execute(args, context) {
10728
+ const indexer = getIndexerForProject(context?.worktree);
10683
10729
  const results = await indexer.search(args.query, args.limit ?? 5, {
10684
10730
  fileType: args.fileType,
10685
10731
  directory: args.directory,
@@ -10695,8 +10741,8 @@ var call_graph = tool({
10695
10741
  direction: z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
10696
10742
  symbolId: z.string().optional().describe("Symbol ID (required for 'callees' direction, returned by previous call_graph queries)")
10697
10743
  },
10698
- async execute(args) {
10699
- const indexer = getIndexer();
10744
+ async execute(args, context) {
10745
+ const indexer = getIndexerForProject(context?.worktree);
10700
10746
  if (args.direction === "callees") {
10701
10747
  if (!args.symbolId) {
10702
10748
  return "Error: 'symbolId' is required when direction is 'callees'. First use direction='callers' to find the symbol ID.";
@@ -10725,10 +10771,11 @@ var add_knowledge_base = tool({
10725
10771
  args: {
10726
10772
  path: z.string().describe("Path to the folder to add as a knowledge base (absolute or relative to project root)")
10727
10773
  },
10728
- async execute(args) {
10774
+ async execute(args, context) {
10775
+ const projectRoot = context?.worktree || defaultProjectRoot;
10729
10776
  const inputPath = args.path.trim();
10730
10777
  const normalizedPath = path15.resolve(
10731
- path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, sharedProjectRoot)
10778
+ path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, projectRoot)
10732
10779
  );
10733
10780
  if (!existsSync9(normalizedPath)) {
10734
10781
  return `Error: Directory does not exist: ${normalizedPath}`;
@@ -10770,21 +10817,21 @@ var add_knowledge_base = tool({
10770
10817
  } catch (error) {
10771
10818
  return `Error: Cannot access directory: ${normalizedPath} - ${error instanceof Error ? error.message : String(error)}`;
10772
10819
  }
10773
- const config = loadEditableConfig(sharedProjectRoot);
10820
+ const config = loadEditableConfig(projectRoot);
10774
10821
  const knowledgeBases = ensureStringArray(config.knowledgeBases);
10775
- const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, sharedProjectRoot);
10822
+ const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, projectRoot);
10776
10823
  if (alreadyExists) {
10777
10824
  return `Knowledge base already configured: ${normalizedPath}`;
10778
10825
  }
10779
10826
  knowledgeBases.push(normalizedPath);
10780
10827
  config.knowledgeBases = knowledgeBases;
10781
- saveConfig(sharedProjectRoot, config);
10782
- refreshIndexerFromConfig();
10828
+ saveConfig(projectRoot, config);
10829
+ refreshIndexerForDirectory(projectRoot);
10783
10830
  let result = `${normalizedPath}
10784
10831
  `;
10785
10832
  result += `Total knowledge bases: ${knowledgeBases.length}
10786
10833
  `;
10787
- result += `Config saved to: ${getConfigPath(sharedProjectRoot)}
10834
+ result += `Config saved to: ${getConfigPath(projectRoot)}
10788
10835
  `;
10789
10836
  result += `
10790
10837
  Run /index to rebuild the index with the new knowledge base.`;
@@ -10794,8 +10841,9 @@ Run /index to rebuild the index with the new knowledge base.`;
10794
10841
  var list_knowledge_bases = tool({
10795
10842
  description: "List all configured knowledge base folders that are indexed alongside the main project.",
10796
10843
  args: {},
10797
- async execute() {
10798
- const config = loadRuntimeConfig(sharedProjectRoot);
10844
+ async execute(_args, context) {
10845
+ const projectRoot = context?.worktree || defaultProjectRoot;
10846
+ const config = loadRuntimeConfig(projectRoot);
10799
10847
  const knowledgeBases = ensureStringArray(config.knowledgeBases);
10800
10848
  if (knowledgeBases.length === 0) {
10801
10849
  return "No knowledge bases configured. Use add_knowledge_base to add folders.";
@@ -10805,7 +10853,7 @@ var list_knowledge_bases = tool({
10805
10853
  `;
10806
10854
  for (let i = 0; i < knowledgeBases.length; i++) {
10807
10855
  const kb = knowledgeBases[i];
10808
- const resolvedPath = resolveKnowledgeBasePath(kb, sharedProjectRoot);
10856
+ const resolvedPath = resolveKnowledgeBasePath(kb, projectRoot);
10809
10857
  const exists = existsSync9(resolvedPath);
10810
10858
  result += `[${i + 1}] ${kb}
10811
10859
  `;
@@ -10823,7 +10871,7 @@ var list_knowledge_bases = tool({
10823
10871
  }
10824
10872
  result += "\n";
10825
10873
  }
10826
- result += `Config file: ${getConfigPath(sharedProjectRoot)}`;
10874
+ result += `Config file: ${getConfigPath(projectRoot)}`;
10827
10875
  return result;
10828
10876
  }
10829
10877
  });
@@ -10832,14 +10880,15 @@ var remove_knowledge_base = tool({
10832
10880
  args: {
10833
10881
  path: z.string().describe("Path of the knowledge base to remove (must match the configured path exactly)")
10834
10882
  },
10835
- async execute(args) {
10883
+ async execute(args, context) {
10884
+ const projectRoot = context?.worktree || defaultProjectRoot;
10836
10885
  const inputPath = args.path.trim();
10837
- const config = loadEditableConfig(sharedProjectRoot);
10886
+ const config = loadEditableConfig(projectRoot);
10838
10887
  const knowledgeBases = ensureStringArray(config.knowledgeBases);
10839
10888
  if (knowledgeBases.length === 0) {
10840
10889
  return "No knowledge bases configured.";
10841
10890
  }
10842
- const index = findKnowledgeBasePathIndex(knowledgeBases, inputPath, sharedProjectRoot);
10891
+ const index = findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot);
10843
10892
  if (index === -1) {
10844
10893
  let result2 = `Knowledge base not found: ${inputPath}
10845
10894
 
@@ -10854,14 +10903,14 @@ var remove_knowledge_base = tool({
10854
10903
  }
10855
10904
  const removed = knowledgeBases.splice(index, 1)[0];
10856
10905
  config.knowledgeBases = knowledgeBases;
10857
- saveConfig(sharedProjectRoot, config);
10858
- refreshIndexerFromConfig();
10906
+ saveConfig(projectRoot, config);
10907
+ refreshIndexerForDirectory(projectRoot);
10859
10908
  let result = `Removed: ${removed}
10860
10909
 
10861
10910
  `;
10862
10911
  result += `Remaining knowledge bases: ${knowledgeBases.length}
10863
10912
  `;
10864
- result += `Config saved to: ${getConfigPath(sharedProjectRoot)}
10913
+ result += `Config saved to: ${getConfigPath(projectRoot)}
10865
10914
  `;
10866
10915
  result += `
10867
10916
  Run /index to rebuild the index without the removed knowledge base.`;
@@ -11204,10 +11253,16 @@ var RoutingHintController = class {
11204
11253
  };
11205
11254
 
11206
11255
  // src/index.ts
11207
- var activeWatcher = null;
11208
- function replaceActiveWatcher(nextWatcher) {
11209
- activeWatcher?.stop();
11210
- activeWatcher = nextWatcher;
11256
+ var activeWatchers = /* @__PURE__ */ new Map();
11257
+ function replaceActiveWatcher(projectRoot, nextWatcher) {
11258
+ const existing = activeWatchers.get(projectRoot);
11259
+ if (existing) {
11260
+ existing.stop();
11261
+ activeWatchers.delete(projectRoot);
11262
+ }
11263
+ if (nextWatcher) {
11264
+ activeWatchers.set(projectRoot, nextWatcher);
11265
+ }
11211
11266
  }
11212
11267
  function getCommandsDir() {
11213
11268
  let currentDir = process.cwd();
@@ -11216,21 +11271,37 @@ function getCommandsDir() {
11216
11271
  }
11217
11272
  return path17.join(currentDir, "..", "commands");
11218
11273
  }
11219
- var plugin = async ({ directory }) => {
11274
+ function appendRoutingHints(output, hints, preferredRole) {
11275
+ const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
11276
+ if (Array.isArray(preferredBucket)) {
11277
+ preferredBucket.push(...hints);
11278
+ return;
11279
+ }
11280
+ if (Array.isArray(output.system)) {
11281
+ output.system.push(...hints);
11282
+ }
11283
+ }
11284
+ var plugin = async ({ directory, worktree }) => {
11220
11285
  try {
11221
- const projectRoot = directory;
11286
+ const projectRoot = worktree || directory;
11222
11287
  const rawConfig = loadMergedConfig(projectRoot);
11223
11288
  const config = parseConfig(rawConfig);
11224
11289
  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) {
11290
+ const getProjectIndexer = () => getIndexerForProject(projectRoot);
11291
+ const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus()) : null;
11292
+ const isHomeDir = path17.resolve(projectRoot) === path17.resolve(os6.homedir());
11293
+ const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
11294
+ if (isHomeDir) {
11295
+ console.warn(
11296
+ `[codebase-index] Refusing to watch or index home directory "${projectRoot}". Open a specific project directory instead.`
11297
+ );
11298
+ } else if (!isValidProject) {
11229
11299
  console.warn(
11230
11300
  `[codebase-index] Skipping file watching and auto-indexing: no project marker found in "${projectRoot}". Set "indexing.requireProjectMarker": false in config to override.`
11231
11301
  );
11232
11302
  }
11233
11303
  if (config.indexing.autoIndex && isValidProject) {
11304
+ const indexer = getProjectIndexer();
11234
11305
  indexer.initialize().then(() => {
11235
11306
  indexer.index().catch(() => {
11236
11307
  });
@@ -11238,9 +11309,9 @@ var plugin = async ({ directory }) => {
11238
11309
  });
11239
11310
  }
11240
11311
  if (config.indexing.watchFiles && isValidProject) {
11241
- replaceActiveWatcher(createWatcherWithIndexer(getSharedIndexer, projectRoot, config));
11312
+ replaceActiveWatcher(projectRoot, createWatcherWithIndexer(getProjectIndexer, projectRoot, config));
11242
11313
  } else {
11243
- replaceActiveWatcher(null);
11314
+ replaceActiveWatcher(projectRoot, null);
11244
11315
  }
11245
11316
  return {
11246
11317
  tool: {
@@ -11262,8 +11333,18 @@ var plugin = async ({ directory }) => {
11262
11333
  routingHints?.observeUserMessage(input.sessionID, output.parts);
11263
11334
  },
11264
11335
  async "experimental.chat.system.transform"(input, output) {
11336
+ if (config.search.routingHintRole !== "system") {
11337
+ return;
11338
+ }
11265
11339
  const hints = await routingHints?.getSystemHints(input.sessionID) ?? [];
11266
- output.system.push(...hints);
11340
+ appendRoutingHints(output, hints, "system");
11341
+ },
11342
+ async "experimental.chat.developer.transform"(input, output) {
11343
+ if (config.search.routingHintRole !== "developer") {
11344
+ return;
11345
+ }
11346
+ const hints = await routingHints?.getSystemHints(input.sessionID) ?? [];
11347
+ appendRoutingHints(output, hints, "developer");
11267
11348
  },
11268
11349
  async "tool.execute.after"(input) {
11269
11350
  routingHints?.markToolUsed(input.sessionID, input.tool);
@@ -11277,8 +11358,8 @@ var plugin = async ({ directory }) => {
11277
11358
  }
11278
11359
  }
11279
11360
  };
11280
- } catch (error) {
11281
- console.error("[codebase-index] Failed to initialize plugin:", error);
11361
+ } catch {
11362
+ console.error("[codebase-index] Failed to initialize plugin (check config and network)");
11282
11363
  return {
11283
11364
  tool: void 0,
11284
11365
  async config() {