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.js CHANGED
@@ -3596,6 +3596,26 @@ function formatCostEstimate(estimate) {
3596
3596
  \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
3597
3597
  `;
3598
3598
  }
3599
+ function formatDryRunEstimate(estimate) {
3600
+ return `Dry run: parsed the file set to measure the embedding workload. No embedding requests were made and the index was not changed.
3601
+
3602
+ Files to embed: ${estimate.filesCount.toLocaleString()}
3603
+ Chunks to embed: ${estimate.chunksCount.toLocaleString()}
3604
+ Tokens to embed: ${estimate.tokensToEmbed.toLocaleString()}
3605
+
3606
+ The "Tokens to embed" value uses the local estimateTokens(text) = ceil(len/4). It
3607
+ matches the live "Tokens used" counter only for providers that report usage on the
3608
+ same basis (ollama); for providers that report a server tokenizer count (OpenAI,
3609
+ Gemini, custom) it is only an estimate.
3610
+
3611
+ For a matching provider and a project-scoped force index, the force pass clears its
3612
+ own cached embeddings, so the live counter climbs to this number. A force index on a
3613
+ shared global index can reuse cached embeddings from other projects, and an
3614
+ incremental index counts cached chunks that are not re-embedded; in both cases this
3615
+ number is an upper bound on the live counter, so a progress percent against this
3616
+ total tops out below 100%.
3617
+ `;
3618
+ }
3599
3619
  function formatBytes(bytes) {
3600
3620
  if (bytes === 0) return "0 B";
3601
3621
  const k = 1024;
@@ -6820,6 +6840,9 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
6820
6840
  "enum_declaration",
6821
6841
  "function_definition",
6822
6842
  "class_definition",
6843
+ // Ruby module/class symbols that are declaration-bearing and navigable.
6844
+ "class",
6845
+ "module",
6823
6846
  "class_specifier",
6824
6847
  "struct_specifier",
6825
6848
  "namespace_definition",
@@ -10866,6 +10889,70 @@ var Indexer = class _Indexer {
10866
10889
  );
10867
10890
  return createCostEstimate(files, configuredProviderInfo);
10868
10891
  }
10892
+ // Dry-run counterpart to index()/forceIndex(): parse the real file set and sum
10893
+ // estimateTokens over the embedding text of every indexable chunk, without
10894
+ // calling the embedding provider or writing to the index. Read-only and
10895
+ // lock-free (mirrors estimateCost). The token sum is the exact value "Tokens
10896
+ // used" climbs to for a force index (cache bypassed); for an incremental it is
10897
+ // an upper bound because cached chunks are counted here but not re-embedded.
10898
+ // Used by index_codebase(dryRun:true) to give a stable, monotonic progress
10899
+ // denominator that matches the live "Tokens used" basis.
10900
+ async dryRunCost() {
10901
+ const { configuredProviderInfo } = await this.ensureInitialized();
10902
+ const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
10903
+ const includePatterns = [...this.config.include, ...this.config.additionalInclude];
10904
+ const { files } = await collectFiles(
10905
+ this.materializedProjectRoot,
10906
+ includePatterns,
10907
+ this.config.exclude,
10908
+ this.config.indexing.maxFileSize,
10909
+ this.getMaterializedKnowledgeBases(),
10910
+ { maxDepth: this.config.indexing.maxDepth, maxFilesPerDirectory: this.config.indexing.maxFilesPerDirectory }
10911
+ );
10912
+ let filesCount = 0;
10913
+ let chunksCount = 0;
10914
+ let tokensToEmbed = 0;
10915
+ for (const batch of iterateOrderedFileBatches(files, (f) => f.size, this.fileBatchLimits)) {
10916
+ const loadedFiles = await Promise.all(batch.map(async (f) => {
10917
+ try {
10918
+ return {
10919
+ path: this.toStoredFilePath(f.path),
10920
+ content: await fsPromises3.readFile(f.path, "utf-8")
10921
+ };
10922
+ } catch {
10923
+ return null;
10924
+ }
10925
+ }));
10926
+ const readable = loadedFiles.filter(
10927
+ (f) => f !== null
10928
+ );
10929
+ filesCount += readable.length;
10930
+ const contentByPath = new Map(readable.map((f) => [f.path, f.content]));
10931
+ const parsedFiles = parseFiles(readable, this.config.indexing.linesPerChunk);
10932
+ for (const parsed of parsedFiles) {
10933
+ let chunksToProcess = parsed.chunks;
10934
+ if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
10935
+ const content = contentByPath.get(parsed.path);
10936
+ if (content !== void 0) {
10937
+ chunksToProcess = parseFileAsText(parsed.path, content, this.config.indexing.linesPerChunk);
10938
+ }
10939
+ }
10940
+ chunksToProcess = selectIndexableChunks(
10941
+ chunksToProcess,
10942
+ this.config.indexing.maxChunksPerFile,
10943
+ this.config.indexing.semanticOnly
10944
+ );
10945
+ for (const chunk of chunksToProcess) {
10946
+ const texts = createEmbeddingTexts(chunk, parsed.path, maxChunkTokens);
10947
+ chunksCount += 1;
10948
+ for (const text of texts) {
10949
+ tokensToEmbed += estimateTokens2(text);
10950
+ }
10951
+ }
10952
+ }
10953
+ }
10954
+ return { filesCount, chunksCount, tokensToEmbed };
10955
+ }
10869
10956
  async index(onProgress) {
10870
10957
  return this.withIndexMutationLease("index", async (recoveredOwners) => {
10871
10958
  return this.indexUnlocked(onProgress, recoveredOwners);
@@ -15369,6 +15456,9 @@ async function runIndexCodebase(projectRoot, host, args, onProgress) {
15369
15456
  if (args.estimateOnly) {
15370
15457
  return { kind: "estimate", estimate: await indexer.estimateCost() };
15371
15458
  }
15459
+ if (args.dryRun) {
15460
+ return { kind: "dryrun", dryrun: await indexer.dryRunCost() };
15461
+ }
15372
15462
  const coordinated = runCoordinatedIndex(root, host, args.force ?? false, (progress) => {
15373
15463
  if (onProgress) {
15374
15464
  void onProgress(formatProgressTitle(progress), {
@@ -18295,6 +18385,7 @@ async function executeCodebaseEditContext(projectRoot, host, args) {
18295
18385
  async function executeIndexCodebase(projectRoot, host, args, onProgress) {
18296
18386
  const result = await runIndexCodebase(projectRoot, host, args, onProgress);
18297
18387
  if (result.kind === "estimate") return { text: formatCostEstimate(result.estimate) };
18388
+ if (result.kind === "dryrun") return { text: formatDryRunEstimate(result.dryrun) };
18298
18389
  if (result.kind === "busy") return { text: result.text, isError: true };
18299
18390
  if (result.kind === "message") return { text: result.text };
18300
18391
  return { text: formatIndexStats(result.stats, args.verbose ?? false) };
@@ -18622,6 +18713,7 @@ ${formatCodebasePeek(results)}`;
18622
18713
  {
18623
18714
  force: allowNullAsUndefined(z2.boolean().optional().default(false)).describe("Force reindex even if already indexed"),
18624
18715
  estimateOnly: allowNullAsUndefined(z2.boolean().optional().default(false)).describe("Only show cost estimate without indexing"),
18716
+ dryRun: allowNullAsUndefined(z2.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)."),
18625
18717
  verbose: allowNullAsUndefined(z2.boolean().optional().default(false)).describe("Show detailed info about skipped files and parsing failures")
18626
18718
  },
18627
18719
  async (args) => {
@@ -18836,7 +18928,7 @@ ${formatSearchResults(results)}` }] };
18836
18928
  // src/adapters/mcp/server.ts
18837
18929
  function getServerInstructions(host) {
18838
18930
  const hostText = `host ${host}`;
18839
- 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.`;
18931
+ 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.`;
18840
18932
  }
18841
18933
  function createMcpServer(projectRoot, config, host) {
18842
18934
  const server = new McpServer({
@@ -22254,6 +22346,7 @@ function parseIndexArgs(argv, cwd) {
22254
22346
  let config;
22255
22347
  let force = false;
22256
22348
  let estimateOnly = false;
22349
+ let dryRun = false;
22257
22350
  let verbose = false;
22258
22351
  for (let i = 0; i < argv.length; i += 1) {
22259
22352
  const arg = argv[i];
@@ -22291,13 +22384,16 @@ function parseIndexArgs(argv, cwd) {
22291
22384
  host = parseHostMode(value);
22292
22385
  continue;
22293
22386
  }
22294
- if (arg === "--force" || arg === "--estimate-only" || arg === "--verbose") {
22387
+ if (arg === "--force" || arg === "--estimate-only" || arg === "--dry-run" || arg === "--verbose") {
22295
22388
  if (arg === "--force") {
22296
22389
  force = true;
22297
22390
  }
22298
22391
  if (arg === "--estimate-only") {
22299
22392
  estimateOnly = true;
22300
22393
  }
22394
+ if (arg === "--dry-run") {
22395
+ dryRun = true;
22396
+ }
22301
22397
  if (arg === "--verbose") {
22302
22398
  verbose = true;
22303
22399
  }
@@ -22308,7 +22404,7 @@ function parseIndexArgs(argv, cwd) {
22308
22404
  }
22309
22405
  throw new Error(`Unknown index option: ${arg}`);
22310
22406
  }
22311
- return { project, host, config, force, estimateOnly, verbose };
22407
+ return { project, host, config, force, estimateOnly, dryRun, verbose };
22312
22408
  }
22313
22409
  function loadCliRawConfig(args) {
22314
22410
  return args.config ? loadConfigFile(args.config) : loadMergedConfig(args.project, args.host);
@@ -22325,6 +22421,7 @@ Options:
22325
22421
  --config <path> Explicit JSON config path
22326
22422
  --force Rebuild index even if already up to date
22327
22423
  --estimate-only Estimate indexing cost only
22424
+ --dry-run Parse only; report the exact embedding token total without indexing
22328
22425
  --verbose Include detailed final index statistics
22329
22426
  --help Show this message
22330
22427
 
@@ -22520,6 +22617,7 @@ async function handleIndexCommand(argv, cwd, deps = {}) {
22520
22617
  const indexArgs = {
22521
22618
  force: parsedArgs.force,
22522
22619
  estimateOnly: parsedArgs.estimateOnly,
22620
+ dryRun: parsedArgs.dryRun,
22523
22621
  verbose: parsedArgs.verbose
22524
22622
  };
22525
22623
  const result = await runIndex(parsedArgs.project, parsedArgs.host, indexArgs, (title, metadata) => {