refactor(mcp): extract coverage and target-coverage sizing into pkg/recfilter (T4+T5) - #1887
Conversation
ApplyCoverage and ApplyTargetCoverage (with its RI and SP branches) lived in package main, so the MCP search tool could not size recommendations the way the CLI does. A model reimplementing --coverage by multiplying costs by 0.8 gets the money wrong by up to ~50%: the RI path scales cost-bearing fields by the DISCRETE ratio newCount/Count, not the requested ratio. Extracting the real implementation is the only way both surfaces agree. Both functions move into pkg/recfilter/sizing.go and take an injected Logf, since cmd's AppLogger writes to os.Stdout and the MCP server owns stdout as its protocol transport. cmd keeps thin wrappers under the existing names passing AppLogger.Printf, so its sizing tests run untouched. The exported ApplyCoverage/applyCoverage pair collapses into one recfilter function taking drops; the split only existed to give the exported form a shorter signature. Refs #1883
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
|
For the record: GitHub marked this PR "merged" automatically when its base branch The work is consolidated into #1885 (base |
Closes #1886
Stacked on #1885 (base branch is
feat/mcp-recfilter-audit, notmain). Review #1885 first; the diff shown here is only this slice.Second slice of the MCP CLI-parity extraction (
docs/plans/mcp/01-parity.mdT4 + T5). Pure extraction: no behaviour change.Why this one matters more than a normal refactor
Both moved functions are on the money-sizing path, and both are easy to reimplement plausibly but wrongly:
ApplyCoveragescales an RI's cost-bearing fields by the discrete rationewCount/rec.Count, not by the requestedcoverage/100.Count=3atcoverage=50givesCount=1— 1/3 of the instances. Scaling the money by 0.5 instead would overstate the sized purchase by ~50%.pkg/recfilter/sizing_test.gopins this as an explicit regression test.ApplyTargetCoveragedoes existing-aware per-pool floor arithmetic againstExistingCoveragePct, with clamped projection metrics and several no-signal passthroughs (avg<=0,RecommendedUtilization<=0,HourlyCommitment<=0, wrong-type or typed-nilDetails).Rather than let
mcp/toolsgrow a second implementation, both move intopkg/recfilter/sizing.go.What changed
pkg/recfilter/sizing.go:ApplyCoverage(recs, coverage, logf, drops)andApplyTargetCoverage(recs, targetPct, logf, drops), plus the unexportedapplyTargetCoverageOne/RI/SPbranches. Bodies and doc comments moved verbatim; everyAppLogger.Printfbecamelogf.printfwith the identical format string.cmd/helpers.go:ApplyCoverage,applyCoverageandApplyTargetCoveragebecome thin wrappers passingAppLogger.Printf. Signatures unchanged, soapplySizingand the existingcmdsizing tests are untouched. The unexported branch functions are deleted (nocmdtest called them directly — checked).ApplyCoverage/applyCoveragepair collapses to onerecfilterfunction takingdrops. The split only existed to give the exported form a shorter signature.stdout is the MCP transport
cmd/helpers.godeclaresvar AppLogger = log.New(os.Stdout, ...). Nothing inpkg/recfiltermay reference a package-level logger, so every logging call site takes the injectedLogfintroduced in #1885.nilis silent, and that is covered by tests on both functions.Tests
pkg/recfilter/sizing_test.go, 28 cases:Count=3,coverage=50->Count=1with money at exactly 1/3)coverage >= 100no-op,coverage <= 0emptyHourlyCommitment, notCountDetailspass through unscaled with exactly one warning (an interface holding a typed nil satisfies the type assertion, which is why the code checksok && details != nil)DropTargetSizedToZero;drops == nildoes not panicgap <= 0->DropTargetAlreadyMet; floor-to-zero ->DropTargetSizedToZero; no-signal passthroughCount == 0with positive avg produces no NaN/Inf in any cost fieldProjectedUtilizationat zero (setting projections on a rec whose money could not be scaled would produce a misleading row)CommitmentTypewarns exactly once per typetargetPctoutside(0,100]returns recs unchanged with one warninglogfsafe on every pathgo build ./...,go test ./cmd/... ./pkg/...,go vet ./...,gocyclo -over 10all clean.One deviation from the plan
The plan lists "
Projected*clamp 100" as a T5 test.ProjectedUtilizationoverflows easily and is tested.ProjectedCoveragecannot exceed 100 under this formula:nTarget = floor(avg * gap / 100)impliesnTarget/avg*100 <= gap, soprojCov <= existing + gap = targetPct <= 100. Its clamp only guards float epsilon. The test asserts thetarget=100boundary lands exactly at 100 rather than fabricating an unreachable overflow case.Follow-ups
DuplicateChecker(T6), then the MCP JSONL audit log (T2).