opencode-codebase-index 0.19.0 → 0.20.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
@@ -787,6 +787,7 @@ function getDefaultIndexingConfig() {
787
787
  autoIndexMaxRetries: 5,
788
788
  autoIndexRetryDelayMs: 100,
789
789
  watchFiles: true,
790
+ pauseBackgroundIndexingOnBattery: false,
790
791
  maxFileSize: 1048576,
791
792
  maxChunksPerFile: 100,
792
793
  semanticOnly: false,
@@ -918,6 +919,7 @@ function parseConfig(raw) {
918
919
  autoIndexMaxRetries: typeof rawIndexing.autoIndexMaxRetries === "number" ? Math.min(10, Math.max(0, Math.floor(rawIndexing.autoIndexMaxRetries))) : defaultIndexing.autoIndexMaxRetries,
919
920
  autoIndexRetryDelayMs: typeof rawIndexing.autoIndexRetryDelayMs === "number" ? Math.min(1e4, Math.max(10, Math.floor(rawIndexing.autoIndexRetryDelayMs))) : defaultIndexing.autoIndexRetryDelayMs,
920
921
  watchFiles: typeof rawIndexing.watchFiles === "boolean" ? rawIndexing.watchFiles : defaultIndexing.watchFiles,
922
+ pauseBackgroundIndexingOnBattery: typeof rawIndexing.pauseBackgroundIndexingOnBattery === "boolean" ? rawIndexing.pauseBackgroundIndexingOnBattery : defaultIndexing.pauseBackgroundIndexingOnBattery,
921
923
  maxFileSize: typeof rawIndexing.maxFileSize === "number" ? rawIndexing.maxFileSize : defaultIndexing.maxFileSize,
922
924
  maxChunksPerFile: typeof rawIndexing.maxChunksPerFile === "number" ? Math.max(1, rawIndexing.maxChunksPerFile) : defaultIndexing.maxChunksPerFile,
923
925
  semanticOnly: typeof rawIndexing.semanticOnly === "boolean" ? rawIndexing.semanticOnly : defaultIndexing.semanticOnly,
@@ -3496,9 +3498,7 @@ var PROJECT_MARKERS = [
3496
3498
  "pom.xml",
3497
3499
  "build.gradle",
3498
3500
  "CMakeLists.txt",
3499
- "Makefile",
3500
- ".opencode",
3501
- ".codebase-index"
3501
+ "Makefile"
3502
3502
  ];
3503
3503
  function hasProjectMarker(projectRoot) {
3504
3504
  for (const marker of PROJECT_MARKERS) {
@@ -4190,9 +4190,21 @@ function parseFiles(files) {
4190
4190
  return result.map((f) => ({
4191
4191
  path: f.path,
4192
4192
  chunks: f.chunks.map(mapChunk),
4193
+ symbols: (f.symbols ?? []).map(mapParsedSymbol),
4193
4194
  hash: f.hash
4194
4195
  }));
4195
4196
  }
4197
+ function mapParsedSymbol(symbol) {
4198
+ return {
4199
+ name: symbol.name,
4200
+ kind: symbol.kind,
4201
+ startLine: symbol.startLine ?? symbol.start_line,
4202
+ startCol: symbol.startCol ?? symbol.start_col,
4203
+ endLine: symbol.endLine ?? symbol.end_line,
4204
+ endCol: symbol.endCol ?? symbol.end_col,
4205
+ language: symbol.language
4206
+ };
4207
+ }
4196
4208
  function mapChunk(c) {
4197
4209
  return {
4198
4210
  content: c.content,
@@ -6709,6 +6721,7 @@ var INDEX_METADATA_VERSION = "1";
6709
6721
  var EMBEDDING_STRATEGY_VERSION = "2";
6710
6722
  var SWIFT_PARSER_VERSION = "1";
6711
6723
  var METAL_PARSER_VERSION = "1";
6724
+ var SYMBOL_EXTRACTOR_VERSION = "1";
6712
6725
  var RANKING_TOKEN_CACHE_LIMIT = 4096;
6713
6726
  var RANK_HYBRID_CACHE_LIMIT = 256;
6714
6727
  function createPendingChunkStorageText(texts) {
@@ -7010,7 +7023,7 @@ function classifyQueryIntentRaw(query) {
7010
7023
  return "neutral";
7011
7024
  }
7012
7025
  function isImplementationChunkType(chunkType) {
7013
- return [
7026
+ return CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(chunkType) || [
7014
7027
  "export_statement",
7015
7028
  "function",
7016
7029
  "function_declaration",
@@ -7455,7 +7468,7 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
7455
7468
  const remainder = combined.filter((candidate) => !promotedIds.has(candidate.id));
7456
7469
  return [...promoted, ...remainder];
7457
7470
  }
7458
- function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
7471
+ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
7459
7472
  if (!prioritizeSourcePaths) {
7460
7473
  return [];
7461
7474
  }
@@ -7469,14 +7482,14 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
7469
7482
  const primaryHint = extractPrimaryIdentifierQueryHint(query);
7470
7483
  const upsertChunkCandidate = (chunk, identifier, normalizedIdentifier, baseScore) => {
7471
7484
  if (branchChunkIds && !branchChunkIds.has(chunk.chunkId)) {
7472
- return;
7485
+ return false;
7473
7486
  }
7474
7487
  const chunkType = chunk.nodeType ?? "other";
7475
7488
  if (!isImplementationChunkType(chunkType)) {
7476
- return;
7489
+ return false;
7477
7490
  }
7478
7491
  if (!isLikelyImplementationPath2(chunk.filePath)) {
7479
- return;
7492
+ return false;
7480
7493
  }
7481
7494
  const nameLower = (chunk.name ?? "").toLowerCase();
7482
7495
  const exactName = nameLower === identifier || nameLower.replace(/_/g, "") === normalizedIdentifier;
@@ -7498,6 +7511,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
7498
7511
  }
7499
7512
  });
7500
7513
  }
7514
+ return true;
7501
7515
  };
7502
7516
  const normalizedHints = identifierHints.flatMap((hint) => [
7503
7517
  hint,
@@ -7519,12 +7533,46 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
7519
7533
  dedupSymbols.set(symbol.id, symbol);
7520
7534
  }
7521
7535
  for (const symbol of dedupSymbols.values()) {
7536
+ if (branchSymbolIds && !branchSymbolIds.has(symbol.id)) {
7537
+ continue;
7538
+ }
7539
+ if (filePathHint && !pathMatchesHint(symbol.filePath, filePathHint)) {
7540
+ continue;
7541
+ }
7522
7542
  const chunks = database.getChunksByFile(symbol.filePath);
7543
+ let foundCoveringChunk = false;
7523
7544
  for (const chunk of chunks) {
7524
7545
  if (chunk.startLine > symbol.startLine || chunk.endLine < symbol.endLine) {
7525
7546
  continue;
7526
7547
  }
7527
- upsertChunkCandidate(chunk, identifier, normalizedIdentifier);
7548
+ const chunkName = (chunk.name ?? "").toLowerCase();
7549
+ const symbolName2 = symbol.name.toLowerCase();
7550
+ if (chunkName !== symbolName2 && chunkName.replace(/_/g, "") !== symbolName2.replace(/_/g, "")) {
7551
+ continue;
7552
+ }
7553
+ foundCoveringChunk = upsertChunkCandidate(chunk, identifier, normalizedIdentifier) || foundCoveringChunk;
7554
+ }
7555
+ if (foundCoveringChunk || !isLikelyImplementationPath2(symbol.filePath)) {
7556
+ continue;
7557
+ }
7558
+ const symbolName = symbol.name.toLowerCase();
7559
+ const exactName = symbolName === identifier || symbolName.replace(/_/g, "") === normalizedIdentifier;
7560
+ const score = exactName ? 0.99 : 0.88;
7561
+ const existing = symbolCandidates.get(symbol.id);
7562
+ if (!existing || score > existing.score) {
7563
+ symbolCandidates.set(symbol.id, {
7564
+ id: symbol.id,
7565
+ score,
7566
+ metadata: {
7567
+ filePath: symbol.filePath,
7568
+ startLine: symbol.startLine,
7569
+ endLine: symbol.endLine,
7570
+ chunkType: symbol.kind,
7571
+ name: symbol.name,
7572
+ language: symbol.language,
7573
+ hash: symbol.id
7574
+ }
7575
+ });
7528
7576
  }
7529
7577
  }
7530
7578
  const dedupChunksByName = /* @__PURE__ */ new Map();
@@ -7532,6 +7580,9 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
7532
7580
  dedupChunksByName.set(chunk.chunkId, chunk);
7533
7581
  }
7534
7582
  for (const chunk of dedupChunksByName.values()) {
7583
+ if (filePathHint && !pathMatchesHint(chunk.filePath, filePathHint)) {
7584
+ continue;
7585
+ }
7535
7586
  upsertChunkCandidate(chunk, identifier, normalizedIdentifier);
7536
7587
  }
7537
7588
  }
@@ -8073,6 +8124,10 @@ var Indexer = class _Indexer {
8073
8124
  const projectHash = hashContent(path14.resolve(this.projectRoot)).slice(0, 16);
8074
8125
  return `${key}.${projectHash}`;
8075
8126
  }
8127
+ getSymbolExtractorVersionMetadataKey(catalogIdentity = this.getBranchCatalogIdentity()) {
8128
+ const branchKey = this.getBranchCatalogKeyFor(catalogIdentity);
8129
+ return `index.symbolExtractorVersion.${hashContent(branchKey).slice(0, 24)}`;
8130
+ }
8076
8131
  hasProjectForceReembedPending() {
8077
8132
  return this.config.scope === "global" && this.database?.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
8078
8133
  }
@@ -9416,7 +9471,8 @@ var Indexer = class _Indexer {
9416
9471
  }
9417
9472
  const branchKey = this.getBranchCatalogKey();
9418
9473
  const alreadyIndexed = database.getBranchChunkIds(branchKey).length > 0 && database.getBranchSymbolIds(branchKey).length > 0;
9419
- if (alreadyIndexed && this.getStoredBranchCommit(database) === normalizedCommit) {
9474
+ const symbolsCurrent = database.getMetadata(this.getSymbolExtractorVersionMetadataKey()) === SYMBOL_EXTRACTOR_VERSION;
9475
+ if (alreadyIndexed && symbolsCurrent && this.getStoredBranchCommit(database) === normalizedCommit) {
9420
9476
  return { prepared: false };
9421
9477
  }
9422
9478
  const stats = await this.indexUnlocked(onProgress, [], true);
@@ -9475,6 +9531,8 @@ var Indexer = class _Indexer {
9475
9531
  const reparseCachedSwiftFiles = database.getMetadata(swiftParserMetadataKey) !== SWIFT_PARSER_VERSION;
9476
9532
  const metalParserMetadataKey = this.getMetalParserVersionMetadataKey();
9477
9533
  const reparseCachedMetalFiles = database.getMetadata(metalParserMetadataKey) !== METAL_PARSER_VERSION;
9534
+ const symbolExtractorMetadataKey = this.getSymbolExtractorVersionMetadataKey();
9535
+ const refreshCachedSymbols = database.getMetadata(symbolExtractorMetadataKey) !== SYMBOL_EXTRACTOR_VERSION;
9478
9536
  if (reparseCachedSwiftFiles && Array.from(this.fileHashCache.keys()).some(
9479
9537
  (filePath) => path14.extname(filePath).toLowerCase() === ".swift"
9480
9538
  )) {
@@ -9518,7 +9576,7 @@ var Indexer = class _Indexer {
9518
9576
  );
9519
9577
  const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path14.extname(canonicalPath).toLowerCase() === ".swift";
9520
9578
  const requiresMetalParserUpgrade = reparseCachedMetalFiles && path14.extname(canonicalPath).toLowerCase() === ".metal";
9521
- if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade) {
9579
+ if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
9522
9580
  unchangedFilePaths.add(canonicalPath);
9523
9581
  this.logger.recordCacheHit();
9524
9582
  } else {
@@ -9729,37 +9787,27 @@ var Indexer = class _Indexer {
9729
9787
  const parsed = parsedFiles[i];
9730
9788
  const changedFile = changedFiles[i];
9731
9789
  const fileSymbols = [];
9732
- for (const chunk of parsed.chunks) {
9733
- if (!chunk.name || !CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(chunk.chunkType)) continue;
9734
- const existingMetalSymbol = chunk.language === "metal" ? fileSymbols.find(
9735
- (symbol2) => symbol2.name === chunk.name && symbol2.kind === chunk.chunkType && symbol2.startLine <= chunk.endLine && chunk.startLine <= symbol2.endLine
9736
- ) : void 0;
9737
- if (existingMetalSymbol) {
9738
- existingMetalSymbol.startLine = Math.min(existingMetalSymbol.startLine, chunk.startLine);
9739
- existingMetalSymbol.endLine = Math.max(existingMetalSymbol.endLine, chunk.endLine);
9740
- existingMetalSymbol.startCol = Math.min(existingMetalSymbol.startCol, chunk.startCol ?? 0);
9741
- existingMetalSymbol.endCol = Math.max(existingMetalSymbol.endCol, chunk.endCol ?? 0);
9742
- continue;
9743
- }
9790
+ for (const parsedSymbol of parsed.symbols) {
9791
+ if (!CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(parsedSymbol.kind)) continue;
9744
9792
  const preparedNamespace = this.getPreparedBranchNamespace();
9745
9793
  const symbolId = `sym_${hashContent(
9746
- (preparedNamespace ? `${preparedNamespace}:` : "") + parsed.path + ":" + chunk.name + ":" + chunk.chunkType + ":" + chunk.startLine + ":" + (chunk.startCol ?? 0) + ":" + changedFile.hash
9794
+ (preparedNamespace ? `${preparedNamespace}:` : "") + parsed.path + ":" + parsedSymbol.name + ":" + parsedSymbol.kind + ":" + parsedSymbol.startLine + ":" + parsedSymbol.startCol + ":" + changedFile.hash
9747
9795
  ).slice(0, 16)}`;
9748
9796
  const symbol = {
9749
9797
  id: symbolId,
9750
9798
  filePath: parsed.path,
9751
- name: chunk.name,
9752
- kind: chunk.chunkType,
9753
- startLine: chunk.startLine,
9754
- startCol: chunk.startCol ?? 0,
9755
- endLine: chunk.endLine,
9756
- endCol: chunk.endCol ?? 0,
9757
- language: chunk.language
9799
+ name: parsedSymbol.name,
9800
+ kind: parsedSymbol.kind,
9801
+ startLine: parsedSymbol.startLine,
9802
+ startCol: parsedSymbol.startCol,
9803
+ endLine: parsedSymbol.endLine,
9804
+ endCol: parsedSymbol.endCol,
9805
+ language: parsedSymbol.language
9758
9806
  };
9759
9807
  fileSymbols.push(symbol);
9760
9808
  allSymbolIds.add(symbolId);
9761
9809
  }
9762
- const fileLanguage = parsed.chunks[0]?.language;
9810
+ const fileLanguage = parsed.symbols[0]?.language ?? parsed.chunks[0]?.language;
9763
9811
  const isCaseInsensitiveLanguage = !!fileLanguage && CASE_INSENSITIVE_LANGUAGES.has(fileLanguage);
9764
9812
  const normalizeSymbolKey = (name) => isCaseInsensitiveLanguage ? name.toLowerCase() : name;
9765
9813
  const symbolsByName = /* @__PURE__ */ new Map();
@@ -9875,6 +9923,7 @@ var Indexer = class _Indexer {
9875
9923
  }
9876
9924
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
9877
9925
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
9926
+ database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
9878
9927
  this.saveBranchCommit(database, indexedCommit);
9879
9928
  this.saveIndexMetadata(configuredProviderInfo);
9880
9929
  this.indexCompatibility = { compatible: true };
@@ -9911,6 +9960,7 @@ var Indexer = class _Indexer {
9911
9960
  }
9912
9961
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
9913
9962
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
9963
+ database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
9914
9964
  this.saveBranchCommit(database, indexedCommit);
9915
9965
  this.saveIndexMetadata(configuredProviderInfo);
9916
9966
  this.indexCompatibility = { compatible: true };
@@ -10190,6 +10240,7 @@ var Indexer = class _Indexer {
10190
10240
  }
10191
10241
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
10192
10242
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
10243
+ database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
10193
10244
  this.saveBranchCommit(database, indexedCommit);
10194
10245
  this.saveIndexMetadata(configuredProviderInfo);
10195
10246
  this.indexCompatibility = { compatible: true };
@@ -10328,10 +10379,11 @@ var Indexer = class _Indexer {
10328
10379
  const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
10329
10380
  const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
10330
10381
  let branchChunkIds = null;
10382
+ let branchSymbolIds = null;
10331
10383
  if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
10332
- branchChunkIds = new Set(
10333
- this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
10334
- );
10384
+ const branchCatalogKeys = this.getBranchCatalogKeys();
10385
+ branchChunkIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchChunkIds(branchKey)));
10386
+ branchSymbolIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchSymbolIds(branchKey)));
10335
10387
  }
10336
10388
  const prefilterStartTime = import_perf_hooks.performance.now();
10337
10389
  const shouldPrefilterByBranch = branchChunkIds !== null && (this.config.scope === "global" || branchChunkIds.size > 0);
@@ -10403,6 +10455,7 @@ var Indexer = class _Indexer {
10403
10455
  query,
10404
10456
  database,
10405
10457
  branchChunkIds,
10458
+ branchSymbolIds,
10406
10459
  maxResults,
10407
10460
  union,
10408
10461
  sourceIntent
@@ -10416,7 +10469,7 @@ var Indexer = class _Indexer {
10416
10469
  (r) => isLikelyImplementationPath2(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
10417
10470
  );
10418
10471
  const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
10419
- const identifierFallback = !options?.definitionIntent && filtered.length === 0 && identifierHints.length > 0 ? buildSymbolDefinitionLane(query, database, branchChunkIds, maxResults, union, true).filter((r) => matchesSearchFilters(r, options, this.config.search.minScore)).slice(0, maxResults) : [];
10472
+ const identifierFallback = !options?.definitionIntent && filtered.length === 0 && identifierHints.length > 0 ? buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, maxResults, union, true).filter((r) => matchesSearchFilters(r, options, this.config.search.minScore)).slice(0, maxResults) : [];
10420
10473
  const finalResults = filtered.length > 0 ? filtered : identifierFallback;
10421
10474
  const totalSearchMs = import_perf_hooks.performance.now() - searchStartTime;
10422
10475
  this.logger.recordSearch(totalSearchMs, {
@@ -10573,7 +10626,7 @@ var Indexer = class _Indexer {
10573
10626
  const extension = path14.extname(filePath).toLowerCase();
10574
10627
  return extension === ".php" || extension === ".c" || extension === ".cc" || extension === ".cpp" || extension === ".cxx";
10575
10628
  });
10576
- if (hasSwiftFiles && database.getMetadata(this.getSwiftParserVersionMetadataKey()) !== SWIFT_PARSER_VERSION || hasMetalFiles && database.getMetadata(this.getMetalParserVersionMetadataKey()) !== METAL_PARSER_VERSION || hasCallGraphMigrationFiles && database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION) {
10629
+ if (hasSwiftFiles && database.getMetadata(this.getSwiftParserVersionMetadataKey()) !== SWIFT_PARSER_VERSION || hasMetalFiles && database.getMetadata(this.getMetalParserVersionMetadataKey()) !== METAL_PARSER_VERSION || database.getMetadata(this.getSymbolExtractorVersionMetadataKey()) !== SYMBOL_EXTRACTOR_VERSION || hasCallGraphMigrationFiles && database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION) {
10577
10630
  return { readable: true, current: false, reason: "migration-required" };
10578
10631
  }
10579
10632
  if (isGitRepo(this.materializedProjectRoot)) {
@@ -11281,7 +11334,10 @@ var Indexer = class _Indexer {
11281
11334
  const requestedRef = opts.pr !== void 0 ? expectedCommit : headRefName ?? resolvedBranch;
11282
11335
  const storedCommit = this.getStoredBranchCommit(database, catalogIdentity);
11283
11336
  const catalogIdentityMatches = storedCommit === expectedCommit;
11284
- if (branchSymbols.length === 0 || !catalogIdentityMatches) {
11337
+ const symbolsCurrent = database.getMetadata(
11338
+ this.getSymbolExtractorVersionMetadataKey(catalogIdentity)
11339
+ ) === SYMBOL_EXTRACTOR_VERSION;
11340
+ if (branchSymbols.length === 0 || !catalogIdentityMatches || !symbolsCurrent) {
11285
11341
  if (!resolvedBranch || resolvedBranch === "default") {
11286
11342
  throw new Error("Run index_codebase first to build the call graph and symbol index for this project.");
11287
11343
  }
@@ -11623,8 +11679,17 @@ function fitTextToContextBudget(text, tokenBudget) {
11623
11679
  function normalizedLineRange(result) {
11624
11680
  return result.startLine <= result.endLine ? { start: result.startLine, end: result.endLine } : { start: result.endLine, end: result.startLine };
11625
11681
  }
11626
- function rankContextCandidates(results) {
11627
- return results.map((result, originalIndex) => ({ result, originalIndex })).sort((left, right) => right.result.score - left.result.score || left.originalIndex - right.originalIndex);
11682
+ function rankContextCandidates(results, preferImplementationPaths) {
11683
+ return results.map((result, originalIndex) => ({ result, originalIndex })).sort((left, right) => {
11684
+ if (preferImplementationPaths) {
11685
+ const leftIsImplementation = isLikelyImplementationPath(left.result.filePath);
11686
+ const rightIsImplementation = isLikelyImplementationPath(right.result.filePath);
11687
+ if (leftIsImplementation !== rightIsImplementation) {
11688
+ return leftIsImplementation ? -1 : 1;
11689
+ }
11690
+ }
11691
+ return right.result.score - left.result.score || left.originalIndex - right.originalIndex;
11692
+ });
11628
11693
  }
11629
11694
  function deduplicateContextCandidates(candidates) {
11630
11695
  const acceptedByFile = /* @__PURE__ */ new Map();
@@ -11713,7 +11778,12 @@ function buildContextPack(results, options = {}) {
11713
11778
  const maxResults = Math.max(0, Math.floor(options.maxResults ?? results.length));
11714
11779
  const includeExactSearchHandoff = options.includeExactSearchHandoff ?? false;
11715
11780
  const candidateCount = results.length;
11716
- const deduplicated = deduplicateContextCandidates(rankContextCandidates(results));
11781
+ const deduplicated = deduplicateContextCandidates(
11782
+ rankContextCandidates(
11783
+ results,
11784
+ options.preferImplementationPaths ?? false
11785
+ )
11786
+ );
11717
11787
  const diversified = diversifyContextCandidates(deduplicated);
11718
11788
  const duplicateCount = candidateCount - deduplicated.length;
11719
11789
  const selectable = diversified.slice(0, maxResults);
@@ -12085,7 +12155,7 @@ ${truncateContent(r.content)}
12085
12155
  }
12086
12156
 
12087
12157
  // src/utils/effectiveness-metrics.ts
12088
- var EFFECTIVENESS_METRICS_SCHEMA_VERSION = 2;
12158
+ var EFFECTIVENESS_METRICS_SCHEMA_VERSION = 3;
12089
12159
  var MAX_EFFECTIVENESS_COUNTER = 1e9;
12090
12160
  var EFFECTIVENESS_TOOL_ROUTES = [
12091
12161
  "context-conceptual",
@@ -12131,6 +12201,9 @@ var EFFECTIVENESS_SCOPE_RELAXATION_BUCKETS = ["none", "directory", "file-type",
12131
12201
  function emptyCounterMap(values) {
12132
12202
  return Object.fromEntries(values.map((value) => [value, 0]));
12133
12203
  }
12204
+ function emptyRouteCounterMap(routes, values) {
12205
+ return Object.fromEntries(routes.map((route) => [route, emptyCounterMap(values)]));
12206
+ }
12134
12207
  function boundedNumber(value) {
12135
12208
  if (value === void 0 || !Number.isFinite(value)) return 0;
12136
12209
  return Math.max(0, Math.floor(value));
@@ -12179,6 +12252,11 @@ function allowedValue(value, allowed, fallback) {
12179
12252
  function cloneCounterMap(counters) {
12180
12253
  return { ...counters };
12181
12254
  }
12255
+ function cloneRouteCounterMap(counters) {
12256
+ return Object.fromEntries(
12257
+ EFFECTIVENESS_TOOL_ROUTES.map((route) => [route, cloneCounterMap(counters[route])])
12258
+ );
12259
+ }
12182
12260
  var EffectivenessMetrics = class {
12183
12261
  constructor(counterCap = MAX_EFFECTIVENESS_COUNTER) {
12184
12262
  this.counterCap = counterCap;
@@ -12194,7 +12272,7 @@ var EffectivenessMetrics = class {
12194
12272
  lifetime: "process",
12195
12273
  reset: "index_metrics-reset-or-process-exit",
12196
12274
  maxCounterValue: this.counterCap,
12197
- dimensions: "bounded-host-and-category-only"
12275
+ dimensions: "bounded-route-and-bucketed-performance-only"
12198
12276
  },
12199
12277
  totalCalls: 0,
12200
12278
  toolRoute: emptyCounterMap(EFFECTIVENESS_TOOL_ROUTES),
@@ -12206,7 +12284,14 @@ var EffectivenessMetrics = class {
12206
12284
  tokenBudget: emptyCounterMap(EFFECTIVENESS_TOKEN_BUDGET_BUCKETS),
12207
12285
  returnedTokenEstimate: emptyCounterMap(EFFECTIVENESS_RETURNED_TOKEN_BUCKETS),
12208
12286
  exactHandoffEmitted: emptyCounterMap(EFFECTIVENESS_BOOLEAN_BUCKETS),
12209
- scopeRelaxation: emptyCounterMap(EFFECTIVENESS_SCOPE_RELAXATION_BUCKETS)
12287
+ scopeRelaxation: emptyCounterMap(EFFECTIVENESS_SCOPE_RELAXATION_BUCKETS),
12288
+ routeOutcome: emptyRouteCounterMap(EFFECTIVENESS_TOOL_ROUTES, EFFECTIVENESS_OUTCOMES),
12289
+ routeLatency: emptyRouteCounterMap(EFFECTIVENESS_TOOL_ROUTES, EFFECTIVENESS_LATENCY_BUCKETS),
12290
+ routeResultCount: emptyRouteCounterMap(EFFECTIVENESS_TOOL_ROUTES, EFFECTIVENESS_RESULT_COUNT_BUCKETS),
12291
+ routeReturnedTokenEstimate: emptyRouteCounterMap(
12292
+ EFFECTIVENESS_TOOL_ROUTES,
12293
+ EFFECTIVENESS_RETURNED_TOKEN_BUCKETS
12294
+ )
12210
12295
  };
12211
12296
  }
12212
12297
  increment(counters, key) {
@@ -12228,12 +12313,19 @@ var EffectivenessMetrics = class {
12228
12313
  this.increment(this.snapshot.hostMode, host);
12229
12314
  this.increment(this.snapshot.outcome, outcome);
12230
12315
  this.increment(this.snapshot.recoveryUsed, recoveryUsed);
12231
- this.increment(this.snapshot.resultCount, resultCountBucket(event.resultCount));
12232
- this.increment(this.snapshot.latency, latencyBucket(event.latencyMs));
12316
+ const resultCountBucketValue = resultCountBucket(event.resultCount);
12317
+ const latencyBucketValue = latencyBucket(event.latencyMs);
12318
+ const returnedTokenBucketValue = returnedTokenBucket(event.returnedTokenEstimate);
12319
+ this.increment(this.snapshot.resultCount, resultCountBucketValue);
12320
+ this.increment(this.snapshot.latency, latencyBucketValue);
12233
12321
  this.increment(this.snapshot.tokenBudget, tokenBudgetBucket(event.tokenBudget));
12234
- this.increment(this.snapshot.returnedTokenEstimate, returnedTokenBucket(event.returnedTokenEstimate));
12322
+ this.increment(this.snapshot.returnedTokenEstimate, returnedTokenBucketValue);
12235
12323
  this.increment(this.snapshot.exactHandoffEmitted, exactHandoffEmitted);
12236
12324
  this.increment(this.snapshot.scopeRelaxation, scopeRelaxation);
12325
+ this.increment(this.snapshot.routeOutcome[route], outcome);
12326
+ this.increment(this.snapshot.routeLatency[route], latencyBucketValue);
12327
+ this.increment(this.snapshot.routeResultCount[route], resultCountBucketValue);
12328
+ this.increment(this.snapshot.routeReturnedTokenEstimate[route], returnedTokenBucketValue);
12237
12329
  }
12238
12330
  getSnapshot() {
12239
12331
  return {
@@ -12248,7 +12340,11 @@ var EffectivenessMetrics = class {
12248
12340
  tokenBudget: cloneCounterMap(this.snapshot.tokenBudget),
12249
12341
  returnedTokenEstimate: cloneCounterMap(this.snapshot.returnedTokenEstimate),
12250
12342
  exactHandoffEmitted: cloneCounterMap(this.snapshot.exactHandoffEmitted),
12251
- scopeRelaxation: cloneCounterMap(this.snapshot.scopeRelaxation)
12343
+ scopeRelaxation: cloneCounterMap(this.snapshot.scopeRelaxation),
12344
+ routeOutcome: cloneRouteCounterMap(this.snapshot.routeOutcome),
12345
+ routeLatency: cloneRouteCounterMap(this.snapshot.routeLatency),
12346
+ routeResultCount: cloneRouteCounterMap(this.snapshot.routeResultCount),
12347
+ routeReturnedTokenEstimate: cloneRouteCounterMap(this.snapshot.routeReturnedTokenEstimate)
12252
12348
  };
12253
12349
  }
12254
12350
  reset() {
@@ -12267,6 +12363,7 @@ function resetProcessEffectivenessMetrics() {
12267
12363
  }
12268
12364
  function formatEffectivenessMetrics(snapshot) {
12269
12365
  const formatCounters = (counters) => Object.entries(counters).map(([bucket, count]) => `${bucket}=${count}`).join(", ");
12366
+ const formatRouteCounters = (counters) => EFFECTIVENESS_TOOL_ROUTES.map((route) => `${route} => ${formatCounters(counters[route])}`).join("; ");
12270
12367
  const lines = [
12271
12368
  `Privacy-safe effectiveness (schema v${snapshot.schemaVersion}):`,
12272
12369
  ` Retention: ${snapshot.retention.storage}, ${snapshot.retention.lifetime}-lifetime; reset with index_metrics(reset=true) or process exit`,
@@ -12281,6 +12378,10 @@ function formatEffectivenessMetrics(snapshot) {
12281
12378
  ` Latency bucket: ${formatCounters(snapshot.latency)}`,
12282
12379
  ` Token-budget bucket: ${formatCounters(snapshot.tokenBudget)}`,
12283
12380
  ` Returned-token estimate: ${formatCounters(snapshot.returnedTokenEstimate)}`,
12381
+ ` Route outcome buckets: ${formatRouteCounters(snapshot.routeOutcome)}`,
12382
+ ` Route latency buckets: ${formatRouteCounters(snapshot.routeLatency)}`,
12383
+ ` Route result-count buckets: ${formatRouteCounters(snapshot.routeResultCount)}`,
12384
+ ` Route returned-token buckets: ${formatRouteCounters(snapshot.routeReturnedTokenEstimate)}`,
12284
12385
  ` Exact handoff emitted: ${formatCounters(snapshot.exactHandoffEmitted)}`,
12285
12386
  ` Scope relaxation: ${formatCounters(snapshot.scopeRelaxation)}`,
12286
12387
  " Privacy: no queries, response text, source, symbols, paths, repository names, user identity, or stable identifiers are retained."
@@ -12292,6 +12393,103 @@ function formatEffectivenessMetrics(snapshot) {
12292
12393
  var import_fs11 = require("fs");
12293
12394
  var os6 = __toESM(require("os"), 1);
12294
12395
  var path16 = __toESM(require("path"), 1);
12396
+
12397
+ // src/utils/power-source.ts
12398
+ var childProcess = __toESM(require("child_process"), 1);
12399
+ var POWER_SOURCE_RECHECK_DELAY_MS = 6e4;
12400
+ var PMSET_TIMEOUT_MS = 5e3;
12401
+ function getErrorMessage4(error) {
12402
+ return error instanceof Error ? error.message : String(error);
12403
+ }
12404
+ function runCommand(file, args, options) {
12405
+ return new Promise((resolve14, reject) => {
12406
+ childProcess.execFile(
12407
+ file,
12408
+ args,
12409
+ { encoding: "utf8", timeout: options.timeoutMs },
12410
+ (error, stdout) => {
12411
+ if (error) {
12412
+ reject(error);
12413
+ return;
12414
+ }
12415
+ resolve14(stdout);
12416
+ }
12417
+ );
12418
+ });
12419
+ }
12420
+ function parseMacOsPowerSource(output) {
12421
+ const match = output.match(/Now drawing from '([^']+)'/i);
12422
+ if (!match) {
12423
+ return "unknown";
12424
+ }
12425
+ const source = match[1].toLowerCase();
12426
+ if (source === "battery power") {
12427
+ return "battery";
12428
+ }
12429
+ if (source === "ac power") {
12430
+ return "ac";
12431
+ }
12432
+ return "unknown";
12433
+ }
12434
+ async function readMacOsPowerSource(commandRunner = runCommand) {
12435
+ const output = await commandRunner(
12436
+ "/usr/bin/pmset",
12437
+ ["-g", "batt"],
12438
+ { timeoutMs: PMSET_TIMEOUT_MS }
12439
+ );
12440
+ return parseMacOsPowerSource(output);
12441
+ }
12442
+ var MacOsBackgroundIndexingPolicy = class {
12443
+ constructor(readPowerSource, recheckDelayMs) {
12444
+ this.readPowerSource = readPowerSource;
12445
+ this.recheckDelayMs = recheckDelayMs;
12446
+ }
12447
+ readPowerSource;
12448
+ recheckDelayMs;
12449
+ lastPaused = null;
12450
+ reportedFailure = false;
12451
+ isPaused() {
12452
+ return this.checkPowerSource();
12453
+ }
12454
+ async checkPowerSource() {
12455
+ try {
12456
+ const source = await this.readPowerSource();
12457
+ if (source === "unknown") {
12458
+ throw new Error("pmset returned an unrecognized power source");
12459
+ }
12460
+ this.reportedFailure = false;
12461
+ const paused = source === "battery";
12462
+ if (paused && this.lastPaused !== true) {
12463
+ console.warn("[codebase-index] Background indexing paused while macOS is using battery power.");
12464
+ } else if (!paused && this.lastPaused === true) {
12465
+ console.warn("[codebase-index] AC power detected; resuming pending background indexing.");
12466
+ }
12467
+ this.lastPaused = paused;
12468
+ return paused;
12469
+ } catch (error) {
12470
+ if (!this.reportedFailure) {
12471
+ console.error(
12472
+ `[codebase-index] Failed to determine the macOS power source; background indexing will continue: ${getErrorMessage4(error)}`
12473
+ );
12474
+ this.reportedFailure = true;
12475
+ }
12476
+ this.lastPaused = false;
12477
+ return false;
12478
+ }
12479
+ }
12480
+ };
12481
+ function createBackgroundIndexingPolicy(pauseOnBattery, options = {}) {
12482
+ const platform2 = options.platform ?? process.platform;
12483
+ if (!pauseOnBattery || platform2 !== "darwin") {
12484
+ return null;
12485
+ }
12486
+ return new MacOsBackgroundIndexingPolicy(
12487
+ options.readPowerSource ?? readMacOsPowerSource,
12488
+ options.recheckDelayMs ?? POWER_SOURCE_RECHECK_DELAY_MS
12489
+ );
12490
+ }
12491
+
12492
+ // src/utils/auto-index.ts
12295
12493
  var MAX_RETRY_DELAY_MS = 1e4;
12296
12494
  var SHUTDOWN_WAIT_MS = 2e3;
12297
12495
  var coordinators = /* @__PURE__ */ new Map();
@@ -12408,7 +12606,13 @@ var AutoIndexCoordinator = class {
12408
12606
  activation = Promise.resolve();
12409
12607
  inFlight = null;
12410
12608
  activeRequest = null;
12609
+ batteryCheck = null;
12610
+ batteryIndexJob = null;
12611
+ batteryDeferredRequest = null;
12612
+ batteryRetryTimer = null;
12613
+ resolveBatteryRetry = null;
12411
12614
  pendingRequest = null;
12615
+ pendingFollowUp = null;
12412
12616
  abortController = null;
12413
12617
  stopped = false;
12414
12618
  constructor(registration) {
@@ -12421,6 +12625,7 @@ var AutoIndexCoordinator = class {
12421
12625
  };
12422
12626
  }
12423
12627
  update(registration) {
12628
+ const pauseOnBatteryChanged = registration.config.indexing.pauseBackgroundIndexingOnBattery !== this.registration.config.indexing.pauseBackgroundIndexingOnBattery;
12424
12629
  this.registration = registration;
12425
12630
  this.status.enabled = registration.config.indexing.autoIndex;
12426
12631
  this.status.blockedReason = registration.blockedReason;
@@ -12436,6 +12641,9 @@ var AutoIndexCoordinator = class {
12436
12641
  this.setState("idle", { source: void 0 });
12437
12642
  }
12438
12643
  }
12644
+ if (pauseOnBatteryChanged) {
12645
+ this.cancelBatteryRetry();
12646
+ }
12439
12647
  }
12440
12648
  activateAfter(activation) {
12441
12649
  this.activation = activation;
@@ -12457,7 +12665,26 @@ var AutoIndexCoordinator = class {
12457
12665
  if (this.stopped) {
12458
12666
  return Promise.resolve({ outcome: "stopped" });
12459
12667
  }
12460
- return this.activation.then(() => this.enqueueRequest(request));
12668
+ return this.activation.then(() => this.enqueueBatteryAwareRequest(request));
12669
+ }
12670
+ enqueueBatteryAwareRequest(request) {
12671
+ if (!this.shouldDeferForBattery(request)) {
12672
+ return this.enqueueRequest(request);
12673
+ }
12674
+ if (this.batteryCheck && this.batteryIndexJob !== null && this.batteryIndexJob === this.inFlight) {
12675
+ return this.enqueueRequest(request);
12676
+ }
12677
+ this.batteryDeferredRequest = mergeRequests(this.batteryDeferredRequest, request);
12678
+ if (this.batteryCheck) {
12679
+ return this.batteryCheck;
12680
+ }
12681
+ const batteryCheck = this.waitForACPower();
12682
+ this.batteryCheck = batteryCheck;
12683
+ void batteryCheck.then(
12684
+ () => this.finishBatteryCheck(batteryCheck),
12685
+ () => this.finishBatteryCheck(batteryCheck)
12686
+ );
12687
+ return batteryCheck;
12461
12688
  }
12462
12689
  enqueueRequest(request) {
12463
12690
  if (this.stopped || !this.canRun(request)) {
@@ -12475,6 +12702,11 @@ var AutoIndexCoordinator = class {
12475
12702
  }
12476
12703
  if (request.source === "watcher") {
12477
12704
  this.pendingRequest = mergeRequests(this.pendingRequest, request);
12705
+ const active = this.inFlight;
12706
+ return active.then(() => {
12707
+ if (this.stopped) return { outcome: "stopped" };
12708
+ return this.pendingFollowUp ?? { outcome: "stopped" };
12709
+ });
12478
12710
  }
12479
12711
  return this.inFlight;
12480
12712
  }
@@ -12491,6 +12723,8 @@ var AutoIndexCoordinator = class {
12491
12723
  }
12492
12724
  async stop(waitForCompletion = false) {
12493
12725
  this.stopped = true;
12726
+ this.batteryDeferredRequest = null;
12727
+ this.cancelBatteryRetry();
12494
12728
  this.pendingRequest = null;
12495
12729
  this.abortController?.abort();
12496
12730
  this.setState("stopped", {
@@ -12520,10 +12754,20 @@ var AutoIndexCoordinator = class {
12520
12754
  this.inFlight = null;
12521
12755
  this.activeRequest = null;
12522
12756
  this.abortController = null;
12757
+ if (this.batteryIndexJob === job) {
12758
+ this.batteryIndexJob = null;
12759
+ this.batteryCheck = null;
12760
+ }
12523
12761
  const pending = this.pendingRequest;
12524
12762
  this.pendingRequest = null;
12525
12763
  if (pending && !this.stopped) {
12526
- this.startRequest(pending);
12764
+ const followUp = this.request(pending);
12765
+ this.pendingFollowUp = followUp;
12766
+ void followUp.then(() => {
12767
+ if (this.pendingFollowUp === followUp) {
12768
+ this.pendingFollowUp = null;
12769
+ }
12770
+ });
12527
12771
  }
12528
12772
  });
12529
12773
  return job;
@@ -12687,6 +12931,68 @@ var AutoIndexCoordinator = class {
12687
12931
  }
12688
12932
  return this.registration.safeToRun && this.registration.config.indexing.autoIndex;
12689
12933
  }
12934
+ shouldDeferForBattery(request) {
12935
+ return this.registration.backgroundIndexingPolicy !== null && (request.source === "startup" || request.source === "watcher");
12936
+ }
12937
+ async waitForACPower() {
12938
+ while (!this.stopped) {
12939
+ const policy = this.registration.backgroundIndexingPolicy;
12940
+ if (!policy || !await this.isBatteryPauseActive(policy)) {
12941
+ const request = this.batteryDeferredRequest;
12942
+ this.batteryDeferredRequest = null;
12943
+ if (!request) return { outcome: "stopped" };
12944
+ const job = this.enqueueRequest(request);
12945
+ if (this.inFlight === job) {
12946
+ this.batteryIndexJob = job;
12947
+ }
12948
+ return job;
12949
+ }
12950
+ await this.waitForBatteryRetry(policy.recheckDelayMs);
12951
+ }
12952
+ return { outcome: "stopped" };
12953
+ }
12954
+ async isBatteryPauseActive(policy) {
12955
+ try {
12956
+ return await policy.isPaused();
12957
+ } catch (error) {
12958
+ console.error(
12959
+ `[codebase-index] Failed to apply the background indexing power policy; background indexing will continue: ${safeFailureMessage(error)}`
12960
+ );
12961
+ return false;
12962
+ }
12963
+ }
12964
+ waitForBatteryRetry(delayMs) {
12965
+ return new Promise((resolve14) => {
12966
+ const timer = setTimeout(() => {
12967
+ if (this.batteryRetryTimer === timer) {
12968
+ this.batteryRetryTimer = null;
12969
+ this.resolveBatteryRetry = null;
12970
+ }
12971
+ resolve14();
12972
+ }, delayMs);
12973
+ timer.unref?.();
12974
+ this.batteryRetryTimer = timer;
12975
+ this.resolveBatteryRetry = resolve14;
12976
+ });
12977
+ }
12978
+ cancelBatteryRetry() {
12979
+ if (this.batteryRetryTimer) {
12980
+ clearTimeout(this.batteryRetryTimer);
12981
+ this.batteryRetryTimer = null;
12982
+ }
12983
+ const resolve14 = this.resolveBatteryRetry;
12984
+ this.resolveBatteryRetry = null;
12985
+ resolve14?.();
12986
+ }
12987
+ finishBatteryCheck(batteryCheck) {
12988
+ if (this.batteryCheck !== batteryCheck) return;
12989
+ this.batteryCheck = null;
12990
+ const deferredRequest = this.batteryDeferredRequest;
12991
+ this.batteryDeferredRequest = null;
12992
+ if (deferredRequest && !this.stopped) {
12993
+ void this.request(deferredRequest);
12994
+ }
12995
+ }
12690
12996
  };
12691
12997
  function getCoordinator(projectRoot, host) {
12692
12998
  const key = coordinatorKeysByProject.get(projectLookupKey(projectRoot, host));
@@ -12696,6 +13002,9 @@ function configureAutoIndex(projectRoot, host, config, getIndexer) {
12696
13002
  const projectKey = projectLookupKey(projectRoot, host);
12697
13003
  const safety = getProjectSafety(projectRoot, config);
12698
13004
  const registration = {
13005
+ backgroundIndexingPolicy: createBackgroundIndexingPolicy(
13006
+ config.indexing.pauseBackgroundIndexingOnBattery
13007
+ ),
12699
13008
  config,
12700
13009
  getIndexer,
12701
13010
  projectRoot,
@@ -15447,7 +15756,10 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
15447
15756
  if (isGitRepo(projectRoot)) {
15448
15757
  gitWatcher = new GitHeadWatcher(projectRoot);
15449
15758
  gitWatcher.start(async (oldBranch, newBranch) => {
15450
- console.log(`Branch changed: ${oldBranch ?? "(none)"} -> ${newBranch}`);
15759
+ getIndexer().getLogger().branch("info", "Branch changed", {
15760
+ oldBranch,
15761
+ newBranch
15762
+ });
15451
15763
  requestReindex();
15452
15764
  });
15453
15765
  }
@@ -15999,6 +16311,7 @@ Explicit symbol lookup only; conceptual search was not attempted.`
15999
16311
  const results = await tryConceptualSearch(attempt.queryText, attempt.scope, attempt.relaxed);
16000
16312
  if (results.length > 0) {
16001
16313
  const heading = buildPackHeading("conceptual", decisions);
16314
+ const intent = analyzeQueryIntent(attempt.queryText);
16002
16315
  return toResult(
16003
16316
  "conceptual",
16004
16317
  attempt.queryText,
@@ -16006,7 +16319,8 @@ Explicit symbol lookup only; conceptual search was not attempted.`
16006
16319
  tokenBudget,
16007
16320
  maxResults: limit,
16008
16321
  heading,
16009
- includeExactSearchHandoff: true
16322
+ includeExactSearchHandoff: true,
16323
+ preferImplementationPaths: intent.preferSourcePaths || intent.primary !== "docs" && intent.primary !== "test"
16010
16324
  })
16011
16325
  );
16012
16326
  }
@@ -17296,6 +17610,21 @@ var CONCEPTUAL_DISCOVERY_HINTS = [
17296
17610
  "pattern",
17297
17611
  "code that"
17298
17612
  ];
17613
+ var BROAD_LOCAL_TASK_HINTS = [
17614
+ "fix the bug",
17615
+ "fix this",
17616
+ "fix issue",
17617
+ "implement ",
17618
+ "add support",
17619
+ "investigate ",
17620
+ "debug ",
17621
+ "refactor ",
17622
+ "review this",
17623
+ "review codebase",
17624
+ "review the codebase",
17625
+ "audit this",
17626
+ "audit the codebase"
17627
+ ];
17299
17628
  var DEFINITION_HINTS = [
17300
17629
  "defined",
17301
17630
  "definition",
@@ -17342,6 +17671,9 @@ function isExternalLookup(text) {
17342
17671
  function hasConceptualDiscoveryHint(text) {
17343
17672
  return includesHint(text, CONCEPTUAL_DISCOVERY_HINTS);
17344
17673
  }
17674
+ function hasBroadLocalTaskHint(text) {
17675
+ return includesHint(text, BROAD_LOCAL_TASK_HINTS);
17676
+ }
17345
17677
  function hasDefinitionHint(text) {
17346
17678
  return includesHint(text, DEFINITION_HINTS);
17347
17679
  }
@@ -17399,10 +17731,11 @@ function assessRoutingIntent(text) {
17399
17731
  const matchedDefinitionHint = hasDefinitionHint(lowered);
17400
17732
  const matchedExactMatchHint = hasExactMatchHint(lowered);
17401
17733
  const matchedNonDiscoveryHint = hasNonDiscoveryHint(lowered);
17734
+ const matchedBroadLocalTask = hasBroadLocalTaskHint(lowered);
17402
17735
  const hasIdentifier = hasIdentifierShape(normalizedText);
17403
17736
  const hasQuotedIdentifier = containsQuotedIdentifier(normalizedText);
17404
17737
  const shortQuery = countWords(lowered) <= 10;
17405
- if (matchedNonDiscoveryHint && !matchedConceptualHint) {
17738
+ if (matchedNonDiscoveryHint && !matchedConceptualHint && !matchedBroadLocalTask) {
17406
17739
  return {
17407
17740
  intent: "other",
17408
17741
  text: normalizedText,
@@ -17423,6 +17756,13 @@ function assessRoutingIntent(text) {
17423
17756
  reason: "definition_lookup_request"
17424
17757
  };
17425
17758
  }
17759
+ if (matchedBroadLocalTask) {
17760
+ return {
17761
+ intent: "local_broad_task",
17762
+ text: normalizedText,
17763
+ reason: "broad_local_code_task"
17764
+ };
17765
+ }
17426
17766
  if ((matchedExactMatchHint || hasQuotedIdentifier || hasIdentifier) && !matchedConceptualHint && shortQuery) {
17427
17767
  return {
17428
17768
  intent: "exact_identifier",
@@ -17450,15 +17790,15 @@ function buildRoutingHint(assessment, status, includeGraphHandoff = false) {
17450
17790
  }
17451
17791
  return "For this turn, prefer `implementation_lookup` to find the authoritative definition site. Use `codebase_search` only if no definition is found, and use `grep` for exhaustive literal matches.";
17452
17792
  }
17453
- if (assessment.intent !== "local_conceptual") {
17793
+ if (assessment.intent !== "local_conceptual" && assessment.intent !== "local_broad_task") {
17454
17794
  return null;
17455
17795
  }
17456
17796
  if (!status || !status.indexed || status.compatibility?.compatible === false) {
17457
17797
  const graphHandoff2 = includeGraphHandoff ? " Use graph tools after semantic discovery identifies relevant symbols." : "";
17458
- return `For this turn, if local code discovery by behavior is needed, check \`index_status\` first and run \`index_codebase\` if the index is missing or incompatible.${graphHandoff2} Use \`grep\` for exact identifiers or exhaustive matches.`;
17798
+ return `For this turn, if local code discovery by behavior is needed, check \`index_status\` first and run \`index_codebase\` if the index is missing or incompatible.${graphHandoff2} Then use \`codebase_context\` as the first local repository lookup. Use \`grep\` for exact identifiers or exhaustive matches.`;
17459
17799
  }
17460
17800
  const graphHandoff = includeGraphHandoff ? " before graph tools such as `call_graph`, `call_graph_path`, `pr_impact`, or OMO CodeGraph" : "";
17461
- return `For this turn, prefer \`codebase_peek\` for local code discovery by behavior or likely location${graphHandoff}. Then use \`codebase_search\` when you need implementation content. Use \`grep\` for exact identifiers or exhaustive matches.`;
17801
+ return `For this turn, prefer \`codebase_context\` for local code discovery, then use \`codebase_peek\` for metadata and \`codebase_search\` when you need implementation content${graphHandoff}. Use \`grep\` for exact identifiers or exhaustive matches.`;
17462
17802
  }
17463
17803
  var RoutingHintController = class {
17464
17804
  constructor(getStatus, maxSessions = 200, includeGraphHandoff = false) {
@@ -17475,7 +17815,7 @@ var RoutingHintController = class {
17475
17815
  this.compactSessions();
17476
17816
  this.sessionState.set(sessionID, {
17477
17817
  assessment,
17478
- pendingHint: assessment.intent === "local_conceptual" || assessment.intent === "definition_lookup",
17818
+ pendingHint: assessment.intent === "local_conceptual" || assessment.intent === "local_broad_task" || assessment.intent === "definition_lookup",
17479
17819
  updatedAt: Date.now()
17480
17820
  });
17481
17821
  return assessment;
@@ -17490,6 +17830,9 @@ var RoutingHintController = class {
17490
17830
  }
17491
17831
  const status = await this.safeGetStatus();
17492
17832
  const hint = buildRoutingHint(state.assessment, status, this.includeGraphHandoff);
17833
+ state.pendingHint = false;
17834
+ state.updatedAt = Date.now();
17835
+ this.sessionState.set(sessionID, state);
17493
17836
  return hint ? [hint] : [];
17494
17837
  }
17495
17838
  markToolUsed(sessionID, toolName) {
@@ -17497,7 +17840,7 @@ var RoutingHintController = class {
17497
17840
  if (!state || !state.pendingHint) {
17498
17841
  return;
17499
17842
  }
17500
- if (toolName === "codebase_peek" || toolName === "codebase_search" || toolName === "implementation_lookup" || toolName === "index_status" || toolName === "index_codebase") {
17843
+ if (toolName === "codebase_context" || toolName === "codebase_peek" || toolName === "codebase_search" || toolName === "implementation_lookup" || toolName === "index_status" || toolName === "index_codebase") {
17501
17844
  state.pendingHint = false;
17502
17845
  state.updatedAt = Date.now();
17503
17846
  this.sessionState.set(sessionID, state);
@@ -17554,9 +17897,15 @@ function appendRoutingHints(output, hints, preferredRole) {
17554
17897
  output.system.push(...hints);
17555
17898
  }
17556
17899
  }
17900
+ function resolveProjectRoot(directory, worktree) {
17901
+ if (worktree && isGitRepo(worktree)) {
17902
+ return worktree;
17903
+ }
17904
+ return directory;
17905
+ }
17557
17906
  var plugin = async ({ directory, worktree }) => {
17558
17907
  try {
17559
- const projectRoot = worktree || directory;
17908
+ const projectRoot = resolveProjectRoot(directory, worktree);
17560
17909
  const rawConfig = loadMergedConfig(projectRoot);
17561
17910
  const config = parseConfig(rawConfig);
17562
17911
  initializeTools2(projectRoot, config);