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.
@@ -786,6 +786,7 @@ function getDefaultIndexingConfig() {
786
786
  autoIndexMaxRetries: 5,
787
787
  autoIndexRetryDelayMs: 100,
788
788
  watchFiles: true,
789
+ pauseBackgroundIndexingOnBattery: false,
789
790
  maxFileSize: 1048576,
790
791
  maxChunksPerFile: 100,
791
792
  semanticOnly: false,
@@ -917,6 +918,7 @@ function parseConfig(raw) {
917
918
  autoIndexMaxRetries: typeof rawIndexing.autoIndexMaxRetries === "number" ? Math.min(10, Math.max(0, Math.floor(rawIndexing.autoIndexMaxRetries))) : defaultIndexing.autoIndexMaxRetries,
918
919
  autoIndexRetryDelayMs: typeof rawIndexing.autoIndexRetryDelayMs === "number" ? Math.min(1e4, Math.max(10, Math.floor(rawIndexing.autoIndexRetryDelayMs))) : defaultIndexing.autoIndexRetryDelayMs,
919
920
  watchFiles: typeof rawIndexing.watchFiles === "boolean" ? rawIndexing.watchFiles : defaultIndexing.watchFiles,
921
+ pauseBackgroundIndexingOnBattery: typeof rawIndexing.pauseBackgroundIndexingOnBattery === "boolean" ? rawIndexing.pauseBackgroundIndexingOnBattery : defaultIndexing.pauseBackgroundIndexingOnBattery,
920
922
  maxFileSize: typeof rawIndexing.maxFileSize === "number" ? rawIndexing.maxFileSize : defaultIndexing.maxFileSize,
921
923
  maxChunksPerFile: typeof rawIndexing.maxChunksPerFile === "number" ? Math.max(1, rawIndexing.maxChunksPerFile) : defaultIndexing.maxChunksPerFile,
922
924
  semanticOnly: typeof rawIndexing.semanticOnly === "boolean" ? rawIndexing.semanticOnly : defaultIndexing.semanticOnly,
@@ -3384,9 +3386,7 @@ var PROJECT_MARKERS = [
3384
3386
  "pom.xml",
3385
3387
  "build.gradle",
3386
3388
  "CMakeLists.txt",
3387
- "Makefile",
3388
- ".opencode",
3389
- ".codebase-index"
3389
+ "Makefile"
3390
3390
  ];
3391
3391
  function hasProjectMarker(projectRoot3) {
3392
3392
  for (const marker of PROJECT_MARKERS) {
@@ -3998,9 +3998,21 @@ function parseFiles(files) {
3998
3998
  return result.map((f) => ({
3999
3999
  path: f.path,
4000
4000
  chunks: f.chunks.map(mapChunk),
4001
+ symbols: (f.symbols ?? []).map(mapParsedSymbol),
4001
4002
  hash: f.hash
4002
4003
  }));
4003
4004
  }
4005
+ function mapParsedSymbol(symbol) {
4006
+ return {
4007
+ name: symbol.name,
4008
+ kind: symbol.kind,
4009
+ startLine: symbol.startLine ?? symbol.start_line,
4010
+ startCol: symbol.startCol ?? symbol.start_col,
4011
+ endLine: symbol.endLine ?? symbol.end_line,
4012
+ endCol: symbol.endCol ?? symbol.end_col,
4013
+ language: symbol.language
4014
+ };
4015
+ }
4004
4016
  function mapChunk(c) {
4005
4017
  return {
4006
4018
  content: c.content,
@@ -6517,6 +6529,7 @@ var INDEX_METADATA_VERSION = "1";
6517
6529
  var EMBEDDING_STRATEGY_VERSION = "2";
6518
6530
  var SWIFT_PARSER_VERSION = "1";
6519
6531
  var METAL_PARSER_VERSION = "1";
6532
+ var SYMBOL_EXTRACTOR_VERSION = "1";
6520
6533
  var RANKING_TOKEN_CACHE_LIMIT = 4096;
6521
6534
  var RANK_HYBRID_CACHE_LIMIT = 256;
6522
6535
  function createPendingChunkStorageText(texts) {
@@ -6818,7 +6831,7 @@ function classifyQueryIntentRaw(query) {
6818
6831
  return "neutral";
6819
6832
  }
6820
6833
  function isImplementationChunkType(chunkType) {
6821
- return [
6834
+ return CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(chunkType) || [
6822
6835
  "export_statement",
6823
6836
  "function",
6824
6837
  "function_declaration",
@@ -7263,7 +7276,7 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
7263
7276
  const remainder = combined.filter((candidate) => !promotedIds.has(candidate.id));
7264
7277
  return [...promoted, ...remainder];
7265
7278
  }
7266
- function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
7279
+ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
7267
7280
  if (!prioritizeSourcePaths) {
7268
7281
  return [];
7269
7282
  }
@@ -7277,14 +7290,14 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
7277
7290
  const primaryHint = extractPrimaryIdentifierQueryHint(query);
7278
7291
  const upsertChunkCandidate = (chunk, identifier, normalizedIdentifier, baseScore) => {
7279
7292
  if (branchChunkIds && !branchChunkIds.has(chunk.chunkId)) {
7280
- return;
7293
+ return false;
7281
7294
  }
7282
7295
  const chunkType = chunk.nodeType ?? "other";
7283
7296
  if (!isImplementationChunkType(chunkType)) {
7284
- return;
7297
+ return false;
7285
7298
  }
7286
7299
  if (!isLikelyImplementationPath2(chunk.filePath)) {
7287
- return;
7300
+ return false;
7288
7301
  }
7289
7302
  const nameLower = (chunk.name ?? "").toLowerCase();
7290
7303
  const exactName = nameLower === identifier || nameLower.replace(/_/g, "") === normalizedIdentifier;
@@ -7306,6 +7319,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
7306
7319
  }
7307
7320
  });
7308
7321
  }
7322
+ return true;
7309
7323
  };
7310
7324
  const normalizedHints = identifierHints.flatMap((hint) => [
7311
7325
  hint,
@@ -7327,12 +7341,46 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
7327
7341
  dedupSymbols.set(symbol.id, symbol);
7328
7342
  }
7329
7343
  for (const symbol of dedupSymbols.values()) {
7344
+ if (branchSymbolIds && !branchSymbolIds.has(symbol.id)) {
7345
+ continue;
7346
+ }
7347
+ if (filePathHint && !pathMatchesHint(symbol.filePath, filePathHint)) {
7348
+ continue;
7349
+ }
7330
7350
  const chunks = database.getChunksByFile(symbol.filePath);
7351
+ let foundCoveringChunk = false;
7331
7352
  for (const chunk of chunks) {
7332
7353
  if (chunk.startLine > symbol.startLine || chunk.endLine < symbol.endLine) {
7333
7354
  continue;
7334
7355
  }
7335
- upsertChunkCandidate(chunk, identifier, normalizedIdentifier);
7356
+ const chunkName = (chunk.name ?? "").toLowerCase();
7357
+ const symbolName2 = symbol.name.toLowerCase();
7358
+ if (chunkName !== symbolName2 && chunkName.replace(/_/g, "") !== symbolName2.replace(/_/g, "")) {
7359
+ continue;
7360
+ }
7361
+ foundCoveringChunk = upsertChunkCandidate(chunk, identifier, normalizedIdentifier) || foundCoveringChunk;
7362
+ }
7363
+ if (foundCoveringChunk || !isLikelyImplementationPath2(symbol.filePath)) {
7364
+ continue;
7365
+ }
7366
+ const symbolName = symbol.name.toLowerCase();
7367
+ const exactName = symbolName === identifier || symbolName.replace(/_/g, "") === normalizedIdentifier;
7368
+ const score = exactName ? 0.99 : 0.88;
7369
+ const existing = symbolCandidates.get(symbol.id);
7370
+ if (!existing || score > existing.score) {
7371
+ symbolCandidates.set(symbol.id, {
7372
+ id: symbol.id,
7373
+ score,
7374
+ metadata: {
7375
+ filePath: symbol.filePath,
7376
+ startLine: symbol.startLine,
7377
+ endLine: symbol.endLine,
7378
+ chunkType: symbol.kind,
7379
+ name: symbol.name,
7380
+ language: symbol.language,
7381
+ hash: symbol.id
7382
+ }
7383
+ });
7336
7384
  }
7337
7385
  }
7338
7386
  const dedupChunksByName = /* @__PURE__ */ new Map();
@@ -7340,6 +7388,9 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
7340
7388
  dedupChunksByName.set(chunk.chunkId, chunk);
7341
7389
  }
7342
7390
  for (const chunk of dedupChunksByName.values()) {
7391
+ if (filePathHint && !pathMatchesHint(chunk.filePath, filePathHint)) {
7392
+ continue;
7393
+ }
7343
7394
  upsertChunkCandidate(chunk, identifier, normalizedIdentifier);
7344
7395
  }
7345
7396
  }
@@ -7881,6 +7932,10 @@ var Indexer = class _Indexer {
7881
7932
  const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7882
7933
  return `${key}.${projectHash}`;
7883
7934
  }
7935
+ getSymbolExtractorVersionMetadataKey(catalogIdentity = this.getBranchCatalogIdentity()) {
7936
+ const branchKey = this.getBranchCatalogKeyFor(catalogIdentity);
7937
+ return `index.symbolExtractorVersion.${hashContent(branchKey).slice(0, 24)}`;
7938
+ }
7884
7939
  hasProjectForceReembedPending() {
7885
7940
  return this.config.scope === "global" && this.database?.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
7886
7941
  }
@@ -9224,7 +9279,8 @@ var Indexer = class _Indexer {
9224
9279
  }
9225
9280
  const branchKey = this.getBranchCatalogKey();
9226
9281
  const alreadyIndexed = database.getBranchChunkIds(branchKey).length > 0 && database.getBranchSymbolIds(branchKey).length > 0;
9227
- if (alreadyIndexed && this.getStoredBranchCommit(database) === normalizedCommit) {
9282
+ const symbolsCurrent = database.getMetadata(this.getSymbolExtractorVersionMetadataKey()) === SYMBOL_EXTRACTOR_VERSION;
9283
+ if (alreadyIndexed && symbolsCurrent && this.getStoredBranchCommit(database) === normalizedCommit) {
9228
9284
  return { prepared: false };
9229
9285
  }
9230
9286
  const stats = await this.indexUnlocked(onProgress, [], true);
@@ -9283,6 +9339,8 @@ var Indexer = class _Indexer {
9283
9339
  const reparseCachedSwiftFiles = database.getMetadata(swiftParserMetadataKey) !== SWIFT_PARSER_VERSION;
9284
9340
  const metalParserMetadataKey = this.getMetalParserVersionMetadataKey();
9285
9341
  const reparseCachedMetalFiles = database.getMetadata(metalParserMetadataKey) !== METAL_PARSER_VERSION;
9342
+ const symbolExtractorMetadataKey = this.getSymbolExtractorVersionMetadataKey();
9343
+ const refreshCachedSymbols = database.getMetadata(symbolExtractorMetadataKey) !== SYMBOL_EXTRACTOR_VERSION;
9286
9344
  if (reparseCachedSwiftFiles && Array.from(this.fileHashCache.keys()).some(
9287
9345
  (filePath) => path12.extname(filePath).toLowerCase() === ".swift"
9288
9346
  )) {
@@ -9326,7 +9384,7 @@ var Indexer = class _Indexer {
9326
9384
  );
9327
9385
  const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path12.extname(canonicalPath).toLowerCase() === ".swift";
9328
9386
  const requiresMetalParserUpgrade = reparseCachedMetalFiles && path12.extname(canonicalPath).toLowerCase() === ".metal";
9329
- if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade) {
9387
+ if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
9330
9388
  unchangedFilePaths.add(canonicalPath);
9331
9389
  this.logger.recordCacheHit();
9332
9390
  } else {
@@ -9537,37 +9595,27 @@ var Indexer = class _Indexer {
9537
9595
  const parsed = parsedFiles[i];
9538
9596
  const changedFile = changedFiles[i];
9539
9597
  const fileSymbols = [];
9540
- for (const chunk of parsed.chunks) {
9541
- if (!chunk.name || !CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(chunk.chunkType)) continue;
9542
- const existingMetalSymbol = chunk.language === "metal" ? fileSymbols.find(
9543
- (symbol2) => symbol2.name === chunk.name && symbol2.kind === chunk.chunkType && symbol2.startLine <= chunk.endLine && chunk.startLine <= symbol2.endLine
9544
- ) : void 0;
9545
- if (existingMetalSymbol) {
9546
- existingMetalSymbol.startLine = Math.min(existingMetalSymbol.startLine, chunk.startLine);
9547
- existingMetalSymbol.endLine = Math.max(existingMetalSymbol.endLine, chunk.endLine);
9548
- existingMetalSymbol.startCol = Math.min(existingMetalSymbol.startCol, chunk.startCol ?? 0);
9549
- existingMetalSymbol.endCol = Math.max(existingMetalSymbol.endCol, chunk.endCol ?? 0);
9550
- continue;
9551
- }
9598
+ for (const parsedSymbol of parsed.symbols) {
9599
+ if (!CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(parsedSymbol.kind)) continue;
9552
9600
  const preparedNamespace = this.getPreparedBranchNamespace();
9553
9601
  const symbolId = `sym_${hashContent(
9554
- (preparedNamespace ? `${preparedNamespace}:` : "") + parsed.path + ":" + chunk.name + ":" + chunk.chunkType + ":" + chunk.startLine + ":" + (chunk.startCol ?? 0) + ":" + changedFile.hash
9602
+ (preparedNamespace ? `${preparedNamespace}:` : "") + parsed.path + ":" + parsedSymbol.name + ":" + parsedSymbol.kind + ":" + parsedSymbol.startLine + ":" + parsedSymbol.startCol + ":" + changedFile.hash
9555
9603
  ).slice(0, 16)}`;
9556
9604
  const symbol = {
9557
9605
  id: symbolId,
9558
9606
  filePath: parsed.path,
9559
- name: chunk.name,
9560
- kind: chunk.chunkType,
9561
- startLine: chunk.startLine,
9562
- startCol: chunk.startCol ?? 0,
9563
- endLine: chunk.endLine,
9564
- endCol: chunk.endCol ?? 0,
9565
- language: chunk.language
9607
+ name: parsedSymbol.name,
9608
+ kind: parsedSymbol.kind,
9609
+ startLine: parsedSymbol.startLine,
9610
+ startCol: parsedSymbol.startCol,
9611
+ endLine: parsedSymbol.endLine,
9612
+ endCol: parsedSymbol.endCol,
9613
+ language: parsedSymbol.language
9566
9614
  };
9567
9615
  fileSymbols.push(symbol);
9568
9616
  allSymbolIds.add(symbolId);
9569
9617
  }
9570
- const fileLanguage = parsed.chunks[0]?.language;
9618
+ const fileLanguage = parsed.symbols[0]?.language ?? parsed.chunks[0]?.language;
9571
9619
  const isCaseInsensitiveLanguage = !!fileLanguage && CASE_INSENSITIVE_LANGUAGES.has(fileLanguage);
9572
9620
  const normalizeSymbolKey = (name) => isCaseInsensitiveLanguage ? name.toLowerCase() : name;
9573
9621
  const symbolsByName = /* @__PURE__ */ new Map();
@@ -9683,6 +9731,7 @@ var Indexer = class _Indexer {
9683
9731
  }
9684
9732
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
9685
9733
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
9734
+ database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
9686
9735
  this.saveBranchCommit(database, indexedCommit);
9687
9736
  this.saveIndexMetadata(configuredProviderInfo);
9688
9737
  this.indexCompatibility = { compatible: true };
@@ -9719,6 +9768,7 @@ var Indexer = class _Indexer {
9719
9768
  }
9720
9769
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
9721
9770
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
9771
+ database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
9722
9772
  this.saveBranchCommit(database, indexedCommit);
9723
9773
  this.saveIndexMetadata(configuredProviderInfo);
9724
9774
  this.indexCompatibility = { compatible: true };
@@ -9998,6 +10048,7 @@ var Indexer = class _Indexer {
9998
10048
  }
9999
10049
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
10000
10050
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
10051
+ database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
10001
10052
  this.saveBranchCommit(database, indexedCommit);
10002
10053
  this.saveIndexMetadata(configuredProviderInfo);
10003
10054
  this.indexCompatibility = { compatible: true };
@@ -10136,10 +10187,11 @@ var Indexer = class _Indexer {
10136
10187
  const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
10137
10188
  const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
10138
10189
  let branchChunkIds = null;
10190
+ let branchSymbolIds = null;
10139
10191
  if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
10140
- branchChunkIds = new Set(
10141
- this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
10142
- );
10192
+ const branchCatalogKeys = this.getBranchCatalogKeys();
10193
+ branchChunkIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchChunkIds(branchKey)));
10194
+ branchSymbolIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchSymbolIds(branchKey)));
10143
10195
  }
10144
10196
  const prefilterStartTime = import_perf_hooks.performance.now();
10145
10197
  const shouldPrefilterByBranch = branchChunkIds !== null && (this.config.scope === "global" || branchChunkIds.size > 0);
@@ -10211,6 +10263,7 @@ var Indexer = class _Indexer {
10211
10263
  query,
10212
10264
  database,
10213
10265
  branchChunkIds,
10266
+ branchSymbolIds,
10214
10267
  maxResults,
10215
10268
  union,
10216
10269
  sourceIntent
@@ -10224,7 +10277,7 @@ var Indexer = class _Indexer {
10224
10277
  (r) => isLikelyImplementationPath2(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
10225
10278
  );
10226
10279
  const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
10227
- 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) : [];
10280
+ 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) : [];
10228
10281
  const finalResults = filtered.length > 0 ? filtered : identifierFallback;
10229
10282
  const totalSearchMs = import_perf_hooks.performance.now() - searchStartTime;
10230
10283
  this.logger.recordSearch(totalSearchMs, {
@@ -10381,7 +10434,7 @@ var Indexer = class _Indexer {
10381
10434
  const extension = path12.extname(filePath).toLowerCase();
10382
10435
  return extension === ".php" || extension === ".c" || extension === ".cc" || extension === ".cpp" || extension === ".cxx";
10383
10436
  });
10384
- 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) {
10437
+ 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) {
10385
10438
  return { readable: true, current: false, reason: "migration-required" };
10386
10439
  }
10387
10440
  if (isGitRepo(this.materializedProjectRoot)) {
@@ -11089,7 +11142,10 @@ var Indexer = class _Indexer {
11089
11142
  const requestedRef = opts.pr !== void 0 ? expectedCommit : headRefName ?? resolvedBranch;
11090
11143
  const storedCommit = this.getStoredBranchCommit(database, catalogIdentity);
11091
11144
  const catalogIdentityMatches = storedCommit === expectedCommit;
11092
- if (branchSymbols.length === 0 || !catalogIdentityMatches) {
11145
+ const symbolsCurrent = database.getMetadata(
11146
+ this.getSymbolExtractorVersionMetadataKey(catalogIdentity)
11147
+ ) === SYMBOL_EXTRACTOR_VERSION;
11148
+ if (branchSymbols.length === 0 || !catalogIdentityMatches || !symbolsCurrent) {
11093
11149
  if (!resolvedBranch || resolvedBranch === "default") {
11094
11150
  throw new Error("Run index_codebase first to build the call graph and symbol index for this project.");
11095
11151
  }
@@ -11431,8 +11487,17 @@ function fitTextToContextBudget(text3, tokenBudget) {
11431
11487
  function normalizedLineRange(result) {
11432
11488
  return result.startLine <= result.endLine ? { start: result.startLine, end: result.endLine } : { start: result.endLine, end: result.startLine };
11433
11489
  }
11434
- function rankContextCandidates(results) {
11435
- return results.map((result, originalIndex) => ({ result, originalIndex })).sort((left, right) => right.result.score - left.result.score || left.originalIndex - right.originalIndex);
11490
+ function rankContextCandidates(results, preferImplementationPaths) {
11491
+ return results.map((result, originalIndex) => ({ result, originalIndex })).sort((left, right) => {
11492
+ if (preferImplementationPaths) {
11493
+ const leftIsImplementation = isLikelyImplementationPath(left.result.filePath);
11494
+ const rightIsImplementation = isLikelyImplementationPath(right.result.filePath);
11495
+ if (leftIsImplementation !== rightIsImplementation) {
11496
+ return leftIsImplementation ? -1 : 1;
11497
+ }
11498
+ }
11499
+ return right.result.score - left.result.score || left.originalIndex - right.originalIndex;
11500
+ });
11436
11501
  }
11437
11502
  function deduplicateContextCandidates(candidates) {
11438
11503
  const acceptedByFile = /* @__PURE__ */ new Map();
@@ -11521,7 +11586,12 @@ function buildContextPack(results, options = {}) {
11521
11586
  const maxResults = Math.max(0, Math.floor(options.maxResults ?? results.length));
11522
11587
  const includeExactSearchHandoff = options.includeExactSearchHandoff ?? false;
11523
11588
  const candidateCount = results.length;
11524
- const deduplicated = deduplicateContextCandidates(rankContextCandidates(results));
11589
+ const deduplicated = deduplicateContextCandidates(
11590
+ rankContextCandidates(
11591
+ results,
11592
+ options.preferImplementationPaths ?? false
11593
+ )
11594
+ );
11525
11595
  const diversified = diversifyContextCandidates(deduplicated);
11526
11596
  const duplicateCount = candidateCount - deduplicated.length;
11527
11597
  const selectable = diversified.slice(0, maxResults);
@@ -11893,7 +11963,7 @@ ${truncateContent(r.content)}
11893
11963
  }
11894
11964
 
11895
11965
  // src/utils/effectiveness-metrics.ts
11896
- var EFFECTIVENESS_METRICS_SCHEMA_VERSION = 2;
11966
+ var EFFECTIVENESS_METRICS_SCHEMA_VERSION = 3;
11897
11967
  var MAX_EFFECTIVENESS_COUNTER = 1e9;
11898
11968
  var EFFECTIVENESS_TOOL_ROUTES = [
11899
11969
  "context-conceptual",
@@ -11939,6 +12009,9 @@ var EFFECTIVENESS_SCOPE_RELAXATION_BUCKETS = ["none", "directory", "file-type",
11939
12009
  function emptyCounterMap(values) {
11940
12010
  return Object.fromEntries(values.map((value) => [value, 0]));
11941
12011
  }
12012
+ function emptyRouteCounterMap(routes, values) {
12013
+ return Object.fromEntries(routes.map((route) => [route, emptyCounterMap(values)]));
12014
+ }
11942
12015
  function boundedNumber(value) {
11943
12016
  if (value === void 0 || !Number.isFinite(value)) return 0;
11944
12017
  return Math.max(0, Math.floor(value));
@@ -11987,6 +12060,11 @@ function allowedValue(value, allowed, fallback) {
11987
12060
  function cloneCounterMap(counters) {
11988
12061
  return { ...counters };
11989
12062
  }
12063
+ function cloneRouteCounterMap(counters) {
12064
+ return Object.fromEntries(
12065
+ EFFECTIVENESS_TOOL_ROUTES.map((route) => [route, cloneCounterMap(counters[route])])
12066
+ );
12067
+ }
11990
12068
  var EffectivenessMetrics = class {
11991
12069
  constructor(counterCap = MAX_EFFECTIVENESS_COUNTER) {
11992
12070
  this.counterCap = counterCap;
@@ -12002,7 +12080,7 @@ var EffectivenessMetrics = class {
12002
12080
  lifetime: "process",
12003
12081
  reset: "index_metrics-reset-or-process-exit",
12004
12082
  maxCounterValue: this.counterCap,
12005
- dimensions: "bounded-host-and-category-only"
12083
+ dimensions: "bounded-route-and-bucketed-performance-only"
12006
12084
  },
12007
12085
  totalCalls: 0,
12008
12086
  toolRoute: emptyCounterMap(EFFECTIVENESS_TOOL_ROUTES),
@@ -12014,7 +12092,14 @@ var EffectivenessMetrics = class {
12014
12092
  tokenBudget: emptyCounterMap(EFFECTIVENESS_TOKEN_BUDGET_BUCKETS),
12015
12093
  returnedTokenEstimate: emptyCounterMap(EFFECTIVENESS_RETURNED_TOKEN_BUCKETS),
12016
12094
  exactHandoffEmitted: emptyCounterMap(EFFECTIVENESS_BOOLEAN_BUCKETS),
12017
- scopeRelaxation: emptyCounterMap(EFFECTIVENESS_SCOPE_RELAXATION_BUCKETS)
12095
+ scopeRelaxation: emptyCounterMap(EFFECTIVENESS_SCOPE_RELAXATION_BUCKETS),
12096
+ routeOutcome: emptyRouteCounterMap(EFFECTIVENESS_TOOL_ROUTES, EFFECTIVENESS_OUTCOMES),
12097
+ routeLatency: emptyRouteCounterMap(EFFECTIVENESS_TOOL_ROUTES, EFFECTIVENESS_LATENCY_BUCKETS),
12098
+ routeResultCount: emptyRouteCounterMap(EFFECTIVENESS_TOOL_ROUTES, EFFECTIVENESS_RESULT_COUNT_BUCKETS),
12099
+ routeReturnedTokenEstimate: emptyRouteCounterMap(
12100
+ EFFECTIVENESS_TOOL_ROUTES,
12101
+ EFFECTIVENESS_RETURNED_TOKEN_BUCKETS
12102
+ )
12018
12103
  };
12019
12104
  }
12020
12105
  increment(counters, key) {
@@ -12036,12 +12121,19 @@ var EffectivenessMetrics = class {
12036
12121
  this.increment(this.snapshot.hostMode, host);
12037
12122
  this.increment(this.snapshot.outcome, outcome);
12038
12123
  this.increment(this.snapshot.recoveryUsed, recoveryUsed);
12039
- this.increment(this.snapshot.resultCount, resultCountBucket(event.resultCount));
12040
- this.increment(this.snapshot.latency, latencyBucket(event.latencyMs));
12124
+ const resultCountBucketValue = resultCountBucket(event.resultCount);
12125
+ const latencyBucketValue = latencyBucket(event.latencyMs);
12126
+ const returnedTokenBucketValue = returnedTokenBucket(event.returnedTokenEstimate);
12127
+ this.increment(this.snapshot.resultCount, resultCountBucketValue);
12128
+ this.increment(this.snapshot.latency, latencyBucketValue);
12041
12129
  this.increment(this.snapshot.tokenBudget, tokenBudgetBucket(event.tokenBudget));
12042
- this.increment(this.snapshot.returnedTokenEstimate, returnedTokenBucket(event.returnedTokenEstimate));
12130
+ this.increment(this.snapshot.returnedTokenEstimate, returnedTokenBucketValue);
12043
12131
  this.increment(this.snapshot.exactHandoffEmitted, exactHandoffEmitted);
12044
12132
  this.increment(this.snapshot.scopeRelaxation, scopeRelaxation);
12133
+ this.increment(this.snapshot.routeOutcome[route], outcome);
12134
+ this.increment(this.snapshot.routeLatency[route], latencyBucketValue);
12135
+ this.increment(this.snapshot.routeResultCount[route], resultCountBucketValue);
12136
+ this.increment(this.snapshot.routeReturnedTokenEstimate[route], returnedTokenBucketValue);
12045
12137
  }
12046
12138
  getSnapshot() {
12047
12139
  return {
@@ -12056,7 +12148,11 @@ var EffectivenessMetrics = class {
12056
12148
  tokenBudget: cloneCounterMap(this.snapshot.tokenBudget),
12057
12149
  returnedTokenEstimate: cloneCounterMap(this.snapshot.returnedTokenEstimate),
12058
12150
  exactHandoffEmitted: cloneCounterMap(this.snapshot.exactHandoffEmitted),
12059
- scopeRelaxation: cloneCounterMap(this.snapshot.scopeRelaxation)
12151
+ scopeRelaxation: cloneCounterMap(this.snapshot.scopeRelaxation),
12152
+ routeOutcome: cloneRouteCounterMap(this.snapshot.routeOutcome),
12153
+ routeLatency: cloneRouteCounterMap(this.snapshot.routeLatency),
12154
+ routeResultCount: cloneRouteCounterMap(this.snapshot.routeResultCount),
12155
+ routeReturnedTokenEstimate: cloneRouteCounterMap(this.snapshot.routeReturnedTokenEstimate)
12060
12156
  };
12061
12157
  }
12062
12158
  reset() {
@@ -12075,6 +12171,7 @@ function resetProcessEffectivenessMetrics() {
12075
12171
  }
12076
12172
  function formatEffectivenessMetrics(snapshot) {
12077
12173
  const formatCounters = (counters) => Object.entries(counters).map(([bucket, count]) => `${bucket}=${count}`).join(", ");
12174
+ const formatRouteCounters = (counters) => EFFECTIVENESS_TOOL_ROUTES.map((route) => `${route} => ${formatCounters(counters[route])}`).join("; ");
12078
12175
  const lines = [
12079
12176
  `Privacy-safe effectiveness (schema v${snapshot.schemaVersion}):`,
12080
12177
  ` Retention: ${snapshot.retention.storage}, ${snapshot.retention.lifetime}-lifetime; reset with index_metrics(reset=true) or process exit`,
@@ -12089,6 +12186,10 @@ function formatEffectivenessMetrics(snapshot) {
12089
12186
  ` Latency bucket: ${formatCounters(snapshot.latency)}`,
12090
12187
  ` Token-budget bucket: ${formatCounters(snapshot.tokenBudget)}`,
12091
12188
  ` Returned-token estimate: ${formatCounters(snapshot.returnedTokenEstimate)}`,
12189
+ ` Route outcome buckets: ${formatRouteCounters(snapshot.routeOutcome)}`,
12190
+ ` Route latency buckets: ${formatRouteCounters(snapshot.routeLatency)}`,
12191
+ ` Route result-count buckets: ${formatRouteCounters(snapshot.routeResultCount)}`,
12192
+ ` Route returned-token buckets: ${formatRouteCounters(snapshot.routeReturnedTokenEstimate)}`,
12092
12193
  ` Exact handoff emitted: ${formatCounters(snapshot.exactHandoffEmitted)}`,
12093
12194
  ` Scope relaxation: ${formatCounters(snapshot.scopeRelaxation)}`,
12094
12195
  " Privacy: no queries, response text, source, symbols, paths, repository names, user identity, or stable identifiers are retained."
@@ -12100,6 +12201,103 @@ function formatEffectivenessMetrics(snapshot) {
12100
12201
  var import_fs10 = require("fs");
12101
12202
  var os6 = __toESM(require("os"), 1);
12102
12203
  var path14 = __toESM(require("path"), 1);
12204
+
12205
+ // src/utils/power-source.ts
12206
+ var childProcess = __toESM(require("child_process"), 1);
12207
+ var POWER_SOURCE_RECHECK_DELAY_MS = 6e4;
12208
+ var PMSET_TIMEOUT_MS = 5e3;
12209
+ function getErrorMessage4(error) {
12210
+ return error instanceof Error ? error.message : String(error);
12211
+ }
12212
+ function runCommand(file, args, options) {
12213
+ return new Promise((resolve12, reject) => {
12214
+ childProcess.execFile(
12215
+ file,
12216
+ args,
12217
+ { encoding: "utf8", timeout: options.timeoutMs },
12218
+ (error, stdout) => {
12219
+ if (error) {
12220
+ reject(error);
12221
+ return;
12222
+ }
12223
+ resolve12(stdout);
12224
+ }
12225
+ );
12226
+ });
12227
+ }
12228
+ function parseMacOsPowerSource(output) {
12229
+ const match = output.match(/Now drawing from '([^']+)'/i);
12230
+ if (!match) {
12231
+ return "unknown";
12232
+ }
12233
+ const source = match[1].toLowerCase();
12234
+ if (source === "battery power") {
12235
+ return "battery";
12236
+ }
12237
+ if (source === "ac power") {
12238
+ return "ac";
12239
+ }
12240
+ return "unknown";
12241
+ }
12242
+ async function readMacOsPowerSource(commandRunner = runCommand) {
12243
+ const output = await commandRunner(
12244
+ "/usr/bin/pmset",
12245
+ ["-g", "batt"],
12246
+ { timeoutMs: PMSET_TIMEOUT_MS }
12247
+ );
12248
+ return parseMacOsPowerSource(output);
12249
+ }
12250
+ var MacOsBackgroundIndexingPolicy = class {
12251
+ constructor(readPowerSource, recheckDelayMs) {
12252
+ this.readPowerSource = readPowerSource;
12253
+ this.recheckDelayMs = recheckDelayMs;
12254
+ }
12255
+ readPowerSource;
12256
+ recheckDelayMs;
12257
+ lastPaused = null;
12258
+ reportedFailure = false;
12259
+ isPaused() {
12260
+ return this.checkPowerSource();
12261
+ }
12262
+ async checkPowerSource() {
12263
+ try {
12264
+ const source = await this.readPowerSource();
12265
+ if (source === "unknown") {
12266
+ throw new Error("pmset returned an unrecognized power source");
12267
+ }
12268
+ this.reportedFailure = false;
12269
+ const paused = source === "battery";
12270
+ if (paused && this.lastPaused !== true) {
12271
+ console.warn("[codebase-index] Background indexing paused while macOS is using battery power.");
12272
+ } else if (!paused && this.lastPaused === true) {
12273
+ console.warn("[codebase-index] AC power detected; resuming pending background indexing.");
12274
+ }
12275
+ this.lastPaused = paused;
12276
+ return paused;
12277
+ } catch (error) {
12278
+ if (!this.reportedFailure) {
12279
+ console.error(
12280
+ `[codebase-index] Failed to determine the macOS power source; background indexing will continue: ${getErrorMessage4(error)}`
12281
+ );
12282
+ this.reportedFailure = true;
12283
+ }
12284
+ this.lastPaused = false;
12285
+ return false;
12286
+ }
12287
+ }
12288
+ };
12289
+ function createBackgroundIndexingPolicy(pauseOnBattery, options = {}) {
12290
+ const platform2 = options.platform ?? process.platform;
12291
+ if (!pauseOnBattery || platform2 !== "darwin") {
12292
+ return null;
12293
+ }
12294
+ return new MacOsBackgroundIndexingPolicy(
12295
+ options.readPowerSource ?? readMacOsPowerSource,
12296
+ options.recheckDelayMs ?? POWER_SOURCE_RECHECK_DELAY_MS
12297
+ );
12298
+ }
12299
+
12300
+ // src/utils/auto-index.ts
12103
12301
  var MAX_RETRY_DELAY_MS = 1e4;
12104
12302
  var SHUTDOWN_WAIT_MS = 2e3;
12105
12303
  var coordinators = /* @__PURE__ */ new Map();
@@ -12216,7 +12414,13 @@ var AutoIndexCoordinator = class {
12216
12414
  activation = Promise.resolve();
12217
12415
  inFlight = null;
12218
12416
  activeRequest = null;
12417
+ batteryCheck = null;
12418
+ batteryIndexJob = null;
12419
+ batteryDeferredRequest = null;
12420
+ batteryRetryTimer = null;
12421
+ resolveBatteryRetry = null;
12219
12422
  pendingRequest = null;
12423
+ pendingFollowUp = null;
12220
12424
  abortController = null;
12221
12425
  stopped = false;
12222
12426
  constructor(registration) {
@@ -12229,6 +12433,7 @@ var AutoIndexCoordinator = class {
12229
12433
  };
12230
12434
  }
12231
12435
  update(registration) {
12436
+ const pauseOnBatteryChanged = registration.config.indexing.pauseBackgroundIndexingOnBattery !== this.registration.config.indexing.pauseBackgroundIndexingOnBattery;
12232
12437
  this.registration = registration;
12233
12438
  this.status.enabled = registration.config.indexing.autoIndex;
12234
12439
  this.status.blockedReason = registration.blockedReason;
@@ -12244,6 +12449,9 @@ var AutoIndexCoordinator = class {
12244
12449
  this.setState("idle", { source: void 0 });
12245
12450
  }
12246
12451
  }
12452
+ if (pauseOnBatteryChanged) {
12453
+ this.cancelBatteryRetry();
12454
+ }
12247
12455
  }
12248
12456
  activateAfter(activation) {
12249
12457
  this.activation = activation;
@@ -12265,7 +12473,26 @@ var AutoIndexCoordinator = class {
12265
12473
  if (this.stopped) {
12266
12474
  return Promise.resolve({ outcome: "stopped" });
12267
12475
  }
12268
- return this.activation.then(() => this.enqueueRequest(request));
12476
+ return this.activation.then(() => this.enqueueBatteryAwareRequest(request));
12477
+ }
12478
+ enqueueBatteryAwareRequest(request) {
12479
+ if (!this.shouldDeferForBattery(request)) {
12480
+ return this.enqueueRequest(request);
12481
+ }
12482
+ if (this.batteryCheck && this.batteryIndexJob !== null && this.batteryIndexJob === this.inFlight) {
12483
+ return this.enqueueRequest(request);
12484
+ }
12485
+ this.batteryDeferredRequest = mergeRequests(this.batteryDeferredRequest, request);
12486
+ if (this.batteryCheck) {
12487
+ return this.batteryCheck;
12488
+ }
12489
+ const batteryCheck = this.waitForACPower();
12490
+ this.batteryCheck = batteryCheck;
12491
+ void batteryCheck.then(
12492
+ () => this.finishBatteryCheck(batteryCheck),
12493
+ () => this.finishBatteryCheck(batteryCheck)
12494
+ );
12495
+ return batteryCheck;
12269
12496
  }
12270
12497
  enqueueRequest(request) {
12271
12498
  if (this.stopped || !this.canRun(request)) {
@@ -12283,6 +12510,11 @@ var AutoIndexCoordinator = class {
12283
12510
  }
12284
12511
  if (request.source === "watcher") {
12285
12512
  this.pendingRequest = mergeRequests(this.pendingRequest, request);
12513
+ const active = this.inFlight;
12514
+ return active.then(() => {
12515
+ if (this.stopped) return { outcome: "stopped" };
12516
+ return this.pendingFollowUp ?? { outcome: "stopped" };
12517
+ });
12286
12518
  }
12287
12519
  return this.inFlight;
12288
12520
  }
@@ -12299,6 +12531,8 @@ var AutoIndexCoordinator = class {
12299
12531
  }
12300
12532
  async stop(waitForCompletion = false) {
12301
12533
  this.stopped = true;
12534
+ this.batteryDeferredRequest = null;
12535
+ this.cancelBatteryRetry();
12302
12536
  this.pendingRequest = null;
12303
12537
  this.abortController?.abort();
12304
12538
  this.setState("stopped", {
@@ -12328,10 +12562,20 @@ var AutoIndexCoordinator = class {
12328
12562
  this.inFlight = null;
12329
12563
  this.activeRequest = null;
12330
12564
  this.abortController = null;
12565
+ if (this.batteryIndexJob === job) {
12566
+ this.batteryIndexJob = null;
12567
+ this.batteryCheck = null;
12568
+ }
12331
12569
  const pending = this.pendingRequest;
12332
12570
  this.pendingRequest = null;
12333
12571
  if (pending && !this.stopped) {
12334
- this.startRequest(pending);
12572
+ const followUp = this.request(pending);
12573
+ this.pendingFollowUp = followUp;
12574
+ void followUp.then(() => {
12575
+ if (this.pendingFollowUp === followUp) {
12576
+ this.pendingFollowUp = null;
12577
+ }
12578
+ });
12335
12579
  }
12336
12580
  });
12337
12581
  return job;
@@ -12495,6 +12739,68 @@ var AutoIndexCoordinator = class {
12495
12739
  }
12496
12740
  return this.registration.safeToRun && this.registration.config.indexing.autoIndex;
12497
12741
  }
12742
+ shouldDeferForBattery(request) {
12743
+ return this.registration.backgroundIndexingPolicy !== null && (request.source === "startup" || request.source === "watcher");
12744
+ }
12745
+ async waitForACPower() {
12746
+ while (!this.stopped) {
12747
+ const policy = this.registration.backgroundIndexingPolicy;
12748
+ if (!policy || !await this.isBatteryPauseActive(policy)) {
12749
+ const request = this.batteryDeferredRequest;
12750
+ this.batteryDeferredRequest = null;
12751
+ if (!request) return { outcome: "stopped" };
12752
+ const job = this.enqueueRequest(request);
12753
+ if (this.inFlight === job) {
12754
+ this.batteryIndexJob = job;
12755
+ }
12756
+ return job;
12757
+ }
12758
+ await this.waitForBatteryRetry(policy.recheckDelayMs);
12759
+ }
12760
+ return { outcome: "stopped" };
12761
+ }
12762
+ async isBatteryPauseActive(policy) {
12763
+ try {
12764
+ return await policy.isPaused();
12765
+ } catch (error) {
12766
+ console.error(
12767
+ `[codebase-index] Failed to apply the background indexing power policy; background indexing will continue: ${safeFailureMessage(error)}`
12768
+ );
12769
+ return false;
12770
+ }
12771
+ }
12772
+ waitForBatteryRetry(delayMs) {
12773
+ return new Promise((resolve12) => {
12774
+ const timer = setTimeout(() => {
12775
+ if (this.batteryRetryTimer === timer) {
12776
+ this.batteryRetryTimer = null;
12777
+ this.resolveBatteryRetry = null;
12778
+ }
12779
+ resolve12();
12780
+ }, delayMs);
12781
+ timer.unref?.();
12782
+ this.batteryRetryTimer = timer;
12783
+ this.resolveBatteryRetry = resolve12;
12784
+ });
12785
+ }
12786
+ cancelBatteryRetry() {
12787
+ if (this.batteryRetryTimer) {
12788
+ clearTimeout(this.batteryRetryTimer);
12789
+ this.batteryRetryTimer = null;
12790
+ }
12791
+ const resolve12 = this.resolveBatteryRetry;
12792
+ this.resolveBatteryRetry = null;
12793
+ resolve12?.();
12794
+ }
12795
+ finishBatteryCheck(batteryCheck) {
12796
+ if (this.batteryCheck !== batteryCheck) return;
12797
+ this.batteryCheck = null;
12798
+ const deferredRequest = this.batteryDeferredRequest;
12799
+ this.batteryDeferredRequest = null;
12800
+ if (deferredRequest && !this.stopped) {
12801
+ void this.request(deferredRequest);
12802
+ }
12803
+ }
12498
12804
  };
12499
12805
  function getCoordinator(projectRoot3, host) {
12500
12806
  const key = coordinatorKeysByProject.get(projectLookupKey(projectRoot3, host));
@@ -12504,6 +12810,9 @@ function configureAutoIndex(projectRoot3, host, config, getIndexer) {
12504
12810
  const projectKey = projectLookupKey(projectRoot3, host);
12505
12811
  const safety = getProjectSafety(projectRoot3, config);
12506
12812
  const registration = {
12813
+ backgroundIndexingPolicy: createBackgroundIndexingPolicy(
12814
+ config.indexing.pauseBackgroundIndexingOnBattery
12815
+ ),
12507
12816
  config,
12508
12817
  getIndexer,
12509
12818
  projectRoot: projectRoot3,
@@ -13851,6 +14160,7 @@ Explicit symbol lookup only; conceptual search was not attempted.`
13851
14160
  const results = await tryConceptualSearch(attempt.queryText, attempt.scope, attempt.relaxed);
13852
14161
  if (results.length > 0) {
13853
14162
  const heading = buildPackHeading("conceptual", decisions);
14163
+ const intent = analyzeQueryIntent(attempt.queryText);
13854
14164
  return toResult(
13855
14165
  "conceptual",
13856
14166
  attempt.queryText,
@@ -13858,7 +14168,8 @@ Explicit symbol lookup only; conceptual search was not attempted.`
13858
14168
  tokenBudget,
13859
14169
  maxResults: limit,
13860
14170
  heading,
13861
- includeExactSearchHandoff: true
14171
+ includeExactSearchHandoff: true,
14172
+ preferImplementationPaths: intent.preferSourcePaths || intent.primary !== "docs" && intent.primary !== "test"
13862
14173
  })
13863
14174
  );
13864
14175
  }