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