fix(vscode): correct Cobertura merge arithmetic, MSTest assembly discovery, and closure-filter documentation (#733) - #748
Merged
drmoisan merged 6 commits intoSep 3, 2026
Conversation
- Add issue documentation with consolidated findings - Add feature specification and requirements - Add implementation research findings - Add atomic plan (preflight-cleared) Refs: #733 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LTjXvNFHVh7Fo7kYGgWsx2
…a-mstest-powershell-tooling-defects-733
…a-mstest-powershell-tooling-defects-733
…ilter docs Addresses the seven consolidated findings tracked under issue 733, all confined to the scripts/vscode PowerShell MSTest/Cobertura tooling. Production changes: - Merge-CoberturaClassesByFilename now recomputes package-level line-rate and branch-rate after a class merge, via a new pure per-package helper Get-CoberturaPackageLineSummary, and unions <methods> entries across the merged group instead of cloning only the primary class's subtree. - Invoke-MSTestWithCoverageMain excludes assemblies discovered beneath a .claude path segment, so sibling agent worktrees no longer pollute the discovered test-assembly set. - Invoke-MSTest.ps1 extracts its discovery pipeline into Get-MSTestAssemblyPathList. The unary comma on the return is load-bearing: a function return enumerates its output, so a plain @(...) is unwrapped at the boundary and a single-match run yields a bare string whose .Count throws under Set-StrictMode -Version Latest. - Invoke-MSTest.ps1 additionally extracts its host-bound top-level body into Invoke-MSTestMain, mirroring Invoke-MSTestWithCoverageMain, so the guards and error paths become reachable from Pester. Behavior is unchanged. - ClosureFilter.ps1 receives documentation only: the local-function exclusion is ratified as intentional, and the bare-name overload collision is documented as failing in the safe under-exclusion direction. Coverage: scripts/vscode/Invoke-MSTest.ps1 rises from 68.89 to 94.00 percent command coverage. No production file was excluded from measurement and no threshold was changed. All six production files are at or above 85 percent. Tests: 92 passing, 0 failed, 0 skipped across tests/scripts/vscode, up from a 70-passing baseline. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0195CL93aqgKF19nj9h6trbt
Records the review pass and closes the last acceptance criterion. - policy-audit, code-review, and feature-audit artifacts: zero blocking findings, zero FAIL, zero blocking-PARTIAL. - AC4 scope-boundary evidence recording the anchored diff against origin/main alongside the plan's literal porcelain check, with the reasoning for why the anchored form is the stronger check once a commit exists. - spec.md AC4 and plan task P5-T9 checked off by the review pass, bringing acceptance criteria to 8 of 8. The review reproduced the test, analyzer, coverage, and line-count figures independently rather than accepting them, and recounted the AC4 prefix distribution. That recount corrected this orchestrator's figures from 51/6/6 to the actual 49/6/8; the correction is recorded in the evidence artifact rather than silently applied. The verdict was unaffected, since it turns on the count of paths outside the allowed prefixes, which is zero. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0195CL93aqgKF19nj9h6trbt
drmoisan
added a commit
that referenced
this pull request
Sep 3, 2026
Records policy reads, feature-document reads (with #733/#748 drift notes), branch/commit baseline (BASELINE_SHA=dc5e8c0f...), citation verification (drifted-anchor branch taken for both checks), and PoshQC format/analyze/test baselines for the ordering fix in Invoke-MSTestWithCoverage.ps1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
drmoisan
added a commit
that referenced
this pull request
Sep 3, 2026
…efect Adds the new It block to Describe 'Invoke-MSTestWithCoverageMain', inserted before the #733 '.claude worktree' test per the plan's primary-anchor resolution (the secondary anchor is now ambiguous after #733/#748 added an intervening test). Confirms RED against the pre-fix order: only the new test fails (Set-Content invoked 0 times, not 1), all 27 other tests in the file pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Suggested title
fix(vscode): correct Cobertura merge arithmetic, MSTest assembly discovery, and closure-filter documentation (#733)
Summary
<package>-levelline-rateandbranch-rateafter a class merge, so merged Cobertura documents no longer report the stale input rate at package scope.<methods>entries across every member of a merged filename group instead of cloning only the primary class's subtree, so method entries contributed by closure classes survive the merge..claudepath segment from MSTest assembly discovery, so sibling agent worktrees no longer pollute the discovered test-assembly set.Invoke-MSTest.ps1discovery pipeline intoGet-MSTestAssemblyPathList, returning an array at every cardinality so a single-match run no longer throws underSet-StrictMode -Version Latest.Invoke-MSTest.ps1intoInvoke-MSTestMain, raising that file's command coverage from 68.89 percent to 94.00 percent without excluding it from measurement.Why
Issue #733 consolidates seven independently reported findings against the
scripts/vscodePowerShell MSTest and Cobertura tooling (source issues 529, 530, 531, 537, 559, 560, 713). All seven are small fixes confined to one subsystem, which is why they were merged into a single issue rather than pursued separately.Two of the findings are arithmetic defects in
Merge-CoberturaClassesByFilename: after merging duplicate class entries that share a filename, the enclosing<package>node kept the rate values from its input rather than the values implied by its merged contents, and the merge cloned only the primary class's<methods>subtree, silently discarding method entries contributed by the other members of the group.One finding is an environment defect: assembly discovery walked into
.claude/worktrees/, so a developer or agent with sibling worktrees checked out would discover another worktree's built test assemblies.One finding is a strict-mode defect: the discovery pipeline in
Invoke-MSTest.ps1was not array-wrapped, so a run matching exactly one assembly produced a bare string, and the subsequent.Countaccess threw.The remaining findings are documentation-only. A signature-based re-key of the closure presence set was evaluated and rejected as infeasible during research, because the closure-naming convention does not carry enough information to recover a parameter signature; the existing bare-name key is therefore documented along with the direction in which it fails.
What Changed
Cobertura merge arithmetic
scripts/vscode/Invoke-MSTestWithCoverage.PackageRate.ps1(new):Get-CoberturaPackageLineSummary, a pure per-package accumulator returning the same shape and using the same rounding and zero-denominator fallback as the existing document-level summarizer.scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1: the document-level summarizer now calls the new helper per package rather than accumulating inline;Merge-CoberturaClassesByFilenamegained the<methods>union and the package-rate recomputation.scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1(new):Assert-CoberturaLineCoverageThresholdrelocated verbatim out of the helpers file, which had reached the 500-line ceiling. Behavior is unchanged; the function is dot-sourced back so every existing caller and mock still resolves.MSTest discovery
scripts/vscode/Invoke-MSTestWithCoverage.ps1: a fourth clause in the discoveryWhere-Objectpredicate excluding paths containing a.claudesegment.scripts/vscode/Invoke-MSTest.ps1: newGet-MSTestAssemblyPathListandGet-VsTestConsolePathseams, and the former top-level body moved intoInvoke-MSTestMain. The top level is now a single guarded invocation.Documentation
scripts/vscode/Invoke-MSTestWithCoverage.ClosureFilter.ps1: two comment-based-help addenda. No behavior change.Tests
Five new Pester files under
tests/scripts/vscode/:Invoke-MSTestWithCoverage.PackageRate.Tests.ps1,.Merge.Tests.ps1,.Threshold.Tests.ps1,Invoke-MSTest.AssemblyDiscovery.Tests.ps1, andInvoke-MSTest.Main.Tests.ps1. Three existing test files were extended.Architecture / How It Fits Together
Invoke-MSTestWithCoverage.Helpers.ps1remains the entry point that callers dot-source; it now dot-sources three siblings rather than one (ClosureFilter,PackageRate,Threshold). That keeps the public surface unchanged while holding every file under the 500-line ceiling.The Cobertura pipeline order is unchanged and remains load-bearing: the exempt-closure filter runs before
Merge-CoberturaClassesByFilename, because after the merge the surviving node is named for the declaring type and no longer carries the closure marker the filter matches on. An existing regression test pins that ordering.Invoke-MSTest.ps1now follows the same shape its siblingInvoke-MSTestWithCoverage.ps1already used: aMainfunction holding the workflow, thin wrapper seams around each external executable, and a top-level guard that runsMainonly when the script is invoked rather than dot-sourced. That guard is what allows the test file to dot-source the script and exercise the guards without launchingvswhere.exeorvstest.console.exe.Verification
Completed
tests/scripts/vscode, against a 70-passing baseline. Each of the ten test files also passes standalone and in reverse-alphabetical order, so there is no inter-file order dependence.Invoke-MSTestWithCoverage.Helpers.ps190.84,Invoke-MSTestWithCoverage.ps190.09,Invoke-MSTestWithCoverage.ClosureFilter.ps1100,Invoke-MSTest.ps194.00 (from 68.89 at baseline),Invoke-MSTestWithCoverage.PackageRate.ps1100,Invoke-MSTestWithCoverage.Threshold.ps188.24. Aggregate 93.10 percent over 565 commands. No production file was excluded from measurement and no threshold was changed.evidence/regression-testing/directory.Recommended
mcp__drm-copilot__run_poshqc_format, thenrun_poshqc_analyze, thenrun_poshqc_test, scoped toscripts/vscodeandtests/scripts/vscode.tests/scripts/vscodewith code coverage enabled over the six production files, to reproduce the per-file figures above.Backward Compatibility / Migration Notes
No breaking change to any public surface.
Invoke-MSTestWithCoverage.Helpers.ps1exposes the same functions to its callers;Assert-CoberturaLineCoverageThresholdmoved file but is dot-sourced back, so its production caller and its existing mock both still resolve.One behavior change is intentional and worth calling out for reviewers:
Merge-CoberturaClassesByFilenamepreviously retained only the primary class's<methods>subtree, and an existing test asserted exactly that. That test's assertion was deliberately reversed to assert the union, and its comment updated to say so. This is the fix for one of the seven findings, not an unintended regression.Invoke-MSTest.ps1runs its workflow only when invoked, not when dot-sourced. Any caller that relied on dot-sourcing the script to execute it must invoke it instead.Risks and Mitigations
<methods>union could duplicate a method entry if two members of a merged group legitimately contributed the same method name. Mitigation: research established that distinct group members do not share an identical method name under the closure-naming convention; a three-way merge fixture asserts all three distinct names appear with no duplication. Deduplication was deliberately not added, since a dedup key would mask the case rather than surface it.Get-MSTestAssemblyPathListis easy to remove during a later cleanup, which would silently reintroduce the strict-mode defect. Mitigation: two assertions read the returned value's array shape without re-wrapping it, and were demonstrated to fail with the comma removed. The reason is also recorded in the function's comment-based help.Invoke-MSTestMainextraction is the largest structural change and touches the script's execution path. Mitigation: every guard, message, throw text, and ordering was moved verbatim, and the extracted path is now covered by tests that assert each failure mode.Review Guide
Suggested order:
scripts/vscode/Invoke-MSTestWithCoverage.PackageRate.ps1— small, self-contained, and the basis for the package-rate fix.Merge-CoberturaClassesByFilenameinscripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1— the union loop and the rate recomputation.scripts/vscode/Invoke-MSTest.ps1— the two seams and theMainextraction. The unary comma on theGet-MSTestAssemblyPathListreturn is load-bearing; the comment-based help explains why.Mechanical, low-signal moves:
Assert-CoberturaLineCoverageThresholdmoving toInvoke-MSTestWithCoverage.Threshold.ps1, and twoDescribeblocks moving out ofInvoke-MSTestWithCoverage.Helpers.Tests.ps1into sibling files. Both were forced by the 500-line ceiling and neither changes behavior.The bulk of the diff by line count is evidence artifacts under the feature folder, not code.
Follow-ups
Invoke-MSTest.ps1: thevswherepipeline inside its seam, and the forwarding call. Covering them would require launching the real executable.GitHub Auto-close