fix(mcp): subtract surface holes from measured area - #563
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit fe7bc5d. Configure here.
|
Good catch — the schema defaults |
`measure` computed gross shoelace area for slabs and ceilings, ignoring `holes` — so a floor with a stairwell opening reported the area of the uncut polygon. Every other net-area path in the repo already subtracts them (`validate-build-json`, `polygonSurfaceArea` behind the quick-measure HUD), so MCP disagreed with what the editor showed for the same surface. Zones have no `holes` field, so their behavior is unchanged. The regression test for an absent `holes` field goes through `loadJSON`, which casts its nodes into the store without a schema parse — that is the real path to a document where the field never existed, rather than mutating the live store dict `getNodes()` returns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1e94c41 to
6030a1e
Compare
|
This is a good PR. The bug is real, the fix is four lines, and it matches the convention the rest of the codebase already uses — Also worth saying: your PR description is accurate, which is rarer than it should be. Every line reference checked out, the "ZoneNode has no holes field" claim is correct, and you volunteered the One change before merging, pushed to your branch. The second test fabricated its condition with The state it was testing is real, though — I checked. That also settles the Merging once CI confirms. Thanks for this. |
Glad this landed, and I appreciated the thorough review — the loadJSON test rewrite was a genuinely better construction. |

What does this PR do?
measurewithfromId === toIdon a slab or ceiling reports the gross polygon area, ignoringthe node's
holes. A 4 m × 4 m slab with a 2 m × 2 m opening measures as 16 m², not 12 m².Repro (at
42ac4be): create a slab withpolygon4×4 and one 2×2 hole, then callmeasure { fromId: <slabId>, toId: <slabId> }→areaSqMeters: 16.Cause: the self-measurement branch calls
shoelaceArea(n.polygon)and never readsn.holes(
packages/mcp/src/tools/measure.ts:108-116). Both schemas carry holes —SlabNode.holes("cutouts in the slab",
packages/core/src/schema/nodes/slab.ts:22-33) andCeilingNode.holes(
packages/core/src/schema/nodes/ceiling.ts:19).Elsewhere the codebase treats surface area as hole-subtracted:
wiki/architecture/measurements.md: "Slab reports expose hole-subtracted surface";clamped at zero (
packages/core/src/validation/validate-build-json.ts:257-268);deriveZoneQuantityReportsubtracts holes from proven floor surface(
packages/core/src/lib/zone-quantities.ts:441-460).The MCP tool is the outlier — and it's the surface agents use for takeoff-style checks, so a
penetration or void silently inflates the reported area.
Fix (6 lines): in the self-measurement branch, subtract the summed shoelace area of the node's
declared holes from the outer polygon, clamped at zero — mirroring the build-validation convention
at
validate-build-json.ts:257-268. Zones are unchanged (ZoneNodehas noholesfield), and thedistance path (
fromId !== toId) is untouched. The tool description now says "net area" so thecontract states what the number is. One nuance flagged rather than hidden:
deriveZoneQuantityReportadditionally classifies non-contained holes as unavailable before subtracting;
measurehere mirrorsthe simpler build-validation form (subtract all declared holes, clamp at zero). If you'd rather
measureapply the containment filter too, happy to adjust.How to test
bun test --cwd packages/mcp— new case inpackages/mcp/src/tools/measure.test.ts:a solid 4×4 slab still measures 16; the same polygon with a 2×2 hole measures 12.
measureit self-to-self → 12.bun test --cwd packages/mcp(298 pass / 0 fail) and theworkflow's Biome check (141 files, no issues).
Screenshots / screen recording
N/A — non-visual change (headless MCP tool output).
Checklist
bun test --cwd packages/mcp+ the workflow'sBiome command; plus a live headless MCP repro —
bun devUI not exercised, this change has noUI surface)
bun checkon the touched package is clean)documents the convention this aligns to)
mainbranchNote
Low Risk
Small behavioral fix in the MCP measure tool with tests; no auth, persistence, or UI surface.
Overview
The MCP
measuretool now reports net polygon area for slabs and ceilings whenfromId === toId, subtracting each declared hole’s shoelace area from the outer polygon and clamping at zero. Zones are unchanged (noholesfield). The tool description now says “net area” instead of gross polygon area.Tests cover a holed vs solid 4×4 slab (16 vs 12 m²) and legacy JSON loaded without a
holesproperty (still uses the outer polygon only).Reviewed by Cursor Bugbot for commit 6030a1e. Bugbot is set up for automated code reviews on this repo. Configure here.