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/index.cjs CHANGED
@@ -6578,6 +6578,26 @@ function formatCostEstimate(estimate) {
6578
6578
  \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
6579
6579
  `;
6580
6580
  }
6581
+ function formatDryRunEstimate(estimate) {
6582
+ return `Dry run: parsed the file set to measure the embedding workload. No embedding requests were made and the index was not changed.
6583
+
6584
+ Files to embed: ${estimate.filesCount.toLocaleString()}
6585
+ Chunks to embed: ${estimate.chunksCount.toLocaleString()}
6586
+ Tokens to embed: ${estimate.tokensToEmbed.toLocaleString()}
6587
+
6588
+ The "Tokens to embed" value uses the local estimateTokens(text) = ceil(len/4). It
6589
+ matches the live "Tokens used" counter only for providers that report usage on the
6590
+ same basis (ollama); for providers that report a server tokenizer count (OpenAI,
6591
+ Gemini, custom) it is only an estimate.
6592
+
6593
+ For a matching provider and a project-scoped force index, the force pass clears its
6594
+ own cached embeddings, so the live counter climbs to this number. A force index on a
6595
+ shared global index can reuse cached embeddings from other projects, and an
6596
+ incremental index counts cached chunks that are not re-embedded; in both cases this
6597
+ number is an upper bound on the live counter, so a progress percent against this
6598
+ total tops out below 100%.
6599
+ `;
6600
+ }
6581
6601
  function formatBytes(bytes) {
6582
6602
  if (bytes === 0) return "0 B";
6583
6603
  const k = 1024;
@@ -8853,6 +8873,9 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
8853
8873
  "enum_declaration",
8854
8874
  "function_definition",
8855
8875
  "class_definition",
8876
+ // Ruby module/class symbols that are declaration-bearing and navigable.
8877
+ "class",
8878
+ "module",
8856
8879
  "class_specifier",
8857
8880
  "struct_specifier",
8858
8881
  "namespace_definition",
@@ -12453,6 +12476,70 @@ var Indexer = class _Indexer {
12453
12476
  );
12454
12477
  return createCostEstimate(files, configuredProviderInfo);
12455
12478
  }
12479
+ // Dry-run counterpart to index()/forceIndex(): parse the real file set and sum
12480
+ // estimateTokens over the embedding text of every indexable chunk, without
12481
+ // calling the embedding provider or writing to the index. Read-only and
12482
+ // lock-free (mirrors estimateCost). The token sum is the exact value "Tokens
12483
+ // used" climbs to for a force index (cache bypassed); for an incremental it is
12484
+ // an upper bound because cached chunks are counted here but not re-embedded.
12485
+ // Used by index_codebase(dryRun:true) to give a stable, monotonic progress
12486
+ // denominator that matches the live "Tokens used" basis.
12487
+ async dryRunCost() {
12488
+ const { configuredProviderInfo } = await this.ensureInitialized();
12489
+ const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
12490
+ const includePatterns = [...this.config.include, ...this.config.additionalInclude];
12491
+ const { files } = await collectFiles(
12492
+ this.materializedProjectRoot,
12493
+ includePatterns,
12494
+ this.config.exclude,
12495
+ this.config.indexing.maxFileSize,
12496
+ this.getMaterializedKnowledgeBases(),
12497
+ { maxDepth: this.config.indexing.maxDepth, maxFilesPerDirectory: this.config.indexing.maxFilesPerDirectory }
12498
+ );
12499
+ let filesCount = 0;
12500
+ let chunksCount = 0;
12501
+ let tokensToEmbed = 0;
12502
+ for (const batch of iterateOrderedFileBatches(files, (f) => f.size, this.fileBatchLimits)) {
12503
+ const loadedFiles = await Promise.all(batch.map(async (f) => {
12504
+ try {
12505
+ return {
12506
+ path: this.toStoredFilePath(f.path),
12507
+ content: await import_fs12.promises.readFile(f.path, "utf-8")
12508
+ };
12509
+ } catch {
12510
+ return null;
12511
+ }
12512
+ }));
12513
+ const readable = loadedFiles.filter(
12514
+ (f) => f !== null
12515
+ );
12516
+ filesCount += readable.length;
12517
+ const contentByPath = new Map(readable.map((f) => [f.path, f.content]));
12518
+ const parsedFiles = parseFiles(readable, this.config.indexing.linesPerChunk);
12519
+ for (const parsed of parsedFiles) {
12520
+ let chunksToProcess = parsed.chunks;
12521
+ if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
12522
+ const content = contentByPath.get(parsed.path);
12523
+ if (content !== void 0) {
12524
+ chunksToProcess = parseFileAsText(parsed.path, content, this.config.indexing.linesPerChunk);
12525
+ }
12526
+ }
12527
+ chunksToProcess = selectIndexableChunks(
12528
+ chunksToProcess,
12529
+ this.config.indexing.maxChunksPerFile,
12530
+ this.config.indexing.semanticOnly
12531
+ );
12532
+ for (const chunk of chunksToProcess) {
12533
+ const texts = createEmbeddingTexts(chunk, parsed.path, maxChunkTokens);
12534
+ chunksCount += 1;
12535
+ for (const text of texts) {
12536
+ tokensToEmbed += estimateTokens(text);
12537
+ }
12538
+ }
12539
+ }
12540
+ }
12541
+ return { filesCount, chunksCount, tokensToEmbed };
12542
+ }
12456
12543
  async index(onProgress) {
12457
12544
  return this.withIndexMutationLease("index", async (recoveredOwners) => {
12458
12545
  return this.indexUnlocked(onProgress, recoveredOwners);
@@ -15006,6 +15093,9 @@ async function runIndexCodebase(projectRoot, host, args, onProgress) {
15006
15093
  if (args.estimateOnly) {
15007
15094
  return { kind: "estimate", estimate: await indexer.estimateCost() };
15008
15095
  }
15096
+ if (args.dryRun) {
15097
+ return { kind: "dryrun", dryrun: await indexer.dryRunCost() };
15098
+ }
15009
15099
  const coordinated = runCoordinatedIndex(root, host, args.force ?? false, (progress) => {
15010
15100
  if (onProgress) {
15011
15101
  void onProgress(formatProgressTitle(progress), {
@@ -18846,6 +18936,7 @@ async function executeCodebaseEditContext(projectRoot, host, args) {
18846
18936
  async function executeIndexCodebase(projectRoot, host, args, onProgress) {
18847
18937
  const result = await runIndexCodebase(projectRoot, host, args, onProgress);
18848
18938
  if (result.kind === "estimate") return { text: formatCostEstimate(result.estimate) };
18939
+ if (result.kind === "dryrun") return { text: formatDryRunEstimate(result.dryrun) };
18849
18940
  if (result.kind === "busy") return { text: result.text, isError: true };
18850
18941
  if (result.kind === "message") return { text: result.text };
18851
18942
  return { text: formatIndexStats(result.stats, args.verbose ?? false) };
@@ -19710,6 +19801,7 @@ var index_codebase = tool({
19710
19801
  args: {
19711
19802
  force: z3.boolean().optional().default(false).describe("Force reindex even if already indexed"),
19712
19803
  estimateOnly: z3.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
19804
+ dryRun: z3.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)."),
19713
19805
  verbose: z3.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
19714
19806
  },
19715
19807
  async execute(args, context) {
@@ -20310,6 +20402,7 @@ function assessRoutingIntent(text) {
20310
20402
  };
20311
20403
  }
20312
20404
  function buildRoutingHint(assessment, status, includeGraphHandoff = false) {
20405
+ const hasSymbolCue = hasIdentifierShape(assessment.text) || containsQuotedIdentifier(assessment.text);
20313
20406
  if (assessment.intent === "definition_lookup") {
20314
20407
  if (!status || !status.indexed || status.compatibility?.compatible === false) {
20315
20408
  return "For this turn, if you need a symbol definition, check `index_status` first and run `index_codebase` if the index is missing or incompatible. Then use `implementation_lookup` for the definition site. Use `grep` for exhaustive literal matches.";
@@ -20319,12 +20412,13 @@ function buildRoutingHint(assessment, status, includeGraphHandoff = false) {
20319
20412
  if (assessment.intent !== "local_conceptual" && assessment.intent !== "local_broad_task") {
20320
20413
  return null;
20321
20414
  }
20415
+ const preEditHint = assessment.intent === "local_broad_task" && hasSymbolCue ? " If a likely target symbol is already known or strongly suspected, consider optional `codebase_edit_context` as a compact pre-edit next step for bounded source plus direct callers and callees." : "";
20322
20416
  if (!status || !status.indexed || status.compatibility?.compatible === false) {
20323
20417
  const graphHandoff2 = includeGraphHandoff ? " Use graph tools after semantic discovery identifies relevant symbols." : "";
20324
- return `For this turn, if local code discovery by behavior is needed, check \`index_status\` first and run \`index_codebase\` if the index is missing or incompatible.${graphHandoff2} Then use \`codebase_context\` as the first local repository lookup. Use \`grep\` for exact identifiers or exhaustive matches.`;
20418
+ return `For this turn, if local code discovery by behavior is needed, check \`index_status\` first and run \`index_codebase\` if the index is missing or incompatible.${graphHandoff2} Then use \`codebase_context\` as the first local repository lookup. Use \`grep\` for exact identifiers or exhaustive matches.${preEditHint}`;
20325
20419
  }
