@danmoisan/drm-copilot-mcp 1.1.17 → 1.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/out/mcp-server.js +1405 -684
  2. package/package.json +6 -4
  3. package/resources/claude-customizations/.claude/agents/atomic-executor.md +11 -0
  4. package/resources/claude-customizations/.claude/agents/atomic-planner.md +10 -0
  5. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +12 -4
  6. package/resources/claude-customizations/.claude/agents/epic-planner.md +5 -4
  7. package/resources/claude-customizations/.claude/agents/feature-review.md +16 -5
  8. package/resources/claude-customizations/.claude/agents/orchestrator.md +21 -7
  9. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +37 -13
  10. package/resources/claude-customizations/.claude/agents/parallel-planner.md +9 -6
  11. package/resources/claude-customizations/.claude/agents/pr-author.md +15 -2
  12. package/resources/claude-customizations/.claude/agents/prd-feature.md +1 -1
  13. package/resources/claude-customizations/.claude/hooks/check-powershell-test-purity.ps1 +7 -1
  14. package/resources/claude-customizations/.claude/hooks/check-python-test-purity.ps1 +7 -1
  15. package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
  16. package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +111 -46
  17. package/resources/claude-customizations/.claude/hooks/enforce-completion-helpers.ps1 +105 -0
  18. package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +8 -1
  19. package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +7 -1
  20. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +116 -59
  21. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +55 -23
  22. package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +74 -75
  23. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +127 -35
  24. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +61 -57
  25. package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +7 -1
  26. package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +129 -39
  27. package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +7 -1
  28. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +9 -3
  29. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +39 -51
  30. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
  31. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
  32. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
  33. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +17 -12
  34. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +9 -3
  35. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +65 -50
  36. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +49 -42
  37. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +54 -72
  38. package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +8 -2
  39. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +279 -0
  40. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +129 -75
  41. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.artifact-root.ps1 +233 -0
  42. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.docs-only.ps1 +224 -0
  43. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +69 -24
  44. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +29 -18
  45. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +10 -4
  46. package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +14 -5
  47. package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +8 -2
  48. package/resources/claude-customizations/.claude/hooks/feature-folder-resolution.ps1 +373 -0
  49. package/resources/claude-customizations/.claude/hooks/feature-review-coverage-thresholds.ps1 +85 -0
  50. package/resources/claude-customizations/.claude/hooks/hook-command-heredoc.ps1 +119 -0
  51. package/resources/claude-customizations/.claude/hooks/hook-command-invocation-operands.ps1 +198 -0
  52. package/resources/claude-customizations/.claude/hooks/hook-command-invocation.ps1 +254 -255
  53. package/resources/claude-customizations/.claude/hooks/hook-command-payload-powershell.ps1 +201 -0
  54. package/resources/claude-customizations/.claude/hooks/hook-command-payload.ps1 +484 -0
  55. package/resources/claude-customizations/.claude/hooks/hook-command-scanner.ps1 +27 -110
  56. package/resources/claude-customizations/.claude/hooks/hook-dependency-guard.ps1 +114 -0
  57. package/resources/claude-customizations/.claude/hooks/hook-docs-only-diff.ps1 +493 -0
  58. package/resources/claude-customizations/.claude/hooks/subagentstop-envelope.ps1 +192 -0
  59. package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +9 -3
  60. package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +40 -42
  61. package/resources/claude-customizations/.claude/hooks/validate-executor-output.ps1 +66 -38
  62. package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-metrics.ps1 +180 -0
  63. package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-rules.ps1 +195 -0
  64. package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage.ps1 +141 -395
  65. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-resolution.ps1 +311 -0
  66. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-routing.ps1 +170 -0
  67. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +115 -190
  68. package/resources/claude-customizations/.claude/hooks/validate-planner-output.ps1 +64 -32
  69. package/resources/claude-customizations/.claude/hooks/validate-pr-author-output.ps1 +39 -44
  70. package/resources/claude-customizations/.claude/hooks/validate-prd-feature-output.ps1 +43 -14
  71. package/resources/claude-customizations/.claude/hooks/validate-required-artifact-output.ps1 +66 -36
  72. package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +31 -34
  73. package/resources/claude-customizations/.claude/lib/bash/mutate-parallel-run.sh +191 -0
  74. package/resources/claude-customizations/.claude/lib/bash/parallel-cohorts.sh +13 -3
  75. package/resources/claude-customizations/.claude/lib/bash/parallel-lane-assertion.sh +4 -2
  76. package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-cli.sh +152 -0
  77. package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-ops.sh +193 -0
  78. package/resources/claude-customizations/.claude/lib/bash/parallel-mutation.sh +322 -0
  79. package/resources/claude-customizations/.claude/lib/bash/remove-parallel-item.sh +146 -0
  80. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +23 -33
  81. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +86 -6
  82. package/resources/claude-customizations/.claude/lib/ci-gate/Invoke-CiGateParser.ps1 +91 -19
  83. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +6 -13
  84. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateEpicWaveBarrier.psm1 +346 -0
  85. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +396 -0
  86. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +22 -15
  87. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRemediationAccounting.psm1 +337 -0
  88. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingContract.psm1 +9 -1
  89. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +79 -2
  90. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +3 -3
  91. package/resources/claude-customizations/.claude/rules/architecture-boundaries.md +5 -5
  92. package/resources/claude-customizations/.claude/rules/csharp.md +2 -2
  93. package/resources/claude-customizations/.claude/rules/general-code-change.md +1 -1
  94. package/resources/claude-customizations/.claude/rules/general-unit-test.md +3 -2
  95. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +153 -4
  96. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +50 -5
  97. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +47 -8
  98. package/resources/claude-customizations/.claude/rules/quality-tiers.md +11 -7
  99. package/resources/claude-customizations/.claude/rules/shell.md +10 -8
  100. package/resources/claude-customizations/.claude/rules/typescript.md +1 -1
  101. package/resources/claude-customizations/.claude/settings.json +3 -4
  102. package/resources/claude-customizations/.claude/skills/acceptance-criteria-tracking/SKILL.md +9 -1
  103. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +7 -1
  104. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +11 -3
  105. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup-worktrees.sh +11 -2
  106. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_detached_lib.sh +1 -1
  107. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_enumerate_lib.sh +171 -5
  108. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_lib.sh +1 -1
  109. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_report_records_lib.sh +6 -43
  110. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_scan_helper.sh +6 -15
  111. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +45 -11
  112. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +1 -1
  113. package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  114. package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +10 -0
  115. package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +7 -4
  116. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +42 -16
  117. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +52 -14
  118. package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +22 -9
  119. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +97 -19
  120. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +63 -7
  121. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +35 -12
  122. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +3 -1
  123. package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +1 -1
  124. package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +7 -3
  125. package/resources/claude-customizations/pack-manifests/core.json +25 -0
  126. package/resources/codex-and-agents-customizations/.agents/skills/acceptance-criteria-tracking/SKILL.md +9 -1
  127. package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +5 -5
  128. package/resources/codex-and-agents-customizations/.agents/skills/atomic-executor/SKILL.md +11 -0
  129. package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +4 -0
  130. package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +10 -0
  131. package/resources/codex-and-agents-customizations/.agents/skills/csharp/SKILL.md +2 -2
  132. package/resources/codex-and-agents-customizations/.agents/skills/csharp-qa-gate/SKILL.md +2 -2
  133. package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +13 -3
  134. package/resources/codex-and-agents-customizations/.agents/skills/epic-plan/SKILL.md +17 -1
  135. package/resources/codex-and-agents-customizations/.agents/skills/epic-run/SKILL.md +2 -1
  136. package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  137. package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +1 -1
  138. package/resources/codex-and-agents-customizations/.agents/skills/feature-review/SKILL.md +30 -6
  139. package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +16 -6
  140. package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +1 -1
  141. package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +3 -2
  142. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +43 -11
  143. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +41 -2
  144. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +19 -4
  145. package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +11 -7
  146. package/resources/codex-and-agents-customizations/.agents/skills/remediation-handoff-atomic-planner/SKILL.md +2 -0
  147. package/resources/codex-and-agents-customizations/.agents/skills/translate-claude-to-codex/SKILL.md +1 -1
  148. package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp/SKILL.md +2 -2
  149. package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +2 -2
  150. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +11 -0
  151. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +11 -0
  152. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +11 -0
  153. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +11 -0
  154. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +11 -0
  155. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +11 -0
  156. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +10 -0
  157. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +10 -0
  158. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +10 -0
  159. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +10 -0
  160. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +10 -0
  161. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +10 -0
  162. package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +2 -1
  163. package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +6 -6
  164. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c1.toml +1 -0
  165. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c2.toml +1 -0
  166. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3-elevated.toml +1 -0
  167. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3.toml +1 -0
  168. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c4.toml +1 -0
  169. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer.toml +1 -0
  170. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +3 -3
  171. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +3 -3
  172. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +3 -3
  173. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +3 -3
  174. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +3 -3
  175. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +3 -3
  176. package/resources/codex-and-agents-customizations/.codex/codex-web-setup.sh +318 -273
  177. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  178. package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +7 -1
  179. package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +7 -1
  180. package/resources/codex-and-agents-customizations/.codex/hooks/codex-epic-child-launch-attestation.ps1 +1 -1
  181. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
  182. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +8 -2
  183. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +97 -44
  184. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-helpers.ps1 +105 -0
  185. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +7 -1
  186. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +37 -5
  187. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +6 -0
  188. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-root-invocation.ps1 +7 -1
  189. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-wave-barrier.ps1 +7 -1
  190. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +54 -41
  191. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +7 -1
  192. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +35 -14
  193. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
  194. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
  195. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
  196. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +24 -11
  197. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +8 -2
  198. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +13 -4
  199. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +8 -2
  200. package/resources/codex-and-agents-customizations/.codex/hooks/feature-folder-resolution.ps1 +373 -0
  201. package/resources/codex-and-agents-customizations/.codex/hooks/feature-review-coverage-thresholds.ps1 +85 -0
  202. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-heredoc.ps1 +119 -0
  203. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation-operands.ps1 +198 -0
  204. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +254 -255
  205. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload-powershell.ps1 +201 -0
  206. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload.ps1 +484 -0
  207. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +27 -110
  208. package/resources/codex-and-agents-customizations/.codex/hooks/hook-dependency-guard.ps1 +114 -0
  209. package/resources/codex-and-agents-customizations/.codex/hooks/hook-docs-only-diff.ps1 +493 -0
  210. package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +8 -2
  211. package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +7 -1
  212. package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +24 -8
  213. package/resources/codex-and-agents-customizations/pack-manifests/core.json +9 -0
  214. package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +2 -2
  215. package/resources/customizations/.github/instructions/csharp-code-change.instructions.md +4 -4
  216. package/resources/customizations/.github/instructions/csharp-unit-test.instructions.md +2 -2
  217. package/resources/customizations/.github/skills/acceptance-criteria-tracking/SKILL.md +9 -1
  218. package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +4 -0
  219. package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  220. package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +1 -1
  221. package/resources/powershell/PoshQC/PoshQC.Coverage.psm1 +314 -0
  222. package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +25 -28
  223. package/resources/powershell/PoshQC/PoshQC.psm1 +1 -0
  224. package/resources/powershell/PoshQC/README.md +8 -2
  225. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +2 -315
