feat(mcp): score-proportional byte allocation for explore, with a relative cliff (CG-12, #1500)
The explore envelope used to follow FILE SIZE, not relevance. Every admitted
file was capped at the same flat `maxCharsPerFile`, while the whole-file rule
handed anything under `maxCharsPerFile * 3` its entire contents — a 3x swing
decided by how big a file happened to be:
- self-query: `memory-budget.ts` (score 18) shipped whole and took 51.2% of
the response; `src/mcp/tools.ts` (score 41, 4x the graph mass, 3x the term
hits — it holds the allocator itself) was clipped at 3,800 and got 32.9%.
- #1500 Go fixture: two generated CRUD files shipped whole at ~4.5K each AND
consumed two of the tier's four file slots, so `BuildPayslip` — the
hand-written "calculate" half of the question — ranked #6 and never
rendered at all.
`allocateExploreBudget` now reserves each ranked file a share of the envelope
before anything renders, so the render loop spends a reservation instead of
racing for whatever the files above it left:
- weight = score x worth x (spine ? 2 : 1), where `worth` is `rankPenalty`
applied a SECOND time — ranking answers "is this file about the query",
allocation answers "will these bytes teach the agent anything", and
generated CRUD can legitimately rank while its bytes stay boilerplate;
- a relative cliff at 15% of the top weight (capped at SCORE_FLOOR_MAX, so a
god-file can't silence peers the score floor just admitted) gives a file
ZERO source — path, symbols and line numbers only — and crucially frees its
`maxFiles` slot for a file that earns its bytes;
- every admitted file gets MIN_CHARS, then the remainder splits by weight:
the floor keeps a diffuse survey question returning a spread, the remainder
concentrates a precise one;
- the flat per-file cap is retired as the primary guard, leaving a 70%-of-
envelope safety valve.
Two changes were needed to make the reservation bite: an oversize cluster now
shrinks by whole MEMBER symbol ranges (a single-cluster god-file previously
took ~40% more than allotted, and the file below it was dropped for lack of
room), and the arrival-order budget stops are gone — they cut files by the
order they were reached rather than by merit.
Measured: payroll-go answer group 25.6% -> 78.7%, generated 57.4% -> 0%, and
`func (s *Service) BuildPayslip` now delivered; self-query `tools.ts` 18.5% ->
60.6%, past the epic's >50% bar. Controls hold: cobra/gin diffuse survey
queries keep their file spread (3->3, 3->4), express's middleware query is
byte-identical, and gin's flow query moves its top file from the thin `ginS`
singleton wrapper to `routergroup.go`.
One documented exception to "no previously-unclipped file becomes clipped":
`memory-budget.ts` was unclipped-whole at 5,672 and now clusters within its
3.1K reservation. That is the epic's own diagnosis of the bug — it scored 18
against 58 and was taking the larger slice purely for being small.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a3898cdc70
commit
5f7f5f59df
@@ -17,6 +17,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
### Fixes
|
||||
|
||||
- `codegraph_explore` now gives the most relevant file the most room, instead of splitting its answer roughly evenly and letting whichever file happened to be small enough take the largest share. Every file's slice is now sized in proportion to how well it matches your question, decided before any code is written out — so the file that actually answers you is no longer trimmed at a fixed per-file limit while a small, weakly-related file is included in full. Files far below the best match are listed by name, symbol and line number instead of taking space with their source, and you can pull any of them up in full with one more `codegraph_explore`. On a Go service with a generated CRUD layer beside a hand-written payroll workflow, the hand-written code went from a quarter of the response to nearly four-fifths of it, and the calculation the question was actually about — previously missing entirely — is now included. (#1500)
|
||||
|
||||
- `codegraph_explore` no longer spends its answer on files that merely share a word with your question. A file whose only connection to the query was an unused local variable or constant of the same name — an eval script with a `const explore` in it, say — used to count as strongly as the file that actually implements the thing, and being small enough to include whole, it could take most of the response while the real answer got trimmed. Matches are now weighted by what was matched (a function, class or route counts for far more than a local variable, and a variable nothing else references counts for almost nothing), and a file has to score within reach of the best match rather than clear a fixed low bar. Generated files are down-weighted throughout the ranking instead of only breaking ties, so a generated CRUD layer no longer outranks the hand-written workflow beside it. Test and spec files in a top-level `test/` or `spec/` directory are now recognized and kept out, which they weren't before. (#1500)
|
||||
|
||||
- A CodeGraph process that gets force-killed — by the stuck-process watchdog, a crash, or the OS — no longer leaves the database's write-ahead log behind to grow without bound. Previously each killed session stacked more data onto the same log file and nothing ever shrank it, which on machines where sessions were killed regularly could quietly eat tens of gigabytes of disk. The log is now capped, and any oversized leftover is reclaimed automatically the next time the project is opened. Thanks @tiendungdev for the exceptional Windows report that pinned this down. (#1431)
|
||||
|
||||
Reference in New Issue
Block a user