test(explore): lock down proportional byte allocation (CG-14, #1500)
Coverage for the CG-12 allocator, built around "would this go red if the lever were removed" rather than line coverage — every way this regresses is silent, ending in an agent falling back to Read. Unit (`explore-proportional-allocation.test.ts`, 18 -> 38): calibration pins, envelope safety across every tier and 30 candidate shapes, the cliff boundary, spine weighting/trim survival, the diffuse control, and the degenerate inputs — identical scores, a lone file, a runaway top scorer, zero results, maxFiles 0, a non-finite score. End-to-end (`explore-allocation-e2e.test.ts`, new): CG-6's second regression fixture as a deterministic synthetic mirror — a large relevant file, a small helper that used to win by shipping whole, and an incidental `explore`/`BUDGET` collision — asserting per-file budget share, not file presence. Plus degenerate result sets and a survey-style diffuse control through the real render loop. The live self-query arm stays in probe-allocation.mjs, where drift is a number to re-baseline rather than a red suite. Reverting the render loop to the pre-CG-12 rules reproduces #1500 on the mirror exactly and takes 5 e2e + 2 payroll gates red: file score pre-CG-12 CG-12 src/mcp/allocator.ts 77.5 4,843 (39.7%) 9,335 (80.1%) src/util/budget-math.ts 36.0 6,079 (49.8%) 1,037 ( 8.9%) Two defects the invariants surfaced, both fixed in tools.ts: - rounded shares could sum past `pool`, so "reservations fit the envelope" was approximate rather than exact; both terms now floor - a non-finite score made every share Infinity/Infinity, handing the render loop a NaN allowance; `weightOf` now fails safe to 0 Also adds a hard-ceiling gate to the payroll fixture — at 19.3K against a 19.5K ceiling it is the only fixture that stresses the ~25K inline cap — and exports EXPLORE_ALLOCATION so invariant tests read the constants while one test pins the literals. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5f7f5f59df
commit
1d9206d2d0
@@ -36,7 +36,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import CodeGraph from '../src/index';
|
||||
import { ToolHandler } from '../src/mcp/tools';
|
||||
import { ToolHandler, getExploreOutputBudget } from '../src/mcp/tools';
|
||||
import { attributeSourceBytes } from '../src/mcp/explore-diagnostics';
|
||||
import { isGeneratedFile, hasGeneratedHeader } from '../src/extraction/generated-detection';
|
||||
|
||||
@@ -267,6 +267,21 @@ describe('#1500 — generated Go CRUD beside a hand-written payroll workflow', (
|
||||
expect(response).toMatch(/internal\/gen\/fkit\/payroll\/payslip\.go: \w+:\d+/);
|
||||
});
|
||||
|
||||
it('CG-14 GATE: holds the response inside the hard ceiling under real pressure', () => {
|
||||
// This fixture is the stress case for the ceiling, not just for the split:
|
||||
// 19 files put it in the very-tiny tier (13,000-char envelope) while the
|
||||
// answer genuinely needs more, so the render loop spends its full allowed
|
||||
// overshoot — ~19.3K against a 19.5K ceiling. That leaves ~1% of headroom,
|
||||
// which is exactly why this is worth pinning: the bound that matters is the
|
||||
// host's ~25K inline cap, and above it the response is written to a file
|
||||
// the agent Reads back, undoing the point of the tool.
|
||||
const budget = getExploreOutputBudget(cg.getFiles().length);
|
||||
const hardCeiling = Math.min(Math.round(budget.maxOutputChars * 1.5), 25000);
|
||||
expect(response.length).toBeGreaterThan(budget.maxOutputChars);
|
||||
expect(response.length).toBeLessThanOrEqual(hardCeiling);
|
||||
expect(response.length).toBeLessThan(25000);
|
||||
});
|
||||
|
||||
it('records the shape of the allocation so a regression is legible', () => {
|
||||
// Not a gate — a snapshot of the split, so a future change that shifts the
|
||||
// numbers shows up in the diff rather than silently flipping a gate.
|
||||
|
||||
Reference in New Issue
Block a user