@@ -170,8 +170,8 @@ If any gate fails, revert/fix immediately before proceeding.
170
170
  Run the repo-standard C# toolchain in this order:
171
171
 
172
172
  1) **Format**: `csharpier .`
173
- 2) **Analyze/Lint build**: `msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform='Any CPU' /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true`
174
- 3) **Type-safe nullable build**: `msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform='Any CPU' /p:Nullable=enable /p:TreatWarningsAsErrors=true`
173
+ 2) **Analyze/Lint build**: `msbuild <solution>.sln /t:Build /p:Configuration=Debug /p:Platform='Any CPU' /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true`
174
+ 3) **Type-safe nullable build**: `msbuild <solution>.sln /t:Build /p:Configuration=Debug /p:Platform='Any CPU' /p:Nullable=enable /p:TreatWarningsAsErrors=true`
175
175
  4) **Test with coverage**: `vstest.console.exe <test-assembly-paths> /EnableCodeCoverage`
176
176
 
177
177
  If tools cannot run in the environment, STOP implementation and provide plan + proposed diffs marked **unverified**.
@@ -38,8 +38,8 @@ These are the required tools for C# code in this repo:
38
38
  - Enforce analyzer diagnostics in build using `EnableNETAnalyzers` and `EnforceCodeStyleInBuild`.
