@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
@@ -26,434 +26,180 @@
26
26
  - For each changed language, the policy audit must contain a
27
27
  coverage-scoped PASS or FAIL verdict.
28
28
  - Scope-narrowing phrases on coverage rows are treated as failures.
29
- - When repo-wide coverage is below 80 percent for an available artifact,
30
- the policy audit must carry a FAIL verdict for that language.
29
+ - Coverage floors follow the threshold precedence of the pushed quality-tiers
30
+ rule: when the repository's root CLAUDE.md states a line or branch coverage
31
+ threshold, that threshold governs; otherwise the 85 percent line and 75 percent
32
+ branch defaults govern.
33
+ - Each metric falls back independently, so a root CLAUDE.md that states only
34
+ a line threshold leaves the 75 percent branch default in force. The figures are
35
+ read by Get-FeatureReviewCoverageThreshold (feature-review-coverage-thresholds.ps1).
36
+ - The root CLAUDE.md is read by Get-FeatureReviewClaudeMdText: an absent root
37
+ CLAUDE.md uses the defaults, and an unreadable root CLAUDE.md fails closed (the
38
+ gate blocks, because the governing floors cannot be resolved).
39
+ - When repo-wide line coverage for an available artifact is below the governing
40
+ line threshold, the policy audit must carry a FAIL verdict for that language;
41
+ branch coverage below the governing branch threshold blocks termination.
31
42
 
32
43
  .NOTES
33
- Reads the hook payload from CLAUDE_HOOK_INPUT as JSON. Exits 0 to allow
34
- termination; exits 1 with an error message to block.
44
+ Reads the SubagentStop envelope from stdin through Read-SubagentStopEnvelope
45
+ (subagentstop-envelope.ps1). Exits 0 to allow termination. Every block path,
46
+ including a payload anomaly and a validator error, blocks through
47
+ Exit-SubagentStopBlock, which writes the reason to stderr and exits 2. A hook
48
+ dependency failure blocks through the inline guard tail (stderr reason, exit 2).
49
+ A condition the subagent can repair (a missing token, path, file, or coverage
50
+ verdict) may block again; a condition it cannot repair blocks once and does not
51
+ block again when stop_hook_active is true.
35
52
  #>
36
53
  [CmdletBinding()]
37
54
  param()
55
+ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
56
+ try { . (Join-Path $PSScriptRoot 'feature-review-coverage-thresholds.ps1') } catch { Add-HookDependencyFailure -Name 'feature-review-coverage-thresholds.ps1' -ErrorRecord $_ }
57
+ try { . (Join-Path $PSScriptRoot 'validate-feature-review-coverage-metrics.ps1') } catch { Add-HookDependencyFailure -Name 'validate-feature-review-coverage-metrics.ps1' -ErrorRecord $_ }
58
+ try { . (Join-Path $PSScriptRoot 'validate-feature-review-coverage-rules.ps1') } catch { Add-HookDependencyFailure -Name 'validate-feature-review-coverage-rules.ps1' -ErrorRecord $_ }
59
+ try { . (Join-Path $PSScriptRoot 'subagentstop-envelope.ps1') } catch { Add-HookDependencyFailure -Name 'subagentstop-envelope.ps1' -ErrorRecord $_ }
38
60
 
39
61
  $ErrorActionPreference = 'Stop'
40
62
 
