perf(context): compute the dominant file once per index state (#1864) - #2086
Merged
Merged
Conversation
getDominantFile() backs context ranking's core-directory boost. Its answer depends only on the graph, but the whole-graph aggregation behind it (edges joined to both endpoints' nodes, grouped by file) ran on every generic codegraph_explore - seconds per call on a large index. Memoize it in QueryBuilder against a database change stamp: total_changes() (rows this connection wrote) plus PRAGMA data_version (commits by any other connection or process). Both are O(1), so no write path has to remember to invalidate, and a sync made by another process is seen on the next call. Any write forces a recompute; the heuristic's result is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1864) total_changes() counts writes that are later rolled back, so a result computed inside a transaction could survive the ROLLBACK under an unchanged stamp and describe edges that no longer exist. getDominantFile now skips the memo while a transaction is open (and on a runtime without isTransaction). Ported from the maintainer's hardening of this fix in #1919. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1864) The memo's change stamp (total_changes + data_version) is per connection, and fresh read-only connections to two different databases report the same one ("0:2"). A pool worker rebound onto a rebuilt index kept serving the old database's dominant file until something wrote through the new connection. A test rebinds onto a second project and fails without the reset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Carries @danusha2345's #1916, rebased onto current
mainwith their commits and authorship intact, plus one maintainer fix on top. Supersedes #1916.Reproduced first
getDominantFile()finds the file with the most in-file edges; context ranking then boosts results in that file's directory. The answer doesn't depend on the query, yet onmainevery genericcodegraph_explorerecomputes it with a whole-graph join and group-by.ToolHandler, the MCP-server shape, with time inside the aggregation measured separately. Machine under load,mainand this branch interleaved:mainThe reporter's A/B also reproduces: with
getDominantFile()forced tonullonmain, the same generic query takes ~92 ms. After its first call, this PR lands at that bypass number.The contributor's fix
The answer is memoized per
QueryBuilderand tagged with an O(1) change stamp:total_changes()for this connection's writes,PRAGMA data_versionfor any other connection's commits. It isn't kept inside a transaction, becausetotal_changes()counts rows aROLLBACKundoes. TheisTransactiongetter it relies on exists in Node 22.20 and in the bundled Node v24.16.0.Maintainer fix: drop the memo on
rebind()QueryBuilder.rebind()swaps a query-pool worker onto a new connection, for example after the index is rebuilt. The stamp is per connection, and fresh read-only connections to two different databases both report0:2. So a rebound worker kept serving the old database's dominant file.rebind()now clears the memo. A new test rebinds onto a second project and fails without the reset.Scope, honestly
This helps every long-lived process: the MCP server and daemon, where agents call
codegraph_explore, andcodegraph ui. A one-shot process still computes it once. That covers a CLIcodegraph explore, which is what the issue's timings measured, and the prompt hook, which opens the index in a fresh process for a structural prompt. Removing that one-per-process cost needs the per-file counts kept up to date by the index itself, a schema and write-path change left for a follow-up.Verification
dominant-file-cache.test.ts: 6 tests covering reuse across explores, a sync on this connection, a sync on another connection, a rolled-back transaction, a rebuilt-and-reopened database, and rebinding onto another database.Fixes #1864
Co-authored-by: danusha2345 danusha2345@users.noreply.github.com
🤖 Generated with Claude Code