39
39
  - Prefer fixing diagnostics over suppressing them.
40
40
  - Approved commands (Windows; choose the variant for your shell):
41
- - **CMD / Developer Command Prompt:** `msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform="Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true`
42
- - **PowerShell:** `msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform='Any CPU' /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true`
41
+ - **CMD / Developer Command Prompt:** `msbuild <solution>.sln /t:Build /p:Configuration=Debug /p:Platform="Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true`
42
+ - **PowerShell:** `msbuild <solution>.sln /t:Build /p:Configuration=Debug /p:Platform='Any CPU' /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true`
43
43
 
44
44
  3. **Type Checking — C# compiler + nullable analysis**
45
45
 
@@ -47,8 +47,8 @@ These are the required tools for C# code in this repo:
47
47
  - Enable nullable reference types and fail builds on warnings for touched code paths.
48
48
  - Avoid introducing nullable warnings; fix the root null-state issue instead.
49
49
  - Approved commands (Windows; choose the variant for your shell):
50
- - **CMD / Developer Command Prompt:** `msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform="Any CPU" /p:Nullable=enable /p:TreatWarningsAsErrors=true`
51
- - **PowerShell:** `msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform='Any CPU' /p:Nullable=enable /p:TreatWarningsAsErrors=true`
50
+ - **CMD / Developer Command Prompt:** `msbuild <solution>.sln /t:Build /p:Configuration=Debug /p:Platform="Any CPU" /p:Nullable=enable /p:TreatWarningsAsErrors=true`
51
+ - **PowerShell:** `msbuild <solution>.sln /t:Build /p:Configuration=Debug /p:Platform='Any CPU' /p:Nullable=enable /p:TreatWarningsAsErrors=true`
52
52
 
53
53
  > **Testing tools and behavior are defined in the unit test policies.** Do not define test behavior here; instead, obey `general-unit-test.instructions.md` and `csharp-unit-test.instructions.md`.
54
54
 
@@ -43,8 +43,8 @@ If there is any conflict between these documents, halt and notify the user.
43
43
 
44
44
  - For C# work, use these concrete commands for the general policy toolchain loop:
45
45
  1. `csharpier .`
46
- 2. `msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform="Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true`
47
- 3. `msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform="Any CPU" /p:Nullable=enable /p:TreatWarningsAsErrors=true`
46
+ 2. `msbuild <solution>.sln /t:Build /p:Configuration=Debug /p:Platform="Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true`
47
+ 3. `msbuild <solution>.sln /t:Build /p:Configuration=Debug /p:Platform="Any CPU" /p:Nullable=enable /p:TreatWarningsAsErrors=true`
48
48
  4. `vstest.console.exe <test-assembly-paths> /EnableCodeCoverage`
49
49
 
50
50
  - The loop behavior (restart rules, must-pass requirements, and audit expectations) is defined by `general-code-change.instructions.md` and is intentionally not repeated here.
@@ -68,6 +68,14 @@ During plan execution, after completing a task whose work satisfies an acceptanc
68
68
 
69
69
  Timing: Check off AC items as soon as the corresponding plan task passes verification — do not defer all AC updates to the end of plan execution.
70
70
 
71
+ ### CI-Dependent Criteria
72
+
73
+ A CI-dependent criterion is one whose verification requires the result of CI on the PR head. It cannot be verified before the pull request exists, so it stays unchecked and is listed as pending-CI in the AC verification artifact until CI has run.
74
+
75
+ - Owner: the item's own orchestrator run owns the check-off and performs it at S9; this is the one case in which an orchestrator run checks off AC. A parent or coordinating session never commits the check-off from its own root.
76
+ - Timing: after the CI green gate (S9) records `ci_gate.conclusion` as `success`, and before the run reports DONE.
77
+ - Push and re-verify: the check-off is committed in the item's own worktree and pushed to the PR branch, and the CI green gate is re-run against the new head SHA so that `ci_gate.head_sha` equals the final PR head before DONE.
78
+
71
79
  ### When Reviewers Check Off AC
72
80
 
73
81
  During feature review (feature-audit phase):
@@ -78,7 +86,7 @@ During feature review (feature-audit phase):
78
86
 
79
87
  ### When Orchestrators Enforce AC Tracking
80
88
 
81
- Orchestrators do not directly check off AC items. Instead:
89
+ Orchestrators do not directly check off AC items. The one exception is a CI-dependent criterion, which the item's own orchestrator run checks off at S9 as described in `### CI-Dependent Criteria` above. For every other criterion, orchestrators instead:
82
90
 
83
91
  1. Ensure delegated executors and reviewers reference this skill.
84
92
  2. After executor or reviewer completion, verify that AC source files reflect delivered work (spot-check; do not re-execute verification).
@@ -102,6 +102,10 @@ For short-path/minimal-audit plans, Phase 0 evidence is incomplete unless both a
102
102
 
103
103
  `Output Summary:` is mandatory for each command-step artifact and must concisely summarize the essential result signal (for example: pass/fail status, key counts, coverage headline, or primary diagnostic).
104
104
 
105
+ ## Evidence Filename Timestamps in Plans
106
+
107
+ Plans name evidence artifacts `<name>.<timestamp>.md` and never fix a plan-time stamp in an evidence filename. An acceptance condition identifies a consumed evidence artifact by the derivation in `evidence-and-timestamp-conventions` (`### Evidence Filename Timestamp`): the lexicographically greatest file matching `<name>.*.md`, not a pre-stamped literal path. This section governs artifact paths only: asserted search tokens remain concrete.
108
+
105
109
  ## Coverage Evidence Contract (Mandatory when policy requires coverage)
