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.
package/dist/cli.cjs CHANGED
@@ -3610,6 +3610,26 @@ function formatCostEstimate(estimate) {
3610
3610
  \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
3611
3611
  `;
3612
3612
  }
3613
+ function formatDryRunEstimate(estimate) {
3614
+ return `Dry run: parsed the file set to measure the embedding workload. No embedding requests were made and the index was not changed.
3615
+
3616
+ Files to embed: ${estimate.filesCount.toLocaleString()}
3617
+ Chunks to embed: ${estimate.chunksCount.toLocaleString()}
3618
+ Tokens to embed: ${estimate.tokensToEmbed.toLocaleString()}
3619
+
3620
+ The "Tokens to embed" value uses the local estimateTokens(text) = ceil(len/4). It
3621
+ matches the live "Tokens used" counter only for providers that report usage on the
3622
+ same basis (ollama); for providers that report a server tokenizer count (OpenAI,
3623
+ Gemini, custom) it is only an estimate.
3624
+
3625
+ For a matching provider and a project-scoped force index, the force pass clears its
3626
+ own cached embeddings, so the live counter climbs to this number. A force index on a
3627
+ shared global index can reuse cached embeddings from other projects, and an
3628
+ incremental index counts cached chunks that are not re-embedded; in both cases this
3629
+ number is an upper bound on the live counter, so a progress percent against this
3630
+ total tops out below 100%.
3631
+ `;
3632
+ }
3613
3633
  function formatBytes(bytes) {
3614
3634
  if (bytes === 0) return "0 B";
3615
3635
  const k = 1024;
@@ -6835,6 +6855,9 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
6835
6855
  "enum_declaration",
6836
6856
  "function_definition",
6837
6857
  "class_definition",
6858
+ // Ruby module/class symbols that are declaration-bearing and navigable.
6859
+ "class",
6860
+ "module",
6838
6861
  "class_specifier",
6839
6862
  "struct_specifier",
6840
6863
  "namespace_definition",
@@ -10871,6 +10894,70 @@ var Indexer = class _Indexer {
10871
10894
  );
10872
10895
  return createCostEstimate(files, configuredProviderInfo);
10873
10896
  }
10897
+ // Dry-run counterpart to index()/forceIndex(): parse the real file set and sum
10898
+ // estimateTokens over the embedding text of every indexable chunk, without
10899
+ // calling the embedding provider or writing to the index. Read-only and
10900
+ // lock-free (mirrors estimateCost). The token sum is the exact value "Tokens
10901
+ // used" climbs to for a force index (cache bypassed); for an incremental it is
10902
+ // an upper bound because cached chunks are counted here but not re-embedded.
10903
+ // Used by index_codebase(dryRun:true) to give a stable, monotonic progress
10904
+ // denominator that matches the live "Tokens used" basis.
10905
+ async dryRunCost() {
10906
+ const { configuredProviderInfo } = await this.ensureInitialized();
10907
+ const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
10908
+ const includePatterns = [...this.config.include, ...this.config.additionalInclude];
10909
+ const { files } = await collectFiles(
10910
+ this.materializedProjectRoot,
10911
+ includePatterns,
10912
+ this.config.exclude,
10913
+ this.config.indexing.maxFileSize,
10914
+ this.getMaterializedKnowledgeBases(),
10915
+ { maxDepth: this.config.indexing.maxDepth, maxFilesPerDirectory: this.config.indexing.maxFilesPerDirectory }
10916
+ );
10917
+ let filesCount = 0;
10918
+ let chunksCount = 0;
10919
+ let tokensToEmbed = 0;
10920
+ for (const batch of iterateOrderedFileBatches(files, (f) => f.size, this.fileBatchLimits)) {
10921
+ const loadedFiles = await Promise.all(batch.map(async (f) => {
10922
+ try {
10923
+ return {
10924
+ path: this.toStoredFilePath(f.path),
10925
+ content: await import_fs10.promises.readFile(f.path, "utf-8")
10926
+ };
10927
+ } catch {
10928
+ return null;
10929
+ }
10930
+ }));
10931
+ const readable = loadedFiles.filter(
10932
+ (f) => f !== null
10933
+ );
10934
+ filesCount += readable.length;
10935
+ const contentByPath = new Map(readable.map((f) => [f.path, f.content]));
10936
+ const parsedFiles = parseFiles(readable, this.config.indexing.linesPerChunk);
10937
+ for (const parsed of parsedFiles) {
10938
+ let chunksToProcess = parsed.chunks;
10939
+ if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
10940
+ const content = contentByPath.get(parsed.path);
10941
+ if (content !== void 0) {
10942
+ chunksToProcess = parseFileAsText(parsed.path, content, this.config.indexing.linesPerChunk);
10943
+ }
10944
+ }
10945
+ chunksToProcess = selectIndexableChunks(
10946
+ chunksToProcess,
10947
+ this.config.indexing.maxChunksPerFile,
10948
+ this.config.indexing.semanticOnly
10949
+ );
10950
+ for (const chunk of chunksToProcess) {
10951
+ const texts = createEmbeddingTexts(chunk, parsed.path, maxChunkTokens);
10952
+ chunksCount += 1;
10953
+ for (const text of texts) {
10954
+ tokensToEmbed += estimateTokens2(text);
10955
+ }
10956
+ }
10957
+ }
10958
+ }
10959
+ return { filesCount, chunksCount, tokensToEmbed };
10960
+ }
10874
10961
  async index(onProgress) {
10875
10962
  return this.withIndexMutationLease("index", async (recoveredOwners) => {
10876
10963
  return this.indexUnlocked(onProgress, recoveredOwners);
@@ -15374,6 +15461,9 @@ async function runIndexCodebase(projectRoot, host, args, onProgress) {
15374
15461
  if (args.estimateOnly) {
15375
15462
  return { kind: "estimate", estimate: await indexer.estimateCost() };
15376
15463
  }
15464
+ if (args.dryRun) {
15465
+ return { kind: "dryrun", dryrun: await indexer.dryRunCost() };
15466
+ }
15377
15467
  const coordinated = runCoordinatedIndex(root, host, args.force ?? false, (progress) => {
15378
15468
  if (onProgress) {
15379
15469
  void onProgress(formatProgressTitle(progress), {
@@ -18301,6 +18391,7 @@ async function executeCodebaseEditContext(projectRoot, host, args) {
18301
18391
  async function executeIndexCodebase(projectRoot, host, args, onProgress) {
18302
18392
  const result = await runIndexCodebase(projectRoot, host, args, onProgress);
18303
18393
  if (result.kind === "estimate") return { text: formatCostEstimate(result.estimate) };
18394
+ if (result.kind === "dryrun") return { text: formatDryRunEstimate(result.dryrun) };
18304
18395
  if (result.kind === "busy") return { text: result.text, isError: true };
18305
18396
  if (result.kind === "message") return { text: result.text };
18306
18397
  return { text: formatIndexStats(result.stats, args.verbose ?? false) };
@@ -18628,6 +18719,7 @@ ${formatCodebasePeek(results)}`;
18628
18719
  {
18629
18720
  force: allowNullAsUndefined(import_zod2.z.boolean().optional().default(false)).describe("Force reindex even if already indexed"),
18630
18721
  estimateOnly: allowNullAsUndefined(import_zod2.z.boolean().optional().default(false)).describe("Only show cost estimate without indexing"),
18722
+ dryRun: allowNullAsUndefined(import_zod2.z.boolean().optional().default(false)).describe("Parse the file set and report the exact embedding token total without indexing. Read-only; the index is not changed. The total is the value 'Tokens used' climbs to for a force index (and an upper bound for an incremental)."),
18631
18723
  verbose: allowNullAsUndefined(import_zod2.z.boolean().optional().default(false)).describe("Show detailed info about skipped files and parsing failures")
18632
18724
  },
18633
18725
  async (args) => {
@@ -18842,7 +18934,7 @@ ${formatSearchResults(results)}` }] };
18842
18934
  // src/adapters/mcp/server.ts
18843
18935
  function getServerInstructions(host) {
18844
18936
  const hostText = `host ${host}`;
18845
- return `This MCP server is the preferred codebase-understanding path for ${hostText}. Start a repository task with index_status when index readiness or freshness is unknown. Use codebase_context as the preferred first entry point because it returns a token-budgeted location pack and routes to definitions or call-graph helpers when symbol intent is present. Keep the default tokenBudget for normal discovery, then use implementation_lookup, codebase_search, or a targeted file read only for selected locations that need source content. Use codebase_peek for direct conceptual location lookup. For exact identifiers or exhaustive matches, use grep. After identifying symbols, use call_graph or call_graph_path to trace dependencies. If the index is unavailable, run index_codebase, then retry the retrieval tool.`;
18937
+ return `This MCP server is the preferred codebase-understanding path for ${hostText}. Start a repository task with index_status when index readiness or freshness is unknown. Use codebase_context as the preferred first entry point because it returns a token-budgeted location pack and routes to definitions or call-graph helpers when symbol intent is present. For code changes with a known or suspected symbol target, optionally call codebase_edit_context as a compact pre-edit step for bounded source plus direct callers and callees before broad file reads. Keep the default tokenBudget for normal discovery, then use implementation_lookup, codebase_search, or a targeted file read only for selected locations that need source content. Use codebase_peek for direct conceptual location lookup. For exact identifiers or exhaustive matches, use grep. After identifying symbols, use call_graph or call_graph_path to trace dependencies. If the index is unavailable, run index_codebase, then retry the retrieval tool.`;
18846
18938
  }
18847
18939
  function createMcpServer(projectRoot, config, host) {
18848
18940
  const server = new import_mcp.McpServer({
@@ -22260,6 +22352,7 @@ function parseIndexArgs(argv, cwd) {
22260
22352
  let config;
22261
22353
  let force = false;
22262
22354
  let estimateOnly = false;
22355
+ let dryRun = false;
22263
22356
  let verbose = false;
22264
22357
  for (let i = 0; i < argv.length; i += 1) {
22265
22358
  const arg = argv[i];
@@ -22297,13 +22390,16 @@ function parseIndexArgs(argv, cwd) {
22297
22390
  host = parseHostMode(value);
22298
22391
  continue;
22299
22392
  }
22300
- if (arg === "--force" || arg === "--estimate-only" || arg === "--verbose") {
22393
+ if (arg === "--force" || arg === "--estimate-only" || arg === "--dry-run" || arg === "--verbose") {
22301
22394
  if (arg === "--force") {
22302
22395
  force = true;
22303
22396
  }
22304
22397
  if (arg === "--estimate-only") {
22305
22398
  estimateOnly = true;
22306
22399
  }
22400
+ if (arg === "--dry-run") {
22401
+ dryRun = true;
22402
+ }
22307
22403
  if (arg === "--verbose") {
22308
22404
  verbose = true;
22309
22405
  }
@@ -22314,7 +22410,7 @@ function parseIndexArgs(argv, cwd) {
22314
22410
  }
22315
22411
  throw new Error(`Unknown index option: ${arg}`);
22316
22412
  }
22317
- return { project, host, config, force, estimateOnly, verbose };
22413
+ return { project, host, config, force, estimateOnly, dryRun, verbose };
22318
22414
  }
22319
22415
  function loadCliRawConfig(args) {
22320
22416
  return args.config ? loadConfigFile(args.config) : loadMergedConfig(args.project, args.host);
@@ -22331,6 +22427,7 @@ Options:
22331
22427
  --config <path> Explicit JSON config path
22332
22428
  --force Rebuild index even if already up to date
22333
22429
  --estimate-only Estimate indexing cost only
22430
+ --dry-run Parse only; report the exact embedding token total without indexing
22334
22431
  --verbose Include detailed final index statistics
22335
22432
  --help Show this message
22336
22433
 
@@ -22526,6 +22623,7 @@ async function handleIndexCommand(argv, cwd, deps = {}) {
22526
22623
  const indexArgs = {
22527
22624
  force: parsedArgs.force,
22528
22625
  estimateOnly: parsedArgs.estimateOnly,
22626
+ dryRun: parsedArgs.dryRun,
22529
22627
  verbose: parsedArgs.verbose
22530
22628
  };
22531
22629
  const result = await runIndex(parsedArgs.project, parsedArgs.host, indexArgs, (title, metadata) => {