opencode-codebase-index 0.24.0 → 0.25.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.
@@ -2043,6 +2043,26 @@ function formatCostEstimate(estimate) {
2043
2043
  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2044
2044
  `;
2045
2045
  }
2046
+ function formatDryRunEstimate(estimate) {
2047
+ return `Dry run: parsed the file set to measure the embedding workload. No embedding requests were made and the index was not changed.
2048
+
2049
+ Files to embed: ${estimate.filesCount.toLocaleString()}
2050
+ Chunks to embed: ${estimate.chunksCount.toLocaleString()}
2051
+ Tokens to embed: ${estimate.tokensToEmbed.toLocaleString()}
2052
+
2053
+ The "Tokens to embed" value uses the local estimateTokens(text) = ceil(len/4). It
2054
+ matches the live "Tokens used" counter only for providers that report usage on the
2055
+ same basis (ollama); for providers that report a server tokenizer count (OpenAI,
2056
+ Gemini, custom) it is only an estimate.
2057
+
2058
+ For a matching provider and a project-scoped force index, the force pass clears its
2059
+ own cached embeddings, so the live counter climbs to this number. A force index on a
2060
+ shared global index can reuse cached embeddings from other projects, and an
2061
+ incremental index counts cached chunks that are not re-embedded; in both cases this
2062
+ number is an upper bound on the live counter, so a progress percent against this
2063
+ total tops out below 100%.
2064
+ `;
2065
+ }
2046
2066
  function formatBytes(bytes) {
2047
2067
  if (bytes === 0) return "0 B";
2048
2068
  const k = 1024;
@@ -8881,6 +8901,9 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
8881
8901
  "enum_declaration",
8882
8902
  "function_definition",
8883
8903
  "class_definition",
8904
+ // Ruby module/class symbols that are declaration-bearing and navigable.
8905
+ "class",
8906
+ "module",
8884
8907
  "class_specifier",
8885
8908
  "struct_specifier",
8886
8909
  "namespace_definition",
@@ -12481,6 +12504,70 @@ var Indexer = class _Indexer {
12481
12504
  );
12482
12505
  return createCostEstimate(files, configuredProviderInfo);
12483
12506
  }
12507
+ // Dry-run counterpart to index()/forceIndex(): parse the real file set and sum
12508
+ // estimateTokens over the embedding text of every indexable chunk, without
12509
+ // calling the embedding provider or writing to the index. Read-only and
12510
+ // lock-free (mirrors estimateCost). The token sum is the exact value "Tokens
12511
+ // used" climbs to for a force index (cache bypassed); for an incremental it is
12512
+ // an upper bound because cached chunks are counted here but not re-embedded.
12513
+ // Used by index_codebase(dryRun:true) to give a stable, monotonic progress
12514
+ // denominator that matches the live "Tokens used" basis.
12515
+ async dryRunCost() {
12516
+ const { configuredProviderInfo } = await this.ensureInitialized();
12517
+ const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
12518
+ const includePatterns = [...this.config.include, ...this.config.additionalInclude];
12519
+ const { files } = await collectFiles(
12520
+ this.materializedProjectRoot,
12521
+ includePatterns,
12522
+ this.config.exclude,
12523
+ this.config.indexing.maxFileSize,
12524
+ this.getMaterializedKnowledgeBases(),
12525
+ { maxDepth: this.config.indexing.maxDepth, maxFilesPerDirectory: this.config.indexing.maxFilesPerDirectory }
12526
+ );
12527
+ let filesCount = 0;
12528
+ let chunksCount = 0;
12529
+ let tokensToEmbed = 0;
12530
+ for (const batch of iterateOrderedFileBatches(files, (f) => f.size, this.fileBatchLimits)) {
12531
+ const loadedFiles = await Promise.all(batch.map(async (f) => {
12532
+ try {
12533
+ return {
12534
+ path: this.toStoredFilePath(f.path),
12535
+ content: await fsPromises3.readFile(f.path, "utf-8")
12536
+ };
12537
+ } catch {
12538
+ return null;
12539
+ }
12540
+ }));
12541
+ const readable = loadedFiles.filter(
12542
+ (f) => f !== null
12543
+ );
12544
+ filesCount += readable.length;
12545
+ const contentByPath = new Map(readable.map((f) => [f.path, f.content]));
12546
+ const parsedFiles = parseFiles(readable, this.config.indexing.linesPerChunk);
12547
+ for (const parsed of parsedFiles) {
12548
+ let chunksToProcess = parsed.chunks;
12549
+ if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
12550
+ const content = contentByPath.get(parsed.path);
12551
+ if (content !== void 0) {
12552
+ chunksToProcess = parseFileAsText(parsed.path, content, this.config.indexing.linesPerChunk);
12553
+ }
12554
+ }
12555
+ chunksToProcess = selectIndexableChunks(
12556
+ chunksToProcess,
12557
+ this.config.indexing.maxChunksPerFile,
12558
+ this.config.indexing.semanticOnly
12559
+ );
12560
+ for (const chunk of chunksToProcess) {
12561
+ const texts = createEmbeddingTexts(chunk, parsed.path, maxChunkTokens);
12562
+ chunksCount += 1;
12563
+ for (const text3 of texts) {
12564
+ tokensToEmbed += estimateTokens(text3);
12565
+ }
12566
+ }
12567
+ }
12568
+ }
12569
+ return { filesCount, chunksCount, tokensToEmbed };
12570
+ }
12484
12571
  async index(onProgress) {
12485
12572
  return this.withIndexMutationLease("index", async (recoveredOwners) => {
12486
12573
  return this.indexUnlocked(onProgress, recoveredOwners);
@@ -15027,6 +15114,9 @@ async function runIndexCodebase(projectRoot3, host, args, onProgress) {
15027
15114
  if (args.estimateOnly) {
15028
15115
  return { kind: "estimate", estimate: await indexer.estimateCost() };
15029
15116
  }
15117
+ if (args.dryRun) {
15118
+ return { kind: "dryrun", dryrun: await indexer.dryRunCost() };
15119
+ }
15030
15120
  const coordinated = runCoordinatedIndex(root, host, args.force ?? false, (progress) => {
15031
15121
  if (onProgress) {
15032
15122
  void onProgress(formatProgressTitle(progress), {
@@ -19103,12 +19193,14 @@ function codebaseIndexPiExtension(pi) {
19103
19193
  parameters: Type2.Object({
19104
19194
  force: Type2.Optional(Type2.Boolean({ default: false })),
19105
19195
  estimateOnly: Type2.Optional(Type2.Boolean({ default: false })),
19196
+ dryRun: Type2.Optional(Type2.Boolean({ default: false })),
19106
19197
  verbose: Type2.Optional(Type2.Boolean({ default: false }))
19107
19198
  }),
19108
19199
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
19109
19200
  try {
19110
19201
  const result = await runIndexCodebase(projectRoot2(ctx), HOST2, params);
19111
19202
  if (result.kind === "estimate") return text2(formatCostEstimate(result.estimate), result.estimate);
19203
+ if (result.kind === "dryrun") return text2(formatDryRunEstimate(result.dryrun), result.dryrun);
19112
19204
  if (result.kind === "busy") return text2(result.text, { code: "INDEX_BUSY" });
19113
19205
  if (result.kind === "message") return text2(result.text);
19114
19206
  return text2(formatIndexStats(result.stats, params.verbose ?? false), result.stats);
@@ -19178,7 +19270,7 @@ function codebaseIndexPiExtension(pi) {
19178
19270
  return {
19179
19271
  systemPrompt: `${event.systemPrompt}
19180
19272
 
19181
- Check index_status first when index readiness is unknown. Use codebase_context only when repository orientation is needed (for layout, key symbols, or cross-file dependency intent), not mechanically for every task. When using codebase_context for orientation, request a compact first pass (for example: tokenBudget: 600, limit: 5) and inspect returned evidence before broad search/grep/bash/read-style reads. Avoid repeating broad reads when the compact evidence already answers the question. Use implementation_lookup for known symbols and call_graph/call_graph_path after endpoints are identified for dependency flow.`
19273
+ Check index_status first when index readiness is unknown. Use codebase_context only when repository orientation is needed (for layout, key symbols, or cross-file dependency intent), not mechanically for every task. When using codebase_context for orientation, request a compact first pass (for example: tokenBudget: 600, limit: 5) and inspect returned evidence before broad search/grep/bash/read-style reads. For change requests with a known or strongly suspected target symbol, optionally use codebase_edit_context as a compact, bounded pre-edit context for source plus direct callers and callees. Avoid repeating broad reads when the compact evidence already answers the question. Use implementation_lookup for known symbols and call_graph/call_graph_path after endpoints are identified for dependency flow.`
19182
19274
  };
19183
19275
  });
19184
19276
  pi.on("session_shutdown", async (_event, ctx) => {