diff --git a/artifacts/pr_body_565.md b/artifacts/pr_body_565.md new file mode 100644 index 000000000..b688325da --- /dev/null +++ b/artifacts/pr_body_565.md @@ -0,0 +1,59 @@ +## Suggested title +fix(vscode): persist the post-processed Cobertura document before the coverage-threshold assertion (#565) + +## Summary +- `scripts/vscode/Invoke-MSTestWithCoverage.ps1` previously called `Assert-CoberturaLineCoverageThreshold` before `Set-Content` persisted the post-processed Cobertura XML, so a failing (sub-threshold) run threw before the judged document was ever written to disk, leaving the raw, un-post-processed `dotnet-coverage` output at the `-CoverageOutput` path instead. +- Fix is a pure two-statement reorder inside `Invoke-MSTestWithCoverageMain`: `Set-Content` now runs immediately after `$processedXmlContent` is computed and before `Assert-CoberturaLineCoverageThreshold` is called. No logic, threshold value, or message text changed. +- Adds one new Pester regression test to `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` proving `Set-Content` is invoked exactly once before the threshold assertion can throw on a sub-threshold run. +- This branch was cut before sibling issue #733 (already merged to `main`) refactored this same file, splitting `Assert-CoberturaLineCoverageThreshold` out into a new file, `scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1`. The branch was reconciled against the current `main`, the defect was re-confirmed still present at the drifted line numbers, and the fix was executed and verified against the reconciled tree. +- Threshold value (80%) and message text are unchanged and independently confirmed unmodified in both `Invoke-MSTestWithCoverage.Helpers.ps1` and the relocated `Invoke-MSTestWithCoverage.Threshold.ps1` (empty diffs against `main` for both files). +- 6/6 acceptance criteria in `spec.md` verified and checked off; zero blocking findings in policy-audit, code-review, and feature-audit. + +## Why +Only the failure path was affected — a passing run always wrote the correct document. But the failure path is exactly when someone reads the artifact on disk to diagnose a coverage shortfall, and what they found was a document with different numbers than the one that produced the failure message (absolute paths, unmerged third-party packages, duplicate classes, and the double-counted line totals that issue #441 had already corrected). A failed gate also left behind an artifact that, if fed to any downstream consumer, silently reported the pre-#441 inflated denominator. Issue #563 (a separate, unrelated contradiction in the threshold *value*) is explicitly out of scope for this fix, which touches statement order only. + +## What Changed +**Core logic:** +- `scripts/vscode/Invoke-MSTestWithCoverage.ps1` — swapped the `Set-Content` and `Assert-CoberturaLineCoverageThreshold` calls inside `Invoke-MSTestWithCoverageMain` (net +2/-2 lines). + +**Tests:** +- `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` — added one new `It` to the existing `Describe 'Invoke-MSTestWithCoverageMain'` block (net +8 lines), inserted between the pre-existing `'fails when the search root cannot be found'` test and the sibling-issue-#733-added `'.claude worktree'` exclusion test. + +**Docs/evidence:** +- `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/` — issue, spec, research, atomic plan, full Phase 0-6 evidence trail, and policy-audit/code-review/feature-audit artifacts. + +## Architecture / How It Fits Together +`Invoke-MSTestWithCoverageMain` collects coverage via `dotnet-coverage`, post-processes the raw Cobertura XML for Koverage compatibility (`ConvertTo-KoverageCoberturaXml`), and previously validated the resulting line-rate before writing it to disk. The fix only reorders when the write happens relative to the validation; `Assert-CoberturaLineCoverageThreshold` remains a pure read-and-throw function with no side effect that could observe or be observed by `Set-Content`, so the reorder is behaviorally safe under both the passing and failing case. + +## Verification +**Completed (from evidence trail):** +- RED before / GREEN after: pre-fix simulation showed the new test failing (`Set-Content` invoked 0 times before the throw); post-fix run showed 28/28 tests passed in the target file. +- Full PowerShell suite (10 files, 93 tests): 93 passed, 0 failed, 0 skipped. +- PoshQC format: no rewrites needed on either owned file. +- PSScriptAnalyzer: 0 diagnostics on either owned file (matches baseline). +- Coverage on `scripts/vscode/Invoke-MSTestWithCoverage.ps1`: 90.09% before and after (no regression). +- `git diff` against `main` confirmed empty for both `Invoke-MSTestWithCoverage.Helpers.ps1` and `Invoke-MSTestWithCoverage.Threshold.ps1` (threshold value/message untouched). +- feature-review: policy-audit PASS, code-review PASS (0 blocking), feature-audit PASS (6/6 AC verified). + +**Recommended (CI):** +- PowerShell workflow(s) covering `scripts/vscode/**` and `tests/scripts/vscode/**`. + +## Backward Compatibility / Migration Notes +None. No function signature, parameter, output format, or configuration surface changed. Passing runs (coverage at or above 80%) are unaffected because both statements always executed in that case; only the failing-run artifact-on-disk behavior changes, and only for the better (it now matches the document that produced the failure). + +## Risks and Mitigations +- **Risk:** none identified beyond the reordered two statements themselves — this is a minimal, self-contained fix confined to a single production file plus one test file. +- **Mitigation / rollback:** the change is a direct two-line swap; rollback is a straightforward revert of the diff. + +## Review Guide +Suggested order: +1. `scripts/vscode/Invoke-MSTestWithCoverage.ps1` — the actual fix (4-line diff). +2. `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` — the new regression test (8-line diff). +3. `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md` — acceptance criteria and their check-off state. +4. Remaining files are evidence artifacts (baseline/QA-gate/regression-testing captures) and the three review artifacts (policy-audit, code-review, feature-audit); skim for the pass/fail signal in each rather than reading line by line. + +## Follow-ups +- Issue #563 (threshold *value* contradiction) remains open and is deliberately out of scope for this fix. + +## GitHub Auto-close +- Closes #565 diff --git a/artifacts/pr_body_565.receipt.json b/artifacts/pr_body_565.receipt.json new file mode 100644 index 000000000..58db124f3 --- /dev/null +++ b/artifacts/pr_body_565.receipt.json @@ -0,0 +1,8 @@ +{ + "skill": "pr-author", + "pr_body_path": "artifacts/pr_body_565.md", + "number": 565, + "sha256": "2903b6e36c66f23579967db50cf13cfd81af59cf305e9e0f0e3063f0a95b3c0d", + "context_summary_path": "artifacts/pr_context.summary.txt", + "created_at": "2026-09-03T11:41:29Z" +} diff --git a/artifacts/pr_context.appendix.txt b/artifacts/pr_context.appendix.txt index e59f2e65d..4475c080a 100644 --- a/artifacts/pr_context.appendix.txt +++ b/artifacts/pr_context.appendix.txt @@ -1,8 +1,8 @@ ===== Context generated ===== -2026-09-03 06:32:25 UTC -Head SHA: 558c9c42d32a9cc5b060212881e86591b39ca380 +2026-09-03 11:38:15 UTC +Head SHA: e5dcbffd6a51e9f92869c390e85d179400657cd5 ===== PR Intent (edit before generating PR body) ===== @@ -20,17 +20,17 @@ origin https://github.com/drmoisan/TaskMaster.git (push) ===== Current branch ===== -bug/ribbon-engine-toggle-defects-735 +bug/invoke-mstestwithcoverage-threshold-before-setcontent-565 ===== Upstream ===== -origin/bug/ribbon-engine-toggle-defects-735 +origin/bug/invoke-mstestwithcoverage-threshold-before-setcontent-565 ===== Status (short) ===== -## bug/ribbon-engine-toggle-defects-735...origin/bug/ribbon-engine-toggle-defects-735 +## bug/invoke-mstestwithcoverage-threshold-before-setcontent-565...origin/bug/invoke-mstestwithcoverage-threshold-before-setcontent-565 ===== Untracked files ===== @@ -52,31 +52,33 @@ origin/bug/ribbon-engine-toggle-defects-735 ===== PR Comparison ===== Base ref (requested): main -Base ref (resolved): origin/main @ b13d5b7b1a6dd0aa79d51d48a7156ee67377f9d0 -Head ref (resolved): bug/ribbon-engine-toggle-defects-735 @ 558c9c42d32a9cc5b060212881e86591b39ca380 -Merge-base: b13d5b7b1a6dd0aa79d51d48a7156ee67377f9d0 -Range: b13d5b7b1a6dd0aa79d51d48a7156ee67377f9d0..558c9c42d32a9cc5b060212881e86591b39ca380 +Base ref (resolved): origin/main @ 87233f867ad60c0a5c0d19b09cc121ae536d7ba1 +Head ref (resolved): bug/invoke-mstestwithcoverage-threshold-before-setcontent-565 @ e5dcbffd6a51e9f92869c390e85d179400657cd5 +Merge-base: 87233f867ad60c0a5c0d19b09cc121ae536d7ba1 +Range: 87233f867ad60c0a5c0d19b09cc121ae536d7ba1..e5dcbffd6a51e9f92869c390e85d179400657cd5 ===== Commits in range ===== -558c9c42 2026-09-03 Dan Moisan docs(735): record the second independent feature-review pass -a2792280 2026-09-03 Dan Moisan docs(735): record feature-review audits and correct the test-population reconciliation -30e66833 2026-09-03 Dan Moisan Merge remote-tracking branch 'origin/main' into bug/ribbon-engine-toggle-defects-735 -3e45428e 2026-09-03 Dan Moisan refactor(ribbon): extract the versioned pressed-state cache and close QA for issue 735 -a68c8598 2026-09-03 Dan Moisan fix(ribbon): make toggle-state writes compare-and-apply for issue 735 -88fc3bfc 2026-09-03 Dan Moisan fix(ribbon): guard the Clear Spam Manager reset behind a testable gate for issue 735 -a3bfb865 2026-09-03 Dan Moisan fix(ribbon): repair dead Explorer CustomUI callback bindings for issue 735 -b6e102e6 2026-09-03 Dan Moisan Merge remote-tracking branch 'origin/main' into bug/ribbon-engine-toggle-defects-735 -044551f0 2026-09-02 Dan Moisan prep(bug-735): promote, research, spec, and plan ribbon-engine-toggle-defects +e5dcbffd 2026-09-03 Dan Moisan docs(565): add policy-audit, code-review, and feature-audit for issue #565 +101d5ec8 2026-09-03 Dan Moisan Merge remote-tracking branch 'origin/main' into bug/invoke-mstestwithcoverage-threshold-before-setcontent-565 +e165f7ba 2026-09-03 Dan Moisan Phase 6 close-out: final clean-tree confirmation and last plan check-off +56f26a55 2026-09-03 Dan Moisan Phase 6: documentation wrap-up and issue-565 mirror +ae0472f3 2026-09-03 Dan Moisan Phase 5: check off all 6 spec.md acceptance criteria for #565 +d6f13e8f 2026-09-03 Dan Moisan Phase 4: final QA loop, iteration 1 (format/analyze/test), clean single pass +b981d80f 2026-09-03 Dan Moisan Phase 3: verify GREEN after fix; zero regression in threshold boundary tests +727f24c2 2026-09-03 Dan Moisan Phase 2: swap Set-Content ahead of the threshold assertion (#565) +6fc57ce2 2026-09-03 Dan Moisan Phase 1 [expect-fail]: add regression test proving pre-fix ordering defect +cbe865d7 2026-09-03 Dan Moisan Phase 0: baseline evidence for #565 statement-reorder fix +dc5e8c0f 2026-09-03 Dan Moisan Merge remote-tracking branch 'origin/main' into bug/invoke-mstestwithcoverage-threshold-before-setcontent-565 +e8bb720b 2026-09-02 Dan Moisan docs(565): de-backtick scope-exclusion paths so blast-radius harvester stops misreading them as writes +00899a72 2026-09-02 Dan Moisan docs(565): prepare feature folder and atomic plan for coverage-gate ordering fix ===== Conventional commit type summary ===== -fix : 3 -refactor : 1 -docs : 2 -other : 3 +docs : 3 +other : 10 ===== Authors ===== @@ -86,115 +88,59 @@ Dan Moisan ===== Changed files (name-status) ===== -M .claude/agent-memory/atomic-planner/MEMORY.md -A .claude/agent-memory/atomic-planner/project_735_evidence_content_sanitization_seams.md -M .claude/agent-memory/task-researcher/MEMORY.md -A .claude/agent-memory/task-researcher/project_ribbon_engine_toggle_defects_735.md -A TaskMaster.Test/Ribbon/EngineTogglePressedStateCacheTests.cs -A TaskMaster.Test/Ribbon/EngineToggleStateCoordinatorTests.Race.cs -M TaskMaster.Test/Ribbon/EngineToggleStateCoordinatorTests.cs -M TaskMaster.Test/Ribbon/RibbonExplorerXmlTests.cs -A TaskMaster.Test/Ribbon/SpamManagerResetGateTests.cs -M TaskMaster.Test/TaskMaster.Test.csproj -A TaskMaster/Ribbon/EngineTogglePressedStateCache.cs -M TaskMaster/Ribbon/EngineToggleStateCoordinator.cs -M TaskMaster/Ribbon/RibbonController.Intelligence.cs -M TaskMaster/Ribbon/RibbonExplorer.xml -A TaskMaster/Ribbon/SpamManagerResetGate.cs -M TaskMaster/TaskMaster.csproj -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/code-review.2026-09-03T06-19.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/code-review.2026-09-03T09-05.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/base-ref.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/coverage-baseline.2026-09-02T12-04.cobertura.xml -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/coverage-baseline.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/csharpier-check.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/file-line-counts.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/msbuild-analyzer.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/msbuild-nullable.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/nuget-restore.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/p0-t8/p0-t8.trx -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/phase0-instructions-read.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/ribbon-tests.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/scope-and-write-set.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/toolchain-bootstrap.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/issue-updates/ac-status.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/other/manual-verification-clear-spam-manager.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/other/reduced-audit-handoff.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-callsite.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-finding1.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-gate-class.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-gate-tests.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-race-fix.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-before-race-fix.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/callsite-edit-scope.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/coordinator-size-contingency.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/coverage-delta.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/coverage-final.2026-09-02T12-04.cobertura.xml -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/csharpier-check-final.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/csharpier-format.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/csharpier-xml-format.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/evidence-completeness.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/evidence-sanitization.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/file-line-counts.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/footprint-scope.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/gate-class-constraints.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/msbuild-analyzer.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/msbuild-nullable.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/no-new-exemption.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/p4-t3/p4-t3.trx -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/partial-keyword-edit.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/race-fix-structure.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/toolchain-loop-closure.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/vstest-coverage-run.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/xml-edit-scope.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/coordinator-fixture-after-fix.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/fail-before-exception.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/fail-before-finding1.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/fail-before-finding3.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/gate-tests.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/p1-t2/p1-t2.trx -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/p1-t7/p1-t7.trx -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/p1-t8/p1-t8.trx -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/p2-t8/p2-t8.trx -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/p3-t11/p3-t11.trx -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/p3-t12/p3-t12.trx -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/p3-t5/p3-t5.trx -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/pass-after-finding1.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/pass-after-finding3.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/ribbon-fixtures-after-finding1.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/feature-audit.2026-09-03T06-19.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/feature-audit.2026-09-03T09-05.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/issue.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/plan.2026-09-02T12-04.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/policy-audit.2026-09-03T06-19.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/policy-audit.2026-09-03T09-05.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/research/2026-09-02T09-15-ribbon-engine-toggle-defects-research.md -A docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/spec.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/code-review.2026-09-03T12-15.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/branch-commit-baseline.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/citation-verification.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/pester-coverage.2026-09-03T11-09.xml +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-feature-documents-read.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-instructions-read.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-analyze.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-format.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-test.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/issue-updates/issue-565.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/ac-status-summary.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/final-clean-tree.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/fix-diff-verification.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/p1-t1-anchor-resolution.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/coverage-delta.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/pester-coverage.iter1.2026-09-03T11-09.xml +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-analyze.iter1.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-format.iter1.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-test.iter1.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/expect-fail-run.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/helpers-boundary-regression.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/pass-after-run.2026-09-03T11-09.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/feature-audit.2026-09-03T12-15.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/issue.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/plan.2026-09-02T08-59.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/policy-audit.2026-09-03T12-15.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/research/invoke-mstestwithcoverage-ordering-fix.2026-09-02T09-00.md +A docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md +M scripts/vscode/Invoke-MSTestWithCoverage.ps1 +M tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1 ===== Diff shortstat ===== - 84 files changed, 400411 insertions(+), 34 deletions(-) + 30 files changed, 2024 insertions(+), 2 deletions(-) ===== Additions/Deletions totals (from numstat) ===== -Additions: 400411 -Deletions: 34 +Additions: 2024 +Deletions: 2 ===== Files by extension ===== - 9 .cs - 2 .csproj - 61 .md - 9 .trx - 3 .xml + 26 .md + 2 .ps1 + 2 .xml ===== Referenced issues (detected) ===== -#503, #504, #505, #506, #518, #524, #525, #735, CR-2, CR-3, SHA-256 +#441, #563, #565, #733, ISO-8601 ===== PRs in range ===== @@ -204,91 +150,37 @@ Deletions: 34 ===== Diff stat ===== - .claude/agent-memory/atomic-planner/MEMORY.md | 1 + - ...ject_735_evidence_content_sanitization_seams.md | 47 + - .claude/agent-memory/task-researcher/MEMORY.md | 1 + - .../project_ribbon_engine_toggle_defects_735.md | 44 + - .../Ribbon/EngineTogglePressedStateCacheTests.cs | 213 + - .../EngineToggleStateCoordinatorTests.Race.cs | 277 + - .../Ribbon/EngineToggleStateCoordinatorTests.cs | 2 +- - TaskMaster.Test/Ribbon/RibbonExplorerXmlTests.cs | 173 + - .../Ribbon/SpamManagerResetGateTests.cs | 326 + - TaskMaster.Test/TaskMaster.Test.csproj | 3 + - TaskMaster/Ribbon/EngineTogglePressedStateCache.cs | 157 + - TaskMaster/Ribbon/EngineToggleStateCoordinator.cs | 56 +- - TaskMaster/Ribbon/RibbonController.Intelligence.cs | 58 +- - TaskMaster/Ribbon/RibbonExplorer.xml | 9 +- - TaskMaster/Ribbon/SpamManagerResetGate.cs | 141 + - TaskMaster/TaskMaster.csproj | 2 + - .../code-review.2026-09-03T06-19.md | 402 + - .../code-review.2026-09-03T09-05.md | 270 + - .../evidence/baseline/base-ref.2026-09-02T12-04.md | 72 + - ...overage-baseline.2026-09-02T12-04.cobertura.xml | 194456 +++++++++++++++++ - .../baseline/coverage-baseline.2026-09-02T12-04.md | 84 + - .../baseline/csharpier-check.2026-09-02T12-04.md | 49 + - .../baseline/file-line-counts.2026-09-02T12-04.md | 46 + - .../baseline/msbuild-analyzer.2026-09-02T12-04.md | 56 + - .../baseline/msbuild-nullable.2026-09-02T12-04.md | 41 + - .../baseline/nuget-restore.2026-09-02T12-04.md | 40 + - .../evidence/baseline/p0-t8/p0-t8.trx | 703 + - .../phase0-instructions-read.2026-09-02T12-04.md | 41 + - .../baseline/ribbon-tests.2026-09-02T12-04.md | 58 + - .../scope-and-write-set.2026-09-02T12-04.md | 61 + - .../toolchain-bootstrap.2026-09-02T12-04.md | 55 + - .../issue-updates/ac-status.2026-09-02T12-04.md | 93 + - ...fication-clear-spam-manager.2026-09-02T12-04.md | 78 + - .../reduced-audit-handoff.2026-09-02T12-04.md | 100 + - .../build-after-callsite.2026-09-02T12-04.md | 25 + - .../build-after-finding1.2026-09-02T12-04.md | 33 + - .../build-after-gate-class.2026-09-02T12-04.md | 53 + - .../build-after-gate-tests.2026-09-02T12-04.md | 46 + - .../build-after-race-fix.2026-09-02T12-04.md | 31 + - .../build-before-race-fix.2026-09-02T12-04.md | 44 + - .../callsite-edit-scope.2026-09-02T12-04.md | 97 + - ...oordinator-size-contingency.2026-09-02T12-04.md | 130 + - .../qa-gates/coverage-delta.2026-09-02T12-04.md | 134 + - .../coverage-final.2026-09-02T12-04.cobertura.xml | 194753 ++++++++++++++++++ - .../csharpier-check-final.2026-09-02T12-04.md | 46 + - .../qa-gates/csharpier-format.2026-09-02T12-04.md | 119 + - .../csharpier-xml-format.2026-09-02T12-04.md | 37 + - .../evidence-completeness.2026-09-02T12-04.md | 80 + - .../evidence-sanitization.2026-09-02T12-04.md | 127 + - .../qa-gates/file-line-counts.2026-09-02T12-04.md | 94 + - .../qa-gates/footprint-scope.2026-09-02T12-04.md | 112 + - .../gate-class-constraints.2026-09-02T12-04.md | 63 + - .../qa-gates/msbuild-analyzer.2026-09-02T12-04.md | 41 + - .../qa-gates/msbuild-nullable.2026-09-02T12-04.md | 43 + - .../qa-gates/no-new-exemption.2026-09-02T12-04.md | 57 + - .../evidence/qa-gates/p4-t3/p4-t3.trx | 865 + - .../partial-keyword-edit.2026-09-02T12-04.md | 52 + - .../race-fix-structure.2026-09-02T12-04.md | 146 + - .../toolchain-loop-closure.2026-09-02T12-04.md | 68 + - .../vstest-coverage-run.2026-09-02T12-04.md | 84 + - .../qa-gates/xml-edit-scope.2026-09-02T12-04.md | 102 + - ...ordinator-fixture-after-fix.2026-09-02T12-04.md | 78 + - .../fail-before-exception.2026-09-02T12-04.md | 78 + - .../fail-before-finding1.2026-09-02T12-04.md | 125 + - .../fail-before-finding3.2026-09-02T12-04.md | 105 + - .../gate-tests.2026-09-02T12-04.md | 86 + - .../evidence/regression-testing/p1-t2/p1-t2.trx | 88 + - .../evidence/regression-testing/p1-t7/p1-t7.trx | 67 + - .../evidence/regression-testing/p1-t8/p1-t8.trx | 711 + - .../evidence/regression-testing/p2-t8/p2-t8.trx | 109 + - .../evidence/regression-testing/p3-t11/p3-t11.trx | 91 + - .../evidence/regression-testing/p3-t12/p3-t12.trx | 199 + - .../evidence/regression-testing/p3-t5/p3-t5.trx | 130 + - .../pass-after-finding1.2026-09-02T12-04.md | 59 + - .../pass-after-finding3.2026-09-02T12-04.md | 57 + - ...bon-fixtures-after-finding1.2026-09-02T12-04.md | 67 + - .../feature-audit.2026-09-03T06-19.md | 117 + - .../feature-audit.2026-09-03T09-05.md | 152 + - .../issue.md | 68 + - .../plan.2026-09-02T12-04.md | 486 + - .../policy-audit.2026-09-03T06-19.md | 421 + - .../policy-audit.2026-09-03T09-05.md | 369 + - ...T09-15-ribbon-engine-toggle-defects-research.md | 886 + - .../spec.md | 199 + - 84 files changed, 400411 insertions(+), 34 deletions(-) + .../code-review.2026-09-03T12-15.md | 120 +++++++++ + .../branch-commit-baseline.2026-09-03T11-09.md | 22 ++ + .../citation-verification.2026-09-03T11-09.md | 45 ++++ + .../baseline/pester-coverage.2026-09-03T11-09.xml | 170 ++++++++++++ + ...ase0-feature-documents-read.2026-09-03T11-09.md | 48 ++++ + .../phase0-instructions-read.2026-09-03T11-09.md | 19 ++ + .../baseline/poshqc-analyze.2026-09-03T11-09.md | 15 ++ + .../baseline/poshqc-format.2026-09-03T11-09.md | 19 ++ + .../baseline/poshqc-test.2026-09-03T11-09.md | 24 ++ + .../issue-updates/issue-565.2026-09-03T11-09.md | 26 ++ + .../other/ac-status-summary.2026-09-03T11-09.md | 8 + + .../other/final-clean-tree.2026-09-03T11-09.md | 7 + + .../fix-diff-verification.2026-09-03T11-09.md | 64 +++++ + .../p1-t1-anchor-resolution.2026-09-03T11-09.md | 23 ++ + .../qa-gates/coverage-delta.2026-09-03T11-09.md | 23 ++ + .../pester-coverage.iter1.2026-09-03T11-09.xml | 170 ++++++++++++ + .../poshqc-analyze.iter1.2026-09-03T11-09.md | 12 + + .../poshqc-format.iter1.2026-09-03T11-09.md | 17 ++ + .../qa-gates/poshqc-test.iter1.2026-09-03T11-09.md | 16 ++ + .../expect-fail-run.2026-09-03T11-09.md | 51 ++++ + ...helpers-boundary-regression.2026-09-03T11-09.md | 39 +++ + .../pass-after-run.2026-09-03T11-09.md | 25 ++ + .../feature-audit.2026-09-03T12-15.md | 47 ++++ + .../issue.md | 83 ++++++ + .../plan.2026-09-02T08-59.md | 160 ++++++++++++ + .../policy-audit.2026-09-03T12-15.md | 215 +++++++++++++++ + ...stwithcoverage-ordering-fix.2026-09-02T09-00.md | 288 +++++++++++++++++++++ + .../spec.md | 258 ++++++++++++++++++ + scripts/vscode/Invoke-MSTestWithCoverage.ps1 | 4 +- + .../vscode/Invoke-MSTest.RunSettings.Tests.ps1 | 8 + + 30 files changed, 2024 insertions(+), 2 deletions(-) ===== Issue details ===== @@ -301,33 +193,59 @@ Deletions: 34 (none) -===== Feature doc: 2026-09-02-ribbon-engine-toggle-defects-735 ===== +===== Feature doc: 2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565 ===== Spec excerpts: -Context: Issue #735 consolidates three code-review defects that all live in the Explorer ribbon subsystem under the directory TaskMaster/Ribbon, with their tests under TaskMaster.Test/Ribbon. They were grouped into one work item because they share a small file set and would otherwise consume three separate orchestration cycles for a combined production delta of roughly forty lines. - -- **Finding 1 (source #504) — dead XML-to-handler bindings.** The Explorer CustomUI document declares five callback names that resolve to no public instance method on the RibbonViewer type. Four are a `_Clicked` versus `_Click` suffix mismatch on the Item Sort Settings check boxes; the fifth, `BtnMigrateIDs_Click`, has no implementation anywhere in the solution. Office CustomUI resolves callbacks by name at invocatio... - -Proposed Fix: ### Finding 1 — repair the bindings, delete the dead one - -Edit the Explorer CustomUI document only; no production C# changes. - -- Rename four `onAction` values from the `_Clicked` to the `_Click` spelling on the four Item Sort Settings check boxes (move entire conversation, save attachments, save email copy, save pictures). Each already declares a correctly-resolving pressed-state callback, and each already has a `_Click` twin on the viewer type carrying the exact Office check-box signature `void (Office.IRibbonControl, bool)`. -- Delete the entire button element whose id is `BtnMigrateIDs`. Removal rather than implementation is justified above; nothing in the working tree contradicts removal. If stronger evidence is wanted, the atomic plan may run a history search for the introducing com... - -Acceptance Criteria: ### Finding 1 — dead XML-to-handler bindings - -- [x] The Explorer CustomUI document declared **five** callback names that resolve to no public instance method on the viewer type before this change; after this change the count of such unresolved names is **zero**. Both the pre-fix and post-fix counts are demonstrated by the new enumeration test, which reports every unresolved name in a single failure message. -- [x] Exactly **four** action-callback attribute values are renamed from the `_Clicked` spelling to the `_Click` spelling, on the move-entire-conversation, save-attachments, save-email-copy and save-pictures check boxes in the Item Sort Settings menu. No method on the viewer type is added, renamed, or removed to satisfy these bindings. -- [x] Exactly **one** element is deleted: the bu... +Context: `scripts/vscode/Invoke-MSTestWithCoverage.ps1` asserts the coverage threshold before it writes the +post-processed Cobertura document to disk. When the assertion fails, the script throws and the +post-processed document is discarded, leaving the raw un-post-processed document at the output path. + +Environment: +- OS/version: Windows 11 Pro (repo default) +- Python version: n/a — PowerShell 7+ coverage/test-runner scripts +- Command/flags used: `scripts/vscode/Invoke-MSTestWithCoverage.ps1` +- Data source or fixture: n/a + +Impact / Severity: +- [ ] Blocker +- [ ] High +- [x] Medium +- [ ] Low + +Only the failure path is affected; a passing run writes the correct document. But the failure path is +exactly when someone reads the artifact to diagnose the shortfall, and what they find is a... + +Proposed Fix: ### Design summary (what changes where): +Pure two-statement reorder inside `Invoke-MSTestWithCoverageMain` +(`scripts/vscode/Invoke-MSTestWithCoverage.ps1`, lines 341 and 343): swap the `Set-Content` call +(line 343) to execute immediately after `$processedXmlContent` is computed (line 340) and before +the `Assert-CoberturaLineCoverageThreshold` call (currently line 341). No function signature or +parameter changes anywhere in the change. + +### Boundaries and invariants to preserve: +- `Assert-CoberturaLineCoverageThreshold`'s own 80% threshold logic and message text + (scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1, line 487) are unchanged. +- The dot-source of Invoke-MSTestWithCoverage.Helpers.ps1 at line 261 continues to precede both + calls (unaffected by the reorder; it a... + +Acceptance Criteria: - [x] The new Pester test in `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, inside the + `Describe 'Invoke-MSTestWithCoverageMain'` block, fails against the pre-fix statement order + (`Assert-CoberturaLineCoverageThreshold` at line 341 ahead of `Set-Content` at line 343) and + passes after the fix (statement order swapped). +- [x] `Set-Content` is invoked before `Assert-CoberturaLineCoverageThreshold` can throw on a + sub-threshold run, verified by `Should -Invoke Set-Content -Times 1 -Exactly` asserted inside + a `{ ... } | Should -Throw` block, using the `ConvertTo-KoverageCoberturaXml` mock returning + `''`. +- [x] The coverage threshold value (80%) is unchanged: no diff touches + `Assert-CoberturaLineCoverag... Plan completed tasks: -- [P0-T1] Read the four policy documents in the mandatory order — CLAUDE.md, then .claude/rules/general-code-change.md, then .claude/rules/general-unit-test.md, then .claude/rules/csharp.md — and record the read in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/phase0-instructions-read.2026-09-02T12-04.md`. -- [P0-T2] Read the acceptance-criteria source `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/spec.md` and the design record `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/research/2026-09-02T09-15-ribbon-engine-toggle-defects-research.md` in full, and record the write set and the two prohibited AppGlobals paths in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/scope-and-write-set.2026-09-02T12-04.md`. -- [P0-T3] Bootstrap the .NET SDK with scripts/vscode/Install-RepoDotNetSdk.ps1 and then run `dotnet tool restore`, recording both invocations in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/toolchain-bootstrap.2026-09-02T12-04.md`. -- [P0-T4] Restore NuGet packages with scripts/vscode/Invoke-Restore.ps1 and record the result in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/nuget-restore.2026-09-02T12-04.md`. -- [P0-T5] Capture the read-only formatter baseline with `dotnet tool run csharpier check .` and record the verbatim unformatted-file set in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/csharpier-check.2026-09-02T12-04.md`. -- [P0-T6] Capture the analyzer baseline by running the CLAUDE.md analyzer rebuild on TaskMaster.sln and record it in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/msbuild-analyzer.2026-09-02T12-04.md`. -- [P0-T7] Capture the nullable baseline by running the CLAUDE.md nullable rebuild on TaskMaster.sln and record it in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/msbuild-nullable.2026-09-02T12-04.md`. -- [P0-T8] Capture the scoped ribbon-fixture test baseline by running every test whose fully qualified name contains `TaskMaster.Test.Ribbon` and record it in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/ribbon-tests.2026-09-02T12-04.md`, with the TRX written to `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/p0-t8`. -- [P0-T9] Capture the coverage baseline for the whole first-party suite with scripts/vscode/Invoke-MSTestWithCoverage.ps1 and record the numeric headline in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/coverage-baseline.2026-09-02T12-04.md`, with the Cobertura document at `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/coverage-baseline.2026-09-02T12-04.cobertura.xml`. -- [P0-T10] Record the pre-change line counts of the six existing in-scope source files in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/file-line-counts.2026-09-02T12-04.md`. \ No newline at end of file +- [P0-T1] Read the repository policy documents in the order defined by `policy-compliance-order` and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-instructions-read..md`. +- [P0-T2] Read the feature requirement documents and the current target source files, and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-feature-documents-read..md`. +- [P0-T3] Record the branch and commit baseline in `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/branch-commit-baseline..md`. +- [P0-T4] Re-derive and record the exact current statement order in `scripts/vscode/Invoke-MSTestWithCoverage.ps1` and the exact current threshold text in scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1, and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/citation-verification..md`. +- [P0-T5] Capture the PoshQC format baseline over the two files this plan edits and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-format..md`. +- [P0-T6] Capture the PSScriptAnalyzer baseline over the same two files and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-analyze..md`. +- [P0-T7] Capture the Pester baseline over the full PowerShell suite, with coverage on the production file, and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-test..md`. +- [P1-T1] [expect-fail] Add one new `It` to the existing `Describe 'Invoke-MSTestWithCoverageMain'` block in `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, immediately after the existing `It 'fails when the search root cannot be found'` block and its closing `}` (currently the block ending at line 413) and before the `Describe` block's own closing `}` (currently line 414), inserting exactly: +- [P1-T2] [expect-fail] Run the new test against the pre-fix statement order and record the RED result in `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/expect-fail-run..md`. +- [P2-T1] In `scripts/vscode/Invoke-MSTestWithCoverage.ps1`, swap the two statements at (or the drifted-anchor equivalent of) lines 341 and 343 so `Set-Content` executes immediately after `$processedXmlContent` is computed and before `Assert-CoberturaLineCoverageThreshold` is called: change line 341 from `Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent` to `Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline`, and change line 343 from `Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline` to `Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent`. Leave the blank line 342 and every other line (including the `Write-Output "Done. Coverage artifact: $resolvedOutputPath"` line) unchanged. Do not modify scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 or scripts/vscode/Invoke-MSTestWithCoverage.ClosureFilter.ps1. \ No newline at end of file diff --git a/artifacts/pr_context.summary.txt b/artifacts/pr_context.summary.txt index e541d56ee..007e973fc 100644 --- a/artifacts/pr_context.summary.txt +++ b/artifacts/pr_context.summary.txt @@ -1,8 +1,8 @@ ===== Context generated ===== -2026-09-03 06:32:25 UTC -Head SHA: 558c9c42d32a9cc5b060212881e86591b39ca380 +2026-09-03 11:38:15 UTC +Head SHA: e5dcbffd6a51e9f92869c390e85d179400657cd5 ===== GitHub CLI status ===== @@ -19,10 +19,10 @@ Author-asserted autoclose issues: ===== Base/Head ===== Base ref (requested): main -Base ref (resolved): origin/main @ b13d5b7b1a6dd0aa79d51d48a7156ee67377f9d0 -Head ref (resolved): bug/ribbon-engine-toggle-defects-735 @ 558c9c42d32a9cc5b060212881e86591b39ca380 -Merge base: b13d5b7b1a6dd0aa79d51d48a7156ee67377f9d0 -Range: b13d5b7b1a6dd0aa79d51d48a7156ee67377f9d0..558c9c42d32a9cc5b060212881e86591b39ca380 +Base ref (resolved): origin/main @ 87233f867ad60c0a5c0d19b09cc121ae536d7ba1 +Head ref (resolved): bug/invoke-mstestwithcoverage-threshold-before-setcontent-565 @ e5dcbffd6a51e9f92869c390e85d179400657cd5 +Merge base: 87233f867ad60c0a5c0d19b09cc121ae536d7ba1 +Range: 87233f867ad60c0a5c0d19b09cc121ae536d7ba1..e5dcbffd6a51e9f92869c390e85d179400657cd5 ===== Issues to autoclose (verified or pending) ===== @@ -36,17 +36,11 @@ Auto-close issues (verified from GitHub PR metadata): None (GitHub CLI unavailable) Auto-close issues (author asserted): -- #503 -- #504 -- #505 -- #506 -- #518 -- #524 -- #525 -- #735 -- #CR-2 -- #CR-3 -- #SHA-256 +- #441 +- #563 +- #565 +- #733 +- #ISO-8601 Referenced issues (detected): (none) @@ -54,135 +48,107 @@ Referenced issues (detected): ===== Additional context files ===== -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/other/manual-verification-clear-spam-manager.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/other/reduced-audit-handoff.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-callsite.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-finding1.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-gate-class.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-gate-tests.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-race-fix.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-before-race-fix.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/callsite-edit-scope.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/coordinator-size-contingency.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/coverage-delta.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/csharpier-check-final.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/csharpier-format.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/csharpier-xml-format.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/evidence-completeness.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/evidence-sanitization.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/file-line-counts.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/footprint-scope.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/gate-class-constraints.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/msbuild-analyzer.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/msbuild-nullable.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/no-new-exemption.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/partial-keyword-edit.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/race-fix-structure.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/toolchain-loop-closure.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/vstest-coverage-run.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/xml-edit-scope.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/coordinator-fixture-after-fix.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/fail-before-exception.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/fail-before-finding1.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/fail-before-finding3.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/gate-tests.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/pass-after-finding1.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/pass-after-finding3.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/ribbon-fixtures-after-finding1.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/issue.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/plan.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/spec.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/ac-status-summary.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/final-clean-tree.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/fix-diff-verification.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/p1-t1-anchor-resolution.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/coverage-delta.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-analyze.iter1.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-format.iter1.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-test.iter1.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/expect-fail-run.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/helpers-boundary-regression.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/pass-after-run.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/issue.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/plan.2026-09-02T08-59.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md ===== Feature doc excerpts ===== -Feature: 2026-09-02-ribbon-engine-toggle-defects-735 +Feature: 2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565 Excerpt: -===== Feature doc: 2026-09-02-ribbon-engine-toggle-defects-735 ===== +===== Feature doc: 2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565 ===== Spec excerpts: -Context: Issue #735 consolidates three code-review defects that all live in the Explorer ribbon subsystem under the directory TaskMaster/Ribbon, with their tests under TaskMaster.Test/Ribbon. They were grouped into one work item because they share a small file set and would otherwise consume three separate orchestration cycles for a combined production delta of roughly forty lines. - -- **Finding 1 (source #504) — dead XML-to-handler bindings.** The Explorer CustomUI document declares five callback names that resolve to no public instance method on the RibbonViewer type. Four are a `_Clicked` versus `_Click` suffix mismatch on the Item Sort Settings check boxes; the fifth, `BtnMigrateIDs_Click`, has no implementation anywhere in the solution. Office CustomUI resolves callbacks by name at invocatio... - -Proposed Fix: ### Finding 1 — repair the bindings, delete the dead one - -Edit the Explorer CustomUI document only; no production C# changes. - -- Rename four `onAction` values from the `_Clicked` to the `_Click` spelling on the four Item Sort Settings check boxes (move entire conversation, save attachments, save email copy, save pictures). Each already declares a correctly-resolving pressed-state callback, and each already has a `_Click` twin on the viewer type carrying the exact Office check-box signature `void (Office.IRibbonControl, bool)`. -- Delete the entire button element whose id is `BtnMigrateIDs`. Removal rather than implementation is justified above; nothing in the working tree contradicts removal. If stronger evidence is wanted, the atomic plan may run a history search for the introducing com... - -Acceptance Criteria: ### Finding 1 — dead XML-to-handler bindings - -- [x] The Explorer CustomUI document declared **five** callback names that resolve to no public instance method on the viewer type before this change; after this change the count of such unresolved names is **zero**. Both the pre-fix and post-fix counts are demonstrated by the new enumeration test, which reports every unresolved name in a single failure message. -- [x] Exactly **four** action-callback attribute values are renamed from the `_Clicked` spelling to the `_Click` spelling, on the move-entire-conversation, save-attachments, save-email-copy and save-pictures check boxes in the Item Sort Settings menu. No method on the viewer type is added, renamed, or removed to satisfy these bindings. -- [x] Exactly **one** element is deleted: the bu... +Context: `scripts/vscode/Invoke-MSTestWithCoverage.ps1` asserts the coverage threshold before it writes the +post-processed Cobertura document to disk. When the assertion fails, the script throws and the +post-processed document is discarded, leaving the raw un-post-processed document at the output path. + +Environment: +- OS/version: Windows 11 Pro (repo default) +- Python version: n/a — PowerShell 7+ coverage/test-runner scripts +- Command/flags used: `scripts/vscode/Invoke-MSTestWithCoverage.ps1` +- Data source or fixture: n/a + +Impact / Severity: +- [ ] Blocker +- [ ] High +- [x] Medium +- [ ] Low + +Only the failure path is affected; a passing run writes the correct document. But the failure path is +exactly when someone reads the artifact to diagnose the shortfall, and what they find is a... + +Proposed Fix: ### Design summary (what changes where): +Pure two-statement reorder inside `Invoke-MSTestWithCoverageMain` +(`scripts/vscode/Invoke-MSTestWithCoverage.ps1`, lines 341 and 343): swap the `Set-Content` call +(line 343) to execute immediately after `$processedXmlContent` is computed (line 340) and before +the `Assert-CoberturaLineCoverageThreshold` call (currently line 341). No function signature or +parameter changes anywhere in the change. + +### Boundaries and invariants to preserve: +- `Assert-CoberturaLineCoverageThreshold`'s own 80% threshold logic and message text + (scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1, line 487) are unchanged. +- The dot-source of Invoke-MSTestWithCoverage.Helpers.ps1 at line 261 continues to precede both + calls (unaffected by the reorder; it a... + +Acceptance Criteria: - [x] The new Pester test in `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, inside the + `Describe 'Invoke-MSTestWithCoverageMain'` block, fails against the pre-fix statement order + (`Assert-CoberturaLineCoverageThreshold` at line 341 ahead of `Set-Content` at line 343) and + passes after the fix (statement order swapped). +- [x] `Set-Content` is invoked before `Assert-CoberturaLineCoverageThreshold` can throw on a + sub-threshold run, verified by `Should -Invoke Set-Content -Times 1 -Exactly` asserted inside + a `{ ... } | Should -Throw` block, using the `ConvertTo-KoverageCoberturaXml` mock returning + `''`. +- [x] The coverage threshold value (80%) is unchanged: no diff touches + `Assert-CoberturaLineCoverag... Plan completed tasks: -- [P0-T1] Read the four policy documents in the mandatory order — CLAUDE.md, then .claude/rules/general-code-change.md, then .claude/rules/general-unit-test.md, then .claude/rules/csharp.md — and record the read in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/phase0-instructions-read.2026-09-02T12-04.md`. -- [P0-T2] Read the acceptance-criteria source `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/spec.md` and the design record `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/research/2026-09-02T09-15-ribbon-engine-toggle-defects-research.md` in full, and record the write set and the two prohibited AppGlobals paths in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/scope-and-write-set.2026-09-02T12-04.md`. -- [P0-T3] Bootstrap the .NET SDK with scripts/vscode/Install-RepoDotNetSdk.ps1 and then run `dotnet tool restore`, recording both invocations in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/toolchain-bootstrap.2026-09-02T12-04.md`. -- [P0-T4] Restore NuGet packages with scripts/vscode/Invoke-Restore.ps1 and record the result in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/nuget-restore.2026-09-02T12-04.md`. -- [P0-T5] Capture the read-only formatter baseline with `dotnet tool run csharpier check .` and record the verbatim unformatted-file set in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/csharpier-check.2026-09-02T12-04.md`. -- [P0-T6] Capture the analyzer baseline by running the CLAUDE.md analyzer rebuild on TaskMaster.sln and record it in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/msbuild-analyzer.2026-09-02T12-04.md`. -- [P0-T7] Capture the nullable baseline by running the CLAUDE.md nullable rebuild on TaskMaster.sln and record it in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/msbuild-nullable.2026-09-02T12-04.md`. -- [P0-T8] Capture the scoped ribbon-fixture test baseline by running every test whose fully qualified name contains `TaskMaster.Test.Ribbon` and record it in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/ribbon-tests.2026-09-02T12-04.md`, with the TRX written to `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/p0-t8`. -- [P0-T9] Capture the coverage baseline for the whole first-party suite with scripts/vscode/Invoke-MSTestWithCoverage.ps1 and record the numeric headline in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/coverage-baseline.2026-09-02T12-04.md`, with the Cobertura document at `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/coverage-baseline.2026-09-02T12-04.cobertura.xml`. -- [P0-T10] Record the pre-change line counts of the six existing in-scope source files in `docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/file-line-counts.2026-09-02T12-04.md`. +- [P0-T1] Read the repository policy documents in the order defined by `policy-compliance-order` and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-instructions-read..md`. +- [P0-T2] Read the feature requirement documents and the current target source files, and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-feature-documents-read..md`. +- [P0-T3] Record the branch and commit baseline in `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/branch-commit-baseline..md`. +- [P0-T4] Re-derive and record the exact current statement order in `scripts/vscode/Invoke-MSTestWithCoverage.ps1` and the exact current threshold text in scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1, and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/citation-verification..md`. +- [P0-T5] Capture the PoshQC format baseline over the two files this plan edits and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-format..md`. +- [P0-T6] Capture the PSScriptAnalyzer baseline over the same two files and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-analyze..md`. +- [P0-T7] Capture the Pester baseline over the full PowerShell suite, with coverage on the production file, and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-test..md`. +- [P1-T1] [expect-fail] Add one new `It` to the existing `Describe 'Invoke-MSTestWithCoverageMain'` block in `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, immediately after the existing `It 'fails when the search root cannot be found'` block and its closing `}` (currently the block ending at line 413) and before the `Describe` block's own closing `}` (currently line 414), inserting exactly: +- [P1-T2] [expect-fail] Run the new test against the pre-fix statement order and record the RED result in `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/expect-fail-run..md`. +- [P2-T1] In `scripts/vscode/Invoke-MSTestWithCoverage.ps1`, swap the two statements at (or the drifted-anchor equivalent of) lines 341 and 343 so `Set-Content` executes immediately after `$processedXmlContent` is computed and before `Assert-CoberturaLineCoverageThreshold` is called: change line 341 from `Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent` to `Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline`, and change line 343 from `Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline` to `Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent`. Leave the blank line 342 and every other line (including the `Write-Output "Done. Coverage artifact: $resolvedOutputPath"` line) unchanged. Do not modify scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 or scripts/vscode/Invoke-MSTestWithCoverage.ClosureFilter.ps1. Context files: -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/other/manual-verification-clear-spam-manager.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/other/reduced-audit-handoff.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-callsite.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-finding1.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-gate-class.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-gate-tests.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-race-fix.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-before-race-fix.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/callsite-edit-scope.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/coordinator-size-contingency.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/coverage-delta.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/csharpier-check-final.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/csharpier-format.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/csharpier-xml-format.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/evidence-completeness.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/evidence-sanitization.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/file-line-counts.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/footprint-scope.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/gate-class-constraints.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/msbuild-analyzer.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/msbuild-nullable.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/no-new-exemption.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/partial-keyword-edit.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/race-fix-structure.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/toolchain-loop-closure.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/vstest-coverage-run.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/xml-edit-scope.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/coordinator-fixture-after-fix.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/fail-before-exception.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/fail-before-finding1.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/fail-before-finding3.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/gate-tests.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/pass-after-finding1.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/pass-after-finding3.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/ribbon-fixtures-after-finding1.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/issue.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/plan.2026-09-02T12-04.md -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/spec.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/ac-status-summary.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/final-clean-tree.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/fix-diff-verification.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/p1-t1-anchor-resolution.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/coverage-delta.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-analyze.iter1.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-format.iter1.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-test.iter1.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/expect-fail-run.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/helpers-boundary-regression.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/pass-after-run.2026-09-03T11-09.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/issue.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/plan.2026-09-02T08-59.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md ===== Referenced issues (classified) ===== -- #503 -- #504 -- #505 -- #506 -- #518 -- #524 -- #525 -- #735 -- #CR-2 -- #CR-3 -- #SHA-256 +- #441 +- #563 +- #565 +- #733 +- #ISO-8601 NOTE: Unverified (GitHub unavailable) @@ -199,68 +165,70 @@ NOTE: Unverified (GitHub unavailable) ===== Scoping docs changed ===== Scoping docs changed (material): -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/spec.md (Reasons: new scoping doc, >=15 lines changed, key section touched) +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md (Reasons: new scoping doc, >=15 lines changed, key section touched) Acceptance Criteria: -### Finding 1 — dead XML-to-handler bindings - -- [x] The Explorer CustomUI document declared **five** callback names that resolve to no public instance method on the viewer type before this change; after this change the count of such unresolved names is **zero**. Both the pre-fix and post-fix counts are demonstrated by the new enumeration test, which reports every unresolved name in a single failure message. -- [x] Exactly **four** action-callback attribute values are renamed from the `_Clicked` spelling to the `_Click` spelling, on the move-entire-conversation, save-attachments, save-email-copy and save-pictures check boxes in the Item Sort Settings menu. No method on the viewer type is added, renamed, or removed to satisfy these bindings. -- [x] Exactly **one** element is deleted: the button whose id is `BtnMigrateIDs`. No other element, attribute, or attribute value in the document is changed beyond the four renames and any CSharpier reflow. -- [x] The rename-versus-removal partition is exactly four renames plus one removal, totalling the five defective names: a name is renamed when a correctly signatured method with the intended spelling already exists, and removed when no implementation exists anywhere in the solution. -- [x] A test named `RibbonExplorerXml_EveryCallbackNameResolvesToAPublicRibbonViewerMethod` exists and passes. It enumerates descendant element nodes only (so commented-out occurrences are excluded structurally), includes the root element's load callback, and treats an attribute as a callback if its local name is `onAction`, `onChange` or `onLoad`, or begins with `get`. -- [x] A test named `RibbonExplorerXml_CheckBoxOnActionCallbacksTakeControlAndPressedParameters` exists and passes. It asserts every check-box action callback resolves to a method returning void and taking the ribbon-control interface followed by a bool, comparing the first parameter by full type name because the test project has no Office interop reference. -- [x] Both new tests are demonstrated to fail against the pre-fix tree — the first reporting five unresolved names, the second reporting four unresolvable check-box callbacks — before the production edit lands. - -### Finding 2 — unguarded globals dereference in Clear Spam Manager - -- [x] A new `internal sealed class SpamManagerResetGate` exists in namespace `TaskMaster` alongside its ribbon siblings, taking an auto-file-objects accessor, an engines accessor and a not-ready notification delegate, and throwing `ArgumentNullException` naming the offending parameter for each of the three. -- [x] The gate's `RunAsync` throws `ArgumentNullException` for a null reset delegate **before** invoking any accessor; emits the not-ready notification exactly once and returns a completed task without invoking the reset delegate when either the resolved manager or the resolved engines facade is null; and otherwise returns the reset invocation directly, with no await and no catch, so a fault propagates unchanged. -- [x] The gate carries **no** `ExcludeFromCodeCoverage` attribute, no `Microsoft.Office` using, no `System.Windows.Forms` using and no logger field, and its XML documentation records that the absence of the coverage attribute is deliberate. -- [x] `ClearSpamManagerAsync` retains its synchronization-context preamble and its confirmation dialog unchanged and in their existing order, and routes only its engine-touching statements through the gate's deferred lambda. No inline ad-hoc null guard is introduced. -- [x] All nine tests in the new gate fixture pass: three constructor null-argument cases, one null-reset case using strict accessors, three not-ready cases (null auto-file objects, unset manager, null engines), one success case asserting the resolved manager and engines are passed through by identity with no notification, and one faulting-reset case asserting the fault propagates with no notification. -- [x] Line coverage for the new gate class is at least 90%, meeting the new-module rule. -- [x] **No new `ExcludeFromCodeCoverage` attribute is introduced anywhere in the diff**, and no existing exemption is widened. The residual lines inside `ClearSpamManagerAsync` remain inside the ribbon controller's pre-existing type-level exemption, and the change description asserts no coverage credit for them. -- [ ] The change description records the manual verification: the not-ready notice is observed instead of a `NullReferenceException` when Clear Spam Manager is confirmed before initialization completes, and the reset still runs end to end when repeated after initialization completes. - -### Finding 3 — toggle-state last-writer race - -- [x] The pressed-state cache is a concurrent dictionary of a private nested reference type carrying an activation flag and a monotonic sequence ticket, keyed ordinally; the sequence source is read and written only through interlocked operations. -- [x] Both writers capture a ticket immediately before invoking the activation read — on the toggle path, after the engine toggle completes — and store through a compare-and-apply helper that applies a write only when no newer observation is already cached for that key, invalidating the control only when the write was applied. -- [x] The pressed-state reader keeps its `bool` return type and still never awaits, blocks, or throws; the existing update-before-invalidate ordering test continues to pass unmodified. -- [x] Prime completion treats any outcome other than ran-to-completion as a failure: the in-flight marker is cleared and the failure is logged, with a synthesized cancellation exception when there is no exception to unwrap. The existing faulted-path behavior, including base-exception unwrapping asserted by reference in an existing test, is preserved. -- [x] All six new tests in the new coordinator race file pass, and the three that reproduce defects — the prime-after-toggle race, the toggle-versus-toggle race, and the canceled-prime logging case — are demonstrated to fail against the pre-fix tree. -- [x] The existing coordinator test class declaration changes by exactly one added `partial` keyword, with no other edit to that file, so the new file reuses the existing private harness with no duplication. - -### Cross-cutting - -- [x] All three new source files are registered as compile items in their respective legacy non-SDK project files, and the solution builds. -- [x] Every file created or modified by this change is under the 500-line ceiling after formatting; line counts are verified for the coordinator source, the Intelligence partial, the XML-consistency fixture, and the new coordinator race file. -- [x] The full toolchain passes in order in a single pass with no failures and no auto-fixes: format, analyzers, nullable type-check, and tests with coverage. -- [x] No behavior outside the three findings changes; in particular the eight QuickFiler-settings members, the orphaned folder-classifier handler, and the three not-implemented bound handlers are left untouched and are recorded here as separate follow-ups. +- [x] The new Pester test in `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, inside the + `Describe 'Invoke-MSTestWithCoverageMain'` block, fails against the pre-fix statement order + (`Assert-CoberturaLineCoverageThreshold` at line 341 ahead of `Set-Content` at line 343) and + passes after the fix (statement order swapped). +- [x] `Set-Content` is invoked before `Assert-CoberturaLineCoverageThreshold` can throw on a + sub-threshold run, verified by `Should -Invoke Set-Content -Times 1 -Exactly` asserted inside + a `{ ... } | Should -Throw` block, using the `ConvertTo-KoverageCoberturaXml` mock returning + `''`. +- [x] The coverage threshold value (80%) is unchanged: no diff touches + `Assert-CoberturaLineCoverageThreshold`'s threshold literal (line 487) or its throw message + text in scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1. Post-#733 drift note: this + logic now actually resides in scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1 + (line 52/54); the [P2-T1] fix-diff-verification artifact confirms an empty `git diff` for + BOTH that file and Helpers.ps1 against BASELINE_SHA. +- [x] No production file other than `scripts/vscode/Invoke-MSTestWithCoverage.ps1` is changed (in + particular, scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 and + scripts/vscode/Invoke-MSTestWithCoverage.ClosureFilter.ps1 are untouched). +- [x] PoshQC format, PSScriptAnalyzer, and Pester all pass cleanly on the changed files + (`scripts/vscode/Invoke-MSTestWithCoverage.ps1` and + `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`), with no format or lint + auto-fixes needed and no regression in the existing `Describe 'Invoke-MSTestWithCoverageMain'` + cases (lines 345-414) or the boundary tests in Invoke-MSTestWithCoverage.Helpers.Tests.ps1. + Post-#733 drift note: the five boundary tests now live in + Invoke-MSTestWithCoverage.Threshold.Tests.ps1, confirmed 5/5 Passed by the [P3-T2] + helpers-boundary-regression artifact. +- [x] Repro steps from `## Repro & Evidence` now produce the expected behavior: after the fix, the + artifact left on disk at `-CoverageOutput` on a sub-threshold run is the same post-processed + Cobertura document that the threshold assertion judged, not the raw `dotnet-coverage` output. Proposed Fix: -### Finding 1 — repair the bindings, delete the dead one +### Design summary (what changes where): +Pure two-statement reorder inside `Invoke-MSTestWithCoverageMain` +(`scripts/vscode/Invoke-MSTestWithCoverage.ps1`, lines 341 and 343): swap the `Set-Content` call +(line 343) to execute immediately after `$processedXmlContent` is computed (line 340) and before +the `Assert-CoberturaLineCoverageThreshold` call (currently line 341). No function signature or +parameter changes anywhere in the change. + +### Boundaries and invariants to preserve: +- `Assert-CoberturaLineCoverageThreshold`'s own 80% threshold logic and message text TRUNCATED: first 40 lines shown ===== Changed files overview ===== -Core logic changes: 0 files +Core logic changes: 2 files +- tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1 (+8/-0) +- scripts/vscode/Invoke-MSTestWithCoverage.ps1 (+2/-2) Mechanical moves/renames: 0 files -Docs/templates/agents/tooling: 68 files -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/coverage-final.2026-09-02T12-04.cobertura.xml (+194753/-0) -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/coverage-baseline.2026-09-02T12-04.cobertura.xml (+194456/-0) -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/research/2026-09-02T09-15-ribbon-engine-toggle-defects-research.md (+886/-0) -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/p4-t3/p4-t3.trx (+865/-0) -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/p1-t8/p1-t8.trx (+711/-0) -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/baseline/p0-t8/p0-t8.trx (+703/-0) -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/plan.2026-09-02T12-04.md (+486/-0) -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/policy-audit.2026-09-03T06-19.md (+421/-0) -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/code-review.2026-09-03T06-19.md (+402/-0) -- docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/policy-audit.2026-09-03T09-05.md (+369/-0) +Docs/templates/agents/tooling: 28 files +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/research/invoke-mstestwithcoverage-ordering-fix.2026-09-02T09-00.md (+288/-0) +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md (+258/-0) +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/policy-audit.2026-09-03T12-15.md (+215/-0) +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/pester-coverage.2026-09-03T11-09.xml (+170/-0) +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/pester-coverage.iter1.2026-09-03T11-09.xml (+170/-0) +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/plan.2026-09-02T08-59.md (+160/-0) +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/code-review.2026-09-03T12-15.md (+120/-0) +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/issue.md (+83/-0) +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/fix-diff-verification.2026-09-03T11-09.md (+64/-0) +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/expect-fail-run.2026-09-03T11-09.md (+51/-0) ===== Issue digests ===== @@ -275,179 +243,46 @@ Docs/templates/agents/tooling: 68 files ===== Verification evidence (feature docs + canonical artifacts) ===== -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/other/manual-verification-clear-spam-manager.2026-09-02T12-04.md - - Timestamp: 2026-09-03T02-21 - - Command: not applicable — this task performs no command. It writes the procedure and its outcome field. - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/other/reduced-audit-handoff.2026-09-02T12-04.md - - Timestamp: 2026-09-03T03-42 - - Command: not applicable — this task assembles pointers and records the follow-up list; it runs no command. - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-callsite.2026-09-02T12-04.md - - Timestamp: 2026-09-03T02-16 - - Command: `& $msbuild TaskMaster.sln /t:Build /m /p:Configuration=Debug "/p:Platform=Any CPU"` - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-finding1.2026-09-02T12-04.md - - Timestamp: 2026-09-03T01-42 - - Command: `& $msbuild TaskMaster.sln /t:Build /m /p:Configuration=Debug "/p:Platform=Any CPU"` - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-gate-class.2026-09-02T12-04.md - - Timestamp: 2026-09-03T02-02 - - Command: `& $msbuild TaskMaster.sln /t:Build /m /p:Configuration=Debug "/p:Platform=Any CPU"` - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-gate-tests.2026-09-02T12-04.md - - Timestamp: 2026-09-03T02-10 - - Command: `& $msbuild TaskMaster.sln /t:Build /m /p:Configuration=Debug "/p:Platform=Any CPU"` - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-after-race-fix.2026-09-02T12-04.md - - Timestamp: 2026-09-03T02-42 - - Command: `& $msbuild TaskMaster.sln /t:Build /m /p:Configuration=Debug "/p:Platform=Any CPU"` - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/build-before-race-fix.2026-09-02T12-04.md - - Timestamp: 2026-09-03T02-32 - - Command: `& $msbuild TaskMaster.sln /t:Build /m /p:Configuration=Debug "/p:Platform=Any CPU"` - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/callsite-edit-scope.2026-09-02T12-04.md - - Timestamp: 2026-09-03T02-17 - - Command: `git diff --unified=0 (git merge-base origin/main HEAD) -- TaskMaster/Ribbon/RibbonController.Intelligence.cs` - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/coordinator-size-contingency.2026-09-02T12-04.md - - Timestamp: 2026-09-03T03-06 - - Command: `@(Get-Content -LiteralPath TaskMaster/Ribbon/EngineToggleStateCoordinator.cs).Count` before and after the extraction, with a re-run of P4-T1 and P4-T2 in between. - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/coverage-delta.2026-09-02T12-04.md - - Timestamp: 2026-09-03T03-20 - - Command: XML aggregation over both Cobertura documents, plus `git diff --unified=0 (git merge-base origin/main HEAD) -- TaskMaster/Ribbon/EngineToggleStateCoordinator.cs` to derive the added line numbers. +- Feature: 2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565 + - Source: docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/final-clean-tree.2026-09-03T11-09.md + - Timestamp: 2026-09-03T11-09 + - Command: git status --porcelain - EXIT_CODE: 0 - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/csharpier-check-final.2026-09-02T12-04.md - - Timestamp: 2026-09-03T03-10 - - Command: `dotnet tool run csharpier check .` (read-only, working directory set to the worktree root) +- Feature: 2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565 + - Source: docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-analyze.iter1.2026-09-03T11-09.md + - Timestamp: 2026-09-03T11-09 + - Command: mcp__drm-copilot__run_poshqc_analyze (scan_folders: scripts/vscode/Invoke-MSTestWithCoverage.ps1, tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1); paired direct run identical to [P0-T6]. - EXIT_CODE: 0 - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/csharpier-format.2026-09-02T12-04.md - - Timestamp: 2026-09-03T02-56 - - Command: `Get-FileHash -Algorithm SHA256` for each of the eight paths, then `dotnet tool run csharpier format `, then the same eight hashes again. +- Feature: 2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565 + - Source: docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-format.iter1.2026-09-03T11-09.md + - Timestamp: 2026-09-03T11-09 + - Command: mcp__drm-copilot__run_poshqc_format (scan_folders: scripts/vscode/Invoke-MSTestWithCoverage.ps1, tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1) - EXIT_CODE: 0 - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/csharpier-xml-format.2026-09-02T12-04.md - - Timestamp: 2026-09-03T01-40 - - Command: `Get-FileHash -Algorithm SHA256 -LiteralPath TaskMaster/Ribbon/RibbonExplorer.xml`, then `dotnet tool run csharpier format TaskMaster/Ribbon/RibbonExplorer.xml`, then the same hash again. +- Feature: 2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565 + - Source: docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-test.iter1.2026-09-03T11-09.md + - Timestamp: 2026-09-03T11-09 + - Command: mcp__drm-copilot__run_poshqc_test (scan_folders: tests/scripts/vscode); paired direct run identical to [P0-T7], with CodeCoverage.OutputPath = docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/pester-coverage.iter1.2026-09-03T11-09.xml - EXIT_CODE: 0 - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/evidence-completeness.2026-09-02T12-04.md - - Timestamp: 2026-09-03T03-40 - - Command: existence check over the 55 artifact paths named by tasks P0-T1 through P5-T9; required-field scan over every command-bearing markdown artifact; `ExpectedExitCode: 1` check on the two intentionally failing runs; then a name scan and a content scan of the whole evidence tree for the two run-time-derived tokens. - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/evidence-sanitization.2026-09-02T12-04.md - - Timestamp: 2026-09-03T03-35 - - Command: for every in-scope file under the feature evidence tree, read the content, count case-insensitive occurrences of the local account token and the machine-name token, replace each with its redaction literal, and write the file back only when at least one substitution was made. - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/file-line-counts.2026-09-02T12-04.md - - Timestamp: 2026-09-03T02-58 - - Command: `@(Get-Content -LiteralPath ).Count` for each file, measured AFTER the P4-T1 format pass. - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/footprint-scope.2026-09-02T12-04.md - - Timestamp: 2026-09-03T03-25 - - Command: `git add -N .; git diff --name-status (git merge-base origin/main HEAD); git status --porcelain` - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/gate-class-constraints.2026-09-02T12-04.md - - Timestamp: 2026-09-03T02-03 - - Command: line-oriented pattern counts over `TaskMaster/Ribbon/SpamManagerResetGate.cs`, using `Get-Content -LiteralPath` and four anchored regular expressions. - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/msbuild-analyzer.2026-09-02T12-04.md - - Timestamp: 2026-09-03T03-11 - - Command: `& $msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true` - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/msbuild-nullable.2026-09-02T12-04.md - - Timestamp: 2026-09-03T03-12 - - Command: `& $msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true` - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/no-new-exemption.2026-09-02T12-04.md - - Timestamp: 2026-09-03T03-24 - - Command: `git add -N .; git diff (git merge-base origin/main HEAD) -- TaskMaster TaskMaster.Test` - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/partial-keyword-edit.2026-09-02T12-04.md - - Timestamp: 2026-09-03T02-45 - - Command: `git diff --numstat (git merge-base origin/main HEAD) -- TaskMaster.Test/Ribbon/EngineToggleStateCoordinatorTests.cs` - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/race-fix-structure.2026-09-02T12-04.md - - Timestamp: 2026-09-03T02-48 - - Command: identifier counts and brace-matched method-body extraction over `TaskMaster/Ribbon/EngineToggleStateCoordinator.cs`, using `Get-Content -LiteralPath` and escaped literal regular expressions. - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/toolchain-loop-closure.2026-09-02T12-04.md - - Timestamp: 2026-09-03T03-27 - - Command: this task performs no command of its own; it reconciles the exit codes of P4-T1 through P4-T10, each recorded in its own artifact. - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/qa-gates/vstest-coverage-run.2026-09-02T12-04.md - - Timestamp: 2026-09-03T03-18 - - Command: `pwsh -NoProfile -File /scripts/vscode/Invoke-MSTestWithCoverage.ps1 -SearchRoot '.' -Configuration 'Debug' -CoverageOutput 'docs\features\active\2026-09-02-ribbon-engine-toggle-defects-735\evidence\qa-gates\coverage-final.2026-09-02T12-04.cobertura.xml'` - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/fail-before-exception.2026-09-02T12-04.md - - Timestamp: 2026-09-03T02-19 - - Command: filesystem enumeration of the feature evidence tree with the patterns listed above; no - - EXIT_CODE: 0 - - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/fail-before-finding1.2026-09-02T12-04.md - - Timestamp: 2026-09-03T01-23 - - Command: the two invocations above, run in that order. +- Feature: 2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565 + - Source: docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/expect-fail-run.2026-09-03T11-09.md + - Timestamp: 2026-09-03T11-09 + - Command: mcp__drm-copilot__run_poshqc_test (scan_folders: tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1); paired direct run: pwsh -NoProfile -Command 'Import-Module Pester -MinimumVersion 5.0; $c = New-PesterConfiguration; $c.Run.Path = "/tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1"; $c.Run.PassThru = $true; $c.Output.Verbosity = "Detailed"; $r = Invoke-Pester -Configuration $c; ...; if ($r.FailedCount -gt 0) { exit 1 } else { exit 0 }' - EXIT_CODE: 1 - - Expected EXIT_CODE: 1 + - Normalized result: fail +- Feature: 2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565 + - Source: docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/helpers-boundary-regression.2026-09-03T11-09.md + - Timestamp: 2026-09-03T11-09 + - Command: mcp__drm-copilot__run_poshqc_test (scan_folders: tests/scripts/vscode/Invoke-MSTestWithCoverage.Helpers.Tests.ps1); paired direct run: pwsh -NoProfile -Command 'Import-Module Pester -MinimumVersion 5.0; $c = New-PesterConfiguration; $c.Run.Path = "/tests/scripts/vscode/Invoke-MSTestWithCoverage.Helpers.Tests.ps1"; $c.Run.PassThru = $true; $c.Output.Verbosity = "Detailed"; $r = Invoke-Pester -Configuration $c; ...; if ($r.FailedCount -gt 0) { exit 1 } else { exit 0 }' + - EXIT_CODE: 0 - Normalized result: pass -- Feature: 2026-09-02-ribbon-engine-toggle-defects-735 - - Source: docs/features/active/2026-09-02-ribbon-engine-toggle-defects-735/evidence/regression-testing/pass-after-finding3.2026-09-02T12-04.md - - Timestamp: 2026-09-03T02-43 - - Command: the pinned scoped form with the same six-clause filter P3-T5 used, +- Feature: 2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565 + - Source: docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/pass-after-run.2026-09-03T11-09.md + - Timestamp: 2026-09-03T11-09 + - Command: mcp__drm-copilot__run_poshqc_test (scan_folders: tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1); paired direct run: pwsh -NoProfile -Command 'Import-Module Pester -MinimumVersion 5.0; $c = New-PesterConfiguration; $c.Run.Path = "/tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1"; $c.Run.PassThru = $true; $c.Output.Verbosity = "Detailed"; $r = Invoke-Pester -Configuration $c; ...; if ($r.FailedCount -gt 0) { exit 1 } else { exit 0 }' - EXIT_CODE: 0 - Normalized result: pass @@ -459,4 +294,4 @@ Docs/templates/agents/tooling: 68 files ===== Appendix pointer ===== -See C:/Users/DanMoisan/repos/TaskMaster/.claude/worktrees/agent-a3324f355df219b0e/artifacts/pr_context.appendix.txt \ No newline at end of file +See C:/Users/DanMoisan/repos/TaskMaster/.claude/worktrees/agent-a7d0dc0826acbf47e/artifacts/pr_context.appendix.txt \ No newline at end of file diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/code-review.2026-09-03T12-15.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/code-review.2026-09-03T12-15.md new file mode 100644 index 000000000..83d0a59fd --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/code-review.2026-09-03T12-15.md @@ -0,0 +1,120 @@ +# Code Review — invoke-mstestwithcoverage-threshold-before-setcontent (#565) + +- Timestamp: 2026-09-03T12-15 +- Scope: `scripts/vscode/Invoke-MSTestWithCoverage.ps1`, `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` + (full `origin/main...HEAD` diff, `b13d5b7b` merge-base) + +## Summary + +The change is a two-statement reorder inside `Invoke-MSTestWithCoverageMain`: `Set-Content` +(persisting the post-processed Cobertura XML) now runs immediately before +`Assert-CoberturaLineCoverageThreshold` (the threshold gate that can throw). Previously the +assertion ran first, so a failing (sub-threshold) run left the raw, un-post-processed +`dotnet-coverage` output on disk instead of the document the assertion actually judged. No logic, +parameter, or return-value change; no new function, class, or file. + +```diff +- Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent +- + Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline ++ ++ Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent +``` + +## Design Principles (`.claude/rules/general-code-change.md`) + +- **Simplicity first**: the fix is the simplest possible resolution — a pure statement swap, no + added indirection, no `try`/`finally` wrapper (the spec explicitly prohibits one, and the diff + correctly avoids it). Assessed: compliant. +- **Separation of concerns**: unaffected — `Set-Content` (I/O) and `Assert-...Threshold` (pure + read-and-throw) remain two independent statements with no new coupling. +- **Fail fast and explicitly**: preserved — `Assert-CoberturaLineCoverageThreshold` still throws + under the identical three conditions (missing, non-numeric, out-of-range/sub-threshold + line-rate) with unchanged message text, confirmed unmodified in + `Invoke-MSTestWithCoverage.Threshold.ps1`. + +## Correctness of the Fix + +The reorder is behaviorally sound for the stated defect: `$processedXmlContent` is already fully +computed before either statement runs (both call orders read the identical string), and +`Assert-CoberturaLineCoverageThreshold` operates on its own local `[xml]` copy parsed from the +string argument — it has no side effect that `Set-Content` could observe, and vice versa. Swapping +the two statements is therefore safe: the passing-run behavior is unchanged (both statements +execute either way), and the failing-run artifact-on-disk is now the post-processed document +instead of the raw one. No case was found where the reorder could change behavior beyond the +stated fix. + +## Test Quality (`.claude/rules/general-unit-test.md`, `.claude/rules/powershell.md`) + +The new test (`tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1:416-422`): + +```powershell +It 'persists the post-processed Cobertura document before the threshold assertion can throw on a sub-threshold run' { + Mock ConvertTo-KoverageCoberturaXml { '' } + + { Invoke-MSTestWithCoverageMain -ScriptRoot $script:scriptDir } | Should -Throw + + Should -Invoke Set-Content -Times 1 -Exactly +} +``` + +- **Correctly proves ordering, not just presence**: `Assert-CoberturaLineCoverageThreshold` is + deliberately left unmocked in this `It` (the `BeforeEach` never mocks it; only one other `It` + block mocks it, and Pester mocks do not leak across `It`s), so it genuinely evaluates the + sub-threshold fixture and throws. If the statement order regressed to pre-fix, the throw would + occur before `Set-Content` is reached and the `Should -Invoke -Times 1 -Exactly` assertion would + fail with "called 0 times" — exactly what the `expect-fail-run` evidence artifact demonstrates. + This is a load-bearing regression test, not a tautology. +- **Independence/isolation**: follows the file's existing `BeforeEach` mocking conventions + (Resolve-Path, Test-Path, Invoke-VsWhereExe, Get-Command, Get-ChildItem, + Invoke-DotnetCoverageCollection, Get-Content, ConvertTo-KoverageCoberturaXml, Set-Content all + mocked); no external process, network, or filesystem dependency; no temp files. +- **Determinism**: fixture is a fixed literal XML string; no randomness or wall-clock dependency. +- **Naming/documentation**: the `It` name states the scenario and expected outcome in full + sentence form, consistent with the surrounding `Describe` block's style. +- **Correct mock-parameter targeting**: the spec called out that `Set-Content` in this file is + invoked with `-Path` (not `-LiteralPath`) — `Should -Invoke Set-Content -Times 1 -Exactly` with + no parameter filter is a valid, unambiguous assertion here because the mock intercepts the + cmdlet regardless of which path parameter is used; no filter mismatch risk. + +One observation, non-blocking: the assertion only checks that `Set-Content` was invoked, not that +it was invoked with the pre-throw value of `$processedXmlContent` specifically (i.e., it does not +assert on the argument value). This is acceptable because `ConvertTo-KoverageCoberturaXml` is +mocked to a single fixed return value in this test, so there is no other value `Set-Content` could +plausibly have been called with; a stricter `-ParameterFilter` would add no discriminating power +here. + +## Test Insertion Point + +The new test is inserted between the pre-existing `It 'fails when the search root cannot be +found'` and the #733-added `It 'excludes assemblies discovered under a .claude worktree segment'`, +matching the plan's primary anchor. Independently confirmed via +`git diff origin/main...HEAD -- tests/.../Invoke-MSTest.RunSettings.Tests.ps1`: a single +insertion-only hunk, no other test's body touched. The #733 test's mocks and assertions are +identical before and after this insertion. + +## Style / Formatting / Linting + +- PoshQC format: 0 auto-fixes on both files (`git status --porcelain` empty before and after). +- PSScriptAnalyzer (via `run_poshqc_analyze`): 0 diagnostics on both files. +- No new PSScriptAnalyzer suppressions introduced. + +## File Size + +`Invoke-MSTestWithCoverage.ps1`: 350 lines (well under 500). `Invoke-MSTest.RunSettings.Tests.ps1`: +496 lines — under the 500-line cap but only 4 lines of headroom remain. This is not a defect +introduced by this PR (the file was already large pre-branch, and #733 added several tests to it +independently of this fix), but future additions to this specific test file should consider +splitting it, since the next similarly-sized addition would exceed the limit. + +## Naming, Comments, Documentation + +No new identifiers were introduced. The existing block comment above the reordered statements +(`# Post-process the Cobertura XML for Koverage compatibility: ...`) still accurately describes +the three-step post-processing pipeline and required no update, since the reorder does not change +what the three enumerated steps do — only when the threshold check runs relative to persistence. + +## Findings + +No blocking findings. No non-blocking code-quality findings beyond the file-size headroom note +above (informational only). diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/branch-commit-baseline.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/branch-commit-baseline.2026-09-03T11-09.md new file mode 100644 index 000000000..f28fa90ae --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/branch-commit-baseline.2026-09-03T11-09.md @@ -0,0 +1,22 @@ +Timestamp: 2026-09-03T11-09 +Command: git rev-parse --abbrev-ref HEAD +EXIT_CODE: 0 +Output Summary: bug/invoke-mstestwithcoverage-threshold-before-setcontent-565 + +Command: git rev-parse HEAD +EXIT_CODE: 0 +Output Summary: dc5e8c0fa39b27b3d5523d6e82daafe8c844ae12 + +Command: git status --porcelain +EXIT_CODE: 0 +Output Summary (verbatim): +?? docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/ + +BASELINE_SHA = dc5e8c0fa39b27b3d5523d6e82daafe8c844ae12 + +Note: BASELINE_SHA is recorded per the delegating orchestrator's directive as the current HEAD +of the reconciled branch (merge commit onto PR #748 / issue #733's merge, b13d5b7b), not the +plan's original 5ebaaf10-era assumption. This is a record of state, not an expectation any later +task asserts against. The only untracked path at capture time is this plan's own evidence +folder, created by this task's Write operations in the same pass; no other worktree drift +exists. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/citation-verification.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/citation-verification.2026-09-03T11-09.md new file mode 100644 index 000000000..4aaa06c73 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/citation-verification.2026-09-03T11-09.md @@ -0,0 +1,45 @@ +Timestamp: 2026-09-03T11-09 + +Check 1 (scripts/vscode/Invoke-MSTestWithCoverage.ps1, plan-assumed lines 338-345): +Does NOT match verbatim at the plan's assumed line numbers. This is the expected outcome per +the delegating orchestrator's pre-derived delta (issue #733 / PR #748 inserted a `Get-Content` +statement ahead of `ConvertTo-KoverageCoberturaXml`, producing a uniform +1 shift). Fallback +branch taken: located the block by its unique surrounding comment +`# Post-process the Cobertura XML for Koverage compatibility:` inside +`Invoke-MSTestWithCoverageMain`. + +Drifted-anchor verbatim excerpt actually found (lines 339-345, read directly from +scripts/vscode/Invoke-MSTestWithCoverage.ps1 in this pass): +``` +339 Write-Output 'Post-processing coverage XML for Koverage compatibility...' +340 $xmlContent = Get-Content $resolvedOutputPath -Raw -Encoding UTF8 +341 $processedXmlContent = ConvertTo-KoverageCoberturaXml -XmlContent $xmlContent -RepoRoot $repoRoot +342 Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent +343 +344 Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline +345 Write-Output "Done. Coverage artifact: $resolvedOutputPath" +``` +Drifted locators for [P2-T1]: swap actual line 342 (`Assert-CoberturaLineCoverageThreshold`) +with actual line 344 (`Set-Content`), not the plan's original 341/343. + +Check 2 (scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1, plan-assumed line 487): +Does NOT match — a file relocation, not merely a line shift. `Assert-CoberturaLineCoverageThreshold` +no longer resides in Invoke-MSTestWithCoverage.Helpers.ps1 at all. Fallback branch taken: located +the function by the unique string `is below the required 80% threshold.`, found in a NEW file, +scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1, extracted by issue #733. Confirmed +Invoke-MSTestWithCoverage.Helpers.ps1 line 4 dot-sources it +(`. (Join-Path $PSScriptRoot 'Invoke-MSTestWithCoverage.Threshold.ps1')`), and +Invoke-MSTestWithCoverage.ps1 line 261 still dot-sources Helpers.ps1, so the function resolves +transitively with no call-site change required. + +Drifted-anchor verbatim excerpt actually found (Invoke-MSTestWithCoverage.Threshold.ps1, lines +around 52-54, read directly in this pass): +``` +52 if ($percentage -lt 80) { +... +54 throw "Cobertura line coverage $formattedPercentage% is below the required 80% threshold." +``` +The threshold value (80) and message text are byte-for-byte unchanged from the plan's original +assumption; only the containing file differs. + +Branch taken: drifted-anchor (fallback) for both Check 1 and Check 2. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/pester-coverage.2026-09-03T11-09.xml b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/pester-coverage.2026-09-03T11-09.xml new file mode 100644 index 000000000..d44953d09 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/pester-coverage.2026-09-03T11-09.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-feature-documents-read.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-feature-documents-read.2026-09-03T11-09.md new file mode 100644 index 000000000..82465c742 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-feature-documents-read.2026-09-03T11-09.md @@ -0,0 +1,48 @@ +Timestamp: 2026-09-03T11-09 + +Work Mode: full-bug +AC Source: docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md (sole source) + +Files Read: +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/issue.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md +- docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/research/invoke-mstestwithcoverage-ordering-fix.2026-09-02T09-00.md +- scripts/vscode/Invoke-MSTestWithCoverage.ps1 +- scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 +- tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1 + +Post-#733 drift note (recorded here per the delegating orchestrator's directive, re-verified +directly against the reconciled tree rather than trusted from the delegation prompt): + +- scripts/vscode/Invoke-MSTestWithCoverage.ps1 now reads, at lines 339-345 (a uniform +1 shift + from the plan's assumed 338-345 anchor, because a new `Get-Content` statement now precedes + `ConvertTo-KoverageCoberturaXml`): + 339: Write-Output 'Post-processing coverage XML for Koverage compatibility...' + 340: $xmlContent = Get-Content $resolvedOutputPath -Raw -Encoding UTF8 + 341: $processedXmlContent = ConvertTo-KoverageCoberturaXml -XmlContent $xmlContent -RepoRoot $repoRoot + 342: Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent + 343: (blank) + 344: Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline + 345: Write-Output "Done. Coverage artifact: $resolvedOutputPath" + The ordering defect (Assert before Set-Content) is confirmed still present: swap target is + actual lines 342 (Assert) and 344 (Set-Content), not the plan's original 341/343. + +- scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 no longer contains + `Assert-CoberturaLineCoverageThreshold`. Issue #733 extracted it into a new file + scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1, dot-sourced from + Invoke-MSTestWithCoverage.Helpers.ps1 line 4 (`. (Join-Path $PSScriptRoot + 'Invoke-MSTestWithCoverage.Threshold.ps1')`), which Invoke-MSTestWithCoverage.ps1 itself + dot-sources transitively at line 261 via Helpers.ps1. No call-site change is required. The + threshold value and message text are byte-for-byte unchanged: confirmed at + scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1 line 52 + (`if ($percentage -lt 80) {`) and line 54 + (`throw "Cobertura line coverage $formattedPercentage% is below the required 80% threshold."`). + +- tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1's `Describe + 'Invoke-MSTestWithCoverageMain'` block (opens line 346) now contains, in order: `It 'fails + when the search root cannot be found'` (lines 409-414), a new #733 test `It 'excludes + assemblies discovered under a .claude worktree segment'` (lines 416-442), then the Describe + block's closing `}` (line 443). This is confirmed directly against the reconciled tree. + +BASELINE_SHA for this execution: dc5e8c0fa39b27b3d5523d6e82daafe8c844ae12 (recorded formally in +P0-T3; re-derived here, not carried forward from the plan's original 5ebaaf10-era assumption). diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-instructions-read.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-instructions-read.2026-09-03T11-09.md new file mode 100644 index 000000000..d4d541a76 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-instructions-read.2026-09-03T11-09.md @@ -0,0 +1,19 @@ +Timestamp: 2026-09-03T11-09 + +Policy Order: +1. CLAUDE.md +2. .claude/rules/general-code-change.md +3. .claude/rules/general-unit-test.md +4. .claude/rules/powershell.md +5. .claude/rules/quality-tiers.md + +Files Read: +- CLAUDE.md +- .claude/rules/general-code-change.md +- .claude/rules/general-unit-test.md +- .claude/rules/powershell.md +- .claude/rules/quality-tiers.md + +Note: CLAUDE.md, general-code-change.md, general-unit-test.md, and quality-tiers.md were +already read in full as part of the delegating orchestrator's injected system context for +this session. `.claude/rules/powershell.md` is read directly in this task (see below). diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-analyze.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-analyze.2026-09-03T11-09.md new file mode 100644 index 000000000..823ab9212 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-analyze.2026-09-03T11-09.md @@ -0,0 +1,15 @@ +Timestamp: 2026-09-03T11-09 +Command: mcp__drm-copilot__run_poshqc_analyze (scan_folders: scripts/vscode/Invoke-MSTestWithCoverage.ps1, tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1); paired direct run: pwsh -NoProfile -Command 'Invoke-ScriptAnalyzer -Path "/scripts/vscode/Invoke-MSTestWithCoverage.ps1"; Invoke-ScriptAnalyzer -Path "/tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1"' +EXIT_CODE: 0 +Output Summary: MCP Result: ok:true. Direct paired run reports zero diagnostics for both files +(File1Count=0 File2Count=0, PSScriptAnalyzer defaults — no repository-local +PSScriptAnalyzerSettings.psd1 found). + +MCP tool result: {"ok":true,"tool":"run_poshqc_analyze","workspace_root":"","summary":"Ran bundled PoshQC analyze against '' with 2 selected scan folder(s)."} + +Direct paired run (absolute paths under the item worktree root, since the Bash tool's ambient +working directory is the session worktree, not the item worktree): +- scripts/vscode/Invoke-MSTestWithCoverage.ps1: 0 diagnostics. +- tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1: 0 diagnostics. + +Verbatim diagnostic list (baseline set for [P4-T2] comparison): EMPTY for both files. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-format.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-format.2026-09-03T11-09.md new file mode 100644 index 000000000..bbc2b38cd --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-format.2026-09-03T11-09.md @@ -0,0 +1,19 @@ +Timestamp: 2026-09-03T11-09 +Command: mcp__drm-copilot__run_poshqc_format (scan_folders: scripts/vscode/Invoke-MSTestWithCoverage.ps1, tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1) +EXIT_CODE: 0 +Output Summary: MCP Result: ok:true. Neither owned file was rewritten (before/after git status --porcelain for the two owned files is identical: empty in both cases). + +Before-run `git status --porcelain -- scripts/vscode/Invoke-MSTestWithCoverage.ps1 tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`: +(empty) + +MCP tool result: {"ok":true,"tool":"run_poshqc_format","workspace_root":"","summary":"Ran bundled PoshQC format against '' with 2 selected scan folder(s)."} + +After-run `git status --porcelain -- scripts/vscode/Invoke-MSTestWithCoverage.ps1 tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`: +(empty) + +Folder-coercion safety check `git status --porcelain -uall -- scripts/vscode tests/scripts/vscode` (immediately after the run): +(empty) — no file in either containing folder was modified, so the tool did not coerce the scan +to the containing folder. + +Owned-file diff record: neither owned file was reported modified by the after-run status check +above, so per this task's acceptance text: No formatter-attributable diff for either owned file. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-test.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-test.2026-09-03T11-09.md new file mode 100644 index 000000000..466b29f88 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-test.2026-09-03T11-09.md @@ -0,0 +1,24 @@ +Timestamp: 2026-09-03T11-09 +Command: mcp__drm-copilot__run_poshqc_test (scan_folders: tests/scripts/vscode); paired direct run: pwsh -NoProfile -Command 'Import-Module Pester -MinimumVersion 5.0; $c = New-PesterConfiguration; $c.Run.Path = "/tests/scripts/vscode"; $c.Run.PassThru = $true; $c.Output.Verbosity = "Detailed"; $c.CodeCoverage.Enabled = $true; $c.CodeCoverage.Path = "/scripts/vscode/Invoke-MSTestWithCoverage.ps1"; $c.CodeCoverage.OutputPath = "/pester-coverage.2026-09-03T11-09.xml"; $r = Invoke-Pester -Configuration $c; ...' +EXIT_CODE: 0 +Output Summary: MCP Result: ok:true. Passed=92 Failed=0 Skipped=0. +MainScriptCommands=111 Executed=100 Percent=90.09 (line coverage of +scripts/vscode/Invoke-MSTestWithCoverage.ps1). branch coverage: not emitted by Pester 5. + +MCP tool result: {"ok":true,"tool":"run_poshqc_test","workspace_root":"","summary":"Ran bundled PoshQC test against '' with 1 selected scan folder(s)."} + +Direct Pester run summary (verbatim tail lines): +Tests completed in 15.88s +Tests Passed: 92, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0 +Passed=92 Failed=0 Skipped=0 +MainScriptCommands=111 Executed=100 Percent=90.09 + +Pester Coverage Artifact: docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/pester-coverage.2026-09-03T11-09.xml + +Suite-composition drift note: Pester discovery found 10 files (92 tests) under +tests/scripts/vscode, not the 5 files the plan's Conventions section describes. This is expected +drift: issue #733 / PR #748 added new test files (confirmed present: +Invoke-MSTest.AssemblyDiscovery.Tests.ps1, Invoke-MSTest.Main.Tests.ps1, and others) to the +reconciled tree after this plan was authored. This does not affect the fix's scope (the plan's +Scope Prohibitions list only the two files this plan edits) and Failed=0 confirms no regression +across the full, larger suite as it exists today. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/issue-updates/issue-565.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/issue-updates/issue-565.2026-09-03T11-09.md new file mode 100644 index 000000000..936fb7cee --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/issue-updates/issue-565.2026-09-03T11-09.md @@ -0,0 +1,26 @@ +Timestamp: 2026-09-03T11-09 + +PostedAs: comment + +Exact text posted: +--- +Fixed on branch `bug/invoke-mstestwithcoverage-threshold-before-setcontent-565`. + +`scripts/vscode/Invoke-MSTestWithCoverage.ps1`'s `Invoke-MSTestWithCoverageMain` now persists the +post-processed Cobertura document (`Set-Content`) immediately after `$processedXmlContent` is +computed, before `Assert-CoberturaLineCoverageThreshold` is called. On a sub-threshold run the +artifact left on disk at `-CoverageOutput` is now the same post-processed document the threshold +assertion judged, not the raw `dotnet-coverage` output. + +A new Pester test in `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` +(`Describe 'Invoke-MSTestWithCoverageMain'`) proves the ordering deterministically: it mocks +`ConvertTo-KoverageCoberturaXml` to return a sub-threshold fixture, asserts the call throws, and +asserts `Set-Content` was invoked exactly once before the throw. + +No change to the 80% threshold value or its message text +(`Assert-CoberturaLineCoverageThreshold`, now in `scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1` +following issue #733's extraction). Full PowerShell toolchain (PoshQC format, PSScriptAnalyzer, +Pester) passes cleanly with no regression; coverage of the changed file is unchanged at 90.09%. +--- + +GitHub comment URL: https://github.com/drmoisan/TaskMaster/issues/565#issuecomment-5524940952 diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/ac-status-summary.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/ac-status-summary.2026-09-03T11-09.md new file mode 100644 index 000000000..5986a642b --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/ac-status-summary.2026-09-03T11-09.md @@ -0,0 +1,8 @@ +Timestamp: 2026-09-03T11-09 + +### Acceptance Criteria Status +- Source: docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md +- Total AC items: 6 +- Checked off (delivered): 6 +- Remaining (unchecked): 0 +- Items remaining: none diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/final-clean-tree.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/final-clean-tree.2026-09-03T11-09.md new file mode 100644 index 000000000..b2f0d0485 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/final-clean-tree.2026-09-03T11-09.md @@ -0,0 +1,7 @@ +Timestamp: 2026-09-03T11-09 +Command: git status --porcelain +EXIT_CODE: 0 + +Output Summary: empty output. The working tree is clean; everything intentional (the production +fix, the test insertion, spec.md/plan.md check-offs, and every evidence artifact this plan +requires) is committed on branch bug/invoke-mstestwithcoverage-threshold-before-setcontent-565. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/fix-diff-verification.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/fix-diff-verification.2026-09-03T11-09.md new file mode 100644 index 000000000..a4a4c6493 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/fix-diff-verification.2026-09-03T11-09.md @@ -0,0 +1,64 @@ +Timestamp: 2026-09-03T11-09 + +Drift note: per the delegating orchestrator's directive item 2, the swap target is the +drifted-anchor equivalent of the plan's original lines 341/343 — actual lines 342 +(Assert-CoberturaLineCoverageThreshold) and 344 (Set-Content), located via the unique surrounding +comment `# Post-process the Cobertura XML for Koverage compatibility:` (matching [P0-T4]'s +fallback technique). + +(a) Direct post-edit content read of scripts/vscode/Invoke-MSTestWithCoverage.ps1, lines 339-345 +(read directly in this pass, after the edit): +``` +339 Write-Output 'Post-processing coverage XML for Koverage compatibility...' +340 $xmlContent = Get-Content $resolvedOutputPath -Raw -Encoding UTF8 +341 $processedXmlContent = ConvertTo-KoverageCoberturaXml -XmlContent $xmlContent -RepoRoot $repoRoot +342 Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline +343 +344 Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent +345 Write-Output "Done. Coverage artifact: $resolvedOutputPath" +``` +Confirmed line-for-line: line 341 (`$processedXmlContent = ConvertTo-KoverageCoberturaXml ...`) +is unchanged; line 342 now reads `Set-Content -Path $resolvedOutputPath -Value +$processedXmlContent -Encoding UTF8 -NoNewline`; line 343 is still blank; line 344 now reads +`Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent`; line 345 is unchanged +(`Write-Output "Done. Coverage artifact: $resolvedOutputPath"`). + +(b) `git diff --name-only dc5e8c0fa39b27b3d5523d6e82daafe8c844ae12` paired with +`git status --porcelain` (both run immediately after this task's edit), verbatim changed-path +list at this point in the plan: +``` +docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/branch-commit-baseline.2026-09-03T11-09.md +docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/citation-verification.2026-09-03T11-09.md +docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/pester-coverage.2026-09-03T11-09.xml +docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-feature-documents-read.2026-09-03T11-09.md +docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-instructions-read.2026-09-03T11-09.md +docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-analyze.2026-09-03T11-09.md +docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-format.2026-09-03T11-09.md +docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-test.2026-09-03T11-09.md +docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/p1-t1-anchor-resolution.2026-09-03T11-09.md +docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/expect-fail-run.2026-09-03T11-09.md +docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/plan.2026-09-02T08-59.md +scripts/vscode/Invoke-MSTestWithCoverage.ps1 +tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1 +``` +`git status --porcelain` at the same point shows `M scripts/vscode/Invoke-MSTestWithCoverage.ps1` +as the only worktree-vs-index delta (the remaining paths above are already staged/committed from +Phase 0/Phase 1). Confirmed: the only production file in the diff is +scripts/vscode/Invoke-MSTestWithCoverage.ps1 (this task's edit); the only test file is +tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1 (already modified by [P1-T1]); every +other path is this feature folder's own evidence/plan artifact. +scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 and +scripts/vscode/Invoke-MSTestWithCoverage.ClosureFilter.ps1 are absent from the diff. + +(c) `git diff dc5e8c0fa39b27b3d5523d6e82daafe8c844ae12 -- scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1`: +(empty) + +`git diff dc5e8c0fa39b27b3d5523d6e82daafe8c844ae12 -- scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1` +(per the delegating orchestrator's directive item 5 — this is the file that actually carries the +threshold text/value now, after issue #733's extraction): +(empty) + +Both confirm line 487's original threshold text/value (now at +Invoke-MSTestWithCoverage.Threshold.ps1 line 52/54, per [P0-T4]'s Check 2 drifted-anchor finding) +is byte-for-byte unchanged, and the dot-source chain at Helpers.ps1 line 4 / +Invoke-MSTestWithCoverage.ps1 line 261 is untouched. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/p1-t1-anchor-resolution.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/p1-t1-anchor-resolution.2026-09-03T11-09.md new file mode 100644 index 000000000..bf3b4feb0 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/p1-t1-anchor-resolution.2026-09-03T11-09.md @@ -0,0 +1,23 @@ +Timestamp: 2026-09-03T11-09 + +[P1-T1] citation-drift record (per the delegating orchestrator's directive item 6). + +The plan's insertion anchor ("immediately after `It 'fails when the search root cannot be +found'` and before the Describe's closing `}`") is ambiguous in the reconciled tree because +issue #733 / PR #748 added a third test, `It 'excludes assemblies discovered under a .claude +worktree segment'`, between that anchor test and the Describe block's closing brace (in order: +`fails when the search root cannot be found` at lines 409-414, the new #733 test at lines +416-442, Describe close at line 443, all confirmed by direct read before this task's edit). + +Resolution: the PRIMARY anchor was applied literally. The new test was inserted immediately +after the `fails when the search root cannot be found` block's closing `}` (between line 414 and +line 416), i.e. BEFORE the #733 `.claude worktree` test, not at the very end of the Describe +block. + +Verification: `git diff dc5e8c0fa39b27b3d5523d6e82daafe8c844ae12 -- tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` +shows a single insertion-only hunk of exactly 8 lines (3 blank lines and 5 code lines) at the +expected location — an insertion between the two pre-existing tests, not at the end of the file. +`git status --porcelain` shows `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` as the +only modified path. No [P0-T5] formatter-attributable diff was recorded for this file (baseline +artifact recorded "No formatter-attributable diff for either owned file"), so no additional hunk +is expected or present. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/coverage-delta.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/coverage-delta.2026-09-03T11-09.md new file mode 100644 index 000000000..35dca3a3d --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/coverage-delta.2026-09-03T11-09.md @@ -0,0 +1,23 @@ +Timestamp: 2026-09-03T11-09 + +Coverage delta verification for scripts/vscode/Invoke-MSTestWithCoverage.ps1: + +- [P0-T7] baseline Percent=90.09 (MainScriptCommands=111 Executed=100) +- [P4-T3] final Percent=90.09 (MainScriptCommands=111 Executed=100) + +The post-change Percent= (90.09) is greater than or equal to the baseline Percent= (90.09): no +regression (delta = 0.00 percentage points; identical MainScriptCommands total, confirming the +statement reorder did not add or remove any measurable commands). + +Name-check: the swapped lines (the Set-Content and Assert-CoberturaLineCoverageThreshold calls, +at drifted-anchor lines 342/344) are among the commands counted as executed in the [P4-T3] run. +Both tests that exercise them are present in that run's Output Summary with Result=Passed: +- Invoke-MSTestWithCoverageMain.collects and post-processes coverage on the fully mocked main happy path + (pre-existing test; per [P3-T1]'s pass-after-run artifact, this test's Set-Content and + Assert-CoberturaLineCoverageThreshold call sites are both invoked exactly once on the happy + path). +- Invoke-MSTestWithCoverageMain.persists the post-processed Cobertura document before the + threshold assertion can throw on a sub-threshold run (the new [P1-T1] test; exercises the + Set-Content call site on the sub-threshold/throwing path). + +Outcome: PASS (post-change percent is not lower than baseline). diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/pester-coverage.iter1.2026-09-03T11-09.xml b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/pester-coverage.iter1.2026-09-03T11-09.xml new file mode 100644 index 000000000..0f02bb107 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/pester-coverage.iter1.2026-09-03T11-09.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-analyze.iter1.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-analyze.iter1.2026-09-03T11-09.md new file mode 100644 index 000000000..970657bb1 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-analyze.iter1.2026-09-03T11-09.md @@ -0,0 +1,12 @@ +Timestamp: 2026-09-03T11-09 +Command: mcp__drm-copilot__run_poshqc_analyze (scan_folders: scripts/vscode/Invoke-MSTestWithCoverage.ps1, tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1); paired direct run identical to [P0-T6]. +EXIT_CODE: 0 +Output Summary: MCP Result: ok:true. Direct paired run reports zero diagnostics for both files +(File1Count=0 File2Count=0). + +MCP tool result: {"ok":true,"tool":"run_poshqc_analyze","workspace_root":"","summary":"Ran bundled PoshQC analyze against '' with 2 selected scan folder(s)."} + +Verbatim diagnostic list: EMPTY for both files. + +Regression check vs [P0-T6] baseline (both files: 0 diagnostics): 0 <= 0 for both files, zero new +rule violations for either file. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-format.iter1.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-format.iter1.2026-09-03T11-09.md new file mode 100644 index 000000000..45f11af26 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-format.iter1.2026-09-03T11-09.md @@ -0,0 +1,17 @@ +Timestamp: 2026-09-03T11-09 +Command: mcp__drm-copilot__run_poshqc_format (scan_folders: scripts/vscode/Invoke-MSTestWithCoverage.ps1, tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1) +EXIT_CODE: 0 +Output Summary: MCP Result: ok:true. Re-run after the [P2-T1] fix and [P1-T1] test insertion are +both in place. Neither owned file was rewritten (before/after git status --porcelain identical: +empty in both cases). No iteration restart needed. + +Before-run `git status --porcelain -- scripts/vscode/Invoke-MSTestWithCoverage.ps1 tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`: +(empty) + +MCP tool result: {"ok":true,"tool":"run_poshqc_format","workspace_root":"","summary":"Ran bundled PoshQC format against '' with 2 selected scan folder(s)."} + +After-run `git status --porcelain -- scripts/vscode/Invoke-MSTestWithCoverage.ps1 tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`: +(empty) + +Folder-coercion safety check `git status --porcelain -uall -- scripts/vscode tests/scripts/vscode`: +(empty) diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-test.iter1.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-test.iter1.2026-09-03T11-09.md new file mode 100644 index 000000000..331fd7021 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-test.iter1.2026-09-03T11-09.md @@ -0,0 +1,16 @@ +Timestamp: 2026-09-03T11-09 +Command: mcp__drm-copilot__run_poshqc_test (scan_folders: tests/scripts/vscode); paired direct run identical to [P0-T7], with CodeCoverage.OutputPath = docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/pester-coverage.iter1.2026-09-03T11-09.xml +EXIT_CODE: 0 +Output Summary: MCP Result: ok:true. Passed=93 Failed=0 Skipped=0 across all ten test files (one +more test than the [P0-T7] baseline's 92, accounting for the new [P1-T1] regression test). +MainScriptCommands=111 Executed=100 Percent=90.09 (line coverage of +scripts/vscode/Invoke-MSTestWithCoverage.ps1). branch coverage: not emitted by Pester 5. + +Direct Pester run summary (verbatim tail lines): +Tests Passed: 93, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0 +Passed=93 Failed=0 Skipped=0 +MainScriptCommands=111 Executed=100 Percent=90.09 + +Pester Coverage Artifact: docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/pester-coverage.iter1.2026-09-03T11-09.xml + +Failed=0 across all five (now ten, per the [P0-T7] suite-composition drift note) test files. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/expect-fail-run.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/expect-fail-run.2026-09-03T11-09.md new file mode 100644 index 000000000..2978c9553 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/expect-fail-run.2026-09-03T11-09.md @@ -0,0 +1,51 @@ +Timestamp: 2026-09-03T11-09 +Command: mcp__drm-copilot__run_poshqc_test (scan_folders: tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1); paired direct run: pwsh -NoProfile -Command 'Import-Module Pester -MinimumVersion 5.0; $c = New-PesterConfiguration; $c.Run.Path = "/tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1"; $c.Run.PassThru = $true; $c.Output.Verbosity = "Detailed"; $r = Invoke-Pester -Configuration $c; ...; if ($r.FailedCount -gt 0) { exit 1 } else { exit 0 }' +MCP Result: ok:false ("Command exited with code 1." — this is the MCP wrapper surfacing the paired direct run's own nonzero exit; a failing regression run is the expected outcome for this [expect-fail] task, per the plan's own note that this MCP tool carries no per-test verdict of its own). +EXIT_CODE: 1 + +Output Summary: +Passed=27 Failed=1 Skipped=0 + +New test result: +Invoke-MSTestWithCoverageMain.persists the post-processed Cobertura document before the threshold +assertion can throw on a sub-threshold run => Failed + +Observed failure detail (verbatim from the direct Pester run): + at Should -Invoke Set-Content -Times 1 -Exactly, tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1:421 + Expected Set-Content to be called 1 times exactly, but was called 0 times + +This confirms the expected pre-fix RED behavior: the outer `{ ... } | Should -Throw` assertion +passes (an exception does occur, since `Assert-CoberturaLineCoverageThreshold` at actual line 342 +runs ahead of `Set-Content` at actual line 344 and throws on the sub-threshold fixture), and the +failure is specifically the `Should -Invoke Set-Content -Times 1 -Exactly` assertion — not a +parse or discovery error. + +Every other test in the file is Passed (27 of 28), confirming no unrelated regression from the +test insertion: +Resolve-RunSettingsPath.resolves the off-root CLI TaskMaster.cli.runsettings path when present => Passed +Resolve-RunSettingsPath.fails fast with a specific error naming the missing path when absent => Passed +Get-VsTestArgumentList (Invoke-MSTest.ps1).includes /Settings: pointing at the off-root CLI TaskMaster.cli.runsettings => Passed +Get-VsTestArgumentList (Invoke-MSTest.ps1).preserves the test assemblies and /InIsolation alongside /Settings: => Passed +Get-VsTestArgumentList (Invoke-MSTest.ps1).appends the /TestCaseFilter excluding the LiveOutlook category => Passed +Invoke-VsTestExe wrapper seam (Invoke-MSTest.ps1).passes the constructed argument list through the mockable seam => Passed +Get-DotnetCoverageArgumentList (Invoke-MSTestWithCoverage.ps1).includes the inner vstest /Settings: pointing at the off-root CLI TaskMaster.cli.runsettings => Passed +Get-DotnetCoverageArgumentList (Invoke-MSTestWithCoverage.ps1).preserves the distinct outer --settings coverage.config (instrumentation excludes) => Passed +Get-DotnetCoverageArgumentList (Invoke-MSTestWithCoverage.ps1).places the inner /Settings: after the -- separator and the vstest path => Passed +Get-DotnetCoverageArgumentList (Invoke-MSTestWithCoverage.ps1).appends the /TestCaseFilter excluding the LiveOutlook category to the inner vstest args => Passed +Invoke-DotnetCoverageExe wrapper seam (Invoke-MSTestWithCoverage.ps1).passes the constructed argument list through the mockable seam => Passed +Invoke-MSTestWithCoverage derived settings.Derived coverage settings lifecycle.retains canonical module exclusions and adds the test assembly exclusion exactly once => Passed +Invoke-MSTestWithCoverage derived settings.Derived coverage settings lifecycle.uses the derived settings path and preserves all eight test assemblies after the vstest boundary => Passed +Invoke-MSTestWithCoverage derived settings.Derived coverage settings lifecycle.removes the derived settings after successful collection without writing the canonical file => Passed +Invoke-MSTestWithCoverage derived settings.Derived coverage settings lifecycle.removes the derived settings after failed collection without writing the canonical file => Passed +Invoke-MSTestWithCoverage main wrapper seam.exposes a callable main entrypoint for isolated mocked execution => Passed +Invoke-MSTestWithCoverage main wrapper seam.exposes a callable vswhere wrapper for executable-free tests => Passed +Invoke-MSTestWithCoverageMain.uses only mocked discovery and builds the vswhere command for the main happy path => Passed +Invoke-MSTestWithCoverageMain.does not start coverage collection when NoExecute is supplied => Passed +Invoke-MSTestWithCoverageMain.collects and post-processes coverage on the fully mocked main happy path => Passed +Invoke-MSTestWithCoverageMain.passes the generated Cobertura result to the threshold evaluator before completing successfully => Passed +Invoke-MSTestWithCoverageMain.fails when the search root cannot be found => Passed +Invoke-MSTestWithCoverageMain.excludes assemblies discovered under a .claude worktree segment => Passed +Invoke-MSTestWithCoverage isolated error paths.fails when coverage settings have no module exclusion node => Passed +Invoke-MSTestWithCoverage isolated error paths.fails when coverage settings repeat the test assembly exclusion => Passed +Invoke-MSTestWithCoverage isolated error paths.fails when the derived path equals the canonical coverage path => Passed +Invoke-MSTestWithCoverage isolated error paths.fails when dotnet coverage returns a nonzero exit code => Passed diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/helpers-boundary-regression.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/helpers-boundary-regression.2026-09-03T11-09.md new file mode 100644 index 000000000..df8aba457 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/helpers-boundary-regression.2026-09-03T11-09.md @@ -0,0 +1,39 @@ +Timestamp: 2026-09-03T11-09 +Command: mcp__drm-copilot__run_poshqc_test (scan_folders: tests/scripts/vscode/Invoke-MSTestWithCoverage.Helpers.Tests.ps1); paired direct run: pwsh -NoProfile -Command 'Import-Module Pester -MinimumVersion 5.0; $c = New-PesterConfiguration; $c.Run.Path = "/tests/scripts/vscode/Invoke-MSTestWithCoverage.Helpers.Tests.ps1"; $c.Run.PassThru = $true; $c.Output.Verbosity = "Detailed"; $r = Invoke-Pester -Configuration $c; ...; if ($r.FailedCount -gt 0) { exit 1 } else { exit 0 }' +MCP Result: ok:true +EXIT_CODE: 0 + +Citation-drift note (a further instance of the #733 relocation already flagged for [P0-T4] Check +2 / [P2-T1] item (c), applying the plan's own fallback technique — locate by unique content, +record drifted location): `Describe 'Assert-CoberturaLineCoverageThreshold'` no longer resides in +tests/scripts/vscode/Invoke-MSTestWithCoverage.Helpers.Tests.ps1 at all. Located by searching for +the unique function name `Assert-CoberturaLineCoverageThreshold` across +tests/scripts/vscode/*.Tests.ps1: it moved to a NEW file, +tests/scripts/vscode/Invoke-MSTestWithCoverage.Threshold.Tests.ps1 (dot-sources +scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1, which transitively dot-sources the +extracted scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1). Both files were run for this +task: Invoke-MSTestWithCoverage.Helpers.Tests.ps1 literally, per the plan's task text, and +Invoke-MSTestWithCoverage.Threshold.Tests.ps1 for the substantive boundary-test check the task's +acceptance text actually requires. + +Output Summary (Invoke-MSTestWithCoverage.Helpers.Tests.ps1, literal task target): +Passed=20 Failed=0 Skipped=0. All 20 tests in this file (ConvertTo-KoverageCoberturaXml, +Get-KoverageProjectAllowlist, Get-CoberturaClassLineSummary) Result=Passed. Confirms +scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 needed no change for this fix (matches +[P2-T1]'s empty-diff observation for this file). + +Output Summary (Invoke-MSTestWithCoverage.Threshold.Tests.ps1, drifted-anchor location of the +five threshold-boundary tests): +Passed=5 Failed=0 Skipped=0. All five `Describe 'Assert-CoberturaLineCoverageThreshold'` tests +Result=Passed: +Assert-CoberturaLineCoverageThreshold.throws when the Cobertura line-coverage summary is missing => Passed +Assert-CoberturaLineCoverageThreshold.throws when the Cobertura line-coverage summary is non-numeric => Passed +Assert-CoberturaLineCoverageThreshold.throws when the Cobertura line coverage is below 80 percent => Passed +Assert-CoberturaLineCoverageThreshold.accepts a Cobertura line coverage result at exactly 80 percent => Passed +Assert-CoberturaLineCoverageThreshold.accepts a Cobertura line coverage result above 80 percent => Passed + +Confirms zero regression in the untouched threshold-boundary tests (missing line-rate, +non-numeric line-rate, below-80, exactly-80, above-80), and that +scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1 (the file that now actually carries this +logic) also needed no change for this fix, matching [P2-T1]'s empty-diff observation for that +file. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/pass-after-run.2026-09-03T11-09.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/pass-after-run.2026-09-03T11-09.md new file mode 100644 index 000000000..58379dca0 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/pass-after-run.2026-09-03T11-09.md @@ -0,0 +1,25 @@ +Timestamp: 2026-09-03T11-09 +Command: mcp__drm-copilot__run_poshqc_test (scan_folders: tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1); paired direct run: pwsh -NoProfile -Command 'Import-Module Pester -MinimumVersion 5.0; $c = New-PesterConfiguration; $c.Run.Path = "/tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1"; $c.Run.PassThru = $true; $c.Output.Verbosity = "Detailed"; $r = Invoke-Pester -Configuration $c; ...; if ($r.FailedCount -gt 0) { exit 1 } else { exit 0 }' +MCP Result: ok:true +EXIT_CODE: 0 + +Output Summary: +Passed=28 Failed=0 Skipped=0 + +New test result (now GREEN post-fix): +Invoke-MSTestWithCoverageMain.persists the post-processed Cobertura document before the threshold +assertion can throw on a sub-threshold run => Passed + +All five pre-existing tests in Describe 'Invoke-MSTestWithCoverageMain' also Passed: +Invoke-MSTestWithCoverageMain.uses only mocked discovery and builds the vswhere command for the main happy path => Passed +Invoke-MSTestWithCoverageMain.does not start coverage collection when NoExecute is supplied => Passed +Invoke-MSTestWithCoverageMain.collects and post-processes coverage on the fully mocked main happy path => Passed +Invoke-MSTestWithCoverageMain.passes the generated Cobertura result to the threshold evaluator before completing successfully => Passed +Invoke-MSTestWithCoverageMain.fails when the search root cannot be found => Passed +Invoke-MSTestWithCoverageMain.excludes assemblies discovered under a .claude worktree segment => Passed (the #733 test, also unaffected) + +This artifact is the sole evidence that the mocked call-order proof (the deterministic, +in-process equivalent of the `## Repro & Evidence` manual steps, per spec.md § Test Strategy's +"no manual validation required beyond the automated toolchain") now holds: Set-Content is +invoked exactly once before Assert-CoberturaLineCoverageThreshold throws on the sub-threshold +fixture. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/feature-audit.2026-09-03T12-15.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/feature-audit.2026-09-03T12-15.md new file mode 100644 index 000000000..2c84f2c8c --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/feature-audit.2026-09-03T12-15.md @@ -0,0 +1,47 @@ +# Feature Audit — invoke-mstestwithcoverage-threshold-before-setcontent (#565) + +- Timestamp: 2026-09-03T12-15 +- Work mode: `full-bug` — AC source: `spec.md` § `## Acceptance Criteria` only (per + `acceptance-criteria-tracking` mode-resolution table; `user-story.md` is not required or present + for `full-bug`, confirmed absent from the feature folder's tracked file list) + +## Acceptance Criteria Evaluation + +| # | Criterion (spec.md) | Verdict | Evidence | +|---|---|---|---| +| 1 | New Pester test fails against the pre-fix statement order and passes after the fix | **PASS** | `evidence/regression-testing/expect-fail-run.2026-09-03T11-09.md` (27 Passed/1 Failed, new test fails with "called 0 times") and `pass-after-run.2026-09-03T11-09.md` (28 Passed/0 Failed, new test passes) — a genuine RED-then-GREEN pair against the same test, both independently re-derivable from the committed diff (pre-fix order confirmed absent from HEAD via the production diff review) | +| 2 | `Set-Content` invoked before `Assert-CoberturaLineCoverageThreshold` can throw on a sub-threshold run, via `Should -Invoke Set-Content -Times 1 -Exactly` inside `{ ... } \| Should -Throw`, using the `line-rate="0.5"` fixture | **PASS** | Test body at `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1:416-422`, read directly — matches the criterion's exact mechanics (mock, fixture, assertion shape) verbatim | +| 3 | Threshold value (80%) unchanged; no diff to `Assert-CoberturaLineCoverageThreshold`'s literal or message text (now in `Invoke-MSTestWithCoverage.Threshold.ps1` post-#733) | **PASS** | `git diff origin/main -- scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1` empty (independently run); threshold text confirmed present and unchanged by direct read | +| 4 | No production file other than `Invoke-MSTestWithCoverage.ps1` changed (in particular `Helpers.ps1` and `ClosureFilter.ps1` untouched) | **PASS** | `git diff origin/main...HEAD --stat` shows exactly one production file changed; `Helpers.ps1` diff independently confirmed empty; `ClosureFilter.ps1` does not appear anywhere in the diff | +| 5 | PoshQC format, PSScriptAnalyzer, and Pester all pass cleanly on the changed files, no auto-fixes, no regression in `Describe 'Invoke-MSTestWithCoverageMain'` or the Helpers boundary tests | **PASS** | `evidence/qa-gates/poshqc-format.iter1...md` (0 auto-fixes), `poshqc-analyze.iter1...md` (0 diagnostics), `poshqc-test.iter1...md` (93/93 passing); `evidence/regression-testing/helpers-boundary-regression...md` (25/25 across both post-#733 threshold-boundary/helpers files) | +| 6 | Repro steps now produce expected behavior: sub-threshold-run artifact on disk is the post-processed document, not the raw `dotnet-coverage` output | **PASS** | Directly entailed by the production diff (Set-Content now precedes the throw unconditionally) and by AC-2's passing regression test, which is the automated equivalent of the manual repro steps per `spec.md` § Test Strategy ("no manual validation required beyond the automated toolchain") | + +### Acceptance Criteria Status + +- Source: `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md` +- Total AC items: 6 +- Checked off (delivered): 6 (all already `[x]` in the committed `spec.md`; independently re-verified against evidence above, no discrepancies found) +- Remaining (unchecked): 0 +- Items remaining: none + +## Baseline Comparison + +- Pre-fix (baseline, captured at merge commit `dc5e8c0f`): 92/92 Pester tests passing across + `tests/scripts/vscode`; `Invoke-MSTestWithCoverage.ps1` line coverage 90.09%. +- Post-fix (final, HEAD `e165f7ba`): 93/93 Pester tests passing (the +1 is the new regression + test); same file, same 90.09% line coverage, identical `MainScriptCommands=111` total — + confirms the reorder added no new executable command and caused no coverage regression. +- No pre-existing test in the suite changed outcome between baseline and final. + +## Out-of-Scope Item Disposition + +The `.claude\` worktree-exclusion filter (`scripts/vscode/Invoke-MSTestWithCoverage.ps1`, filter +predicate `-notmatch '\\\.claude\\'`) was independently confirmed present, byte-identical, in +`origin/main` and untouched by this branch's diff. It is correctly treated as pre-existing and out +of scope for this fix; not flagged as a regression. + +## Overall Feature-Audit Verdict + +**PASS.** All 6 acceptance criteria independently verified against evidence, with no gaps or +discrepancies. Working tree is clean (`git status --porcelain` empty); the branch is reconciled +against `origin/main` at merge commit `dc5e8c0f`. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/issue.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/issue.md new file mode 100644 index 000000000..ec3be9d22 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/issue.md @@ -0,0 +1,83 @@ +# invoke-mstestwithcoverage-threshold-before-setcontent (Potential Bug) + +- Date captured: 2026-09-02 +- Author: Dan Moisan +- Status: Already promoted (GitHub issue #565 opened 2026-08-15, prior to this session) + +> Automation note: Keep the section headings below unchanged; the promotion tooling maps each of them into the GitHub bug issue template. + +- Issue: #565 +- Issue URL: https://github.com/drmoisan/TaskMaster/issues/565 +- Last Updated: 2026-09-02 + +- Work Mode: full-bug + +## Summary + +`scripts/vscode/Invoke-MSTestWithCoverage.ps1` asserts the coverage threshold before it writes the +post-processed Cobertura document to disk. When the assertion fails, the script throws and the +post-processed document is discarded, leaving the raw un-post-processed document at the output path. + +## Environment + +- OS/version: Windows 11 Pro (repo default) +- Python version: n/a — PowerShell 7+ coverage/test-runner scripts +- Command/flags used: `scripts/vscode/Invoke-MSTestWithCoverage.ps1` +- Data source or fixture: n/a + +## Steps to Reproduce + +1. Run `scripts/vscode/Invoke-MSTestWithCoverage.ps1` against a test suite whose measured line + coverage is below the configured 80% threshold. +2. Observe that `Assert-CoberturaLineCoverageThreshold` (in `Invoke-MSTestWithCoverage.Helpers.ps1`) + throws before the post-processed XML is persisted. +3. Inspect the coverage output path named by `-CoverageOutput` after the throw. + +## Expected Behavior + +The artifact on disk should be the same post-processed Cobertura document that the threshold +assertion judged, in both the passing and failing case. + +## Actual Behavior + +At `scripts/vscode/Invoke-MSTestWithCoverage.ps1:341-343` the threshold assertion runs ahead of the +`Set-Content` that persists the post-processed XML. On a failing run, the artifact left on disk is +the raw `dotnet-coverage` output — absolute paths, third-party packages included, unmerged duplicate +classes, and the double-counted line totals that #441 corrected. + +## Logs / Screenshots + +- [ ] Attached minimal logs or screenshot +- Snippet: n/a — see file/line citation above. + +## Impact / Severity + +- [ ] Blocker +- [ ] High +- [x] Medium +- [ ] Low + +Only the failure path is affected; a passing run writes the correct document. But the failure path is +exactly when someone reads the artifact to diagnose the shortfall, and what they find is a document +with different numbers than the one that produced the failure message. It also means a failed gate +leaves behind an artifact that, if fed to any downstream consumer, reports the pre-#441 inflated +denominator. + +## Suspected Cause / Notes + +Statement ordering defect only, not a logic change. Found during the `build-ci-coverage-gate-fidelity` +epic fan-in review; identified independently by two review passes. Note: issue #563 (threshold VALUE +contradiction) is a separate, deliberately excluded concern — this fix must not change the threshold +value, only the statement order. + +## Proposed Fix / Validation Ideas + +- [x] Move the `Set-Content` above the `Assert-CoberturaLineCoverageThreshold` call so the judged + document is persisted before the threshold is evaluated. +- [ ] Add a Pester test under `tests/scripts/vscode/` asserting that a sub-threshold run still leaves + the post-processed document on disk (not the raw `dotnet-coverage` output). + +## Next Step + +- [x] Promote to GitHub issue (bug-report template) — already promoted as issue #565. +- [ ] Move to active fix folder / branch diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/plan.2026-09-02T08-59.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/plan.2026-09-02T08-59.md new file mode 100644 index 000000000..26254631a --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/plan.2026-09-02T08-59.md @@ -0,0 +1,160 @@ +# invoke-mstestwithcoverage-threshold-before-setcontent (Plan) + +- **Issue:** #565 +- **Parent (optional):** none +- **Owner:** drmoisan +- **Last Updated:** 2026-09-02T08-59 +- **Status:** Draft — ready for preflight +- **Version:** 0.2 +- **Work Mode:** `full-bug` — `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md` (its `## Acceptance Criteria` section) is the sole acceptance-criteria source. `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/issue.md` is background only, and there is no `user-story.md` for this work mode. + +## Conventions + +- `` = `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565`. Every evidence path below is written out in full (not abbreviated) in each task's acceptance text; `` is used only in this Conventions section as shorthand for readability. +- All evidence artifacts resolve under `/evidence//` where `` is one of `baseline/`, `regression-testing/`, `qa-gates/`, `other/`, `issue-updates/`. No `artifacts/`-rooted evidence path is valid anywhere in this plan. +- `` = ISO-8601 `yyyy-MM-ddTHH-mm`, captured at artifact-write time by the executing task. +- Every command-step artifact carries `Timestamp:`, `Command:`, `EXIT_CODE:`, `Output Summary:`. +- `BASELINE_SHA` = the full `HEAD` SHA recorded by `[P0-T3]`, captured before any edit in this plan runs. Every `git diff` in this plan is anchored to `BASELINE_SHA` (never an unanchored worktree-vs-index diff), and every name-listing diff (`--name-only`) is paired with a `git status --porcelain` companion in the same task, per the plan-acceptance-gates G8/G8b rules. +- This repository's entire Pester suite is exactly five files under `tests/scripts/vscode`: tests/scripts/vscode/Install-RepoDotNetSdk.Tests.ps1, `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, tests/scripts/vscode/Invoke-MSTestWithCoverage.ClosureFilter.Tests.ps1, tests/scripts/vscode/Invoke-MSTestWithCoverage.Helpers.Tests.ps1, tests/scripts/vscode/Invoke-VSBuild.Tests.ps1 (confirmed by `Glob tests/**/*.Tests.ps1` at plan-authoring time). "The full PowerShell suite" in this plan means a Pester run over the `tests/scripts/vscode` folder. +- `mcp__drm-copilot__run_poshqc_test` returns only `{ok, tool, workspace_root, summary}` — no exit code, no per-test counts, no coverage figure. Every task requiring numeric or per-test PowerShell evidence runs the MCP tool for the policy record (require `MCP Result: ok:true`) AND pairs it with a direct Pester run that supplies the numbers, using this template (outer single quotes, inner bare double quotes only — no `\"` escaping, no double-quoted outer wrapper; this quoting is load-bearing, not stylistic, per this repository's prior measured PowerShell plans): + `pwsh -NoProfile -Command 'Import-Module Pester -MinimumVersion 5.0; $c = New-PesterConfiguration; $c.Run.Path = ""; $c.Run.PassThru = $true; $c.Output.Verbosity = "Detailed"; $r = Invoke-Pester -Configuration $c; "Passed=$($r.PassedCount) Failed=$($r.FailedCount) Skipped=$($r.SkippedCount)"; $r.Tests | ForEach-Object { "$($_.ExpandedPath) => $($_.Result)" }; if ($r.FailedCount -gt 0) { exit 1 } else { exit 0 }'` + `Run.Exit` defaults to `$false` in Pester 5 and `Invoke-Pester` sets no process exit code; the explicit trailing `if ($r.FailedCount -gt 0) { exit 1 } else { exit 0 }` is what makes `EXIT_CODE:` load-bearing for every task using this template. Without it `EXIT_CODE:` is `0` regardless of failures. + When coverage is required (`[P0-T7]` and `[P4-T3]` only), the template gains `$c.CodeCoverage.Enabled = $true; $c.CodeCoverage.Path = "scripts/vscode/Invoke-MSTestWithCoverage.ps1"; $c.CodeCoverage.OutputPath = "";` before `$r = Invoke-Pester -Configuration $c;`, and the summary line gains `$hit = @($r.CodeCoverage.CommandsExecuted).Count; $miss = @($r.CodeCoverage.CommandsMissed).Count; "MainScriptCommands=$($hit+$miss) Executed=$hit Percent=$(if (($hit+$miss) -gt 0) { [math]::Round(100*$hit/($hit+$miss),2) } else { 0 })";` immediately after the `Passed=...` line. `CodeCoverage.Path` names exactly one file (`scripts/vscode/Invoke-MSTestWithCoverage.ps1`) so every command counted by `CommandsExecuted`/`CommandsMissed` already belongs to that file; no additional `-like` filter is needed. `CodeCoverage.OutputPath` is always redirected under `/evidence//`, never left at the `coverage.xml` default (which is not `.gitignore`d in this repository and is not a deliberate committed artifact). Pester 5 emits no branch-coverage metric for PowerShell; record `branch coverage: not emitted by Pester 5` in every task that captures coverage — a measured tooling fact, not a placeholder — matching the exemption in .claude/rules/powershell.md and .claude/rules/general-unit-test.md. +- Every command run in this plan is executed with the repository root as the working directory, so the repo-relative paths in every command above resolve without further prefixing. +- Evidence filename uniqueness: Phase 4 loop-iteration artifacts are suffixed `iter` (`` starting at `1`, incrementing on every restart of the Phase 4 loop), so a same-minute `` collision across iterations cannot overwrite a prior iteration's artifact. + +## Fail-Closed Evidence Rule + +If any required baseline artifact, regression artifact, QA-gate artifact, or coverage-comparison artifact is missing or has an incomplete field set, the outcome is BLOCKED or INCOMPLETE, never PASS. A plan checkbox must remain unchecked when its artifact is absent or incomplete. + +## Scope Prohibitions (binding on every task in this plan) + +- The only files this plan edits are `scripts/vscode/Invoke-MSTestWithCoverage.ps1` (production, a pure two-statement reorder) and `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` (test, one new `It`), plus this feature folder's own evidence/doc artifacts under `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/`. +- Do NOT change the coverage threshold value or message text inside `Assert-CoberturaLineCoverageThreshold` (scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1, line 487 at plan-authoring time). That is issue #563, explicitly out of scope here. +- Do NOT modify scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 or scripts/vscode/Invoke-MSTestWithCoverage.ClosureFilter.ps1 in any way. +- Do NOT add a `try`/`finally` wrapper or any other indirection; the fix is a pure statement reorder. +- Do NOT write under .claude/**, .codex/**, .agents/**, config/blast-radius.json, or config/orchestration-routing.json. +- Do NOT create temporary files anywhere, in production code, in tests, or in evidence capture. Every test fixture is an inline PowerShell string; no `TestDrive`, `New-TemporaryFile`, or on-disk fixture file. +- This repository has no `extensions/` tree, no `scripts/dev_tools/` tree, and no Python toolchain in scope for this fix; no Python-oriented step appears in this plan. + +### Phase 0 — Context, policy reads, and baseline capture + +- [x] [P0-T1] Read the repository policy documents in the order defined by `policy-compliance-order` and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-instructions-read..md`. + - Read order: CLAUDE.md, .claude/rules/general-code-change.md, .claude/rules/general-unit-test.md, .claude/rules/powershell.md, .claude/rules/quality-tiers.md. + - Acceptance: the artifact exists and contains `Timestamp:`, `Policy Order:` (the ordered list above), and an explicit `Files Read:` list naming each file with its repo-relative path. + +- [x] [P0-T2] Read the feature requirement documents and the current target source files, and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/phase0-feature-documents-read..md`. + - Documents: `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/issue.md`, `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md`, `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/research/invoke-mstestwithcoverage-ordering-fix.2026-09-02T09-00.md`, `scripts/vscode/Invoke-MSTestWithCoverage.ps1`, scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1, `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`. + - Acceptance: the artifact exists, contains `Timestamp:` and the explicit file list, and records `Work Mode: full-bug` and `AC Source: docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md` (sole source). + +- [x] [P0-T3] Record the branch and commit baseline in `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/branch-commit-baseline..md`. + - Commands: `git rev-parse --abbrev-ref HEAD`, `git rev-parse HEAD`, `git status --porcelain`. + - Acceptance: the artifact records the branch name, the full `HEAD` SHA (this becomes `BASELINE_SHA` for every later anchored diff in this plan), and the verbatim porcelain output. The recorded SHA is a record of state, never an expectation any later task asserts against. + +- [x] [P0-T4] Re-derive and record the exact current statement order in `scripts/vscode/Invoke-MSTestWithCoverage.ps1` and the exact current threshold text in scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1, and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/citation-verification..md`. + - Check 1: read `scripts/vscode/Invoke-MSTestWithCoverage.ps1` lines 338-345 verbatim and confirm line 340 is `$processedXmlContent = ConvertTo-KoverageCoberturaXml -XmlContent $xmlContent -RepoRoot $repoRoot`, line 341 is `Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent`, line 342 is blank, line 343 is `Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline`, and line 344 is `Write-Output "Done. Coverage artifact: $resolvedOutputPath"`. + - Check 2: read scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 line 487 verbatim and confirm it reads `if ($percentage -lt 80) {`. + - Fallback if either check does not match at execution time: locate the block by its unique surrounding comment (`# Post-process the Cobertura XML for Koverage compatibility:`) inside `Invoke-MSTestWithCoverageMain`, or by the unique string `is below the required 80% threshold.` inside `Assert-CoberturaLineCoverageThreshold`, record the drifted line numbers actually found, and proceed using those as the locators for `[P2-T1]` instead of the numbers above. Record which branch (exact-line-match or drifted-anchor) was taken. + - Acceptance: the artifact records `Timestamp:`, the verbatim 8-line excerpt (lines 338-345) of `scripts/vscode/Invoke-MSTestWithCoverage.ps1`, the verbatim line 487 of scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1, and which branch was taken. + +- [x] [P0-T5] Capture the PoshQC format baseline over the two files this plan edits and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-format..md`. + - Command: `mcp__drm-copilot__run_poshqc_format` with `scan_folders` supplied explicitly as `scripts/vscode/Invoke-MSTestWithCoverage.ps1` and `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` (the tool accepts individual file paths; `config/poshqc-scan.json` does not exist in this repository, so `scan_folders` is always supplied explicitly). + - This is a write-mode command (it rewrites files in place on a diff). Record, immediately before and immediately after the run, the verbatim output of `git status --porcelain -- scripts/vscode/Invoke-MSTestWithCoverage.ps1 tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` as the before/after tree observation, since the tool's success-case stdout literal has not been independently confirmed in this plan. + - Folder-coercion safety check: also record `git status --porcelain -uall -- scripts/vscode tests/scripts/vscode` immediately after the run. If any file other than the two named above appears modified, the tool coerced the scan to the containing folder: record the verbatim `git diff BASELINE_SHA -- ` for each such file, restore each with `git checkout -- `, and re-record the restricted status listing showing only the two files this plan owns. + - Owned-file diff record: for each of the two owned files that the after-run `git status --porcelain -- scripts/vscode/Invoke-MSTestWithCoverage.ps1 tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` reports as modified, also record the verbatim `git diff BASELINE_SHA -- ` for that file immediately after this run, before any later task touches it. `[P1-T1]` and `[P2-T1]` treat this exact recorded diff (if any) as the formatter-attributable hunk to exempt from their own single-hunk / line-citation checks; if neither owned file is modified, record `No formatter-attributable diff for either owned file.` instead. + - Acceptance: the artifact carries `Timestamp:`, `Command:`, `EXIT_CODE:`, `Output Summary:` (`MCP Result: ok:true` plus whether either owned file was rewritten), the before/after `git status --porcelain` pair, the folder-coercion check result, and the owned-file diff record (or its explicit `No formatter-attributable diff` statement). + +- [x] [P0-T6] Capture the PSScriptAnalyzer baseline over the same two files and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-analyze..md`. + - Command: `mcp__drm-copilot__run_poshqc_analyze` with `scan_folders` supplied explicitly as `scripts/vscode/Invoke-MSTestWithCoverage.ps1` and `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, paired with a direct run for the diagnostic list (the MCP payload reports only a count): `pwsh -NoProfile -Command 'Invoke-ScriptAnalyzer -Path "scripts/vscode/Invoke-MSTestWithCoverage.ps1"; Invoke-ScriptAnalyzer -Path "tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1"'`. No repository-local `PSScriptAnalyzerSettings.psd1` was found at plan-authoring time (confirmed by search); the direct run therefore uses PSScriptAnalyzer defaults. + - Acceptance: the artifact carries the four required fields (`MCP Result: ok:true` recorded); `Output Summary:` records the diagnostic count by severity and the full verbatim diagnostic list (rule name, severity, file, line) from the paired direct run — this verbatim list is the baseline set `[P4-T2]` compares against. + +- [x] [P0-T7] Capture the Pester baseline over the full PowerShell suite, with coverage on the production file, and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/poshqc-test..md`. + - Command: `mcp__drm-copilot__run_poshqc_test` with `scan_folders` supplied explicitly as `tests/scripts/vscode`, paired with the direct Pester run from Conventions using `Run.Path = "tests/scripts/vscode"` and `CodeCoverage.OutputPath = "docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/baseline/pester-coverage..xml"`. + - Acceptance: the artifact carries the four required fields; `MCP Result: ok:true` recorded; `Output Summary:` records numeric `Passed=`/`Failed=`/`Skipped=` counts, the numeric `MainScriptCommands=`/`Executed=`/`Percent=` line coverage of `scripts/vscode/Invoke-MSTestWithCoverage.ps1`, and `branch coverage: not emitted by Pester 5` (no placeholder values); `Failed=0` is expected on `main`. Record `Pester Coverage Artifact:` as the coverage XML path. + +### Phase 1 — Regression test (must fail first) + +- [x] [P1-T1] [expect-fail] Add one new `It` to the existing `Describe 'Invoke-MSTestWithCoverageMain'` block in `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, immediately after the existing `It 'fails when the search root cannot be found'` block and its closing `}` (currently the block ending at line 413) and before the `Describe` block's own closing `}` (currently line 414), inserting exactly: + ``` + + It 'persists the post-processed Cobertura document before the threshold assertion can throw on a sub-threshold run' { + Mock ConvertTo-KoverageCoberturaXml { '' } + + { Invoke-MSTestWithCoverageMain -ScriptRoot $script:scriptDir } | Should -Throw + + Should -Invoke Set-Content -Times 1 -Exactly + } + ``` + - This follows the existing `BeforeEach` mocking conventions exactly (`Assert-CoberturaLineCoverageThreshold` stays real/unmocked so it genuinely throws on the 50% line-rate fixture; `Set-Content` stays mocked by the existing `Mock Set-Content {}` in `BeforeEach`). The `-Path` parameter name matches the production call site (`Set-Content -Path $resolvedOutputPath ...`, not `-LiteralPath`), so no `-ParameterFilter` is required to disambiguate it from the unrelated `-LiteralPath` call in `Invoke-DotnetCoverageCollection` (which is itself fully mocked in this `Describe` block's `BeforeEach` and never reaches its own `Set-Content` call). + - Acceptance: `git diff BASELINE_SHA -- tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` shows the insertion-only hunk of exactly the 8 lines shown above (3 blank lines and 5 code lines: the `It` opening, the `Mock` line, the `Should -Throw` line, the `Should -Invoke` line, and the closing `}`) plus, if and only if `[P0-T5]` recorded a formatter-attributable diff for this file, that identical recorded diff byte-for-byte — no other line in the file is touched beyond those two accounted-for sources. `git status --porcelain` shows `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` as the only modified path. + +- [x] [P1-T2] [expect-fail] Run the new test against the pre-fix statement order and record the RED result in `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/expect-fail-run..md`. + - Command: `mcp__drm-copilot__run_poshqc_test` with `scan_folders` supplied explicitly as `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, paired with the direct Pester run from Conventions using `Run.Path = "tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1"` (no coverage capture on this task; behavior only). + - Expected result against the pre-fix order: the named test `Invoke-MSTestWithCoverageMain.persists the post-processed Cobertura document before the threshold assertion can throw on a sub-threshold run` is `Failed`, because `Assert-CoberturaLineCoverageThreshold` (still at line 341, ahead of `Set-Content` at line 343) throws before `Set-Content` is ever reached, so `Should -Invoke Set-Content -Times 1 -Exactly` observes zero invocations. The outer `{ ... } | Should -Throw` assertion on its own still passes pre-fix (an exception does occur); the failure is specifically the `Should -Invoke` assertion, not a parse or discovery error. Every other test in the file is expected `Passed=` (no unrelated regression from the insertion). + - Acceptance: the artifact carries `Timestamp:`, `Command:`, `MCP Result: ok:true`, `EXIT_CODE:` non-zero from the paired direct run, and `Output Summary:` naming the new test by its full `ExpandedPath` with `Result=Failed` and the observed failure reason (`Set-Content` invocation count `0`, not `1`), plus the `Passed=`/`Failed=`/`Skipped=` totals for the whole file. + +### Phase 2 — Minimal fix + +- [x] [P2-T1] In `scripts/vscode/Invoke-MSTestWithCoverage.ps1`, swap the two statements at (or the drifted-anchor equivalent of) lines 341 and 343 so `Set-Content` executes immediately after `$processedXmlContent` is computed and before `Assert-CoberturaLineCoverageThreshold` is called: change line 341 from `Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent` to `Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline`, and change line 343 from `Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline` to `Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent`. Leave the blank line 342 and every other line (including the `Write-Output "Done. Coverage artifact: $resolvedOutputPath"` line) unchanged. Do not modify scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 or scripts/vscode/Invoke-MSTestWithCoverage.ClosureFilter.ps1. + - Write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/fix-diff-verification..md` recording: (a) a direct content read of the four-statement block in `scripts/vscode/Invoke-MSTestWithCoverage.ps1` after the edit, verbatim — at lines 338-345 if those still hold the block at this point in the plan, or at the drifted-anchor equivalent (located by the unique surrounding comment `# Post-process the Cobertura XML for Koverage compatibility:`, per the same technique `[P0-T4]`'s fallback uses) if `[P0-T5]`'s format pass has shifted the file — confirming line-for-line that the `$processedXmlContent = ConvertTo-KoverageCoberturaXml -XmlContent $xmlContent -RepoRoot $repoRoot` line is unchanged, the next line now reads `Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline`, the line after that is still blank, the line after that now reads `Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent`, and the line after that is unchanged (`Write-Output "Done. Coverage artifact: $resolvedOutputPath"`) — a direct post-edit content read is used here instead of asserting a predicted `git diff` hunk shape, because a content swap around a shared blank line is not guaranteed to render as a fixed number of removed/added lines under an LCS-based diff (the blank line may or may not be treated as unchanged context depending on the diff algorithm's tie-breaking), so a hunk-shape prediction is not a reliable gate; (b) the verbatim `git diff --name-only BASELINE_SHA` paired with `git status --porcelain`, together showing exactly the following changed paths in the whole repository at this point in the plan: `scripts/vscode/Invoke-MSTestWithCoverage.ps1` (this task's edit) and `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` (already modified by `[P1-T1]`'s test insertion, and possibly by `[P0-T5]`'s format pass) — confirming no other path appears, in particular that scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 and scripts/vscode/Invoke-MSTestWithCoverage.ClosureFilter.ps1 are absent from the diff; (c) the verbatim `git diff BASELINE_SHA -- scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1`, which must be empty, confirming line 487's threshold text and message are byte-for-byte unchanged. + - Acceptance: the evidence artifact exists and records all three observations above exactly as specified; a post-edit content read that does not match the four lines specified in (a) (at their exact or drifted-anchor location), a diff in (b) naming any path other than the two identified above, or any diff at all against Invoke-MSTestWithCoverage.Helpers.ps1 in (c), is a BLOCKED outcome for this task. + +### Phase 3 — Verification (confirm pass after fix) + +- [x] [P3-T1] Re-run `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` against the post-fix statement order and record the GREEN result in `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/pass-after-run..md`. + - Command: `mcp__drm-copilot__run_poshqc_test` with `scan_folders` supplied explicitly as `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, paired with the direct Pester run from Conventions using `Run.Path = "tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1"`. + - Expected result: every test in the file is `Passed=`, `Failed=0`, including the new test `Invoke-MSTestWithCoverageMain.persists the post-processed Cobertura document before the threshold assertion can throw on a sub-threshold run` (now `Set-Content` is invoked exactly once before `Assert-CoberturaLineCoverageThreshold` throws) and the five pre-existing tests in `Describe 'Invoke-MSTestWithCoverageMain'` (`uses only mocked discovery...`, `does not start coverage collection...`, `collects and post-processes coverage...`, `passes the generated Cobertura result...`, `fails when the search root cannot be found`). + - Acceptance: the artifact carries `Timestamp:`, `Command:`, `MCP Result: ok:true`, `EXIT_CODE: 0`, and `Output Summary:` naming the new test's full `ExpandedPath` with `Result=Passed`, plus the file-wide `Passed=`/`Failed=`/`Skipped=` totals showing `Failed=0`. This artifact is the sole evidence that the mocked call-order proof (the deterministic, in-process equivalent of the `## Repro & Evidence` manual steps, per `spec.md` § Test Strategy's "no manual validation required beyond the automated toolchain") now holds. + +- [x] [P3-T2] Re-run tests/scripts/vscode/Invoke-MSTestWithCoverage.Helpers.Tests.ps1 to confirm zero regression in the untouched threshold-boundary tests, and record `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/regression-testing/helpers-boundary-regression..md`. + - Command: `mcp__drm-copilot__run_poshqc_test` with `scan_folders` supplied explicitly as tests/scripts/vscode/Invoke-MSTestWithCoverage.Helpers.Tests.ps1, paired with the direct Pester run from Conventions using `Run.Path = "tests/scripts/vscode/Invoke-MSTestWithCoverage.Helpers.Tests.ps1"`. + - Acceptance: the artifact carries the four required fields; `MCP Result: ok:true`; `EXIT_CODE: 0`; `Output Summary:` names all five `Describe 'Assert-CoberturaLineCoverageThreshold'` tests (missing line-rate, non-numeric line-rate, below-80, exactly-80, above-80) as `Result=Passed`, confirming scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 needed no change for this fix. + +### Phase 4 — Final QA loop (format -> lint -> test, coverage-enabled) + +Run the three steps below in this exact order. If any step fails, or rewrites a file (per the format step's before/after observation), fix the cause and restart from `[P4-T1]` with the iteration counter incremented; do not stop the loop until a single pass completes with all three steps clean. + +- [x] [P4-T1] PoshQC format over the two owned files (iteration `iter1` on first execution) and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-format.iter1..md`. + - Command and before/after tree observation: identical to `[P0-T5]`, re-run after the `[P2-T1]` fix and `[P1-T1]` test insertion are both in place. + - Acceptance: the artifact carries the four required fields, the before/after `git status --porcelain` pair for the two owned files, and the folder-coercion check; `Output Summary:` states whether either owned file was rewritten. If a subsequent iteration is needed, its artifact is named with the incremented `iter` suffix. + +- [x] [P4-T2] PSScriptAnalyzer over the two owned files (matching iteration suffix) and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-analyze.iter1..md`. + - Command: identical to `[P0-T6]`. + - Acceptance: the artifact carries the four required fields, `MCP Result: ok:true`, and the full verbatim diagnostic list from the paired direct run; the diagnostic count for each of the two files is less than or equal to the `[P0-T6]` baseline count for that file, with zero new rule violations (a diagnostic present in `[P0-T6]`'s list for a given file/rule pair is not counted as new — matching on file+rule rather than file+line+rule, because the statement swap in `[P2-T1]` and the test insertion in `[P1-T1]` can shift line numbers for a pre-existing, substantively-unchanged diagnostic below the edit point). + +- [x] [P4-T3] Full Pester suite with coverage (matching iteration suffix) and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/poshqc-test.iter1..md`. + - Command: identical to `[P0-T7]`, with `CodeCoverage.OutputPath = "docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/pester-coverage.iter1..xml"`. + - Acceptance: the artifact carries the four required fields, `MCP Result: ok:true`, `EXIT_CODE: 0`, `Failed=0` across all five test files, and the numeric `MainScriptCommands=`/`Executed=`/`Percent=` coverage of `scripts/vscode/Invoke-MSTestWithCoverage.ps1`. + +- [x] [P4-T4] Verify the coverage delta and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/qa-gates/coverage-delta..md`. + - Acceptance: the artifact records, side by side: the `[P0-T7]` baseline `Percent=` for `scripts/vscode/Invoke-MSTestWithCoverage.ps1`; the final `[P4-T3]` `Percent=` for the same file; and the explicit statement that the post-change `Percent=` is greater than or equal to the baseline `Percent=` (no regression). It additionally records that the swapped lines (the `Set-Content` and `Assert-CoberturaLineCoverageThreshold` calls, both now covered by the pre-existing `It 'collects and post-processes coverage on the fully mocked main happy path'` and the new `It` from `[P1-T1]`) are among the commands counted as executed in the `[P4-T3]` run, by name-checking both tests in that run's `Output Summary:`. If the post-change percent is lower than baseline, the outcome is BLOCKED, not PASS. + +### Phase 5 — Acceptance-criteria verification and check-off + +- [x] [P5-T1] Check off spec AC 1 (`docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md`, "The new Pester test ... fails against the pre-fix statement order ... and passes after the fix") in `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md`, citing the `[P1-T2]` expect-fail-run artifact and the `[P3-T1]` pass-after-run artifact. + +- [x] [P5-T2] Check off spec AC 2 (`Set-Content` invoked before `Assert-CoberturaLineCoverageThreshold` can throw on a sub-threshold run, verified by `Should -Invoke Set-Content -Times 1 -Exactly` inside a `{ ... } | Should -Throw` block) in `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md`, citing the `[P1-T1]` test-body insertion and the `[P3-T1]` pass-after-run artifact. + +- [x] [P5-T3] Check off spec AC 3 (the 80% threshold value and message text in scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 line 487 are unchanged) in `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md`, citing the `[P2-T1]` `fix-diff-verification` artifact's empty-diff observation for that file. + +- [x] [P5-T4] Check off spec AC 4 (no production file other than `scripts/vscode/Invoke-MSTestWithCoverage.ps1` is changed) in `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md`, citing the `[P2-T1]` `fix-diff-verification` artifact's changed-path observation (confirming no production file other than `scripts/vscode/Invoke-MSTestWithCoverage.ps1` appears in the diff). + +- [x] [P5-T5] Check off spec AC 5 (PoshQC format, PSScriptAnalyzer, and Pester all pass cleanly on the changed files, no auto-fixes needed, no regression in `Describe 'Invoke-MSTestWithCoverageMain'` or the Invoke-MSTestWithCoverage.Helpers.Tests.ps1 boundary tests) in `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md`, citing the `[P4-T1]`, `[P4-T2]`, and `[P4-T3]` final-iteration artifacts and the `[P3-T2]` helpers-boundary-regression artifact. + +- [x] [P5-T6] Check off spec AC 6 (the repro steps now produce the expected post-fix behavior) in `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md`, citing the `[P3-T1]` pass-after-run artifact as the deterministic, in-process equivalent of the manual repro (per `spec.md` § Test Strategy, no manual validation is required beyond the automated toolchain for this fix). + +### Phase 6 — Documentation, issue mirror, and wrap-up + +- [x] [P6-T1] Update `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md` § Status and § Test Strategy checkboxes to reflect completion (the two `- [x]`/`- [ ]` items under "Seeded from issue" both become `- [x]`). + - Acceptance: `spec.md` `- **Status:**` reads a completed value (e.g. `Implemented`), and both "Seeded from issue" checkboxes are `- [x]`. + +- [x] [P6-T2] Emit the acceptance-criteria status summary required by `acceptance-criteria-tracking` into `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/ac-status-summary..md`. + - Required block shape: `### Acceptance Criteria Status` with `- Source:`, `- Total AC items:`, `- Checked off (delivered):`, `- Remaining (unchecked):`, `- Items remaining:`. + - Acceptance: `Source:` names `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md` only; `Total AC items:` equals `6` (the count of checkbox items under `## Acceptance Criteria` in `spec.md`); any unchecked item is listed verbatim with the reason it could not be verified. If any item remains unchecked, the plan outcome is INCOMPLETE, not PASS. + +- [x] [P6-T3] Mirror an issue update for #565 and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/issue-updates/issue-565..md`. + - Acceptance: the artifact carries `Timestamp:`, the exact text intended/posted (summarizing the statement-reorder fix and linking the branch `bug/invoke-mstestwithcoverage-threshold-before-setcontent-565`), and `PostedAs: comment` or `PostedAs: unknown` with a `POSTING BLOCKED` header and reason if not posted. + +- [x] [P6-T4] Confirm a clean working tree and write `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/other/final-clean-tree..md`. + - Command: `git status --porcelain`. + - Acceptance: the artifact records the verbatim output; either it is empty (everything committed) or it lists only files intentionally left uncommitted with an explicit reason. The plan outcome is INCOMPLETE if any in-scope edit (the two owned files, or this feature folder's own evidence/doc artifacts) remains uncommitted with no reason recorded. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/policy-audit.2026-09-03T12-15.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/policy-audit.2026-09-03T12-15.md new file mode 100644 index 000000000..f9441e275 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/policy-audit.2026-09-03T12-15.md @@ -0,0 +1,215 @@ +# Policy Audit — invoke-mstestwithcoverage-threshold-before-setcontent (#565) + +- Timestamp: 2026-09-03T12-15 +- Reviewer: feature-review agent (parallel mode, issue_num 565) +- Worktree: `C:/Users/DanMoisan/repos/TaskMaster/.claude/worktrees/agent-a7d0dc0826acbf47e` +- Branch: `bug/invoke-mstestwithcoverage-threshold-before-setcontent-565` +- Branch head: `e165f7ba` (verified via `git log --oneline -1`) +- Reconciliation merge commit onto origin/main: `dc5e8c0f` (verified via `git log --oneline dc5e8c0f -1`) +- Resolved base: `origin/main` (`87233f86` at audit time); `git merge-base origin/main HEAD` = `b13d5b7b` (issue #733 / PR #748 merge commit) +- Work mode: `full-bug` (confirmed via `- Work Mode: full-bug` in `issue.md`) +- AC source: `spec.md` § `## Acceptance Criteria` (6 items) + +## Rejected Scope Narrowing + +None found. The delegating prompt's framing of the `.claude\` worktree-exclusion filter as a +"known, separately-tracked, OUT-OF-SCOPE environment defect" was independently verified rather +than accepted on faith (see § Pre-existing Defect Verification below); it did not instruct this +agent to skip any language, file class, or coverage check, and the full `origin/main...HEAD` diff +was audited in its entirety regardless. + +## Scope Verification + +`git diff --stat origin/main...HEAD` (merge-base `b13d5b7b`) shows 27 changed files, 1642 +insertions(+), 2 deletions(-): + +- 2 production/test files: `scripts/vscode/Invoke-MSTestWithCoverage.ps1` (+2/-2), + `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` (+8/-0) +- 25 files under this feature's own `docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/` (issue.md, spec.md, plan.md, research/, and 20 evidence artifacts) + +No other production file, script, or `.claude/**` / `.codex/**` / `.agents/**` / `config/blast-radius.json` / +`config/orchestration-routing.json` path is touched. **Verdict: PASS.** + +## Production Diff Verification + +`git diff origin/main...HEAD -- scripts/vscode/Invoke-MSTestWithCoverage.ps1`: + +```diff +- Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent +- + Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline ++ ++ Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent +``` + +Pure two-statement reorder, exactly as specced. No other line in the file changed. **Verdict: PASS.** + +## Threshold File Non-Modification (Post-#733 Drift Check) + +Independently confirmed the caller's claim about issue #733's extraction: + +- `scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1` line 4 dot-sources + `Invoke-MSTestWithCoverage.Threshold.ps1` (confirmed by direct grep of the committed file). +- `git diff origin/main -- scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1` is empty (confirmed). +- The threshold value (80) and throw message text in `Invoke-MSTestWithCoverage.Threshold.ps1` + (`"Cobertura line coverage $formattedPercentage% is below the required 80% threshold."`) are + present and unchanged (confirmed by direct read of the committed file). +- `Invoke-MSTestWithCoverage.Helpers.ps1` also has an empty diff against `origin/main` (no changes + to that file either). + +**Verdict: PASS.** The threshold value/message and the dot-source chain are unmodified by this branch. + +## Test Insertion Point Verification (Post-#733 Drift Check) + +`git diff origin/main...HEAD -- tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` shows a +single 8-line insertion-only hunk, landing immediately after the `It 'fails when the search root +cannot be found'` block's closing brace and immediately before the `It 'excludes assemblies +discovered under a .claude worktree segment'` block (the #733-added test, confirmed present and +untouched by this diff). **Verdict: PASS** — the insertion point is correct and does not disturb +the #733-added test. + +## Pre-existing Defect Verification (`.claude\` worktree filter) + +`git show origin/main:scripts/vscode/Invoke-MSTestWithCoverage.ps1` contains the same +`$_.FullName -notmatch '\\\.claude\\'` filter at (now) line 301, and this branch's diff does not +touch that region. **Verdict: confirmed pre-existing**, not a regression introduced by this +branch, consistent with the delegating prompt's framing. + +## Evidence Location Compliance + +`git diff origin/main...HEAD --name-only | grep -i '^artifacts/'` returns no matches — no evidence +artifact is committed under `artifacts/baselines/`, `artifacts/qa/`, `artifacts/coverage/`, or +`artifacts/evidence/`. All 20 evidence artifacts in this branch's diff are correctly placed under +`docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/evidence/{baseline,qa-gates,regression-testing,issue-updates,other}/`, +matching the canonical `/evidence//` convention. + +`validate_evidence_locations.py` does not exist anywhere in this repository (`git ls-files` for +that filename returns nothing) — this script is a cross-repo artifact documented elsewhere and is +not part of TaskMaster's toolchain; manual verification (above) was substituted. +`EVIDENCE_LOCATION_OVERRIDE_REJECTED`: not applicable — the delegating prompt did not supply any +non-canonical evidence path. **Verdict: PASS.** + +## Absolute Host Path Check + +`Grep -pattern "C:\\Users|DanMoisan|C:/Users"` across the full feature folder returns no matches. +Evidence artifacts consistently use ``, ``, and relative repo paths in +place of literal host paths. **Verdict: PASS.** + +## Toolchain Verification (`.claude/rules/powershell.md`: format → analyze → test) + +All three stages run via the MCP-based PoshQC tools (per policy, not VS Code task wrappers), each +paired with a direct-tool run for independent corroboration: + +| Stage | Evidence artifact | Result | +|---|---|---| +| Format | `evidence/qa-gates/poshqc-format.iter1.2026-09-03T11-09.md` | `git status --porcelain` before/after both empty on both owned files — no reformatting needed, 0 auto-fixes | +| Analyze | `evidence/qa-gates/poshqc-analyze.iter1.2026-09-03T11-09.md` | 0 diagnostics on both files (direct paired run) | +| Test | `evidence/qa-gates/poshqc-test.iter1.2026-09-03T11-09.md` | Passed=93 Failed=0 Skipped=0 across ten test files (direct Pester run tail matches MCP summary) | + +Single clean pass, no restart required (consistent with `plan.2026-09-02T08-59.md` Phase 4 "iteration 1" +naming and no `iter2` artifact present). **Verdict: PASS.** + +## Regression-Test RED/GREEN Proof (Bugfix Workflow) + +- `evidence/regression-testing/expect-fail-run.2026-09-03T11-09.md`: pre-fix-order simulation — + 27 Passed / 1 Failed, with the new test's `Should -Invoke Set-Content -Times 1 -Exactly` failing + with "Expected Set-Content to be called 1 times exactly, but was called 0 times" — proves that + under the pre-fix order, `Assert-CoberturaLineCoverageThreshold` throws before `Set-Content` ever + runs. +- `evidence/regression-testing/pass-after-run.2026-09-03T11-09.md`: post-fix — 28 Passed / 0 + Failed, all six `Describe 'Invoke-MSTestWithCoverageMain'` tests (five pre-existing plus the new + one) green, including the unaffected #733 `.claude` worktree test. + +This is a genuine RED-then-GREEN regression-test proof, not a post-hoc assertion. **Verdict: PASS.** + +## Boundary-Test Non-Regression (Threshold Logic, Untouched) + +`evidence/regression-testing/helpers-boundary-regression.2026-09-03T11-09.md`: the five +`Describe 'Assert-CoberturaLineCoverageThreshold'` boundary tests (missing, non-numeric, below-80, +exactly-80, above-80), now located in the post-#733 file +`tests/scripts/vscode/Invoke-MSTestWithCoverage.Threshold.Tests.ps1`, all Passed=5/Failed=0. The +literal plan-target file `Invoke-MSTestWithCoverage.Helpers.Tests.ps1` also ran clean (20/20) +confirming it needed no change. **Verdict: PASS.** + +## Coverage Verification + +Only PowerShell has changed files in this branch (`scripts/vscode/Invoke-MSTestWithCoverage.ps1`, +`tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`). No new files were added; the +production change is a 2-line reorder (net 0 lines added), and the 8 new lines are test-file +content (Pester tests are not part of the coverage-measurement denominator). + +**Canonical artifact check** (`artifacts/pester/powershell-coverage.xml`): the file exists on disk +in the worktree (untracked/gitignored, `.gitignore:57`), with `LastWriteTime` 2026-09-03 07:19 — +close to but not identical to the evidence timestamp band. Parsing its top-level `` +elements gives: + +``` +INSTRUCTION missed=8881 covered=0 +LINE missed=6403 covered=0 +METHOD missed=570 covered=0 +CLASS missed=73 covered=0 +``` + +0% covered across every counter, in every one of 9 packages, is not a credible measurement of a +93/93-passing suite — it is a known invalid-capture defect in the bundled +`mcp__drm-copilot__run_poshqc_test` coverage output, previously documented in this project's +review history (`poshqc-bundled-coverage-artifact-reads-zero`, `441-review-residuals`). Per the +literal verification procedure (repo-wide % < 80% ⇒ FAIL), this is recorded as: + +**PowerShell coverage (canonical artifact): FAIL — non-blocking.** Reason: canonical artifact +present but its capture is invalid (0% for all 9 packages including files known to have real +coverage), a pre-existing tooling defect independent of this branch's changes, not attributable to +this PR. + +**Corroborating evidence** (feature-scoped, direct-Pester, JaCoCo-format, committed artifacts — +not a substitute for the canonical repo-wide figure, but a valid independent measurement of the +changed file): + +- Baseline (`evidence/baseline/poshqc-test.2026-09-03T11-09.md` / `pester-coverage.2026-09-03T11-09.xml`, + captured at `dc5e8c0f`, before this task's fix): 92/92 tests passing; + `scripts/vscode/Invoke-MSTestWithCoverage.ps1` line coverage 90.09% (MainScriptCommands=111, + Executed=100). +- Post-fix (`evidence/qa-gates/poshqc-test.iter1.2026-09-03T11-09.md` / + `pester-coverage.iter1.2026-09-03T11-09.xml`): 93/93 tests passing; same file, same 90.09% + (111/100), confirmed by `evidence/qa-gates/coverage-delta.2026-09-03T11-09.md`. + +This corroborates, for the touched file specifically: line coverage 90.09% >= 85% floor, and zero +regression on changed lines (identical MainScriptCommands total before/after, both swapped +statements confirmed executed by name-checked passing tests). Branch coverage: not applicable — +Pester does not measure branch coverage for PowerShell (per `.claude/rules/general-unit-test.md` +and `.claude/rules/powershell.md`); no branch-coverage gate applies. + +**Repo-wide PowerShell coverage (>= 85% floor): FAIL (non-blocking)** — no valid canonical +artifact was available to confirm the repo-wide percentage, and this agent does not rerun coverage +generation. This is a systemic, pre-existing tooling gap (the bundled MCP coverage capture), not a +defect newly introduced by issue #565's fix. Recommend that a future issue re-verify or replace the +bundled coverage-capture path in `run_poshqc_test`; not blocking for this PR because (a) the change +under review adds no new production code, (b) the one touched file's coverage is independently +proven unchanged and above floor, and (c) the same defect pattern recurs across unrelated features +in this repo's review history and is unrelated to this fix's substance. + +## Quality-Tiers Classification + +No `quality-tiers.yml` exists at repo root on this branch (nor on `origin/main`) — a pre-existing +repository-level gap, already documented in this project's review history +(`build-ci-coverage-gate-fidelity-epic-outcome`), not attributable to this PR. Uniform coverage/lint/format +thresholds from `.claude/rules/quality-tiers.md` were applied directly since tier classification +does not affect the uniform gates relevant to this 2-line change. + +## File Size Compliance + +`scripts/vscode/Invoke-MSTestWithCoverage.ps1`: 350 lines. `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`: +496 lines — under the 500-line limit but close to it (this branch adds 8 of those 496 lines). +**Verdict: PASS**, with an advisory note in `code-review` that the test file is approaching the +limit. + +## Working-Tree Cleanliness + +`git status --porcelain` in the item worktree returns empty output (independently re-verified at +the start and end of this audit). **Verdict: PASS.** + +## Overall Policy-Audit Verdict + +**PASS**, with one non-blocking finding (canonical PowerShell coverage-artifact capture defect, +pre-existing and unrelated to this branch's substance — see § Coverage Verification). No blocking +findings identified. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/research/invoke-mstestwithcoverage-ordering-fix.2026-09-02T09-00.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/research/invoke-mstestwithcoverage-ordering-fix.2026-09-02T09-00.md new file mode 100644 index 000000000..5ff976d48 --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/research/invoke-mstestwithcoverage-ordering-fix.2026-09-02T09-00.md @@ -0,0 +1,288 @@ +--- +title: Invoke-MSTestWithCoverage.ps1 — Assert-before-SetContent ordering fix +issue: 565 +date: 2026-09-02T09-00 +status: research +--- + +## 1. Current state of `scripts/vscode/Invoke-MSTestWithCoverage.ps1` + +File: `scripts/vscode/Invoke-MSTestWithCoverage.ps1` +Content ends at line 491 in the sibling helpers file (see section 2) and at line 349 in this +file; a bare trailing newline is reported as an extra blank "line 350"/"line 492" by the read +tool. Confirmed 349 lines of content (not 341-343 as the issue's line numbers suggested — those +have not drifted for the two calls in question, which are exactly where the issue describes, but +the file's total content length differs slightly from any prior report). + +The two calls under investigation are at their exact current line numbers, unchanged from the +issue's description: + +``` +333 # Post-process the Cobertura XML for Koverage compatibility: +334 # 1. Rewrite absolute paths to workspace-relative paths using native separators. +335 # 2. Inject . (required by cobertura-parse). +336 # 3. Remove elements for third-party assemblies that are not part +337 # of the solution (dotnet-coverage instruments all loaded DLLs at runtime). +338 Write-Output 'Post-processing coverage XML for Koverage compatibility...' +339 $xmlContent = Get-Content $resolvedOutputPath -Raw -Encoding UTF8 +340 $processedXmlContent = ConvertTo-KoverageCoberturaXml -XmlContent $xmlContent -RepoRoot $repoRoot +341 Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent +342 +343 Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline +344 Write-Output "Done. Coverage artifact: $resolvedOutputPath" +345 } +``` + +- **`Assert-CoberturaLineCoverageThreshold` call: line 341** (unchanged from the issue). +- **`Set-Content` call: line 343** (unchanged from the issue). +- Both calls are the last two statements of `Invoke-MSTestWithCoverageMain`, which spans lines + 248-345. There is one blank line (342) between them; no other statement sits between the two + calls today. +- `Set-Content` uses **`-Path`** (not `-LiteralPath`) — this differs from the other `Set-Content` + call in the same file (`Invoke-DotnetCoverageCollection`, line 219, which uses `-LiteralPath` + for the derived coverage-settings file). Any new Pester mock/parameter-filter targeting this + call must filter on `$Path`, not `$LiteralPath`. +- The fix is a pure statement reorder: swap the order of lines 341 and 343 (moving the + `Set-Content` call above the `Assert-CoberturaLineCoverageThreshold` call) so `$resolvedOutputPath` + always receives `$processedXmlContent` before the threshold check can throw. No other line in + the function needs to change; `$processedXmlContent` is already fully computed by line 340 in + both call orders. +- `Invoke-MSTestWithCoverageMain` is dot-sourced by `Invoke-MSTestWithCoverage.Helpers.ps1` at + line 261 (`. (Join-Path $ScriptRoot 'Invoke-MSTestWithCoverage.Helpers.ps1')`), and the script + guards its own top-level auto-invocation with `if ($MyInvocation.InvocationName -ne '.')` at + lines 347-349 — this guard is unaffected by the fix. + +## 2. `Assert-CoberturaLineCoverageThreshold` (Helpers.ps1) + +File: `scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1`, lines 459-491 (491 lines of content +total in the file). + +Exact signature: + +```powershell +function Assert-CoberturaLineCoverageThreshold { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$CoberturaXml + ) + ... +} +``` + +- Single mandatory `[string]$CoberturaXml` parameter. No `OutputType` attribute (the function + never returns a value on success — it either throws or falls through to the end of the function + body, which is `$null`). +- Body: parses `$CoberturaXml` into a **new local** `[xml]$coverageDocument` variable (line 466); + it does not mutate the `$CoberturaXml` string parameter, nor any variable outside its own local + scope. It reads `/coverage/line-rate`, throws on a missing/non-numeric/out-of-range line-rate, + computes `$percentage = $lineRate * 100`, and throws `"Cobertura line coverage {0}% is below the + required 80% threshold."` when `$percentage -lt 80` (line 487, threshold literal `80` — out of + scope per the task background, tracked separately by #563). +- No I/O, no writes, no global state changes, and no return value consumed by the caller (the call + site at line 341 does not capture a return value). This confirms reordering `Set-Content` ahead + of this call is safe: the function has no side effect that `Set-Content` could observe or that + could observe `Set-Content`'s effect, and it does not depend on the output file having already + been written. + +## 3. Testability of `Invoke-MSTestWithCoverageMain` via Pester + +`Invoke-MSTestWithCoverageMain` **is already exercised directly by an existing Pester suite** +without any live `dotnet-coverage`/`vstest` run: +`tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, `Describe 'Invoke-MSTestWithCoverageMain'` +(lines 345-414). + +Dot-sourcing pattern used to make the function callable (lines 3-25 of that test file): + +```powershell +BeforeAll { + $script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..')).Path + $script:mstestScript = Join-Path $script:repoRoot 'scripts\vscode\Invoke-MSTest.ps1' + $script:coverageScript = Join-Path $script:repoRoot 'scripts\vscode\Invoke-MSTestWithCoverage.ps1' + $script:scriptDir = Join-Path $script:repoRoot 'scripts\vscode' + + try { . $script:mstestScript -NoExecute } catch { Write-Verbose "Invoke-MSTest body skipped: $_" } + + $tokens = $null + $parseErrors = $null + $coverageAst = [System.Management.Automation.Language.Parser]::ParseFile( + $script:coverageScript, + [ref]$tokens, + [ref]$parseErrors) + $parseErrors | Should -BeNullOrEmpty + . $coverageAst.GetScriptBlock() + . (Join-Path $script:scriptDir 'Invoke-MSTestWithCoverage.Helpers.ps1') + + $script:expectedRunSettings = Join-Path $script:scriptDir 'TaskMaster.cli.runsettings' +} +``` + +The script is parsed via `[System.Management.Automation.Language.Parser]::ParseFile` and then +dot-sourced as a scriptblock (`. $coverageAst.GetScriptBlock()`) rather than invoked with `&` or +by path. Because dot-sourcing sets `$MyInvocation.InvocationName` to `'.'` for the scriptblock's +own top-level execution, the file's own bottom guard (`if ($MyInvocation.InvocationName -ne '.')`) +evaluates false and `Invoke-MSTestWithCoverageMain` is **not** auto-invoked — only its (and every +other function's) definition is imported. This is the same technique the two sibling helper test +files use for `Invoke-MSTestWithCoverage.Helpers.ps1` (plain `. $helperScriptPath`, since that +file has no top-level guard/auto-invocation at all). + +`Invoke-DotnetCoverageCollection` — the function that shells out to `dotnet-coverage`/`vstest` — is +mocked with plain `Mock` in the existing `BeforeEach` (line 366: `Mock Invoke-DotnetCoverageCollection +{ $script:coverageCallCount++ }`), confirming it is directly mockable via Pester's `Mock` cmdlet +with no additional seam work required. The same `BeforeEach` (lines 346-372) also mocks every +other I/O boundary the fixed-up main function touches: `Resolve-Path`, `Test-Path`, +`Resolve-RunSettingsPath`, `Invoke-VsWhereExe`, `Get-Command`, `Get-ChildItem`, `Get-Content`, +`ConvertTo-KoverageCoberturaXml`, and `Set-Content`. This means the entire post-collection block +(lines 333-344, containing the two statements under investigation) is already fully reachable and +fully mockable in-process — a new regression test needs no new seam. + +## 4. Deterministic reproduction of a sub-threshold run (no temp files) + +The existing `BeforeEach` for `Describe 'Invoke-MSTestWithCoverageMain'` (lines 346-372) already +mocks `ConvertTo-KoverageCoberturaXml` to return a fixed in-memory string: + +```powershell +Mock ConvertTo-KoverageCoberturaXml { '' } +``` + +and one existing `It` (lines 400-406) demonstrates overriding that mock per-test and reading the +value that reaches `Assert-CoberturaLineCoverageThreshold` by mocking the assert function itself: + +```powershell +It 'passes the generated Cobertura result to the threshold evaluator before completing successfully' { + $script:evaluatedCoberturaXml = $null + Mock Assert-CoberturaLineCoverageThreshold { param([string]$CoberturaXml) $script:evaluatedCoberturaXml = $CoberturaXml } + Mock ConvertTo-KoverageCoberturaXml { '' } + Invoke-MSTestWithCoverageMain -ScriptRoot $script:scriptDir + $script:evaluatedCoberturaXml | Should -Be '' +} +``` + +The smallest deterministic way to reproduce a sub-threshold run for this issue's regression test +is the same pattern, but leaving `Assert-CoberturaLineCoverageThreshold` **unmocked** (real) so it +genuinely throws, and overriding only `ConvertTo-KoverageCoberturaXml`'s mock return value to a +fixed Cobertura XML string whose `line-rate` is below 0.80: + +```powershell +Mock ConvertTo-KoverageCoberturaXml { '' } +``` + +**Recommended fixture line-rate value: `0.5`** (50%). It is comfortably below the 80% threshold +(avoiding any boundary/rounding ambiguity — contrast with the existing boundary-focused fixtures +`0.799999`, `0.8`, `0.800001` in `Invoke-MSTestWithCoverage.Helpers.Tests.ps1` lines 495-497, +which exist specifically to pin the boundary and are not appropriate for a "clearly below +threshold, persistence still expected" regression test) and formats without decimal noise in the +thrown message (`"Cobertura line coverage 50% is below the required 80% threshold."`). + +This is the convention already used throughout the repo's Helper tests: every Cobertura fixture in +`Invoke-MSTestWithCoverage.Helpers.Tests.ps1` and `Invoke-MSTestWithCoverage.ClosureFilter.Tests.ps1` +is an in-memory PowerShell here-string (`@'...'@`) assigned to a local variable and passed directly +as a string parameter — none of them touch the filesystem. The `Invoke-MSTest.RunSettings.Tests.ps1` +`Describe 'Invoke-MSTestWithCoverageMain'` block goes one step further and mocks `Get-Content` +itself (`Mock Get-Content { '' }`, line 369) so that even the *raw* pre-post-processing +read never touches disk; a new regression test in that `Describe` block should follow the same +in-memory-only mocking, matching repo policy against temporary files in tests. + +An alternative, higher-fidelity approach mentioned in the task — leaving `ConvertTo-KoverageCoberturaXml` +unmocked and exercising it for real against a raw fixture — is possible in principle (the function +is pure and accepts an `-XmlContent` string plus a `-ProjectNames` override per the Helpers tests), +but it is not necessary here: the ordering defect and its fix are entirely about the *order of two +statements* operating on an already-computed `$processedXmlContent` string, not about the content +of that string. Mocking `ConvertTo-KoverageCoberturaXml` directly (as the existing `Describe` block +already does for its other cases) is both the minimal-diff option and the one consistent with this +file's established convention. + +**Assertion shape for the new regression test** (illustrative, not a directive to write test code +here): call `Invoke-MSTestWithCoverageMain -ScriptRoot $script:scriptDir` inside `{ ... } | Should +-Throw`, then assert `Should -Invoke Set-Content -Times 1 -Exactly` (optionally with a +`-ParameterFilter { $Path -eq -and $Value -eq '' }`, +using `$Path` per the exact parameter name at line 343, not `$LiteralPath`). Against the current +(pre-fix) statement order this assertion fails, because `Assert-CoberturaLineCoverageThreshold` +throws before `Set-Content` is ever reached; after the fix (swap lines 341/343) it passes, because +`Set-Content` runs first, unconditionally, regardless of the following threshold outcome. + +## 5. Other call sites of the two functions + +Repository-wide search (production `.ps1` files and all test files) confirms: + +- `Assert-CoberturaLineCoverageThreshold` has exactly **one production call site**: line 341 of + `scripts/vscode/Invoke-MSTestWithCoverage.ps1` (the one under investigation). All other matches + are Pester test files (`Invoke-MSTestWithCoverage.Helpers.Tests.ps1` — direct unit tests of the + function in isolation; `Invoke-MSTest.RunSettings.Tests.ps1` — the mocked + `Invoke-MSTestWithCoverageMain` cases) and historical/archived Markdown research, plan, and audit + documents under `docs/features/`. +- `Invoke-MSTestWithCoverageMain` has exactly **one production call site**: line 348 of the same + file, inside its own bottom `if ($MyInvocation.InvocationName -ne '.')` guard. All other matches + are the same Pester test file's mocked calls and archived Markdown documents. +- No other production script in the repository dot-sources or otherwise depends on the current + (pre-fix) call order between these two functions. A grep for `Invoke-MSTestWithCoverage\.ps1| + Invoke-MSTestWithCoverage\.Helpers\.ps1` across all `*.ps1` files returns only: the two subject + files themselves (Helpers.ps1 is dot-sourced by the main script at line 261) and the three + sibling Pester test files (`Invoke-MSTestWithCoverage.Helpers.Tests.ps1`, + `Invoke-MSTestWithCoverage.ClosureFilter.Tests.ps1`, `Invoke-MSTest.RunSettings.Tests.ps1`). + +## 6. File size + +- `scripts/vscode/Invoke-MSTestWithCoverage.ps1`: 349 lines of content (the read tool reports an + extra blank "line 350" for the file's trailing newline). Well under the repository's 500-line + limit. The fix is a pure two-line swap (net zero or +0 line-count change if no comment is added, + or a small positive delta if a one-line clarifying comment is added) — it cannot push the file + toward the limit. +- `scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1`: 491 lines of content (again, the read + tool reports an extra trailing blank "line 492"). This file is not touched by the fix (the + `Assert-CoberturaLineCoverageThreshold` function itself is unchanged; only its caller's statement + order in the other file changes) and remains under the 500-line limit. + +## 7. Dot-sourcing by other production scripts + +`scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1` is dot-sourced by exactly one production +script: `scripts/vscode/Invoke-MSTestWithCoverage.ps1` line 261 +(`. (Join-Path $ScriptRoot 'Invoke-MSTestWithCoverage.Helpers.ps1')`), inside +`Invoke-MSTestWithCoverageMain`. `Invoke-MSTestWithCoverage.Helpers.ps1` itself dot-sources one +further sibling, `Invoke-MSTestWithCoverage.ClosureFilter.ps1`, at its own line 2 +(`. (Join-Path $PSScriptRoot 'Invoke-MSTestWithCoverage.ClosureFilter.ps1')`). + +`scripts/vscode/Invoke-MSTestWithCoverage.ps1` itself is not dot-sourced by any other production +script in the repository — only by the three Pester test files identified in section 5 (one via +plain `. $script:coverageScript -NoExecute`-style invocation is *not* used for this file; it is +always parsed via `ParseFile`/`GetScriptBlock` per section 3, precisely because the file has a +top-level auto-invocation guard that a plain `. ` dot-source would still respect but that the +test authors chose to make explicit via AST parsing). No other production `.ps1`/`.psm1` file +references either of the two subject file paths. + +An agent editing lines 341/343 of `Invoke-MSTestWithCoverage.ps1` therefore has exactly one +production caller to consider (the file's own bottom guard) and one function-definition dependency +to preserve (the dot-source of `Invoke-MSTestWithCoverage.Helpers.ps1` at line 261, which must +continue to precede the two calls being reordered — it already does, at line 261, far above line +341/343, and is unaffected by the fix). + +## Candidate approaches + +**Approach A (recommended): pure statement reorder.** Swap lines 341 and 343 so `Set-Content` +executes immediately after `ConvertTo-KoverageCoberturaXml` (line 340) and before +`Assert-CoberturaLineCoverageThreshold`. No signature changes, no new parameters, no new +functions. Confirmed safe by section 2 (the assert function is a pure read-and-throw over its own +local `[xml]` copy) and section 5 (no other caller depends on the old order). This is the smallest +possible diff and matches the issue's stated fix. + +**Rejected alternative:** wrapping both calls in a `try`/`finally` that always writes +`$processedXmlContent` regardless of assert outcome. Rejected because it is strictly more complex +than swapping two statements for the same observable result — the assert function has no output to +preserve past a throw, and `finally` semantics add an indirection with no behavioral benefit here. + +## Testing implications + +- Add one new `It` inside the existing `Describe 'Invoke-MSTestWithCoverageMain'` block in + `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` (see section 4 for the exact fixture + value and assertion shape). This follows the file's existing `BeforeEach` mocking conventions + exactly and requires no new seam, no new mock target, and no temporary file. + This is a **bugfix regression test** per the repository's Bugfix Workflow: it must fail against + the current (pre-fix) statement order and pass after the fix. +- No changes are needed to `Invoke-MSTestWithCoverage.Helpers.Tests.ps1` or + `Invoke-MSTestWithCoverage.ClosureFilter.Tests.ps1` — the threshold-boundary tests in the former + (lines 492-498) already fully cover `Assert-CoberturaLineCoverageThreshold`'s own behavior in + isolation and remain unaffected by the caller-side reorder. +- No numeric spec.md acceptance criterion in this issue depends on a population/enumeration count, + so the Numeric Derivation Evidence section is not applicable here; the two "exactly one call + site" findings in section 5 are call-site verifications via a repository-wide grep across + production `.ps1` files, not spec.md-driven numeric claims. diff --git a/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md new file mode 100644 index 000000000..cb23a997d --- /dev/null +++ b/docs/features/active/2026-09-02-invoke-mstestwithcoverage-threshold-before-setcontent-565/spec.md @@ -0,0 +1,258 @@ +# invoke-mstestwithcoverage-threshold-before-setcontent (Spec) + +- **Issue:** #565 +- **Parent (optional):** none +- **Owner:** drmoisan +- **Last Updated:** 2026-09-02T08-59 +- **Status:** Implemented +- **Version:** 0.1 + +## Write Set +`scripts/vscode/Invoke-MSTestWithCoverage.ps1` +`tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` + +## Context +`scripts/vscode/Invoke-MSTestWithCoverage.ps1` asserts the coverage threshold before it writes the +post-processed Cobertura document to disk. When the assertion fails, the script throws and the +post-processed document is discarded, leaving the raw un-post-processed document at the output path. + +Environment: +- OS/version: Windows 11 Pro (repo default) +- Python version: n/a — PowerShell 7+ coverage/test-runner scripts +- Command/flags used: `scripts/vscode/Invoke-MSTestWithCoverage.ps1` +- Data source or fixture: n/a + +Impact / Severity: +- [ ] Blocker +- [ ] High +- [x] Medium +- [ ] Low + +Only the failure path is affected; a passing run writes the correct document. But the failure path is +exactly when someone reads the artifact to diagnose the shortfall, and what they find is a document +with different numbers than the one that produced the failure message. It also means a failed gate +leaves behind an artifact that, if fed to any downstream consumer, reports the pre-#441 inflated +denominator. + + +## Repro & Evidence +Steps to Reproduce: +1. Run `scripts/vscode/Invoke-MSTestWithCoverage.ps1` against a test suite whose measured line + coverage is below the configured 80% threshold. +2. Observe that `Assert-CoberturaLineCoverageThreshold` (in Invoke-MSTestWithCoverage.Helpers.ps1) + throws before the post-processed XML is persisted. +3. Inspect the coverage output path named by `-CoverageOutput` after the throw. + +Expected: +The artifact on disk should be the same post-processed Cobertura document that the threshold +assertion judged, in both the passing and failing case. + +Actual: +At `scripts/vscode/Invoke-MSTestWithCoverage.ps1:341-343` the threshold assertion runs ahead of the +`Set-Content` that persists the post-processed XML. On a failing run, the artifact left on disk is +the raw `dotnet-coverage` output — absolute paths, third-party packages included, unmerged duplicate +classes, and the double-counted line totals that #441 corrected. + +Logs / Screenshots: +- [ ] Attached minimal logs or screenshot +- Snippet: n/a — see file/line citation above. + + +## Scope & Non-Goals +- In scope: + - Reorder two statements in `scripts/vscode/Invoke-MSTestWithCoverage.ps1`: move the + `Set-Content` call (currently line 343) above the `Assert-CoberturaLineCoverageThreshold` call + (currently line 341), inside `Invoke-MSTestWithCoverageMain`. + - Add one new Pester regression test to the existing `Describe 'Invoke-MSTestWithCoverageMain'` + block in `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, asserting that the + post-processed Cobertura document is persisted before the threshold assertion can throw. +- Out of scope / non-goals: + - Do NOT change the 80% threshold value inside `Assert-CoberturaLineCoverageThreshold` + (scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1, line 487). That threshold-value + contradiction is tracked separately by issue #563 and is explicitly excluded from this fix. + - Do not change `Assert-CoberturaLineCoverageThreshold`'s parsing, throw conditions, or message + text. + - Do not add a `try`/`finally` wrapper or any other indirection; the fix is a pure statement + reorder (see Proposed Fix). +- Explicitly excluded systems, integrations, or datasets: + - .claude/**, .codex/**, .agents/**, config/blast-radius.json, + config/orchestration-routing.json — these are published from an upstream repository and must + not be edited as part of this fix. + +## Root Cause Analysis +Statement ordering defect only, not a logic change. Found during the `build-ci-coverage-gate-fidelity` +epic fan-in review; identified independently by two review passes. Note: issue #563 (threshold VALUE +contradiction) is a separate, deliberately excluded concern — this fix must not change the threshold +value, only the statement order. + + +## Proposed Fix + +### Design summary (what changes where): +Pure two-statement reorder inside `Invoke-MSTestWithCoverageMain` +(`scripts/vscode/Invoke-MSTestWithCoverage.ps1`, lines 341 and 343): swap the `Set-Content` call +(line 343) to execute immediately after `$processedXmlContent` is computed (line 340) and before +the `Assert-CoberturaLineCoverageThreshold` call (currently line 341). No function signature or +parameter changes anywhere in the change. + +### Boundaries and invariants to preserve: +- `Assert-CoberturaLineCoverageThreshold`'s own 80% threshold logic and message text + (scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1, line 487) are unchanged. +- The dot-source of Invoke-MSTestWithCoverage.Helpers.ps1 at line 261 continues to precede both + calls (unaffected by the reorder; it already sits far above lines 341/343). +- `Assert-CoberturaLineCoverageThreshold` remains a pure read-and-throw function over its own + local `[xml]$coverageDocument` copy — it has no side effect that `Set-Content` could observe or + that could observe `Set-Content`'s effect, so reordering is behaviorally safe. +- The file's bottom auto-invocation guard (`if ($MyInvocation.InvocationName -ne '.')`, + lines 347-349) is unaffected. + +### Dependencies or blocked work: +None. The fix is self-contained to the one file and its regression test; it has no dependency on +issue #563 (threshold-value contradiction), which is deliberately excluded from this fix. + +### Implementation strategy (what changes, not sequencing): + +#### Files/modules to change: +- `scripts/vscode/Invoke-MSTestWithCoverage.ps1` (production) — swap lines 341 and 343. +- `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1` (test) — add one new `It` inside the + existing `Describe 'Invoke-MSTestWithCoverageMain'` block. + +#### Functions/classes/CLI commands impacted: +- `Invoke-MSTestWithCoverageMain` only. `Assert-CoberturaLineCoverageThreshold` is not modified. + +#### Data flow and validation changes: +None. `$processedXmlContent` is already fully computed by line 340 under both call orders; no data +computed, transformed, or validated differently. + +#### Error handling and logging updates: +None. `Assert-CoberturaLineCoverageThreshold` still throws under the same conditions with the same +message; only the point in the statement sequence at which that throw can occur (relative to +`Set-Content`) changes. + +#### Rollback/feature-flag considerations (if applicable): +None required. The change is a two-line swap with no new configuration surface; rollback is a +direct revert of the diff. + +### Technical specifications (interfaces/contracts): +No interface or contract changes. `Invoke-MSTestWithCoverageMain`'s parameters, return behavior, +and external call sites (see research, section 5: exactly one production call site each for +`Invoke-MSTestWithCoverageMain` and `Assert-CoberturaLineCoverageThreshold`) are unaffected. + +#### Inputs/outputs and formats: +Unchanged. Output artifact format (post-processed Cobertura XML at `-CoverageOutput`) is +unaffected; only the ordering of when it is written relative to the threshold check changes. + +#### Required configuration keys and defaults: +None added or changed. + +#### Backward-compatibility expectations: +Fully backward compatible. Passing runs (coverage at or above 80%) are unaffected because both +statements already execute in that case today; only the failing-run artifact content changes (the +post-processed document is now what is left on disk instead of the raw `dotnet-coverage` output). + +#### Performance constraints (latency/throughput/memory): +None. The reorder does not add, remove, or change any I/O or computation — it only changes the +sequence of two already-existing statements. + +## Assumptions, Constraints, Dependencies +- Assumptions (environment, data, access): +- Constraints (budget, performance, compatibility): +- External dependencies (services, libraries, releases): + +## Data / API / Config Impact +- User-facing or API changes: +- Data or migration considerations: +- Logging/telemetry updates (if any): +- Compatibility notes (CLI flags, config schemas, versioning): + +## Test Strategy +Seeded from issue: + +- [x] Move the `Set-Content` above the `Assert-CoberturaLineCoverageThreshold` call so the judged + document is persisted before the threshold is evaluated. +- [x] Add a Pester test under `tests/scripts/vscode/` asserting that a sub-threshold run still leaves + the post-processed document on disk (not the raw `dotnet-coverage` output). + +- Regression tests to add or update: + - One new `It` inside the existing `Describe 'Invoke-MSTestWithCoverageMain'` block in + `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, following the file's existing + `BeforeEach` mocking conventions exactly: + - Override `Mock ConvertTo-KoverageCoberturaXml { '' }` (fixture + well below the 80% threshold, avoiding boundary/rounding ambiguity). + - Leave `Assert-CoberturaLineCoverageThreshold` unmocked (real), so it genuinely throws. + - Call `Invoke-MSTestWithCoverageMain -ScriptRoot $script:scriptDir` inside + `{ ... } | Should -Throw`. + - Assert `Should -Invoke Set-Content -Times 1 -Exactly` (filtered on `$Path`, not + `$LiteralPath` — this call site uses `-Path`, unlike the other `Set-Content` call at line 219 + of the same file). + - This is a **bugfix regression test** per the repository's Bugfix Workflow: it is tagged + `[expect-fail]` against the pre-fix statement order (fails, because + `Assert-CoberturaLineCoverageThreshold` throws before `Set-Content` is ever reached) and must + pass after the fix (because `Set-Content` runs first, unconditionally, before the threshold + outcome is evaluated). +- Unit tests (pytest) for the fixed behavior and boundaries: n/a — PowerShell/Pester repo, no + Python involved in this change. +- Edge cases and negative scenarios (invalid inputs, missing data, boundary values): + - Sub-threshold run (this fix's regression test, fixture `line-rate="0.5"`). + - Existing boundary fixtures (`0.799999`, `0.8`, `0.800001` in + Invoke-MSTestWithCoverage.Helpers.Tests.ps1, lines 495-497) already cover + `Assert-CoberturaLineCoverageThreshold`'s own boundary behavior in isolation and require no + change. + - At/above-threshold run: already covered by the existing `It` at lines 400-406 of + `Invoke-MSTest.RunSettings.Tests.ps1` and remains unaffected by the reorder. +- Error handling and logging verification: + - The new test confirms `Assert-CoberturaLineCoverageThreshold` still throws on a sub-threshold + run (via `{ ... } | Should -Throw`); only the timing of the throw relative to `Set-Content` + changes. +- Coverage impact and targets for changed lines/modules: the changed lines (341/343 swap) are + already fully exercised by the existing `Describe 'Invoke-MSTestWithCoverageMain'` block plus the + new test; no new uncovered lines are introduced. +- Toolchain commands to run (format → lint → type-check → test): this repository's PowerShell + toolchain has no type-check stage. + 1. PoshQC format (PowerShell formatting check/apply). + 2. PSScriptAnalyzer (linting/static analysis). + 3. Pester (`tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, and the full PowerShell + suite for regression safety). +- Manual validation steps (if required): none required beyond the automated toolchain; the + regression test deterministically reproduces the sub-threshold-run scenario in-memory (no temp + files, matching repo policy). + + +## Acceptance Criteria +- [x] The new Pester test in `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`, inside the + `Describe 'Invoke-MSTestWithCoverageMain'` block, fails against the pre-fix statement order + (`Assert-CoberturaLineCoverageThreshold` at line 341 ahead of `Set-Content` at line 343) and + passes after the fix (statement order swapped). +- [x] `Set-Content` is invoked before `Assert-CoberturaLineCoverageThreshold` can throw on a + sub-threshold run, verified by `Should -Invoke Set-Content -Times 1 -Exactly` asserted inside + a `{ ... } | Should -Throw` block, using the `ConvertTo-KoverageCoberturaXml` mock returning + `''`. +- [x] The coverage threshold value (80%) is unchanged: no diff touches + `Assert-CoberturaLineCoverageThreshold`'s threshold literal (line 487) or its throw message + text in scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1. Post-#733 drift note: this + logic now actually resides in scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1 + (line 52/54); the [P2-T1] fix-diff-verification artifact confirms an empty `git diff` for + BOTH that file and Helpers.ps1 against BASELINE_SHA. +- [x] No production file other than `scripts/vscode/Invoke-MSTestWithCoverage.ps1` is changed (in + particular, scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 and + scripts/vscode/Invoke-MSTestWithCoverage.ClosureFilter.ps1 are untouched). +- [x] PoshQC format, PSScriptAnalyzer, and Pester all pass cleanly on the changed files + (`scripts/vscode/Invoke-MSTestWithCoverage.ps1` and + `tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1`), with no format or lint + auto-fixes needed and no regression in the existing `Describe 'Invoke-MSTestWithCoverageMain'` + cases (lines 345-414) or the boundary tests in Invoke-MSTestWithCoverage.Helpers.Tests.ps1. + Post-#733 drift note: the five boundary tests now live in + Invoke-MSTestWithCoverage.Threshold.Tests.ps1, confirmed 5/5 Passed by the [P3-T2] + helpers-boundary-regression artifact. +- [x] Repro steps from `## Repro & Evidence` now produce the expected behavior: after the fix, the + artifact left on disk at `-CoverageOutput` on a sub-threshold run is the same post-processed + Cobertura document that the threshold assertion judged, not the raw `dotnet-coverage` output. + +## Risks & Mitigations +- Technical or operational risks: +- Mitigations and rollbacks: + +## Rollout & Follow-up +- Release/rollout steps: +- Post-fix monitoring or clean-up tasks: +- Links: issue, PRs, related docs diff --git a/scripts/vscode/Invoke-MSTestWithCoverage.ps1 b/scripts/vscode/Invoke-MSTestWithCoverage.ps1 index 2e386a9bc..7d4e3c36f 100644 --- a/scripts/vscode/Invoke-MSTestWithCoverage.ps1 +++ b/scripts/vscode/Invoke-MSTestWithCoverage.ps1 @@ -339,9 +339,9 @@ function Invoke-MSTestWithCoverageMain { Write-Output 'Post-processing coverage XML for Koverage compatibility...' $xmlContent = Get-Content $resolvedOutputPath -Raw -Encoding UTF8 $processedXmlContent = ConvertTo-KoverageCoberturaXml -XmlContent $xmlContent -RepoRoot $repoRoot - Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent - Set-Content -Path $resolvedOutputPath -Value $processedXmlContent -Encoding UTF8 -NoNewline + + Assert-CoberturaLineCoverageThreshold -CoberturaXml $processedXmlContent Write-Output "Done. Coverage artifact: $resolvedOutputPath" } diff --git a/tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1 b/tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1 index 4b168b079..7948ae0ec 100644 --- a/tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1 +++ b/tests/scripts/vscode/Invoke-MSTest.RunSettings.Tests.ps1 @@ -413,6 +413,14 @@ Describe 'Invoke-MSTestWithCoverageMain' { Should -Throw -ExpectedMessage 'Search root not found: C:\repo\.' } + It 'persists the post-processed Cobertura document before the threshold assertion can throw on a sub-threshold run' { + Mock ConvertTo-KoverageCoberturaXml { '' } + + { Invoke-MSTestWithCoverageMain -ScriptRoot $script:scriptDir } | Should -Throw + + Should -Invoke Set-Content -Times 1 -Exactly + } + It 'excludes assemblies discovered under a .claude worktree segment' { # Issue #733 finding 3: agent worktrees under .claude carry their own built # copy of every test assembly, so discovery must drop them before collection.