106
110
 
107
111
  For any language in scope where repository policy requires coverage validation:
@@ -19,6 +19,17 @@ Use this skill when:
19
19
  Use `yyyy-MM-ddTHH-mm` for all audit, remediation, and evidence artifacts.
20
20
  Example: `2026-02-06T14-30`.
21
21
 
22
+ The `Timestamp` value is UTC read from the host system clock when the recorded command runs (for example `Get-Date -AsUTC -Format yyyy-MM-ddTHH-mm` in PowerShell 7, `(Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH-mm')` in Windows PowerShell 5.1, or `date -u +%Y-%m-%dT%H-%M` in a POSIX shell); the agent never composes or estimates it. The value is written without a zone suffix.
23
+
24
+ ### Evidence Filename Timestamp
25
+
26
+ The stamp in the filename of every artifact under `<FEATURE>/evidence/<kind>/` is the UTC time at which that file is written, read from the host system clock immediately before the write, in the same `yyyy-MM-ddTHH-mm` shape with no zone suffix. It is never a plan-time stamp, a plan-cycle identifier, or any other value fixed before the write.
27
+
28
+ - Plans name such an artifact `<name>.<timestamp>.md`, and the executor substitutes the write time. A later loop pass writes a new file with a new stamp.
29
+ - A task that consumes an evidence artifact selects the lexicographically greatest file matching `<name>.*.md`. The fixed-width UTC shape sorts in chronological order, so that file is the most recently written one.
30
+ - Plan files (`plan.<timestamp>.md`, stamped by the feature-folder tooling in America/New_York) and the review artifact set (`policy-audit`, `code-review`, `feature-audit`, `remediation-inputs`, which share one stamp) are outside this rule.
31
+ - Existing evidence files whose names carry a plan-time stamp are not renamed.
32
+
22
33
  ## Canonical Evidence Locations
23
34
 
24
35
  - Baseline evidence: `evidence/baseline/`
@@ -83,6 +94,8 @@ When evidence artifacts are used for automated checking or plan reconciliation,
83
94
  - `Command: <exact command>`
84
95
  - `EXIT_CODE: <int>`
85
96
 
97
+ The first occurrence of `Timestamp`, `Command`, `EXIT_CODE`, and `ExpectedExitCode` forms the record in both the Python and the TypeScript parser; later occurrences, including empty later values, are ignored. An artifact that records several gates therefore reports its first gate.
98
+
86
99
  One optional field may also be declared:
87
100
  - `ExpectedExitCode: <int>` — the exit code the gate is expected to produce.
88
101
 
@@ -123,7 +123,7 @@ Always apply:
123
123
  - toolchain checks fail
124
124
  - the code review contains blockers
125
125
  - required acceptance criteria are FAIL or PARTIAL
126
- - coverage regression below policy threshold (< 80% repo-wide or < 90% for new code)
126
+ - coverage below the governing thresholds (the root `CLAUDE.md` thresholds when stated, otherwise the defaults: line 85%, branch 75% for branch-capable languages; repo-wide per language, modified files including any regression on changed lines, or new files)
127
127
  - Create `remediation-inputs.<timestamp>.md` first.
128
128
  - Create the target remediation plan file from the canonical plan template.
129
129
  - Hand off plan creation through `remediation-handoff-atomic-planner`.
