feat: add benchmark aggregator and reporting (task 0030) #31
Reference in New Issue
Block a user
Delete Branch "task-0030-bench-aggregator-and-reporting"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements
.claude/tasks/0030-bench-aggregator-and-reporting.md.Summary
Adds
bench/aggregate.ts, the pure aggregator seam that renders the accumulated benchmark sample store into a readable comparison:~$) secondary column. Arms with no samples render as an em dash rather than a misleading zero.{ freshInput: 1, cacheCreation: 1.25, cacheRead: 0.1, output: 5 }), so stored records can be re-weighted without re-running. The 1.25× cache-write weight is the 5-minute-TTL multiplier the single un-TTL'dcacheCreationcomponent maps to.renderReportrenders whatever exists and annotates each arm's coverage as complete or incomplete with its partial (below-floor) and missing (unsampled) cell counts, so a half-run matrix never reads as complete.readAllSamples(store)drains aSampleStoreinto the flat record listaggregateconsumes.Unit-tested with 9 tests, including an append-order-stability test driving two real
createSampleStoreinstances.Deviations
bench/slices split their command wiring into their own tasks (task 0029 was the dedicated run-loop CLI). Abench:reportcommand over this seam is flagged as a natural follow-up inbench/README.mdrather than folded in here.aggregatetakes the suite asPick<BenchTask, "id" | "tier">, not the fullBenchTask, since it only needs each task's id and tier and never scores — cleaner typing that the real suite still satisfies.Review
Overall: Low
bench/is dev-only, excluded from the published package.Unaddressed findings
Spec: none.
Standards (all judgement-call smells, left as deliberate trade-offs):
records.filter(...)+ metric-trio shape recurs across the headline/tier/component/bonus passes. Left flat because the four view interfaces genuinely diverge in their extra fields; the shared cost-equivalent-mean is already factored intomeanCostEquivalent.{ samples, costEquivalentTokens, successRate }travels together across the view types. Not extracted because each view carries different extra fields (headline: turns/duration/imputed; tier: coverage), so a shared base would obscure more than it saves.