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