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.
@@ -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,
@@ -3996,9 +3998,21 @@ function parseFiles(files) {
3996
3998
  return result.map((f) => ({
3997
3999
  path: f.path,
3998
4000
  chunks: f.chunks.map(mapChunk),
4001
+ symbols: (f.symbols ?? []).map(mapParsedSymbol),
3999
4002
  hash: f.hash
4000
4003
  }));
4001
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
+ }
4002
4016
  function mapChunk(c) {
4003
4017
  return {
4004
4018
  content: c.content,
@@ -6515,6 +6529,7 @@ var INDEX_METADATA_VERSION = "1";
6515
6529
  var EMBEDDING_STRATEGY_VERSION = "2";
6516
6530
  var SWIFT_PARSER_VERSION = "1";
6517
6531
  var METAL_PARSER_VERSION = "1";
6532
+ var SYMBOL_EXTRACTOR_VERSION = "1";
6518
6533
  var RANKING_TOKEN_CACHE_LIMIT = 4096;
6519
6534
  var RANK_HYBRID_CACHE_LIMIT = 256;
6520
6535
  function createPendingChunkStorageText(texts) {
@@ -6816,7 +6831,7 @@ function classifyQueryIntentRaw(query) {
6816
6831
  return "neutral";
6817
6832
  }
6818
6833
  function isImplementationChunkType(chunkType) {
6819
- return [
6834
+ return CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(chunkType) || [
6820
6835
  "export_statement",
6821
6836
  "function",
6822
6837
  "function_declaration",
@@ -7261,7 +7276,7 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
7261
7276
  const remainder = combined.filter((candidate) => !promotedIds.has(candidate.id));
7262
7277
  return [...promoted, ...remainder];
7263
7278
  }
7264
- function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
7279
+ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
7265
7280
  if (!prioritizeSourcePaths) {
7266
7281
  return [];
7267
7282
  }
@@ -7275,14 +7290,14 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
7275
7290
  const primaryHint = extractPrimaryIdentifierQueryHint(query);
7276
7291
  const upsertChunkCandidate = (chunk, identifier, normalizedIdentifier, baseScore) => {
7277
7292
  if (branchChunkIds && !branchChunkIds.has(chunk.chunkId)) {
7278
- return;
7293
+ return false;
7279
7294
  }
7280
7295
  const chunkType = chunk.nodeType ?? "other";
7281
7296
  if (!isImplementationChunkType(chunkType)) {
7282
- return;
7297
+ return false;
7283
7298
  }
7284
7299
  if (!isLikelyImplementationPath2(chunk.filePath)) {
7285
- return;
7300
+ return false;
7286
7301
  }
7287
7302
  const nameLower = (chunk.name ?? "").toLowerCase();
7288
7303
  const exactName = nameLower === identifier || nameLower.replace(/_/g, "") === normalizedIdentifier;
@@ -7304,6 +7319,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
7304
7319
  }
7305
7320
  });
7306
7321
  }
7322
+ return true;
7307
7323
  };
7308
7324
  const normalizedHints = identifierHints.flatMap((hint) => [
7309
7325
  hint,
@@ -7325,12 +7341,46 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
7325
7341
  dedupSymbols.set(symbol.id, symbol);
7326
7342
  }
7327
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
+ }
7328
7350
  const chunks = database.getChunksByFile(symbol.filePath);
7351
+ let foundCoveringChunk = false;
7329
7352
  for (const chunk of chunks) {
7330
7353
  if (chunk.startLine > symbol.startLine || chunk.endLine < symbol.endLine) {
7331
7354
  continue;
7332
7355
  }
7333
- 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
+ });
7334
7384
  }
7335
7385
  }
7336
7386
  const dedupChunksByName = /* @__PURE__ */ new Map();
@@ -7338,6 +7388,9 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
7338
7388
  dedupChunksByName.set(chunk.chunkId, chunk);
7339
7389
  }
7340
7390
  for (const chunk of dedupChunksByName.values()) {
7391
+ if (filePathHint && !pathMatchesHint(chunk.filePath, filePathHint)) {
7392
+ continue;
7393
+ }
7341
7394
  upsertChunkCandidate(chunk, identifier, normalizedIdentifier);
7342
7395
  }
7343
7396
  }
@@ -7879,6 +7932,10 @@ var Indexer = class _Indexer {
7879
7932
  const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7880
7933
  return `${key}.${projectHash}`;
7881
7934
  }
