fix(explore): shrink a later cluster into the remainder instead of dropping it (CG-36)

A file's ranked clusters were all-or-nothing past the first one: the top-ranked
cluster was taken (shrunk to fit when it had to be) and every cluster below it
was rendered whole, then either fit the remainder or was dropped entirely. On a
file whose top-ranked cluster is TRIVIAL that discards the answer — django's
`db/models/sql/query.py` kept a 22-line glue cluster and dropped the 624-line
`Query` body, spending 1,923 of a 7,947 reservation; okhttp's
`RealInterceptorChain.kt` did the same behind its import header.

The response stayed full, which is why this was invisible: the unspent
reservation carried forward exactly as designed and a file scoring a fifth as
much took the bytes.

Two sites, the same rule — hold the remainder while it is still worth a section
(CG-26's between-FILES lesson, applied between CLUSTERS):

- selection now shrinks a later cluster into what is left of the file's budget,
  by the same whole-member rule the first cluster already used;
- the ceiling trim re-renders the weakest cluster into the room that remains
  before dropping it. On excalidraw's `typeChecks.ts` the section-cost estimate
  missed by 13 chars and a 1,512-char cluster — the file's highest-SCORING one —
  was thrown away to pay for it.

Cluster RANKING is untouched: measured, both real cases lost on `maxImportance`,
not on the density tiebreak the issue suspected, and density-first is what keeps
Alamofire's `Session.swift` from burying its methods under the property list.

Suite (6 repos, clean-rebuilt indexes): all 8 starvation flags cleared,
+1,012 source chars net. django's `sql/query.py` 1,923 -> 10,082 of 7,947,
okhttp's `RealInterceptorChain.kt` 1,474 -> 6,038 of 6,058, gin's
`routergroup.go` 3,273 -> 5,632. okhttp trades its rank-6 file (score 21) for
+7,196 chars in the two files that answer the question.

Ships two fixtures pulling in opposite directions (`starved-cluster-ts` and
`dense-header-ts`), a `spendShareAtLeast` gate in probe-allocation, and
probe-file-spend.mjs — a standing per-file reservation-vs-delivered sweep.
This commit is contained in:
Colby McHenry
2026-08-06 15:10:46 -05:00
parent 76ab1fe130
commit eed16447c3
21 changed files with 1457 additions and 17 deletions
+17
View File
@@ -199,6 +199,23 @@ function evaluate(fixture, report, text) {
: 'not among the ranked candidates',
);
}
// Reservation-vs-delivered, per file (CG-36). The share gates above ask which
// files won the envelope; this asks whether a file that WON its share then
// actually spent it. A file can rank #1, be reserved the largest slice, and
// still deliver a quarter of it because the cluster carrying the answer was
// dropped whole instead of shrunk — and the share gates read that as a pass,
// since the unspent bytes carry forward and the envelope stays full.
for (const [path, floor] of Object.entries(want.spendShareAtLeast ?? {})) {
const rec = report.files.find((f) => f.path === path);
const spent = rec && rec.allowance ? rec.finalChars / rec.allowance : 0;
add(
`${path} spends >= ${pct(floor)} of its reservation`,
!!rec && rec.allowance > 0 && spent >= floor,
rec
? `${num(rec.finalChars)} delivered of a ${num(rec.allowance ?? 0)} reservation (${pct(spent)})`
: 'not among the ranked candidates',
);
}
for (const needle of want.mustContain ?? []) {
add(`response contains "${needle}"`, text.includes(needle), text.includes(needle) ? 'present' : 'absent');
}