20326
20420
  const graphHandoff = includeGraphHandoff ? " before graph tools such as `call_graph`, `call_graph_path`, `pr_impact`, or OMO CodeGraph" : "";
20327
- return `For this turn, prefer \`codebase_context\` for local code discovery, then use \`codebase_peek\` for metadata and \`codebase_search\` when you need implementation content${graphHandoff}. Use \`grep\` for exact identifiers or exhaustive matches.`;
20421
+ return `For this turn, prefer \`codebase_context\` for local code discovery, then use \`codebase_peek\` for metadata and \`codebase_search\` when you need implementation content${graphHandoff}. Use \`grep\` for exact identifiers or exhaustive matches.${preEditHint}`;
20328
20422
  }
20329
20423
  var RoutingHintController = class {
20330
20424
  constructor(getStatus, maxSessions = 200, includeGraphHandoff = false) {
@@ -20366,7 +20460,7 @@ var RoutingHintController = class {
20366
20460
  if (!state || !state.pendingHint) {
20367
20461
  return;
20368
20462
  }
20369
- if (toolName === "codebase_context" || toolName === "codebase_peek" || toolName === "codebase_search" || toolName === "implementation_lookup" || toolName === "index_status" || toolName === "index_codebase") {
20463
+ if (toolName === "codebase_context" || toolName === "codebase_edit_context" || toolName === "codebase_peek" || toolName === "codebase_search" || toolName === "implementation_lookup" || toolName === "index_status" || toolName === "index_codebase") {
20370
20464
  state.pendingHint = false;
20371
20465
  state.updatedAt = Date.now();
20372
20466
  this.sessionState.set(sessionID, state);