@@ -0,0 +1,314 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Returns the non-blank string entries of one list in a Pester settings table.
4
+ .DESCRIPTION
5
+ Reads Settings[Section][Key] and returns its non-blank string entries in their original order.
6
+ Returns nothing when Settings or the section is not a dictionary or when the key is absent.
7
+ Used for 'CodeCoverage.Path' and 'Run.Path' (issue #527).
8
+ .PARAMETER Settings
9
+ Settings table, normally the hashtable loaded from the Pester runsettings file.
10
+ .PARAMETER Section
11
+ Top-level section name, for example 'CodeCoverage' or 'Run'.
12
+ .PARAMETER Key
13
+ Key inside the section, for example 'Path'.
14
+ #>
15
+ function Get-PoshQCSettingsList {
16
+ [CmdletBinding()]
17
+ [OutputType([string])]
18
+ param(
19
+ $Settings,
20
+ [string] $Section,
21
+ [string] $Key
22
+ )
23
+
24
+ if ($Settings -isnot [System.Collections.IDictionary]) {
25
+ return
26
+ }
27
+
28
+ $sectionValue = $Settings[$Section]
29
+ if ($sectionValue -isnot [System.Collections.IDictionary]) {
30
+ return
31
+ }
32
+
33
+ if (-not $sectionValue.Contains($Key)) {
34
+ return
35
+ }
36
+
37
+ foreach ($entry in @($sectionValue[$Key])) {
38
+ if ($entry -is [string] -and -not [string]::IsNullOrWhiteSpace($entry)) {
39
+ [string] $entry
40
+ }
41
+ }
42
+ }
43
+
44
+ <#
45
+ .SYNOPSIS
46
+ Reads and validates the workspace coverage configuration (issue #527).
47
+ .DESCRIPTION
48
+ Resolves the workspace-relative configuration file (default 'config/poshqc-coverage.json')
49
+ against Root. An absent file reports Present = $false. A present file must be a JSON object
50
+ with 'version' equal to 1 and a 'roots' array of workspace-relative paths; every validation
51
+ failure throws a message that names the configuration file. An empty 'roots' array is valid
52
+ and yields an empty coverage population.
53
+ .PARAMETER Root
54
+ Workspace root the configuration path resolves against.
55
+ .PARAMETER ConfigRelativePath
56
+ Workspace-relative path to the configuration file.
57
+ .PARAMETER TestPathExists
58
+ Injectable file-existence seam. Defaults to Test-Path -PathType Leaf.
59
+ .PARAMETER ReadContent
60
+ Injectable read seam returning the raw file text. Defaults to Get-Content -Raw.
61
+ #>
62
+ function Get-PoshQCCoverageConfigRoot {
63
+ [CmdletBinding()]
64
+ [OutputType([pscustomobject])]
65
+ param(
66
+ [string] $Root,
67
+ [string] $ConfigRelativePath = 'config/poshqc-coverage.json',
68
+ [scriptblock] $TestPathExists = { param([string] $Path) Test-Path -LiteralPath $Path -PathType Leaf },
69
+ [scriptblock] $ReadContent = { param([string] $Path) Get-Content -LiteralPath $Path -Raw }
70
+ )
71
+
72
+ $ErrorActionPreference = 'Stop'
73
+
74
+ $configPath = if ([IO.Path]::IsPathRooted($ConfigRelativePath)) {
75
+ $ConfigRelativePath
76
+ } else {
77
+ Join-Path -Path $Root -ChildPath $ConfigRelativePath
78
+ }
79
+
80
+ if (-not (& $TestPathExists $configPath)) {
81
+ return [pscustomobject]@{ Present = $false; Roots = [string[]] @() }
82
+ }
83
+
84
+ $raw = [string] (& $ReadContent $configPath)
85
+ if ([string]::IsNullOrWhiteSpace($raw)) {
86
+ throw "Coverage configuration '$ConfigRelativePath' is empty."
87
+ }
88
+
89
+ try {
90
+ $parsed = $raw | ConvertFrom-Json -ErrorAction Stop
91
+ } catch {
92
+ throw "Invalid JSON in coverage configuration '$ConfigRelativePath': $($_.Exception.Message)"
93
+ }
94
+
95
+ if ($parsed -isnot [System.Management.Automation.PSCustomObject]) {
96
+ throw "Coverage configuration '$ConfigRelativePath' must contain a JSON object."
97
+ }
98
+
99
+ # Read properties through PSObject so a missing property yields $null under StrictMode.
100
+ $versionProperty = $parsed.PSObject.Properties['version']
101
+ $versionIsInteger = $null -ne $versionProperty -and ($versionProperty.Value -is [long] -or $versionProperty.Value -is [int])
102
+ if (-not $versionIsInteger -or $versionProperty.Value -ne 1) {
103
+ throw "Coverage configuration '$ConfigRelativePath' must declare 'version' equal to 1."
104
+ }
105
+
106
+ $rootsProperty = $parsed.PSObject.Properties['roots']
107
+ if ($null -eq $rootsProperty) {
108
+ throw "Coverage configuration '$ConfigRelativePath' must declare a 'roots' array."
109
+ }
110
+ if ($rootsProperty.Value -isnot [System.Array]) {
111
+ throw "Coverage configuration '$ConfigRelativePath' must declare 'roots' as an array."
112
+ }
113
+
114
+ $validated = [System.Collections.Generic.List[string]]::new()
115
+ foreach ($entry in $rootsProperty.Value) {
116
+ if ($entry -isnot [string] -or [string]::IsNullOrWhiteSpace($entry)) {
117
+ throw "Coverage configuration '$ConfigRelativePath' contains a blank or non-string 'roots' entry."
118
+ }
119
+ if ([IO.Path]::IsPathRooted($entry)) {
120
+ throw "Coverage configuration '$ConfigRelativePath' entry '$entry' must be a workspace-relative path, not an absolute path."
121
+ }
122
+ if (@($entry -split '[\\/]+') -contains '..') {
123
+ throw "Coverage configuration '$ConfigRelativePath' entry '$entry' must not contain '..' segments."
124
+ }
125
+ $validated.Add($entry)
126
+ }
127
+
128
+ return [pscustomobject]@{ Present = $true; Roots = [string[]] $validated.ToArray() }
129
+ }
130
+
131
+ <#
132
+ .SYNOPSIS
133
+ Enumerates the PowerShell production files under a set of coverage roots (issue #527).
134
+ .DESCRIPTION
135
+ Joins each non-rooted root to Root, skips a nonexistent root with one warning, and keeps
136
+ files with a .ps1 or .psm1 extension (case-insensitive) whose name does not match
137
+ '*.Tests.ps1', whose first root-relative segment is not 'tests', and whose root-relative
138
+ directory segments are not in ExcludeDirs. Root-relative paths use forward slashes. The result
139
+ is sorted ordinally on RelativePath and de-duplicated case-insensitively, keeping the
140
+ ordinally smallest variant.
141
+ .PARAMETER Root
142
+ Workspace root that relative roots and relative paths are computed against.
143
+ .PARAMETER Roots
144
+ Coverage roots, workspace-relative or absolute.
145
+ .PARAMETER ExcludeDirs
146
+ Directory names excluded from the population.
147
+ .PARAMETER TestPathExists
148
+ Injectable directory-existence seam. Defaults to Test-Path -PathType Container.
149
+ .PARAMETER EnumerateFiles
150
+ Injectable recursive file enumeration seam. Defaults to Get-ChildItem -Recurse -File -Force.
151
+ .PARAMETER WarningLogger
152
+ Injectable warning seam for skipped roots. Defaults to Write-Warning.
153
+ #>
154
+ function Get-PoshQCCoverageFileSet {
155
+ [CmdletBinding()]
156
+ [OutputType([pscustomobject])]
157
+ param(
158
+ [string] $Root,
159
+ [string[]] $Roots,
160
+ [string[]] $ExcludeDirs = $script:DefaultExcludedDirs,
161
+ [scriptblock] $TestPathExists = { param([string] $Path) Test-Path -LiteralPath $Path -PathType Container },
162
+ [scriptblock] $EnumerateFiles = { param([string] $Path) Get-ChildItem -LiteralPath $Path -Recurse -File -Force },
163
+ [scriptblock] $WarningLogger = { param([string] $Message) Write-Warning $Message }
164
+ )
165
+
166
+ $ErrorActionPreference = 'Stop'
167
+
168
+ $rootPrefix = ([string] $Root -replace '\\', '/').TrimEnd('/')
169
+ $candidates = [System.Collections.Generic.Dictionary[string, object]]::new([StringComparer]::Ordinal)
170
+ foreach ($coverageRoot in @($Roots | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })) {
171
+ $rootPath = if ([IO.Path]::IsPathRooted($coverageRoot)) { $coverageRoot } else { Join-Path -Path $Root -ChildPath $coverageRoot }
172
+ if (-not (& $TestPathExists $rootPath)) {
173
+ & $WarningLogger "Coverage root '$coverageRoot' does not exist under '$Root'; skipping."
174
+ continue
175
+ }
176
+
177
+ foreach ($file in @(& $EnumerateFiles $rootPath)) {
178
+ if ($null -eq $file) { continue }
179
+ $extension = [string] $file.Extension
180
+ if (-not ($extension -ieq '.ps1' -or $extension -ieq '.psm1')) { continue }
181
+ if ([string] $file.Name -like '*.Tests.ps1') { continue }
182
+
183
+ $fullPath = [string] $file.FullName
184
+ $normalizedPath = $fullPath -replace '\\', '/'
185
+ $relativePath = $normalizedPath
186
+ if ($rootPrefix -and $normalizedPath.StartsWith("$rootPrefix/", [StringComparison]::OrdinalIgnoreCase)) {
187
+ $relativePath = $normalizedPath.Substring($rootPrefix.Length + 1)
188
+ }
189
+
190
+ $segments = @($relativePath -split '/' | Where-Object { $_ -ne '' })
191
+ if ($segments.Count -gt 0 -and $segments[0] -ieq 'tests') { continue }
192
+ $directorySegments = if ($segments.Count -gt 1) { @($segments[0..($segments.Count - 2)]) } else { @() }
193
+ $excludedSegments = @($directorySegments | Where-Object { $ExcludeDirs -contains $_ })
194
+ if ($excludedSegments.Count -gt 0) { continue }
195
+
196
+ if (-not $candidates.ContainsKey($relativePath)) {
197
+ $candidates[$relativePath] = [pscustomobject]@{ RelativePath = $relativePath; FullPath = $fullPath }
198
+ }
199
+ }
200
+ }
201
+
202
+ [string[]] $keys = @($candidates.Keys)
203
+ [Array]::Sort($keys, [StringComparer]::Ordinal)
204
+ $seen = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
205
+ foreach ($key in $keys) {
206
+ if ($seen.Add($key)) {
207
+ $candidates[$key]
208
+ }
209
+ }
210
+ }
211
+
212
+ <#
213
+ .SYNOPSIS
214
+ Resolves the code-coverage population for one Invoke-PoshQCTest run (issue #527).
215
+ .DESCRIPTION
216
+ Applies the coverage precedence and returns the source name and the absolute file paths:
217
+ (1) 'settings': a caller-supplied settings file (one whose full path differs from the
218
+ module-default settings file) with a non-empty CodeCoverage.Path; relative entries are joined
219
+ to Root, rooted entries are kept as written, and each nonexistent entry is pruned and logged.
220
+ A non-empty CodeCoverage.Path in the module-default settings file is ignored and logged.
221
+ (2) 'config': the workspace coverage configuration reports Present.
222
+ (3) 'fallback': the effective scan-folder roots when non-empty, else the settings Run.Path.
223
+ .PARAMETER Root
224
+ Absolute workspace root.
225
+ .PARAMETER Settings
226
+ Settings table loaded from the Pester runsettings file.
227
+ .PARAMETER SettingsFile
228
+ Path of the settings file the table was loaded from.
229
+ .PARAMETER ScanFolderRoots
230
+ Effective test scan folders of the run (explicit -ScanFolders or the scan configuration).
231
+ .PARAMETER ExcludeDirs
232
+ Directory names excluded from the population.
233
+ .PARAMETER DefaultSettingsFile
234
+ The module-default settings file; a list in it is not honored.
235
+ .PARAMETER SettingsPathExists
236
+ Injectable existence seam for caller-supplied settings entries. Defaults to Test-Path.
237
+ .PARAMETER Logger
238
+ Injectable information seam. Defaults to Write-Information.
239
+ .PARAMETER WarningLogger
240
+ Injectable warning seam passed to the file-set seam. Defaults to Write-Warning.
241
+ .PARAMETER ReadConfig
242
+ Injectable configuration seam. Defaults to Get-PoshQCCoverageConfigRoot.
243
+ .PARAMETER GetFileSet
244
+ Injectable file-set seam. Defaults to Get-PoshQCCoverageFileSet.
245
+ #>
246
+ function Resolve-PoshQCCoveragePopulation {
247
+ [CmdletBinding()]
248
+ [OutputType([pscustomobject])]
249
+ param(
250
+ [string] $Root,
251
+ $Settings,
252
+ [string] $SettingsFile,
253
+ [string[]] $ScanFolderRoots,
254
+ [string[]] $ExcludeDirs = $script:DefaultExcludedDirs,
255
+ [string] $DefaultSettingsFile = $script:PesterSettings,
256
+ [scriptblock] $SettingsPathExists = { param([string] $Path) Test-Path $Path },
257
+ [scriptblock] $Logger = { param([string] $Message) Write-Information $Message -InformationAction Continue },
258
+ [scriptblock] $WarningLogger = { param([string] $Message) Write-Warning $Message },
259
+ [scriptblock] $ReadConfig = { param([string] $RootPath) Get-PoshQCCoverageConfigRoot -Root $RootPath },
260
+ [scriptblock] $GetFileSet = {
261
+ param([string] $RootPath, [string[]] $Roots, [string[]] $Excluded, [scriptblock] $Warn)
262
+ Get-PoshQCCoverageFileSet -Root $RootPath -Roots $Roots -ExcludeDirs $Excluded -WarningLogger $Warn
263
+ }
264
+ )
265
+
266
+ $ErrorActionPreference = 'Stop'
267
+
268
+ $settingsPaths = @(Get-PoshQCSettingsList -Settings $Settings -Section 'CodeCoverage' -Key 'Path')
269
+ if ($settingsPaths.Count -gt 0) {
270
+ # A settings file is caller-supplied custom when its full path differs from the module default.
271
+ $isCustomSettingsFile = $false
272
+ if (-not [string]::IsNullOrWhiteSpace($SettingsFile)) {
273
+ $settingsFullPath = [IO.Path]::GetFullPath([IO.Path]::Combine($PWD.ProviderPath, $SettingsFile))
274
+ $defaultFullPath = ''
275
+ if (-not [string]::IsNullOrWhiteSpace($DefaultSettingsFile)) {
276
+ $defaultFullPath = [IO.Path]::GetFullPath([IO.Path]::Combine($PWD.ProviderPath, $DefaultSettingsFile))
277
+ }
278
+ $isCustomSettingsFile = -not [string]::Equals($settingsFullPath, $defaultFullPath, [StringComparison]::OrdinalIgnoreCase)
279
+ }
280
+
281
+ if ($isCustomSettingsFile) {
282
+ $survivingPaths = [System.Collections.Generic.List[string]]::new()
283
+ foreach ($entry in $settingsPaths) {
284
+ # Rooted entries are kept and tested as written; they are never re-joined to Root.
285
+ $candidate = if ([IO.Path]::IsPathRooted($entry)) { $entry } else { Join-Path -Path $Root -ChildPath $entry }
286
+ if (& $SettingsPathExists $candidate) {
287
+ $survivingPaths.Add($candidate)
288
+ } else {
289
+ & $Logger "Pruned nonexistent code coverage path: $candidate"
290
+ }
291
+ }
292
+
293
+ return [pscustomobject]@{ Source = 'settings'; Paths = [string[]] $survivingPaths.ToArray() }
294
+ }
295
+
296
+ & $Logger 'Ignored CodeCoverage.Path from the module-default settings file; the coverage population is derived from the workspace.'
297
+ }
298
+
299
+ $configRoot = & $ReadConfig $Root
300
+ if ($null -ne $configRoot -and $configRoot.Present) {
301
+ $source = 'config'
302
+ $coverageRoots = @($configRoot.Roots)
303
+ } else {
304
+ $source = 'fallback'
305
+ $coverageRoots = @($ScanFolderRoots | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
306
+ if ($coverageRoots.Count -eq 0) {
307
+ $coverageRoots = @(Get-PoshQCSettingsList -Settings $Settings -Section 'Run' -Key 'Path')
308
+ }
309
+ }
310
+
311
+ $files = @(& $GetFileSet $Root ([string[]] $coverageRoots) $ExcludeDirs $WarningLogger)
312
+ $paths = [string[]] @($files | Where-Object { $null -ne $_ } | ForEach-Object { $_.FullPath })
313
+ return [pscustomobject]@{ Source = $source; Paths = $paths }
314
+ }
@@ -147,6 +147,9 @@ Custom path for Koverage-compatible coverage XML output.
147
147
  Skip creation of Koverage-friendly coverage copy.
148
148
  .PARAMETER ScanFolders
149
149
  Optional workspace-relative or workspace-contained folders to scan instead of the entire root.
150
+ .PARAMETER ResolveCoveragePopulation
151
+ Injectable seam that derives the code-coverage file population from the workspace (issue #527).
152
+ Defaults to Resolve-PoshQCCoveragePopulation.
150
153
  #>
151
154
  function Invoke-PoshQCTest {
152
155
  [CmdletBinding()]
@@ -282,6 +285,10 @@ function Invoke-PoshQCTest {
282
285
  [scriptblock] $CopyCoverage = {
283
286
  param([string] $CoveragePath, [string] $RepoRoot, [string] $KoveragePath)
284
287
  Convert-PoshQCCoverageToRelative -InputPath $CoveragePath -OutputPath $KoveragePath -RepoRoot $RepoRoot
288
+ },
289
+ [scriptblock] $ResolveCoveragePopulation = {
290
+ param([string] $RootPath, $Settings, [string] $SettingsFile, [string[]] $ScanFolderRoots, [string[]] $Excluded, [scriptblock] $PathExists, [scriptblock] $Log)
291
+ Resolve-PoshQCCoveragePopulation -Root $RootPath -Settings $Settings -SettingsFile $SettingsFile -ScanFolderRoots $ScanFolderRoots -ExcludeDirs $Excluded -SettingsPathExists $PathExists -Logger $Log
285
292
  }
286
293
  )
287
294
 
@@ -290,6 +297,10 @@ function Invoke-PoshQCTest {
290
297
  if (-not $Root) {
291
298
  $Root = $PWD.ProviderPath
292
299
  }
300
+ # Resolve a relative -Root once so run, coverage, and output paths are absolute (issue #527).
301
+ if (-not [IO.Path]::IsPathRooted($Root)) {
302
+ $Root = [IO.Path]::GetFullPath([IO.Path]::Combine($PWD.ProviderPath, $Root))
303
+ }
293
304
 
294
305
  & $EnsureModule 'Pester' "Pester is not installed. Run Install-PoshQCTool (alias Install-PoshQCTools) first."
295
306
 
@@ -336,34 +347,20 @@ function Invoke-PoshQCTest {
336
347
  }
337
348
 
338
349
  if ($coverageEnabled -and $config.CodeCoverage) {
339
- if ($config.CodeCoverage.Path.Value) {
340
- $resolvedCoveragePaths = @(
341
- $config.CodeCoverage.Path.Value |
342
- ForEach-Object {
343
- if ([IO.Path]::IsPathRooted($_)) { $_ } else { Join-Path $Root $_ }
344
- }
345
- )
346
-
347
- # Prune coverage paths that do not exist under the resolved root before Pester sees
348
- # them. Pester's Resolve-CoverageInfo discards the whole set and raises a terminating
349
- # error on the first unresolvable entry, which aborts the run at RunStart in any
350
- # workspace that does not contain this repository's coverage layout (issue #409).
351
- # Rooted entries are tested as-is; they are never re-joined to $Root.
352
- $survivingCoveragePaths = @($resolvedCoveragePaths | Where-Object { & $TestPathExists $_ })
353
- foreach ($prunedPath in @($resolvedCoveragePaths | Where-Object { $survivingCoveragePaths -notcontains $_ })) {
354
- # Log every prune individually so coverage removal is never silent.
355
- & $Logger "Pruned nonexistent code coverage path: $prunedPath"
356
- }
357
-
358
- if ($survivingCoveragePaths.Count -gt 0) {
359
- $config.CodeCoverage.Path = $survivingCoveragePaths
360
- } else {
361
- # An enabled-but-empty path set makes Pester instrument every Run.Path directory,
362
- # so disable coverage for this invocation instead and continue with the test run.
363
- $config.CodeCoverage.Enabled = $false
364
- $coverageEnabled = $false
365
- & $Logger "Code coverage disabled for this invocation: no configured coverage path exists under root '$Root'."
366
- }
350
+ # The coverage population is derived from the workspace, not from a list shipped with the
351
+ # module, so the measured set does not depend on which module copy was loaded (issue #527).
352
+ # Nonexistent caller-supplied entries are pruned and logged by the resolver (issue #409).
353
+ $population = & $ResolveCoveragePopulation $Root $settings $SettingsPath $effectiveScanFolders $ExcludeDirs $TestPathExists $Logger
354
+ $populationPaths = @($population.Paths | Where-Object { $_ })
355
+ & $Logger "Code coverage population: source=$($population.Source); files=$($populationPaths.Count)"
356
+ if ($populationPaths.Count -gt 0) {
357
+ $config.CodeCoverage.Path = $populationPaths
358
+ } else {
359
+ # An enabled-but-empty path set makes Pester instrument every Run.Path directory,
360
+ # so disable coverage for this invocation instead and continue with the test run.
361
+ $config.CodeCoverage.Enabled = $false
362
+ $coverageEnabled = $false
363
+ & $Logger "Code coverage disabled for this invocation: no configured coverage path exists under root '$Root'."
367
364
  }
368
365
 
369
366
  if ($config.CodeCoverage.OutputPath.Value) {
@@ -113,6 +113,7 @@ if (-not $subModuleCache) {
113
113
  foreach ($subModuleName in @(
114
114
  'PoshQC.FileDiscovery.psm1',
115
115
  'PoshQC.ScanConfig.psm1',
116
+ 'PoshQC.Coverage.psm1',
116
117
  'PoshQC.Analyzer.psm1',
117
118
  'PoshQC.Testing.psm1'
118
119
  )) {
@@ -65,8 +65,14 @@ PoshQC is a lightweight PowerShell quality gate that wraps Invoke-Formatter, PSS
65
65
  - PSScriptAnalyzer settings: `./settings/pssa.settings.psd1`
66
66
  - Enforces compatible syntax for 5.1 and 7.6, 4-space indentation, ShouldProcess for state-changing functions, and safety guards (no Invoke-Expression, no global vars, etc.).
67
67
  - Pester settings: `./settings/pester.runsettings.psd1`
68
- - Runs tests under `scripts` and `tests/powershell`, outputs JUnit XML and CoverageGutters coverage, and enables coverage over `scripts/dev-tools/*.ps1`, `scripts/powershell/**/*.psm1`, and `src/**/*.ps1`.
68
+ - Runs tests under `scripts` and `tests/powershell`, outputs JUnit XML and CoverageGutters coverage, and enables coverage. The shipped runsettings carry no `CodeCoverage.Path` list; `Invoke-PoshQCTest` derives the coverage population from the workspace (see Coverage population below).
69
69
  - When `-ScanFolders` is supplied to `Invoke-PoshQCSuite` or the lower-level commands, the suite narrows discovery to those workspace-relative or workspace-contained folders.
70
+ - Coverage population (issue #527): `Invoke-PoshQCTest` measures the same file set for the same workspace whichever module copy is loaded.
71
+ - Workspace configuration: `config/poshqc-coverage.json` with the schema `{"version": 1, "roots": [...]}`. Each root is a workspace-relative folder. The file is validated fail-fast, and every error names `config/poshqc-coverage.json`: empty content, invalid JSON, a `version` other than 1, a missing or non-array `roots`, and a blank, absolute, or `..`-segment entry are rejected. An empty `roots` array yields an empty population.
72
+ - Precedence, reported as the source name: (1) `settings` - a caller-supplied settings file (`-SettingsPath` other than the module's own `settings/pester.runsettings.psd1`) with a non-empty `CodeCoverage.Path` is honored, and each entry that does not exist is pruned and logged; a `CodeCoverage.Path` list in the module's own settings file is ignored and the ignore is logged. (2) `config` - `config/poshqc-coverage.json` exists. (3) `fallback` - the effective test scan folders: explicit `-ScanFolders`, else `config/poshqc-scan.json`, else the settings `Run.Path`.
73
+ - Built-in exclusions: `.ps1` and `.psm1` files only; files named `*.Tests.ps1` are excluded, as are files whose first root-relative segment is `tests` and files under any root-relative directory named in `DefaultExcludedDirs` (for example `node_modules`, `artifacts`, `.venv`). A nonexistent root is skipped with a warning.
74
+ - An empty population disables coverage for that invocation, logs `Code coverage disabled for this invocation`, and still runs the tests.
75
+ - Each run logs `Code coverage population: source=<source>; files=<count>` before Pester starts.
70
76
 
71
77
  ## Typical workflow
72
78
 
@@ -77,4 +83,4 @@ PoshQC is a lightweight PowerShell quality gate that wraps Invoke-Formatter, PSS
77
83
  ## Notes for standalone use
78
84
 
79
85
  - Place `PoshQC.psm1`, `PoshQC.psd1`, and the `settings/` folder together; import the module from that directory.
80
- - Adjust `settings/pester.runsettings.psd1` and `settings/pssa.settings.psd1` to match your repo paths and policies.
86
+ - Adjust `settings/pester.runsettings.psd1` and `settings/pssa.settings.psd1` to match your repo paths and policies. Declare coverage roots in `config/poshqc-coverage.json` at the workspace root rather than in the runsettings.