7935
+ getSymbolExtractorVersionMetadataKey(catalogIdentity = this.getBranchCatalogIdentity()) {
7936
+ const branchKey = this.getBranchCatalogKeyFor(catalogIdentity);
7937
+ return `index.symbolExtractorVersion.${hashContent(branchKey).slice(0, 24)}`;
7938
+ }
7882
7939
  hasProjectForceReembedPending() {
7883
7940
  return this.config.scope === "global" && this.database?.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
7884
7941
  }
@@ -9222,7 +9279,8 @@ var Indexer = class _Indexer {
9222
9279
  }
9223
9280
  const branchKey = this.getBranchCatalogKey();
9224
9281
  const alreadyIndexed = database.getBranchChunkIds(branchKey).length > 0 && database.getBranchSymbolIds(branchKey).length > 0;
9225
- if (alreadyIndexed && this.getStoredBranchCommit(database) === normalizedCommit) {
9282
+ const symbolsCurrent = database.getMetadata(this.getSymbolExtractorVersionMetadataKey()) === SYMBOL_EXTRACTOR_VERSION;
9283
+ if (alreadyIndexed && symbolsCurrent && this.getStoredBranchCommit(database) === normalizedCommit) {
9226
9284
  return { prepared: false };
9227
9285
  }
9228
9286
  const stats = await this.indexUnlocked(onProgress, [], true);
@@ -9281,6 +9339,8 @@ var Indexer = class _Indexer {
9281
9339
  const reparseCachedSwiftFiles = database.getMetadata(swiftParserMetadataKey) !== SWIFT_PARSER_VERSION;
9282
9340
  const metalParserMetadataKey = this.getMetalParserVersionMetadataKey();
9283
9341
  const reparseCachedMetalFiles = database.getMetadata(metalParserMetadataKey) !== METAL_PARSER_VERSION;
9342
+ const symbolExtractorMetadataKey = this.getSymbolExtractorVersionMetadataKey();
9343
+ const refreshCachedSymbols = database.getMetadata(symbolExtractorMetadataKey) !== SYMBOL_EXTRACTOR_VERSION;
9284
9344
  if (reparseCachedSwiftFiles && Array.from(this.fileHashCache.keys()).some(
9285
9345
  (filePath) => path12.extname(filePath).toLowerCase() === ".swift"
9286
9346
  )) {
@@ -9324,7 +9384,7 @@ var Indexer = class _Indexer {
9324
9384
  );
9325
9385
  const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path12.extname(canonicalPath).toLowerCase() === ".swift";
9326
9386
  const requiresMetalParserUpgrade = reparseCachedMetalFiles && path12.extname(canonicalPath).toLowerCase() === ".metal";
9327
- if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade) {
9387
+ if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
9328
9388
  unchangedFilePaths.add(canonicalPath);
9329
9389
  this.logger.recordCacheHit();
9330
9390
  } else {
@@ -9535,37 +9595,27 @@ var Indexer = class _Indexer {
9535
9595
  const parsed = parsedFiles[i];
9536
9596
  const changedFile = changedFiles[i];
9537
9597
  const fileSymbols = [];
9538
- for (const chunk of parsed.chunks) {
9539
- if (!chunk.name || !CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(chunk.chunkType)) continue;
9540
- const existingMetalSymbol = chunk.language === "metal" ? fileSymbols.find(
9541
- (symbol2) => symbol2.name === chunk.name && symbol2.kind === chunk.chunkType && symbol2.startLine <= chunk.endLine && chunk.startLine <= symbol2.endLine
9542
- ) : void 0;
9543
- if (existingMetalSymbol) {
9544
- existingMetalSymbol.startLine = Math.min(existingMetalSymbol.startLine, chunk.startLine);
9545
- existingMetalSymbol.endLine = Math.max(existingMetalSymbol.endLine, chunk.endLine);
9546
- existingMetalSymbol.startCol = Math.min(existingMetalSymbol.startCol, chunk.startCol ?? 0);
9547
- existingMetalSymbol.endCol = Math.max(existingMetalSymbol.endCol, chunk.endCol ?? 0);
9548
- continue;
9549
- }
9598
+ for (const parsedSymbol of parsed.symbols) {
9599
+ if (!CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(parsedSymbol.kind)) continue;
9550
9600
  const preparedNamespace = this.getPreparedBranchNamespace();
9551
9601
  const symbolId = `sym_${hashContent(
9552
- (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
9553
9603
  ).slice(0, 16)}`;
9554
9604
  const symbol = {
9555
9605
  id: symbolId,
9556
9606
  filePath: parsed.path,
9557
- name: chunk.name,
9558
- kind: chunk.chunkType,
9559
- startLine: chunk.startLine,
9560
- startCol: chunk.startCol ?? 0,
9561
- endLine: chunk.endLine,
9562
- endCol: chunk.endCol ?? 0,
9563
- 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
9564
9614
  };
9565
9615
  fileSymbols.push(symbol);
9566
9616
  allSymbolIds.add(symbolId);
9567
9617
  }
9568
- const fileLanguage = parsed.chunks[0]?.language;
9618
+ const fileLanguage = parsed.symbols[0]?.language ?? parsed.chunks[0]?.language;
9569
9619
  const isCaseInsensitiveLanguage = !!fileLanguage && CASE_INSENSITIVE_LANGUAGES.has(fileLanguage);
9570
9620
  const normalizeSymbolKey = (name) => isCaseInsensitiveLanguage ? name.toLowerCase() : name;
9571
9621
  const symbolsByName = /* @__PURE__ */ new Map();
@@ -9681,6 +9731,7 @@ var Indexer = class _Indexer {
9681
9731
  }
9682
9732
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
9683
9733
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
9734
+ database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
9684
9735
  this.saveBranchCommit(database, indexedCommit);
9685
9736
  this.saveIndexMetadata(configuredProviderInfo);
9686
9737
  this.indexCompatibility = { compatible: true };
@@ -9717,6 +9768,7 @@ var Indexer = class _Indexer {
9717
9768
  }
9718
9769
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
9719
9770
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
9771
+ database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
9720
9772
  this.saveBranchCommit(database, indexedCommit);
9721
9773
  this.saveIndexMetadata(configuredProviderInfo);
9722
9774
  this.indexCompatibility = { compatible: true };
@@ -9996,6 +10048,7 @@ var Indexer = class _Indexer {
9996
10048
  }
9997
10049
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
9998
10050
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
10051
+ database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
9999
10052
  this.saveBranchCommit(database, indexedCommit);
10000
10053
  this.saveIndexMetadata(configuredProviderInfo);
10001
10054
  this.indexCompatibility = { compatible: true };
@@ -10134,10 +10187,11 @@ var Indexer = class _Indexer {
10134
10187
  const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
10135
10188
  const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
10136
10189
  let branchChunkIds = null;
10190
+ let branchSymbolIds = null;
10137
10191
  if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
10138
- branchChunkIds = new Set(
10139
- this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
10140
- );
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)));
10141
10195
  }
10142
10196
  const prefilterStartTime = import_perf_hooks.performance.now();
10143
10197
  const shouldPrefilterByBranch = branchChunkIds !== null && (this.config.scope === "global" || branchChunkIds.size > 0);
@@ -10209,6 +10263,7 @@ var Indexer = class _Indexer {
10209
10263
  query,
10210
10264
  database,
10211
10265
  branchChunkIds,
10266
+ branchSymbolIds,
10212
10267
  maxResults,
10213
10268
  union,
10214
10269
  sourceIntent
@@ -10222,7 +10277,7 @@ var Indexer = class _Indexer {
10222
10277
  (r) => isLikelyImplementationPath2(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
10223
10278
  );
10224
10279
  const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
10225
- 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) : [];
10226
10281
  const finalResults = filtered.length > 0 ? filtered : identifierFallback;
10227
10282
  const totalSearchMs = import_perf_hooks.performance.now() - searchStartTime;
10228
10283
  this.logger.recordSearch(totalSearchMs, {
@@ -10379,7 +10434,7 @@ var Indexer = class _Indexer {
10379
10434
  const extension = path12.extname(filePath).toLowerCase();
10380
10435
  return extension === ".php" || extension === ".c" || extension === ".cc" || extension === ".cpp" || extension === ".cxx";
10381
10436
  });
10382
- 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) {
10383
10438
  return { readable: true, current: false, reason: "migration-required" };
10384
10439
  }
10385
10440
  if (isGitRepo(this.materializedProjectRoot)) {
@@ -11087,7 +11142,10 @@ var Indexer = class _Indexer {
11087
11142
  const requestedRef = opts.pr !== void 0 ? expectedCommit : headRefName ?? resolvedBranch;
11088
11143
  const storedCommit = this.getStoredBranchCommit(database, catalogIdentity);
11089
11144
  const catalogIdentityMatches = storedCommit === expectedCommit;
11090
- 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) {
11091
11149
  if (!resolvedBranch || resolvedBranch === "default") {
11092
11150
  throw new Error("Run index_codebase first to build the call graph and symbol index for this project.");
11093
11151
  }
@@ -11429,8 +11487,17 @@ function fitTextToContextBudget(text3, tokenBudget) {
11429
11487
  function normalizedLineRange(result) {
11430
11488
  return result.startLine <= result.endLine ? { start: result.startLine, end: result.endLine } : { start: result.endLine, end: result.startLine };
11431
11489
  }
11432
- function rankContextCandidates(results) {
11433
- 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
+ });
11434
11501
  }
11435
11502
  function deduplicateContextCandidates(candidates) {
11436
11503
  const acceptedByFile = /* @__PURE__ */ new Map();
@@ -11519,7 +11586,12 @@ function buildContextPack(results, options = {}) {
11519
11586
  const maxResults = Math.max(0, Math.floor(options.maxResults ?? results.length));
11520
11587
  const includeExactSearchHandoff = options.includeExactSearchHandoff ?? false;
11521
11588
  const candidateCount = results.length;
11522
- const deduplicated = deduplicateContextCandidates(rankContextCandidates(results));
11589
+ const deduplicated = deduplicateContextCandidates(
11590
+ rankContextCandidates(
11591
+ results,
11592
+ options.preferImplementationPaths ?? false
11593
+ )
11594
+ );
11523
11595
  const diversified = diversifyContextCandidates(deduplicated);
11524
11596
  const duplicateCount = candidateCount - deduplicated.length;
11525
11597
  const selectable = diversified.slice(0, maxResults);
@@ -11891,7 +11963,7 @@ ${truncateContent(r.content)}
11891
11963
  }
11892
11964
 
11893
11965
  // src/utils/effectiveness-metrics.ts
11894
- var EFFECTIVENESS_METRICS_SCHEMA_VERSION = 2;
11966
+ var EFFECTIVENESS_METRICS_SCHEMA_VERSION = 3;
11895
11967
  var MAX_EFFECTIVENESS_COUNTER = 1e9;
11896
11968
  var EFFECTIVENESS_TOOL_ROUTES = [
11897
11969
  "context-conceptual",
@@ -11937,6 +12009,9 @@ var EFFECTIVENESS_SCOPE_RELAXATION_BUCKETS = ["none", "directory", "file-type",
11937
12009
  function emptyCounterMap(values) {
11938
12010
  return Object.fromEntries(values.map((value) => [value, 0]));
11939
12011
  }
12012
+ function emptyRouteCounterMap(routes, values) {
12013
+ return Object.fromEntries(routes.map((route) => [route, emptyCounterMap(values)]));
12014
+ }
11940
12015
  function boundedNumber(value) {
11941
12016
  if (value === void 0 || !Number.isFinite(value)) return 0;
11942
12017
  return Math.max(0, Math.floor(value));
@@ -11985,6 +12060,11 @@ function allowedValue(value, allowed, fallback) {
11985
12060
  function cloneCounterMap(counters) {
11986
12061
  return { ...counters };
11987
12062
  }
12063
+ function cloneRouteCounterMap(counters) {
12064
+ return Object.fromEntries(
12065
+ EFFECTIVENESS_TOOL_ROUTES.map((route) => [route, cloneCounterMap(counters[route])])
12066
+ );
12067
+ }
11988
12068
  var EffectivenessMetrics = class {
11989
12069
  constructor(counterCap = MAX_EFFECTIVENESS_COUNTER) {
11990
12070
  this.counterCap = counterCap;
@@ -12000,7 +12080,7 @@ var EffectivenessMetrics = class {
12000
12080
  lifetime: "process",
12001
12081
  reset: "index_metrics-reset-or-process-exit",
12002
12082
  maxCounterValue: this.counterCap,
12003
- dimensions: "bounded-host-and-category-only"
12083
+ dimensions: "bounded-route-and-bucketed-performance-only"
12004
12084
  },
12005
12085
  totalCalls: 0,
12006
12086
  toolRoute: emptyCounterMap(EFFECTIVENESS_TOOL_ROUTES),
@@ -12012,7 +12092,14 @@ var EffectivenessMetrics = class {
12012
12092
  tokenBudget: emptyCounterMap(EFFECTIVENESS_TOKEN_BUDGET_BUCKETS),
12013
12093
  returnedTokenEstimate: emptyCounterMap(EFFECTIVENESS_RETURNED_TOKEN_BUCKETS),
12014
12094
  exactHandoffEmitted: emptyCounterMap(EFFECTIVENESS_BOOLEAN_BUCKETS),
12015
- 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
+ )
12016
12103
  };
12017
12104
  }
12018
12105
  increment(counters, key) {
@@ -12034,12 +12121,19 @@ var EffectivenessMetrics = class {
12034
12121
  this.increment(this.snapshot.hostMode, host);
12035
12122
  this.increment(this.snapshot.outcome, outcome);
12036
12123
  this.increment(this.snapshot.recoveryUsed, recoveryUsed);
12037
- this.increment(this.snapshot.resultCount, resultCountBucket(event.resultCount));
12038
- 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);
12039
12129
  this.increment(this.snapshot.tokenBudget, tokenBudgetBucket(event.tokenBudget));
12040
- this.increment(this.snapshot.returnedTokenEstimate, returnedTokenBucket(event.returnedTokenEstimate));
12130
+ this.increment(this.snapshot.returnedTokenEstimate, returnedTokenBucketValue);
12041
12131
  this.increment(this.snapshot.exactHandoffEmitted, exactHandoffEmitted);
12042
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);
12043
12137
  }
12044
12138
  getSnapshot() {
12045
12139
  return {
@@ -12054,7 +12148,11 @@ var EffectivenessMetrics = class {
12054
12148
  tokenBudget: cloneCounterMap(this.snapshot.tokenBudget),
12055
12149
  returnedTokenEstimate: cloneCounterMap(this.snapshot.returnedTokenEstimate),
12056
12150
  exactHandoffEmitted: cloneCounterMap(this.snapshot.exactHandoffEmitted),
12057
- 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)
12058
12156
  };
12059
12157
  }
12060
12158
  reset() {
@@ -12073,6 +12171,7 @@ function resetProcessEffectivenessMetrics() {
12073
12171
  }
12074
12172
  function formatEffectivenessMetrics(snapshot) {
12075
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("; ");
12076
12175
  const lines = [
12077
12176
  `Privacy-safe effectiveness (schema v${snapshot.schemaVersion}):`,
12078
12177
  ` Retention: ${snapshot.retention.storage}, ${snapshot.retention.lifetime}-lifetime; reset with index_metrics(reset=true) or process exit`,
@@ -12087,6 +12186,10 @@ function formatEffectivenessMetrics(snapshot) {
12087
12186
  ` Latency bucket: ${formatCounters(snapshot.latency)}`,
12088
12187
  ` Token-budget bucket: ${formatCounters(snapshot.tokenBudget)}`,
12089
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)}`,
12090
12193
  ` Exact handoff emitted: ${formatCounters(snapshot.exactHandoffEmitted)}`,
12091
12194
  ` Scope relaxation: ${formatCounters(snapshot.scopeRelaxation)}`,
12092
12195
  " Privacy: no queries, response text, source, symbols, paths, repository names, user identity, or stable identifiers are retained."
@@ -12098,6 +12201,103 @@ function formatEffectivenessMetrics(snapshot) {
12098
12201
  var import_fs10 = require("fs");
12099
12202
  var os6 = __toESM(require("os"), 1);
12100
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
12101
12301
  var MAX_RETRY_DELAY_MS = 1e4;
12102
12302
  var SHUTDOWN_WAIT_MS = 2e3;
12103
12303
  var coordinators = /* @__PURE__ */ new Map();
@@ -12214,7 +12414,13 @@ var AutoIndexCoordinator = class {
12214
12414
  activation = Promise.resolve();
12215
12415
  inFlight = null;
12216
12416
  activeRequest = null;
12417
+ batteryCheck = null;
12418
+ batteryIndexJob = null;
12419
+ batteryDeferredRequest = null;
12420
+ batteryRetryTimer = null;
12421
+ resolveBatteryRetry = null;
12217
12422
  pendingRequest = null;
12423
+ pendingFollowUp = null;
12218
12424
  abortController = null;
12219
12425
  stopped = false;
12220
12426
  constructor(registration) {
@@ -12227,6 +12433,7 @@ var AutoIndexCoordinator = class {
12227
12433
  };
12228
12434
  }
12229
12435
  update(registration) {
12436
+ const pauseOnBatteryChanged = registration.config.indexing.pauseBackgroundIndexingOnBattery !== this.registration.config.indexing.pauseBackgroundIndexingOnBattery;
12230
12437
  this.registration = registration;
12231
12438
  this.status.enabled = registration.config.indexing.autoIndex;
12232
12439
  this.status.blockedReason = registration.blockedReason;
@@ -12242,6 +12449,9 @@ var AutoIndexCoordinator = class {
12242
12449
  this.setState("idle", { source: void 0 });
12243
12450
  }
12244
12451
  }
12452
+ if (pauseOnBatteryChanged) {
12453
+ this.cancelBatteryRetry();
12454
+ }
12245
12455
  }
12246
12456
  activateAfter(activation) {
12247
12457
  this.activation = activation;
@@ -12263,7 +12473,26 @@ var AutoIndexCoordinator = class {
12263
12473
  if (this.stopped) {
12264
12474
  return Promise.resolve({ outcome: "stopped" });
12265
12475
  }
12266
- 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;
12267
12496
  }
12268
12497
  enqueueRequest(request) {
12269
12498
  if (this.stopped || !this.canRun(request)) {
@@ -12281,6 +12510,11 @@ var AutoIndexCoordinator = class {
12281
12510
  }
12282
12511
  if (request.source === "watcher") {
12283
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
+ });
12284
12518
  }
12285
12519
  return this.inFlight;
12286
12520
  }
@@ -12297,6 +12531,8 @@ var AutoIndexCoordinator = class {
12297
12531
  }
12298
12532
  async stop(waitForCompletion = false) {
12299
12533
  this.stopped = true;
12534
+ this.batteryDeferredRequest = null;
12535
+ this.cancelBatteryRetry();
12300
12536
  this.pendingRequest = null;
12301
12537
  this.abortController?.abort();
12302
12538
  this.setState("stopped", {
@@ -12326,10 +12562,20 @@ var AutoIndexCoordinator = class {
12326
12562
  this.inFlight = null;
12327
12563
  this.activeRequest = null;
12328
12564
  this.abortController = null;
12565
+ if (this.batteryIndexJob === job) {
12566
+ this.batteryIndexJob = null;
12567
+ this.batteryCheck = null;
12568
+ }
12329
12569
  const pending = this.pendingRequest;
12330
12570
  this.pendingRequest = null;
12331
12571
  if (pending && !this.stopped) {
12332
- 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
+ });
12333
12579
  }
12334
12580
  });
12335
12581
  return job;
@@ -12493,6 +12739,68 @@ var AutoIndexCoordinator = class {
12493
12739
  }
12494
12740
  return this.registration.safeToRun && this.registration.config.indexing.autoIndex;
12495
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
+ }
12496
12804
  };
12497
12805
  function getCoordinator(projectRoot3, host) {
12498
12806
  const key = coordinatorKeysByProject.get(projectLookupKey(projectRoot3, host));
@@ -12502,6 +12810,9 @@ function configureAutoIndex(projectRoot3, host, config, getIndexer) {
12502
12810
  const projectKey = projectLookupKey(projectRoot3, host);
12503
12811
  const safety = getProjectSafety(projectRoot3, config);
12504
12812
  const registration = {
12813
+ backgroundIndexingPolicy: createBackgroundIndexingPolicy(
12814
+ config.indexing.pauseBackgroundIndexingOnBattery
12815
+ ),
12505
12816
  config,
12506
12817
  getIndexer,
12507
12818
  projectRoot: projectRoot3,
@@ -13849,6 +14160,7 @@ Explicit symbol lookup only; conceptual search was not attempted.`
13849
14160
  const results = await tryConceptualSearch(attempt.queryText, attempt.scope, attempt.relaxed);
13850
14161
  if (results.length > 0) {
13851
14162
  const heading = buildPackHeading("conceptual", decisions);
14163
+ const intent = analyzeQueryIntent(attempt.queryText);
13852
14164
  return toResult(
13853
14165
  "conceptual",
13854
14166
  attempt.queryText,
@@ -13856,7 +14168,8 @@ Explicit symbol lookup only; conceptual search was not attempted.`
13856
14168
  tokenBudget,
13857
14169
  maxResults: limit,
13858
14170
  heading,
13859
- includeExactSearchHandoff: true
14171
+ includeExactSearchHandoff: true,
14172
+ preferImplementationPaths: intent.preferSourcePaths || intent.primary !== "docs" && intent.primary !== "test"
13860
14173
  })
13861
14174
  );
13862
14175
  }