feat(indexing): add dryRun mode to index_codebase for parse-only token totals - #305
Open
dkhokhlov wants to merge 3 commits into
Open
feat(indexing): add dryRun mode to index_codebase for parse-only token totals#305dkhokhlov wants to merge 3 commits into
dkhokhlov wants to merge 3 commits into
Conversation
…n totals index_codebase now accepts dryRun:true (CLI: --dry-run) to parse the file set and sum estimateTokens over the real chunk embedding text WITHOUT embedding or writing to the index. The token total is the exact value "Tokens used" climbs to for a force index (cache bypassed) and a stable upper bound for an incremental, so it serves as a fixed, monotonic percent denominator for live progress reporting (e.g. the ~/bin/ci wrapper's preflight). Implemented as a standalone read-only Indexer.dryRunCost() that reuses the exact index() pipeline (collectFiles + parseFiles + fallbackToTextOnMaxChunks + selectIndexableChunks + createEmbeddingTexts + estimateTokens); no DB writes, no ollama call, no lock. Plumbed through operations.ts (result union kind:"dryrun"), execute-common.ts, contracts.ts (SharedIndexCodebaseArgs), the MCP/opencode/pi tool schemas, and the CLI (parseIndexArgs + --dry-run + usage). Formatted by formatDryRunEstimate in utils/cost.ts. Tests: parseIndexArgs --dry-run case + the dryRun:false field in the existing CLI arg/execution assertions; formatDryRunEstimate coverage in cost.test.ts.
Address review (codex) of the dryRun feature. The "exact force-index token total" claim was overstated. dryRunCost() sums the local estimate (estimateTokens = ceil(len/4)); the live "Tokens used" counter is provider-reported. They match only for providers that report usage on the same basis (ollama counts ceil(len/4) per embedded text). For providers that report a server tokenizer count (OpenAI, Gemini, custom) the dry-run value is an estimate, not an exact match. Reword the DryRunEstimate docstring and the formatDryRunEstimate output so the claim is provider-conditional, and note that a force index on a shared global index can reuse cached embeddings from other projects, so the dry-run value is an upper bound there (as it is for any incremental index). Update the formatDryRunEstimate unit assertions to the scoped wording. Add tests/dryrun-index.test.ts: an integration test that runs dryRunCost() against a temp project (AST chunking + fallback-to-text + maxChunksPerFile cap) with a mocked ollama provider and asserts (1) no embedding provider call and the index stays unindexed, (2) the dry-run total equals a force-index tokensUsed for an estimate-based provider and the chunk counts match, and (3) dryRunCost is idempotent and writes no embeddings across repeated calls.
dkhokhlov
force-pushed
the
feat/index-dryrun
branch
from
August 18, 2026 07:23
5400f14 to
041fac5
Compare
Owner
|
Thanks for the contribution. I exercised the native build and the affected dry-run, token-cost, and MCP CLI suites locally: 50 tests pass. Before this is ready to merge, could you please address these items?
|
Address the four review items on the dryRun PR. 1. Narrow the read-only guarantee to "no embedding requests" (not "no provider contact"): dryRunCost still runs provider detection as part of normal initialization (for ollama this contacts /api/tags and /api/show) because it needs maxTokens to cap chunk size; no embeddings are requested and no writes occur. Update the DryRunEstimate docstring, formatDryRunEstimate output, and the cost test assertion. 2. Count chunksCount once per source chunk (matching forceIndex indexedChunks), not once per embedding text. tokensToEmbed still sums per embedding text, so it matches tokensUsed. Add a split-chunk regression test: one oversized source chunk that splits into multiple embedding texts still keeps chunksCount == indexedChunks and tokensToEmbed == tokensUsed, with more embedding requests than chunks. 3. Document dryRun in docs/tools.md, docs/installation.md, and CHANGELOG. The PR description is updated separately via gh pr edit.
Contributor
Author
|
@Helweg |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
index_codebaseaccepts a newdryRun: trueflag (CLI:index --dry-run) that parses the real file set, builds the embedding text for every indexable chunk, and sumsestimateTokensoverthose texts without requesting embeddings or writing to the index. It returns the file count, chunk count, and the token total a force index would consume — a read-only preflight.
Motivation
A live
index_codebaserun reports progress as a moving chunk percentage whose denominator grows during parse, so the percent dips backwards (60% → 36% → 62%) and is unfit as a progress gauge.The dry-run total is a fixed, monotonic denominator:
percent = tokensUsed * 100 / dryRunTotalclimbs cleanly to ~100% for a force index and tops out below 100% for an incremental (cachedchunks counted in the total but not re-embedded).
It also gives a cost/throughput preview before committing GPU/time: e.g.
~/.claude/projects→ 970 files / 72,488 chunks / 76.5M tokens in ~13 s, with no ollama call.Behavior
Indexer.dryRunCost()that reuses the exactindex()pipeline:collectFiles→parseFiles(linesPerChunk)→fallbackToTextOnMaxChunks→selectIndexableChunks→createEmbeddingTexts→estimateTokens. Same chunking as a real index, so the chunk count and token sum match a force index exactly (same provider).withIndexMutationLease, opens the DB read-only. (Provider detection may still contact the provider's tags endpoint, e.g. ollama/api/tags+/api/show; no embeddings are requested and no writes occur.)kind: "dryrun"member of the index result union, formatted byformatDryRunEstimateinsrc/utils/cost.ts.chunksCountcounts source chunks (matchesforceIndex'sindexedChunks);tokensToEmbedsums per embedding text (matchestokensUsed). A chunk that splits into multiple embedding textsstill counts as one chunk.
operations.ts,execute-common.ts,contracts.ts(SharedIndexCodebaseArgs), the MCP/opencode/pi tool schemas, and the CLI (parseIndexArgs+--dry-run+ usage).Exactness of the token total (important)
The dry-run sums the local
estimateTokens(text) = ceil(len/4). This equals the live "Tokens used" counter only for providers that report usage on the same basis — ollama countsceil(len/4), so for ollama the dry-run total is exact. For providers that report a server tokenizer count (OpenAI, Gemini, custom), the dry-run value is an estimate, not exact.It is also an upper bound in two cases even for a matching provider:
In both cases a progress percent against this total tops out below 100%. The output states this explicitly.
Tests
tests/dryrun-index.test.ts(new): integration test with a mocked ollama provider — asserts no embedding calls are made and the index is left unindexed; a subsequent force index reachesstats.tokensUsed == dryRun.tokensToEmbedandstats.indexedChunks == dryRun.chunksCount; idempotent across repeated dry-runs. Includes a split-chunk regression test: one oversized source chunkthat splits into multiple embedding texts still keeps
chunksCount == indexedChunksandtokensToEmbed == tokensUsed, with more embedding requests than chunks.tests/cost.test.ts:formatDryRunEstimateformatting + the provider-conditional / upper-bound wording assertions.tests/mcp-cli-index.test.ts:parseIndexArgs --dry-runparsing + thedryRun: falsefield in the existing CLI arg/execution assertions.typecheck + lint clean; full vitest suite green (one pre-existing watcher flake is unrelated to this change).