41
- function Get-ArtifactFileContent {
42
- [CmdletBinding()]
43
- [OutputType([hashtable])]
44
- param(
45
- [Parameter(Mandatory = $true)]
46
- [string] $Path
47
- )
48
-
49
- if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
50
- return @{ Exists = $false; Text = $null; Lines = @() }
51
- }
52
-
53
- $text = Get-Content -LiteralPath $Path -Raw -ErrorAction Stop
54
- $lines = Get-Content -LiteralPath $Path -ErrorAction Stop
55
- if ($null -eq $lines) {
56
- $lines = @()
57
- }
58
- elseif ($lines -isnot [array]) {
59
- $lines = @($lines)
60
- }
61
-
62
- return @{ Exists = $true; Text = $text; Lines = $lines }
63
- }
64
-
65
- function Get-ReviewArtifactPathFromOutput {
66
- [CmdletBinding()]
67
- [OutputType([string])]
68
- param(
69
- [Parameter(Mandatory = $true)]
70
- [string] $AgentOutput,
71
-
72
- [Parameter(Mandatory = $true)]
73
- [string] $Token
74
- )
75
-
76
- $escapedToken = [regex]::Escape($Token)
77
- $pattern = "$escapedToken\s*[:=]\s*[""']?([^\s""'\)`]+)|\[$escapedToken\]\(([^)]+)\)"
78
- $match = [regex]::Match(
79
- $AgentOutput,
80
- $pattern,
81
- [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
82
- )
83
- if (-not $match.Success) {
84
- return $null
85
- }
86
-
87
- $value = if ($match.Groups[1].Success) { $match.Groups[1].Value } else { $match.Groups[2].Value }
88
- $value = $value.Trim()
89
- if ([string]::IsNullOrWhiteSpace($value)) {
90
- return $null
91
- }
92
-
93
- return $value
94
- }
95
-
96
- function Get-ReviewArtifactInfo {
97
- [CmdletBinding()]
98
- [OutputType([hashtable])]
99
- param(
100
- [Parameter(Mandatory = $true)]
101
- [string] $Path,
102
-
103
- [Parameter(Mandatory = $true)]
104
- [string] $Stem
105
- )
106
-
107
- $normalized = $Path -replace '\\', '/'
108
- $pattern = "^docs/features/active/(?<Folder>.+)/$Stem\.(?<Timestamp>\d{4}-\d{2}-\d{2}T\d{2}-\d{2})\.md$"
109
- $match = [regex]::Match($normalized, $pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
110
- if (-not $match.Success) {
111
- return $null
112
- }
113
-
114
- return @{
115
- Folder = $match.Groups['Folder'].Value
116
- Timestamp = $match.Groups['Timestamp'].Value
117
- Path = $normalized
118
- }
119
- }
120
-
121
- function Get-ChangedLanguageSet {
122
- [OutputType([System.Collections.Hashtable])]
123
- param([string[]]$Lines)
124
-
125
- $langs = [ordered]@{}
126
- foreach ($line in $Lines) {
127
- if ($line -notmatch '^\s*-\s+(\S+)\s+\(\+\d+/-\d+\)\s*$') { continue }
128
- $path = $matches[1]
129
- $ext = [IO.Path]::GetExtension($path).ToLowerInvariant()
130
- switch -Regex ($ext) {
131
- '^\.(ts|tsx)$' { $langs['TypeScript'] = $true }
132
- '^\.py$' { $langs['Python'] = $true }
133
- '^\.(ps1|psm1)$' { $langs['PowerShell'] = $true }
134
- '^\.cs$' { $langs['CSharp'] = $true }
135
- }
136
- }
137
- return $langs
138
- }
139
-
140
- function Get-LcovRepoCoverage {
141
- [OutputType([Nullable[double]])]
142
- param([string]$Path)
143
-
144
- $file = Get-ArtifactFileContent -Path $Path
145
- if (-not $file.Exists) { return $null }
146
-
147
- $totalFound = 0
148
- $totalHit = 0
149
- foreach ($line in $file.Lines) {
150
- if ($line.StartsWith('LF:')) {
151
- $totalFound += [int]($line.Substring(3))
152
- }
153
- elseif ($line.StartsWith('LH:')) {
154
- $totalHit += [int]($line.Substring(3))
155
- }
156
- }
157
- if ($totalFound -le 0) { return $null }
158
- return [math]::Round(($totalHit * 100.0) / $totalFound, 2)
159
- }
160
-
161
- function Get-LcovBranchCoverage {
162
- # Parses LCOV branch counters (BRF: branches found, BRH: branches hit) and returns a percent.
163
- # LCOV emits one BRF/BRH line per source file in the report; we sum across the file to compute
164
- # repo-wide branch coverage. Returns $null when the artifact is absent or when no branch
165
- # information is recorded (BRF total = 0).
166
- [OutputType([Nullable[double]])]
167
- param([string]$Path)
168
-
169
- $file = Get-ArtifactFileContent -Path $Path
170
- if (-not $file.Exists) { return $null }
171
-
172
- $totalFound = 0
173
- $totalHit = 0
174
- foreach ($line in $file.Lines) {
175
- if ($line.StartsWith('BRF:')) {
176
- $totalFound += [int]($line.Substring(4))
177
- }
178
- elseif ($line.StartsWith('BRH:')) {
179
- $totalHit += [int]($line.Substring(4))
180
- }
181
- }
182
- if ($totalFound -le 0) { return $null }
183
- return [math]::Round(($totalHit * 100.0) / $totalFound, 2)
184
- }
185
-
186
- function Get-JacocoBranchCoverage {
187
- [OutputType([Nullable[double]])]
188
- param([string]$Path)
189
-
190
- $file = Get-ArtifactFileContent -Path $Path
191
- if (-not $file.Exists) { return $null }
192
-
193
- [xml]$doc = $file.Text
194
- $counters = $doc.SelectNodes('//counter[@type="BRANCH"]')
195
- if (-not $counters -or $counters.Count -eq 0) { return $null }
196
-
197
- $missed = 0
198
- $covered = 0
199
- foreach ($counter in $counters) {
200
- $missed += [int]$counter.missed
201
- $covered += [int]$counter.covered
202
- }
203
- $total = $missed + $covered
204
- if ($total -le 0) { return $null }
205
- return [math]::Round(($covered * 100.0) / $total, 2)
206
- }
207
-
208
- function Get-LanguageBranchCoverage {
209
- [OutputType([Nullable[double]])]
210
- param([string]$Language)
211
-
212
- switch ($Language) {
213
- 'TypeScript' { return Get-LcovBranchCoverage -Path 'coverage/lcov.info' }
214
- 'Python' { return Get-LcovBranchCoverage -Path 'artifacts/python/lcov.info' }
215
- 'PowerShell' { return Get-JacocoBranchCoverage -Path 'artifacts/pester/powershell-coverage.xml' }
216
- 'CSharp' { return Get-JacocoBranchCoverage -Path 'artifacts/csharp/coverage.xml' }
217
- }
218
- return $null
219
- }
220
-
221
- function Get-JacocoRepoCoverage {
222
- [OutputType([Nullable[double]])]
223
- param([string]$Path)
224
-
225
- $file = Get-ArtifactFileContent -Path $Path
226
- if (-not $file.Exists) { return $null }
227
-
228
- [xml]$doc = $file.Text
229
- $counters = $doc.SelectNodes('//counter[@type="LINE"]')
230
- if (-not $counters -or $counters.Count -eq 0) { return $null }
231
-
232
- $missed = 0
233
- $covered = 0
234
- foreach ($counter in $counters) {
235
- $missed += [int]$counter.missed
236
- $covered += [int]$counter.covered
237
- }
238
- $total = $missed + $covered
239
- if ($total -le 0) { return $null }
240
- return [math]::Round(($covered * 100.0) / $total, 2)
241
- }
242
-
243
- function Get-LanguageRepoCoverage {
244
- [OutputType([Nullable[double]])]
245
- param(
246
- [string]$Language
247
- )
248
-
249
- switch ($Language) {
250
- 'TypeScript' { return Get-LcovRepoCoverage -Path 'coverage/lcov.info' }
251
- 'Python' { return Get-LcovRepoCoverage -Path 'artifacts/python/lcov.info' }
252
- 'PowerShell' { return Get-JacocoRepoCoverage -Path 'artifacts/pester/powershell-coverage.xml' }
253
- 'CSharp' { return Get-JacocoRepoCoverage -Path 'artifacts/csharp/coverage.xml' }
254
- }
255
- return $null
256
- }
257
-
258
- function Test-LanguageCoverageRow {
259
- [OutputType([System.Collections.Hashtable])]
260
- param(
261
- [string]$AuditText,
262
- [string]$Language,
263
- [Nullable[double]]$RepoWidePct,
264
- [Nullable[double]]$BranchPct
265
- )
266
-
267
- $languageLabelMap = @{
268
- 'TypeScript' = @('TypeScript', 'typescript')
269
- 'Python' = @('Python', 'python', 'pytest')
270
- 'PowerShell' = @('PowerShell', 'powershell', 'pester')
271
- 'CSharp' = @('C#', 'CSharp', 'csharp', '.NET', 'dotnet')
272
- }
273
-
274
- $labels = $languageLabelMap[$Language]
275
- $labelPattern = '(?i)(' + (($labels | ForEach-Object { [regex]::Escape($_) }) -join '|') + ')'
276
-
277
- $lines = $AuditText -split "`r?`n"
278
- $languageLines = $lines | Where-Object { $_ -match $labelPattern }
279
-
280
- if (-not $languageLines -or $languageLines.Count -eq 0) {
281
- return @{
282
- Ok = $false
283
- Reason = "$Language has changed files on the branch but the policy-audit does not mention $Language."
284
- }
285
- }
286
-
287
- $coverageLines = $languageLines | Where-Object { $_ -match '(?i)(coverage|lcov|line[s]?\s+hit|pester)' }
288
- if (-not $coverageLines -or $coverageLines.Count -eq 0) {
289
- return @{
290
- Ok = $false
291
- Reason = "$Language has changed files on the branch but no coverage-scoped row in the policy-audit mentions $Language."
292
- }
293
- }
294
-
295
- $narrowingPattern = '(?i)(informational only|context only|out of plan scope|out of scope|not applicable|\bN/A\b|\bUNVERIFIED\b)'
296
- $narrowing = $coverageLines | Where-Object { $_ -match $narrowingPattern }
297
- if ($narrowing -and $narrowing.Count -gt 0) {
298
- $first = ($narrowing | Select-Object -First 1).ToString().Trim()
299
- return @{
300
- Ok = $false
301
- Reason = "$Language has changed files on the branch but a coverage-scoped row narrows scope: '$first'. Scope narrowing is not permitted for languages with changed files."
302
- }
303
- }
304
-
305
- $verdictLines = $coverageLines | Where-Object { $_ -match '\b(PASS|FAIL)\b' }
306
- if (-not $verdictLines -or $verdictLines.Count -eq 0) {
307
- return @{
308
- Ok = $false
309
- Reason = "$Language coverage rows contain neither a PASS nor a FAIL verdict."
310
- }
311
- }
312
-
313
- if ($null -ne $RepoWidePct -and $RepoWidePct -lt 85.0) {
314
- $failLines = $coverageLines | Where-Object { $_ -match '\bFAIL\b' }
315
- if (-not $failLines -or $failLines.Count -eq 0) {
316
- return @{
317
- Ok = $false
318
- Reason = ("{0} repo-wide coverage is {1}% (below the 85% line coverage floor) but the policy-audit contains no FAIL verdict on a coverage row for {0}." -f $Language, $RepoWidePct)
319
- }
320
- }
321
- }
322
-
323
- $BranchFloor = 75.0
324
- if ($null -ne $BranchPct -and $BranchPct -lt $BranchFloor) {
325
- return @{
326
- Ok = $false
327
- Reason = ("{0} branch coverage is {1}% (below the 75% branch coverage floor); policy-audit must record FAIL on the corresponding coverage row." -f $Language, $BranchPct)
328
- }
329
- }
330
-
331
- return @{ Ok = $true; Reason = $null }
332
- }
333
-
334
63
  function Invoke-FeatureReviewCoverageValidation {
335
64
  [CmdletBinding()]
336
65
  [OutputType([hashtable])]
337
66
  param(
338
- [string] $RawPayload
67
+ [AllowNull()] [AllowEmptyString()] [string] $InputText
339
68
  )
69
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'validate-feature-review-coverage:'
70
+ if ($null -ne $dependencyDecision) { return $dependencyDecision }
340
71
 
341
- if ([string]::IsNullOrWhiteSpace($RawPayload)) {
342
- return @{ Ok = $false; Message = 'feature-review hook: CLAUDE_HOOK_INPUT is empty; cannot validate review output.' }
72
+ $envelope = if ($PSBoundParameters.ContainsKey('InputText')) { Read-SubagentStopEnvelope -InputText $InputText } else { Read-SubagentStopEnvelope }
73
+ if (-not $envelope.IsValid) {
74
+ return @{ Ok = $false; Message = ('validate-feature-review-coverage: payload anomaly - ' + (Get-SubagentStopEnvelopeAnomalyReason -Anomaly $envelope.Anomaly)) }
343
75
  }
344
76
 
77
+ # A single-pass do-while block carries the validation so that each finding exits with break; a
78
+ # script-block invocation would be a non-constant command position, which the enforcement-hook
79
+ # invocation scan rejects as unverifiable.
80
+ $finding = $null
345
81
  try {
346
- $payload = $RawPayload | ConvertFrom-Json -ErrorAction Stop
347
- }
348
- catch {
349
- return @{ Ok = $false; Message = "feature-review hook: failed to parse CLAUDE_HOOK_INPUT as JSON: $($_.Exception.Message)" }
350
- }
351
-
352
- $agentOutput = $null
353
- if ($payload.PSObject.Properties.Name -contains 'output') {
354
- $agentOutput = $payload.output
355
- }
356
- if ([string]::IsNullOrWhiteSpace($agentOutput)) {
357
- return @{ Ok = $false; Message = 'feature-review hook: agent output is empty; feature-review must report required artifact paths before termination.' }
358
- }
359
-
360
- $requiredArtifacts = @(
361
- @{ Token = 'policy-audit-path'; Stem = 'policy-audit'; Description = 'policy audit artifact' },
362
- @{ Token = 'code-review-path'; Stem = 'code-review'; Description = 'code review artifact' },
363
- @{ Token = 'feature-audit-path'; Stem = 'feature-audit'; Description = 'feature audit artifact' }
364
- )
365
-
366
- $artifactMeta = @{}
367
- $errors = [System.Collections.Generic.List[string]]::new()
368
-
369
- foreach ($artifact in $requiredArtifacts) {
370
- $path = Get-ReviewArtifactPathFromOutput -AgentOutput $agentOutput -Token $artifact.Token
371
- if ($null -eq $path) {
372
- $errors.Add("missing $($artifact.Token): <path> for the required $($artifact.Description)")
373
- continue
374
- }
375
-
376
- $metadata = Get-ReviewArtifactInfo -Path $path -Stem $artifact.Stem
377
- if ($null -eq $metadata) {
378
- $errors.Add("$($artifact.Token) '$path' is outside the required docs/features/active/.../$($artifact.Stem).<timestamp>.md location")
379
- continue
380
- }
381
-
382
- $file = Get-ArtifactFileContent -Path $path
383
- if (-not $file.Exists) {
384
- $errors.Add("$($artifact.Token) '$path' was advertised for the $($artifact.Description) but no file exists at that location")
385
- continue
386
- }
82
+ do {
83
+ $agentOutput = [string]$envelope.AssistantText
84
+ if ([string]::IsNullOrWhiteSpace($agentOutput)) {
85
+ $finding = @{ Message = 'validate-feature-review-coverage: agent output is empty; feature-review must report required artifact paths before termination.'; HasRepairable = $true; HasNonRepairable = $false }
86
+ break
87
+ }
387
88
 
388
- $metadata['Text'] = $file.Text
389
- $artifactMeta[$artifact.Stem] = $metadata
390
- }
89
+ $requiredArtifacts = @(
90
+ @{ Token = 'policy-audit-path'; Stem = 'policy-audit'; Description = 'policy audit artifact' },
91
+ @{ Token = 'code-review-path'; Stem = 'code-review'; Description = 'code review artifact' },
92
+ @{ Token = 'feature-audit-path'; Stem = 'feature-audit'; Description = 'feature audit artifact' }
93
+ )
94
+
95
+ $artifactMeta = @{}
96
+ $errors = [System.Collections.Generic.List[string]]::new()
97
+
98
+ foreach ($artifact in $requiredArtifacts) {
99
+ $path = Get-ReviewArtifactPathFromOutput -AgentOutput $agentOutput -Token $artifact.Token
100
+ if ($null -eq $path) {
101
+ $errors.Add("missing $($artifact.Token): <path> for the required $($artifact.Description)")
102
+ continue
103
+ }
104
+
105
+ $metadata = Get-ReviewArtifactInfo -Path $path -Stem $artifact.Stem
106
+ if ($null -eq $metadata) {
107
+ $errors.Add("$($artifact.Token) '$path' is outside the required docs/features/active/.../$($artifact.Stem).<timestamp>.md location")
108
+ continue
109
+ }
110
+
111
+ $file = Get-ArtifactFileContent -Path $path
112
+ if (-not $file.Exists) {
113
+ $errors.Add("$($artifact.Token) '$path' was advertised for the $($artifact.Description) but no file exists at that location")
114
+ continue
115
+ }
116
+
117
+ $metadata['Text'] = $file.Text
118
+ $artifactMeta[$artifact.Stem] = $metadata
119
+ }
391
120
 
392
- if ($artifactMeta.ContainsKey('policy-audit')) {
393
- $reference = $artifactMeta['policy-audit']
394
- foreach ($stem in @('code-review', 'feature-audit')) {
395
- if (-not $artifactMeta.ContainsKey($stem)) {
396
- continue
121
+ if ($artifactMeta.ContainsKey('policy-audit')) {
122
+ $reference = $artifactMeta['policy-audit']
123
+ foreach ($stem in @('code-review', 'feature-audit')) {
124
+ if (-not $artifactMeta.ContainsKey($stem)) {
125
+ continue
126
+ }
127
+ $candidate = $artifactMeta[$stem]
128
+ if ($candidate.Folder -ne $reference.Folder -or $candidate.Timestamp -ne $reference.Timestamp) {
129
+ $errors.Add("$stem artifact must share the same feature folder and timestamp as the policy audit artifact.")
130
+ }
131
+ }
132
+
133
+ $remediationPath = Get-ReviewArtifactPathFromOutput -AgentOutput $agentOutput -Token 'remediation-inputs-path'
134
+ if ($null -ne $remediationPath) {
135
+ $remediation = Get-ReviewArtifactInfo -Path $remediationPath -Stem 'remediation-inputs'
136
+ if ($null -eq $remediation) {
137
+ $errors.Add("remediation-inputs-path '$remediationPath' is outside the required docs/features/active/.../remediation-inputs.<timestamp>.md location")
138
+ }
139
+ elseif ($remediation.Folder -ne $reference.Folder -or $remediation.Timestamp -ne $reference.Timestamp) {
140
+ $errors.Add('remediation-inputs artifact must share the same feature folder and timestamp as the policy audit artifact.')
141
+ }
142
+ elseif (-not (Get-ArtifactFileContent -Path $remediationPath).Exists) {
143
+ $errors.Add("remediation-inputs-path '$remediationPath' was advertised but no file exists at that location")
144
+ }
145
+ }
397
146
  }
398
- $candidate = $artifactMeta[$stem]
399
- if ($candidate.Folder -ne $reference.Folder -or $candidate.Timestamp -ne $reference.Timestamp) {
400
- $errors.Add("$stem artifact must share the same feature folder and timestamp as the policy audit artifact.")
147
+
148
+ if ($errors.Count -gt 0) {
149
+ $message = "validate-feature-review-coverage: required review artifact validation failed:`n - " + ($errors -join "`n - ")
150
+ $finding = @{ Message = $message; HasRepairable = $true; HasNonRepairable = $false }
151
+ break
401
152
  }
402
- }
403
153
 
404
- $remediationPath = Get-ReviewArtifactPathFromOutput -AgentOutput $agentOutput -Token 'remediation-inputs-path'
405
- if ($null -ne $remediationPath) {
406
- $remediation = Get-ReviewArtifactInfo -Path $remediationPath -Stem 'remediation-inputs'
407
- if ($null -eq $remediation) {
408
- $errors.Add("remediation-inputs-path '$remediationPath' is outside the required docs/features/active/.../remediation-inputs.<timestamp>.md location")
154
+ $prSummary = Get-ArtifactFileContent -Path 'artifacts/pr_context.summary.txt'
155
+ $changedLanguages = if ($prSummary.Exists) { Get-ChangedLanguageSet -Lines $prSummary.Lines } else { [ordered]@{} }
156
+ if ($changedLanguages.Count -eq 0) {
157
+ break
409
158
  }
410
- elseif ($remediation.Folder -ne $reference.Folder -or $remediation.Timestamp -ne $reference.Timestamp) {
411
- $errors.Add('remediation-inputs artifact must share the same feature folder and timestamp as the policy audit artifact.')
159
+
160
+ $policyAuditText = $artifactMeta['policy-audit'].Text
161
+ $claudeMd = Get-FeatureReviewClaudeMdText
162
+ if ($null -ne $claudeMd.ReadError) {
163
+ $finding = @{ Message = "validate-feature-review-coverage: root CLAUDE.md exists but could not be read ($($claudeMd.ReadError)); coverage floors cannot be resolved, so the gate fails closed."; HasRepairable = $false; HasNonRepairable = $true }
164
+ break
412
165
  }
413
- elseif (-not (Get-ArtifactFileContent -Path $remediationPath).Exists) {
414
- $errors.Add("remediation-inputs-path '$remediationPath' was advertised but no file exists at that location")
166
+ $thresholds = Get-FeatureReviewCoverageThreshold -ClaudeMdText $claudeMd.Text
167
+ $coverageFailures = [System.Collections.Generic.List[hashtable]]::new()
168
+ foreach ($lang in $changedLanguages.Keys) {
169
+ $repoPct = Get-LanguageRepoCoverage -Language $lang
170
+ $branchPct = Get-LanguageBranchCoverage -Language $lang
171
+ $result = Test-LanguageCoverageRow -AuditText $policyAuditText -Language $lang -RepoWidePct $repoPct -BranchPct $branchPct -LineFloor $thresholds.Line -BranchFloor $thresholds.Branch -LineSource $thresholds.LineSource -BranchSource $thresholds.BranchSource
172
+ if (-not $result.Ok) {
173
+ $coverageFailures.Add($result)
174
+ }
415
175
  }
416
- }
417
- }
418
-
419
- if ($errors.Count -gt 0) {
420
- $message = "feature-review hook: required review artifact validation failed:`n - " + ($errors -join "`n - ")
421
- return @{ Ok = $false; Message = $message }
422
- }
423
176
 
424
- $prSummary = Get-ArtifactFileContent -Path 'artifacts/pr_context.summary.txt'
425
- $changedLanguages = if ($prSummary.Exists) { Get-ChangedLanguageSet -Lines $prSummary.Lines } else { [ordered]@{} }
426
- if ($changedLanguages.Count -eq 0) {
427
- return @{ Ok = $true; Message = $null }
428
- }
429
-
430
- $policyAuditText = $artifactMeta['policy-audit'].Text
431
- $coverageFailures = [System.Collections.Generic.List[string]]::new()
432
- foreach ($lang in $changedLanguages.Keys) {
433
- $repoPct = Get-LanguageRepoCoverage -Language $lang
434
- $branchPct = Get-LanguageBranchCoverage -Language $lang
435
- $result = Test-LanguageCoverageRow -AuditText $policyAuditText -Language $lang -RepoWidePct $repoPct -BranchPct $branchPct
436
- if (-not $result.Ok) {
437
- $coverageFailures.Add($result.Reason)
438
- }
177
+ if ($coverageFailures.Count -gt 0) {
178
+ $message = "validate-feature-review-coverage: coverage validation failed against branch diff:`n - " + (@($coverageFailures | ForEach-Object { $_.Reason }) -join "`n - ")
179
+ $hasRepairable = @($coverageFailures | Where-Object { $_.Repairable -eq $true }).Count -gt 0
180
+ $hasNonRepairable = @($coverageFailures | Where-Object { $_.Repairable -eq $false }).Count -gt 0
181
+ $finding = @{ Message = $message; HasRepairable = $hasRepairable; HasNonRepairable = $hasNonRepairable }
182
+ }
183
+ } while ($false)
439
184
  }
440
-
441
- if ($coverageFailures.Count -gt 0) {
442
- $message = "feature-review hook: coverage validation failed against branch diff:`n - " + ($coverageFailures -join "`n - ")
443
- return @{ Ok = $false; Message = $message }
185
+ catch {
186
+ $finding = @{ Message = ('validate-feature-review-coverage: validator error: ' + ([string]$_.Exception.Message -split "`r?`n")[0] + '; the gate fails closed.'); HasRepairable = $false; HasNonRepairable = $true }
444
187
  }
445
188
 
446
- return @{ Ok = $true; Message = $null }
189
+ if ($null -eq $finding) { return @{ Ok = $true; Message = $null } }
190
+ if ($envelope.StopHookActive -and -not $finding.HasRepairable) { return @{ Ok = $true; Message = $null } }
191
+ $message = if ($finding.HasNonRepairable) { "$($finding.Message) The subagent cannot repair this condition; report it in the final response and stop." } else { $finding.Message }
192
+ return @{ Ok = $false; Message = $message }
447
193
  }
448
194
 
449
195
  if ($MyInvocation.InvocationName -eq '.') {
450
196
  return
451
197
  }
198
+ if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('validate-feature-review-coverage: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
199
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'validate-feature-review-coverage:'
200
+ if ($null -ne $dependencyDecision) { [Console]::Error.WriteLine($dependencyDecision.Reason); exit $dependencyDecision.ExitCode }
452
201
 
453
- $result = Invoke-FeatureReviewCoverageValidation -RawPayload $env:CLAUDE_HOOK_INPUT
454
- if (-not $result.Ok) {
455
- Write-Error $result.Message
456
- exit 1
457
- }
458
-
202
+ try { $result = Invoke-FeatureReviewCoverageValidation }
203
+ catch { Exit-SubagentStopBlock -Message ('validate-feature-review-coverage: validator error: ' + ([string]$_.Exception.Message -split "`r?`n")[0] + '; the gate fails closed. The subagent cannot repair this condition; report it in the final response and stop.') }
204
+ if (-not $result.Ok) { Exit-SubagentStopBlock -Message $result.Message }
459
205
  exit 0