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.js CHANGED
@@ -6576,6 +6576,26 @@ function formatCostEstimate(estimate) {
6576
6576
  \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
6577
6577
  `;
6578
6578
  }
6579
+ function formatDryRunEstimate(estimate) {
6580
+ return `Dry run: parsed the file set to measure the embedding workload. No embedding requests were made and the index was not changed.
6581
+
6582
+ Files to embed: ${estimate.filesCount.toLocaleString()}
6583
+ Chunks to embed: ${estimate.chunksCount.toLocaleString()}
6584
+ Tokens to embed: ${estimate.tokensToEmbed.toLocaleString()}
6585
+
6586
+ The "Tokens to embed" value uses the local estimateTokens(text) = ceil(len/4). It
6587
+ matches the live "Tokens used" counter only for providers that report usage on the
6588
+ same basis (ollama); for providers that report a server tokenizer count (OpenAI,
6589
+ Gemini, custom) it is only an estimate.
6590
+
6591
+ For a matching provider and a project-scoped force index, the force pass clears its
6592
+ own cached embeddings, so the live counter climbs to this number. A force index on a
6593
+ shared global index can reuse cached embeddings from other projects, and an
6594
+ incremental index counts cached chunks that are not re-embedded; in both cases this
6595
+ number is an upper bound on the live counter, so a progress percent against this
6596
+ total tops out below 100%.
6597
+ `;
6598
+ }
6579
6599
  function formatBytes(bytes) {
6580
6600
  if (bytes === 0) return "0 B";
6581
6601
  const k = 1024;
@@ -8850,6 +8870,9 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
8850
8870
  "enum_declaration",
8851
8871
  "function_definition",
8852
8872
  "class_definition",
8873
+ // Ruby module/class symbols that are declaration-bearing and navigable.
8874
+ "class",
8875
+ "module",
8853
8876
  "class_specifier",
8854
8877
  "struct_specifier",
8855
8878
  "namespace_definition",
@@ -12450,6 +12473,70 @@ var Indexer = class _Indexer {
12450
12473
  );
12451
12474
  return createCostEstimate(files, configuredProviderInfo);
12452
12475
  }
12476
+ // Dry-run counterpart to index()/forceIndex(): parse the real file set and sum
12477
+ // estimateTokens over the embedding text of every indexable chunk, without
12478
+ // calling the embedding provider or writing to the index. Read-only and
12479
+ // lock-free (mirrors estimateCost). The token sum is the exact value "Tokens
12480
+ // used" climbs to for a force index (cache bypassed); for an incremental it is
12481
+ // an upper bound because cached chunks are counted here but not re-embedded.
12482
+ // Used by index_codebase(dryRun:true) to give a stable, monotonic progress
12483
+ // denominator that matches the live "Tokens used" basis.
12484
+ async dryRunCost() {
12485
+ const { configuredProviderInfo } = await this.ensureInitialized();
12486
+ const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
12487
+ const includePatterns = [...this.config.include, ...this.config.additionalInclude];
12488
+ const { files } = await collectFiles(
12489
+ this.materializedProjectRoot,
12490
+ includePatterns,
12491
+ this.config.exclude,
12492
+ this.config.indexing.maxFileSize,
12493
+ this.getMaterializedKnowledgeBases(),
12494
+ { maxDepth: this.config.indexing.maxDepth, maxFilesPerDirectory: this.config.indexing.maxFilesPerDirectory }
12495
+ );
12496
+ let filesCount = 0;
12497
+ let chunksCount = 0;
12498
+ let tokensToEmbed = 0;
12499
+ for (const batch of iterateOrderedFileBatches(files, (f) => f.size, this.fileBatchLimits)) {
12500
+ const loadedFiles = await Promise.all(batch.map(async (f) => {
12501
+ try {
12502
+ return {
12503
+ path: this.toStoredFilePath(f.path),
12504
+ content: await fsPromises3.readFile(f.path, "utf-8")
12505
+ };
12506
+ } catch {
12507
+ return null;
12508
+ }
12509
+ }));
12510
+ const readable = loadedFiles.filter(
12511
+ (f) => f !== null
12512
+ );
12513
+ filesCount += readable.length;
12514
+ const contentByPath = new Map(readable.map((f) => [f.path, f.content]));
12515
+ const parsedFiles = parseFiles(readable, this.config.indexing.linesPerChunk);
12516
+ for (const parsed of parsedFiles) {
12517
+ let chunksToProcess = parsed.chunks;
12518
+ if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
12519
+ const content = contentByPath.get(parsed.path);
12520
+ if (content !== void 0) {
12521
+ chunksToProcess = parseFileAsText(parsed.path, content, this.config.indexing.linesPerChunk);
12522
+ }
12523
+ }
12524
+ chunksToProcess = selectIndexableChunks(
12525
+ chunksToProcess,
12526
+ this.config.indexing.maxChunksPerFile,
12527
+ this.config.indexing.semanticOnly
12528
+ );
12529
+ for (const chunk of chunksToProcess) {
12530
+ const texts = createEmbeddingTexts(chunk, parsed.path, maxChunkTokens);
12531
+ chunksCount += 1;
12532
+ for (const text of texts) {
12533
+ tokensToEmbed += estimateTokens(text);
12534
+ }
12535
+ }
12536
+ }
12537
+ }
12538
+ return { filesCount, chunksCount, tokensToEmbed };
12539
+ }
12453
12540
  async index(onProgress) {
12454
12541
  return this.withIndexMutationLease("index", async (recoveredOwners) => {
12455
12542
  return this.indexUnlocked(onProgress, recoveredOwners);
@@ -15003,6 +15090,9 @@ async function runIndexCodebase(projectRoot, host, args, onProgress) {
15003
15090
  if (args.estimateOnly) {
15004
15091
  return { kind: "estimate", estimate: await indexer.estimateCost() };
15005
15092
  }
15093
+ if (args.dryRun) {
15094
+ return { kind: "dryrun", dryrun: await indexer.dryRunCost() };
15095
+ }
15006
15096
  const coordinated = runCoordinatedIndex(root, host, args.force ?? false, (progress) => {
15007
15097
  if (onProgress) {
15008
15098
  void onProgress(formatProgressTitle(progress), {
@@ -18843,6 +18933,7 @@ async function executeCodebaseEditContext(projectRoot, host, args) {
18843
18933
  async function executeIndexCodebase(projectRoot, host, args, onProgress) {
18844
18934
  const result = await runIndexCodebase(projectRoot, host, args, onProgress);
18845
18935
  if (result.kind === "estimate") return { text: formatCostEstimate(result.estimate) };
18936
+ if (result.kind === "dryrun") return { text: formatDryRunEstimate(result.dryrun) };
18846
18937
  if (result.kind === "busy") return { text: result.text, isError: true };
18847
18938
  if (result.kind === "message") return { text: result.text };
18848
18939
  return { text: formatIndexStats(result.stats, args.verbose ?? false) };
@@ -19707,6 +19798,7 @@ var index_codebase = tool({
19707
19798
  args: {
19708
19799
  force: z3.boolean().optional().default(false).describe("Force reindex even if already indexed"),
19709
19800
  estimateOnly: z3.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
19801
+ 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)."),
19710
19802
  verbose: z3.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
19711
19803
  },
19712
19804
  async execute(args, context) {
@@ -20307,6 +20399,7 @@ function assessRoutingIntent(text) {
20307
20399
  };
20308
20400
  }
20309
20401
  function buildRoutingHint(assessment, status, includeGraphHandoff = false) {
20402
+ const hasSymbolCue = hasIdentifierShape(assessment.text) || containsQuotedIdentifier(assessment.text);
20310
20403
  if (assessment.intent === "definition_lookup") {
20311
20404
  if (!status || !status.indexed || status.compatibility?.compatible === false) {
20312
20405
  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.";
@@ -20316,12 +20409,13 @@ function buildRoutingHint(assessment, status, includeGraphHandoff = false) {
20316
20409
  if (assessment.intent !== "local_conceptual" && assessment.intent !== "local_broad_task") {
20317
20410
  return null;
20318
20411
  }
20412
+ 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." : "";
20319
20413
  if (!status || !status.indexed || status.compatibility?.compatible === false) {
20320
20414
  const graphHandoff2 = includeGraphHandoff ? " Use graph tools after semantic discovery identifies relevant symbols." : "";
20321
- 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.`;
20415
+ 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}`;
20322
20416
  }
20323
20417
  const graphHandoff = includeGraphHandoff ? " before graph tools such as `call_graph`, `call_graph_path`, `pr_impact`, or OMO CodeGraph" : "";
20324
- 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.`;
20418
+ 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}`;
20325
20419
  }
20326
20420
  var RoutingHintController = class {
20327
20421
  constructor(getStatus, maxSessions = 200, includeGraphHandoff = false) {
@@ -20363,7 +20457,7 @@ var RoutingHintController = class {
20363
20457
  if (!state || !state.pendingHint) {
20364
20458
  return;
20365
20459
  }
20366
- if (toolName === "codebase_context" || toolName === "codebase_peek" || toolName === "codebase_search" || toolName === "implementation_lookup" || toolName === "index_status" || toolName === "index_codebase") {
20460
+ if (toolName === "codebase_context" || toolName === "codebase_edit_context" || toolName === "codebase_peek" || toolName === "codebase_search" || toolName === "implementation_lookup" || toolName === "index_status" || toolName === "index_codebase") {
20367
20461
  state.pendingHint = false;
20368
20462
  state.updatedAt = Date.now();
20369
20463
  this.sessionState.set(sessionID, state);