@danmoisan/drm-copilot-mcp 1.1.18 → 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 (206) hide show
  1. package/out/mcp-server.js +989 -662
  2. package/package.json +3 -2
  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 +6 -4
  8. package/resources/claude-customizations/.claude/agents/orchestrator.md +5 -4
  9. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +31 -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-mutation-cli.sh +152 -0
  76. package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-ops.sh +193 -0
  77. package/resources/claude-customizations/.claude/lib/bash/parallel-mutation.sh +322 -0
  78. package/resources/claude-customizations/.claude/lib/bash/remove-parallel-item.sh +146 -0
  79. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +23 -33
  80. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +86 -6
  81. package/resources/claude-customizations/.claude/lib/ci-gate/Invoke-CiGateParser.ps1 +91 -19
  82. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateEpicWaveBarrier.psm1 +346 -0
  83. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +24 -3
  84. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +79 -2
  85. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +3 -3
  86. package/resources/claude-customizations/.claude/rules/architecture-boundaries.md +5 -5
  87. package/resources/claude-customizations/.claude/rules/csharp.md +2 -2
  88. package/resources/claude-customizations/.claude/rules/general-code-change.md +1 -1
  89. package/resources/claude-customizations/.claude/rules/general-unit-test.md +3 -2
  90. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +37 -4
  91. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +50 -5
  92. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +47 -8
  93. package/resources/claude-customizations/.claude/rules/quality-tiers.md +11 -7
  94. package/resources/claude-customizations/.claude/rules/shell.md +10 -8
  95. package/resources/claude-customizations/.claude/rules/typescript.md +1 -1
  96. package/resources/claude-customizations/.claude/settings.json +3 -4
  97. package/resources/claude-customizations/.claude/skills/acceptance-criteria-tracking/SKILL.md +1 -1
  98. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +5 -1
  99. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_detached_lib.sh +1 -1
  100. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_enumerate_lib.sh +2 -0
  101. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_scan_helper.sh +2 -0
  102. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +33 -6
  103. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +1 -1
  104. package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
  105. package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +1 -1
  106. package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +4 -3
  107. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +14 -6
  108. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +52 -14
  109. package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +22 -9
  110. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +79 -16
  111. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +63 -7
  112. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +35 -12
  113. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +3 -1
  114. package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +1 -1
  115. package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +2 -2
  116. package/resources/claude-customizations/pack-manifests/core.json +23 -0
  117. package/resources/codex-and-agents-customizations/.agents/skills/acceptance-criteria-tracking/SKILL.md +1 -1
  118. package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +5 -5
  119. package/resources/codex-and-agents-customizations/.agents/skills/atomic-executor/SKILL.md +11 -0
  120. package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +4 -0
  121. package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +10 -0
  122. package/resources/codex-and-agents-customizations/.agents/skills/csharp/SKILL.md +2 -2
  123. package/resources/codex-and-agents-customizations/.agents/skills/csharp-qa-gate/SKILL.md +2 -2
  124. package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +8 -0
  125. package/resources/codex-and-agents-customizations/.agents/skills/epic-plan/SKILL.md +17 -1
  126. package/resources/codex-and-agents-customizations/.agents/skills/epic-run/SKILL.md +2 -1
  127. package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
  128. package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +13 -5
  129. package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +1 -1
  130. package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +3 -2
  131. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +15 -6
  132. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +1 -1
  133. package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +11 -7
  134. package/resources/codex-and-agents-customizations/.agents/skills/translate-claude-to-codex/SKILL.md +1 -1
  135. package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp/SKILL.md +2 -2
  136. package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +2 -2
  137. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +11 -0
  138. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +11 -0
  139. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +11 -0
  140. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +11 -0
  141. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +11 -0
  142. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +11 -0
  143. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +10 -0
  144. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +10 -0
  145. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +10 -0
  146. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +10 -0
  147. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +10 -0
  148. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +10 -0
  149. package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +2 -1
  150. package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +6 -6
  151. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +2 -2
  152. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +2 -2
  153. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +2 -2
  154. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +2 -2
  155. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +2 -2
  156. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +2 -2
  157. package/resources/codex-and-agents-customizations/.codex/codex-web-setup.sh +318 -273
  158. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  159. package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +7 -1
  160. package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +7 -1
  161. package/resources/codex-and-agents-customizations/.codex/hooks/codex-epic-child-launch-attestation.ps1 +1 -1
  162. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
  163. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +8 -2
  164. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +97 -44
  165. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-helpers.ps1 +105 -0
  166. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +7 -1
  167. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +37 -5
  168. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +6 -0
  169. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-root-invocation.ps1 +7 -1
  170. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-wave-barrier.ps1 +7 -1
  171. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +54 -41
  172. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +7 -1
  173. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +35 -14
  174. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
  175. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
  176. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
  177. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +24 -11
  178. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +8 -2
  179. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +13 -4
  180. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +8 -2
  181. package/resources/codex-and-agents-customizations/.codex/hooks/feature-folder-resolution.ps1 +373 -0
  182. package/resources/codex-and-agents-customizations/.codex/hooks/feature-review-coverage-thresholds.ps1 +85 -0
  183. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-heredoc.ps1 +119 -0
  184. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation-operands.ps1 +198 -0
  185. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +254 -255
  186. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload-powershell.ps1 +201 -0
  187. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload.ps1 +484 -0
  188. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +27 -110
  189. package/resources/codex-and-agents-customizations/.codex/hooks/hook-dependency-guard.ps1 +114 -0
  190. package/resources/codex-and-agents-customizations/.codex/hooks/hook-docs-only-diff.ps1 +493 -0
  191. package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +8 -2
  192. package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +7 -1
  193. package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +24 -8
  194. package/resources/codex-and-agents-customizations/pack-manifests/core.json +9 -0
  195. package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +2 -2
  196. package/resources/customizations/.github/instructions/csharp-code-change.instructions.md +4 -4
  197. package/resources/customizations/.github/instructions/csharp-unit-test.instructions.md +2 -2
  198. package/resources/customizations/.github/skills/acceptance-criteria-tracking/SKILL.md +1 -1
  199. package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +4 -0
  200. package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
  201. package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +1 -1
  202. package/resources/powershell/PoshQC/PoshQC.Coverage.psm1 +314 -0
  203. package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +25 -28
  204. package/resources/powershell/PoshQC/PoshQC.psm1 +1 -0
  205. package/resources/powershell/PoshQC/README.md +8 -2
  206. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +2 -319
@@ -3,18 +3,21 @@
3
3
  SubagentStop hook that verifies the pr-author agent reported a created or updated PR.
4
4
 
5
5
  .DESCRIPTION
6
- Invoked by the Claude Code SubagentStop hook for the pr-author matcher. Reads the agent
7
- transcript JSON from the CLAUDE_HOOK_INPUT environment variable, extracts the .output field,
8
- and confirms the output reports that a pull request was created or updated. The hook allows
9
- (exit 0) when the output contains:
6
+ Invoked by the Claude Code SubagentStop hook for the pr-author matcher. Reads the SubagentStop
7
+ envelope JSON from stdin through the shared envelope helper (subagentstop-envelope.ps1) and
8
+ validates its last_assistant_message field, the pr-author agent's last assistant message. The
9
+ hook allows (exit 0) when that message contains:
10
10
  - a GitHub PR URL (github.com/<owner>/<repo>/pull/<n>), or
11
11
  - a PR reference of the form "PR #<n>", or
12
12
  - a "gh pr create"/"gh pr edit" confirmation that includes a PR number.
13
- The hook blocks (exit 1) when:
14
- - CLAUDE_HOOK_INPUT is empty,
15
- - the input is not valid JSON,
16
- - the .output field is empty, or
17
- - the output contains no PR URL or PR number.
13
+ The hook blocks, writing the reason to stderr and exiting 2 through the shared envelope
14
+ helper, when:
15
+ - the envelope is empty, is not valid JSON, is not a JSON object, or carries no string
16
+ last_assistant_message (reason prefix "PR_AUTHOR_OUTPUT_MALFORMED: payload anomaly"),
17
+ - the last assistant message is blank (PR_AUTHOR_OUTPUT_EMPTY), or
18
+ - the last assistant message contains no PR URL or PR number (PR_AUTHOR_OUTPUT_NO_PR).
19
+ A hook dependency that fails to load blocks with exit 2 and the PR_AUTHOR_OUTPUT_MISSING:
20
+ prefix.
18
21
 
19
22
  .NOTES
20
23
  Compatible with PowerShell 7+. No external module dependencies.
@@ -24,9 +27,12 @@
24
27
  that the referenced PR actually exists on GitHub, and the output text is forgeable by any actor
25
28
  that controls the agent transcript. It MUST NOT be described as tamper-proof or as a security
26
29
  boundary.
30
+ An unhandled error during validation blocks with exit 2 and a reason (issue #928).
27
31
  #>
28
32
  [CmdletBinding()]
29
33
  param()
34
+ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
35
+ try { . (Join-Path $PSScriptRoot 'subagentstop-envelope.ps1') } catch { Add-HookDependencyFailure -Name 'subagentstop-envelope.ps1' -ErrorRecord $_ }
30
36
 
31
37
  function Test-PrAuthorOutputReportsPr {
32
38
  <#
@@ -36,7 +42,7 @@ function Test-PrAuthorOutputReportsPr {
36
42
  Detection helper (injectable boundary for tests). Matches a GitHub PR URL, a "PR #<n>"
37
43
  reference, or a "gh pr create"/"gh pr edit" confirmation that contains a PR number.
38
44
  .PARAMETER OutputText
39
- The pr-author agent's final output text extracted from CLAUDE_HOOK_INPUT.output.
45
+ The pr-author agent's last assistant message text.
40
46
  .OUTPUTS
41
47
  System.Boolean
42
48
  #>
@@ -71,53 +77,43 @@ function Test-PrAuthorOutputReportsPr {
71
77
  function Get-PrAuthorOutputDecision {
72
78
  <#
73
79
  .SYNOPSIS
74
- Parse CLAUDE_HOOK_INPUT and return an allow-or-block decision with exit code semantics.
80
+ Read the SubagentStop envelope and return an allow-or-block decision.
75
81
  .DESCRIPTION
76
82
  Returns an ordered dictionary with 'allowed' (bool) and 'reason' (string). Allowed is $true
77
- only when the parsed .output reports a PR per Test-PrAuthorOutputReportsPr. Empty input,
78
- malformed JSON, empty output, and PR-less output all yield allowed = $false.
79
- .PARAMETER HookInputRaw
80
- The raw JSON transcript payload supplied by Claude Code via CLAUDE_HOOK_INPUT.
83
+ only when the envelope's last assistant message reports a PR per
84
+ Test-PrAuthorOutputReportsPr. An invalid envelope (payload anomaly), a blank last assistant
85
+ message, and a message without a PR reference all yield allowed = $false.
86
+ .PARAMETER InputText
87
+ The SubagentStop envelope JSON. When the parameter is not bound, Read-SubagentStopEnvelope
88
+ reads the envelope from stdin.
81
89
  .OUTPUTS
82
90
  System.Collections.Specialized.OrderedDictionary
83
91
  #>
84
92
  [CmdletBinding()]
85
93
  [OutputType([System.Collections.Specialized.OrderedDictionary])]
86
94
  param(
87
- [string] $HookInputRaw
95
+ [AllowNull()] [AllowEmptyString()] [string] $InputText
88
96
  )
97
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'PR_AUTHOR_OUTPUT_MISSING:'
98
+ if ($null -ne $dependencyDecision) { return $dependencyDecision }
99
+ $envelope = if ($PSBoundParameters.ContainsKey('InputText')) { Read-SubagentStopEnvelope -InputText $InputText } else { Read-SubagentStopEnvelope }
100
+ if (-not $envelope.IsValid) { return [ordered]@{ allowed = $false; reason = "PR_AUTHOR_OUTPUT_MALFORMED: payload anomaly - $(Get-SubagentStopEnvelopeAnomalyReason -Anomaly $envelope.Anomaly)" } }
101
+ $assistantMessage = [string]$envelope.AssistantText
89
102
 
90
- if ([string]::IsNullOrWhiteSpace($HookInputRaw)) {
103
+ if ([string]::IsNullOrWhiteSpace($assistantMessage)) {
91
104
  return [ordered]@{
92
105
  allowed = $false
93
- reason = 'PR_AUTHOR_OUTPUT_MISSING: CLAUDE_HOOK_INPUT is empty; the pr-author agent produced no transcript to validate.'
106
+ reason = 'PR_AUTHOR_OUTPUT_EMPTY: the pr-author last assistant message is empty; it must report the created or updated PR URL or number.'
94
107
  }
95
108
  }
96
109
 
97
- try {
98
- $parsed = $HookInputRaw | ConvertFrom-Json -ErrorAction Stop
99
- } catch {
100
- return [ordered]@{
101
- allowed = $false
102
- reason = 'PR_AUTHOR_OUTPUT_MALFORMED: CLAUDE_HOOK_INPUT is not valid JSON.'
103
- }
104
- }
105
-
106
- $outputText = [string]$parsed.output
107
- if ([string]::IsNullOrWhiteSpace($outputText)) {
108
- return [ordered]@{
109
- allowed = $false
110
- reason = 'PR_AUTHOR_OUTPUT_EMPTY: the pr-author agent output is empty; it must report the created or updated PR URL or number.'
111
- }
112
- }
113
-
114
- if (Test-PrAuthorOutputReportsPr -OutputText $outputText) {
110
+ if (Test-PrAuthorOutputReportsPr -OutputText $assistantMessage) {
115
111
  return [ordered]@{ allowed = $true }
116
112
  }
117
113
 
118
114
  return [ordered]@{
119
115
  allowed = $false
120
- reason = 'PR_AUTHOR_OUTPUT_NO_PR: the pr-author agent output does not reference a PR URL or PR number; it must report the created or updated pull request.'
116
+ reason = 'PR_AUTHOR_OUTPUT_NO_PR: the pr-author last assistant message does not reference a PR URL or PR number; it must report the created or updated pull request.'
121
117
  }
122
118
  }
123
119
 
@@ -125,12 +121,11 @@ function Get-PrAuthorOutputDecision {
125
121
  if ($MyInvocation.InvocationName -eq '.') {
126
122
  return
127
123
  }
124
+ if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('PR_AUTHOR_OUTPUT_MISSING: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
125
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'PR_AUTHOR_OUTPUT_MISSING:'
126
+ if ($null -ne $dependencyDecision) { [Console]::Error.WriteLine($dependencyDecision.Reason); exit $dependencyDecision.ExitCode }
128
127
 
129
- $decision = Get-PrAuthorOutputDecision -HookInputRaw $env:CLAUDE_HOOK_INPUT
130
-
131
- if (-not $decision['allowed']) {
132
- Write-Error $decision['reason']
133
- exit 1
134
- }
135
-
128
+ try { $decision = Get-PrAuthorOutputDecision }
129
+ catch { Exit-SubagentStopBlock -Message ('PR_AUTHOR_OUTPUT_MISSING: validator error: ' + ([string]$_.Exception.Message -split "`r?`n")[0] + '; the gate fails closed.' + "`n" + 'This condition is not repairable by the subagent. Report it to the operator and halt.') }
130
+ if (-not $decision['allowed']) { Exit-SubagentStopBlock -Message $decision['reason'] }
136
131
  exit 0
@@ -1,10 +1,21 @@
1
1
  <#
2
2
  .SYNOPSIS
3
3
  Validates numeric acceptance criteria produced by the prd-feature worker.
4
+
5
+ .NOTES
6
+ Reads the SubagentStop envelope JSON from stdin through the shared envelope helper
7
+ (subagentstop-envelope.ps1) and validates its last_assistant_message field, the prd-feature
8
+ worker's last assistant message. Exits 0 to allow. A block writes the reason to stderr and
9
+ exits 2 through the shared envelope helper; an invalid envelope blocks with a payload anomaly
10
+ reason. A hook dependency that fails to load blocks with exit 2 and the
11
+ validate-prd-feature-output: prefix.
12
+ An unhandled error during validation blocks with exit 2 and a reason (issue #928).
4
13
  #>
5
14
 
6
15
  [CmdletBinding()]
7
16
  param()
17
+ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
18
+ try { . (Join-Path $PSScriptRoot 'subagentstop-envelope.ps1') } catch { Add-HookDependencyFailure -Name 'subagentstop-envelope.ps1' -ErrorRecord $_ }
8
19
 
9
20
  Set-StrictMode -Version Latest
10
21
  $ErrorActionPreference = 'Stop'
@@ -38,7 +49,7 @@ function Test-NumericDerivationEvidence {
38
49
  if (-not $match.Success) { return @{ Ok = $false; Message = "prd-feature hook: numeric derivation evidence is missing $label." } }
39
50
  $values[$label] = $match.Groups['value'].Value.Trim()
40
51
  }
41
- if ($values['Exhaustive Search Scope'] -notmatch '(?i)\b(entire|all|complete)\b.*\b(repository|repo|source tree|tree)\b') { return @{ Ok = $false; Message = 'prd-feature hook: numeric derivation evidence does not declare an exhaustive repository search scope.' } }
52
+ if ($values['Exhaustive Search Scope'] -notmatch '(?i)\b(entire|whole|all|complete)\b.*\b(repository|repo|worktree|source tree|tree)\b') { return @{ Ok = $false; Message = 'prd-feature hook: numeric derivation evidence does not declare an exhaustive repository search scope.' } }
42
53
  $primaryStrategy = $values['Primary Search Strategy or Query Expression']
43
54
  $crossCheckStrategy = $values['Cross-check Search Strategy or Query Expression']
44
55
  if ($primaryStrategy -match '(?i)\b(single|narrow|named[- ]?pattern)\b' -or $crossCheckStrategy -match '(?i)\b(single|narrow|named[- ]?pattern)\b') { return @{ Ok = $false; Message = 'prd-feature hook: numeric derivation evidence uses a narrow named-pattern search.' } }
@@ -64,28 +75,46 @@ function Test-SpecNumericCriterion {
64
75
  param([Parameter(Mandatory = $true)][AllowEmptyString()][string] $SpecContent)
65
76
 
66
77
  $section = [regex]::Match($SpecContent, '(?ims)^##\s+Acceptance\s+Criteria\s*$.*?(?=^##\s|\z)')
67
- return $section.Success -and [regex]::IsMatch($section.Value, '(?m)^\s*[-*]\s*\[.\].*\b\d+\b')
78
+ if (-not $section.Success) { return $false }
79
+ # Rule R2 (issue #905): a criterion is numeric only when it states a determiner-led cardinal count of a
80
+ # plural noun and cites no policy file.
81
+ $derivedCountPattern = '\b(?:all|exactly|the|these|those)\s+(?:[1-9]\d*|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)\s+(?:(?!(?:seconds|minutes|hours|days|weeks|milliseconds|lines|bytes|characters|percent|is|as|us|has|was|does|this|its|thus|plus|less|unless|across|always|yes)\b)[\w`./-]+\s+){0,3}?(?!(?:seconds|minutes|hours|days|weeks|milliseconds|lines|bytes|characters|percent|is|as|us|has|was|does|this|its|thus|plus|less|unless|across|always|yes)\b)[a-z][\w-]*s\b'
82
+ $policyMarkers = @('.claude/rules/', '.github/instructions/', '.github/copilot-instructions.md')
83
+ foreach ($line in ($section.Value -split "`r?`n")) {
84
+ if ($line -notmatch '^\s*[-*]\s*\[.\]') { continue }
85
+ if (-not [regex]::IsMatch($line, $derivedCountPattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)) { continue }
86
+ $normalized = $line -replace '\\', '/'
87
+ if (@($policyMarkers | Where-Object { $normalized.Contains($_) }).Count -gt 0) { continue }
88
+ return $true
89
+ }
90
+ return $false
68
91
  }
69
92
 
70
93
  function Invoke-PrdFeatureOutputValidation {
71
94
  [CmdletBinding()]
72
95
  [OutputType([hashtable])]
73
- param([string] $RawPayload)
74
-
75
- if ([string]::IsNullOrWhiteSpace($RawPayload)) { return @{ Ok = $false; Message = 'prd-feature hook: CLAUDE_HOOK_INPUT is empty.' } }
76
- try { $payload = $RawPayload | ConvertFrom-Json -ErrorAction Stop } catch { return @{ Ok = $false; Message = "prd-feature hook: failed to parse CLAUDE_HOOK_INPUT as JSON: $($_.Exception.Message)" } }
77
- $output = if ($null -ne $payload.PSObject.Properties['output']) { $payload.output } else { $null }
78
- if ([string]::IsNullOrWhiteSpace($output)) { return @{ Ok = $false; Message = 'prd-feature hook: agent output is empty.' } }
79
- $specPath = Get-ArtifactPathFromOutput -Output $output -Label 'spec-path'
96
+ param([AllowNull()] [AllowEmptyString()] [string] $InputText)
97
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'validate-prd-feature-output:'
98
+ if ($null -ne $dependencyDecision) { return $dependencyDecision }
99
+ $envelope = if ($PSBoundParameters.ContainsKey('InputText')) { Read-SubagentStopEnvelope -InputText $InputText } else { Read-SubagentStopEnvelope }
100
+ if (-not $envelope.IsValid) { return @{ Ok = $false; Message = "prd-feature hook: payload anomaly - $(Get-SubagentStopEnvelopeAnomalyReason -Anomaly $envelope.Anomaly)" } }
101
+ $assistantMessage = [string]$envelope.AssistantText
102
+ if ([string]::IsNullOrWhiteSpace($assistantMessage)) { return @{ Ok = $false; Message = 'prd-feature hook: last assistant message is empty.' } }
103
+ $specPath = Get-ArtifactPathFromOutput -Output $assistantMessage -Label 'spec-path'
80
104
  if ([string]::IsNullOrWhiteSpace($specPath) -or -not (Test-Path -LiteralPath $specPath -PathType Leaf)) { return @{ Ok = $false; Message = 'prd-feature hook: spec-path is required and must exist.' } }
81
- $specContent = Get-Content -LiteralPath $specPath -Raw -ErrorAction Stop
105
+ try { $specContent = Get-Content -LiteralPath $specPath -Raw -ErrorAction Stop } catch { return @{ Ok = $false; Message = "prd-feature hook: failed to read spec-path '$specPath': $($_.Exception.Message)" } }
82
106
  if (-not (Test-SpecNumericCriterion -SpecContent $specContent)) { return @{ Ok = $true; Message = $null } }
83
- $researchPath = Get-ArtifactPathFromOutput -Output $output -Label 'research-path'
107
+ $researchPath = Get-ArtifactPathFromOutput -Output $assistantMessage -Label 'research-path'
84
108
  if ([string]::IsNullOrWhiteSpace($researchPath) -or -not (Test-Path -LiteralPath $researchPath -PathType Leaf)) { return @{ Ok = $false; Message = 'prd-feature hook: numeric acceptance criterion requires an existing research-path.' } }
85
- return Test-NumericDerivationEvidence -Content (Get-Content -LiteralPath $researchPath -Raw -ErrorAction Stop)
109
+ try { $researchContent = Get-Content -LiteralPath $researchPath -Raw -ErrorAction Stop } catch { return @{ Ok = $false; Message = "prd-feature hook: failed to read research-path '$researchPath': $($_.Exception.Message)" } }
110
+ return Test-NumericDerivationEvidence -Content $researchContent
86
111
  }
87
112
 
88
113
  if ($MyInvocation.InvocationName -eq '.') { return }
89
- $result = Invoke-PrdFeatureOutputValidation -RawPayload $env:CLAUDE_HOOK_INPUT
90
- if (-not $result.Ok) { Write-Error $result.Message; exit 1 }
114
+ if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('validate-prd-feature-output: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
115
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'validate-prd-feature-output:'
116
+ if ($null -ne $dependencyDecision) { [Console]::Error.WriteLine($dependencyDecision.Reason); exit $dependencyDecision.ExitCode }
117
+ try { $result = Invoke-PrdFeatureOutputValidation }
118
+ catch { Exit-SubagentStopBlock -Message ('validate-prd-feature-output: validator error: ' + ([string]$_.Exception.Message -split "`r?`n")[0] + '; the gate fails closed.' + "`n" + 'This condition is not repairable by the subagent. Report it to the operator and halt.') }
119
+ if (-not $result.Ok) { Exit-SubagentStopBlock -Message $result.Message }
91
120
  exit 0
@@ -3,10 +3,10 @@
3
3
  Generic SubagentStop hook for agents that must advertise exact artifact paths.
4
4
 
5
5
  .DESCRIPTION
6
- Validates that the hook payload is well-formed JSON, the agent output is
7
- non-empty, each required artifact token is advertised in the output, the
8
- advertised path matches the configured contract regex, and the file exists
9
- on disk.
6
+ Validates that the SubagentStop envelope read from stdin carries a non-empty
7
+ last_assistant_message, each required artifact token is advertised in that
8
+ text, the advertised path matches the configured contract regex, and the file
9
+ exists on disk.
10
10
 
11
11
  Each required artifact spec must use:
12
12
  token|regex|description
@@ -15,10 +15,17 @@
15
15
  spec-path|^docs/features/active/.+/spec\.md$|spec artifact
16
16
 
17
17
  .NOTES
18
- Reads the hook payload from CLAUDE_HOOK_INPUT as JSON. Exits 0 to allow
19
- termination; exits 1 with an error message to block. Filesystem existence
20
- checks go through Test-ArtifactFile so tests can mock the boundary without
21
- writing temporary files.
18
+ Reads the SubagentStop envelope from stdin through Read-SubagentStopEnvelope
19
+ (subagentstop-envelope.ps1); the assistant text comes from the helper's
20
+ AssistantText. A non-repairable condition blocks once: when StopHookActive is
21
+ set, the re-entered stop is allowed. Exits 0 to allow termination; a block is
22
+ written to stderr with exit 2 through Exit-SubagentStopBlock. A missing
23
+ AgentName or RequiredArtifact argument exits 2 inline and blocks once. The
24
+ dependency guard and the E1 helper are guarded loads: a failure blocks with
25
+ exit 2, naming the file. Filesystem existence checks go through
26
+ Test-ArtifactFile so tests can mock the boundary without writing temporary
27
+ files.
28
+ An unhandled error during validation blocks with exit 2 and a reason (issue #928).
22
29
  #>
23
30
 
24
31
  [CmdletBinding()]
@@ -27,9 +34,12 @@ param(
27
34
 
28
35
  [string[]] $RequiredArtifact
29
36
  )
37
+ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
30
38
 
31
39
  Set-StrictMode -Version Latest
40
+ try { . (Join-Path $PSScriptRoot 'subagentstop-envelope.ps1') } catch { Add-HookDependencyFailure -Name 'subagentstop-envelope.ps1' -ErrorRecord $_ }
32
41
  $ErrorActionPreference = 'Stop'
42
+ $script:SubagentStopReportAndHaltLine = 'This condition is not repairable by the subagent. Report it to the operator and halt.'
33
43
 
34
44
  function Test-ArtifactFile {
35
45
  [CmdletBinding()]
@@ -90,31 +100,53 @@ function Get-ArtifactPathFromOutput {
90
100
  }
91
101
 
92
102
  function Invoke-RequiredArtifactOutputValidation {
103
+ <#
104
+ .SYNOPSIS
105
+ Reads the SubagentStop envelope, validates the assistant text, and applies the re-entry policy.
106
+ #>
93
107
  [CmdletBinding()]
94
108
  [OutputType([hashtable])]
95
109
  param(
96
- [string] $RawPayload,
110
+ [AllowNull()] [AllowEmptyString()] [string] $InputText,
97
111
  [string] $AgentName,
98
112
  [string[]] $RequiredArtifact
99
113
  )
100
114
 
101
- if ([string]::IsNullOrWhiteSpace($RawPayload)) {
102
- return @{ Ok = $false; Message = "$AgentName hook: CLAUDE_HOOK_INPUT is empty; cannot validate required artifact output." }
115
+ $envelope = if ($PSBoundParameters.ContainsKey('InputText')) { Read-SubagentStopEnvelope -InputText $InputText } else { Read-SubagentStopEnvelope }
116
+ if (-not $envelope.IsValid) { $decision = @{ Ok = $false; Repairable = $false; Message = ('{0} payload anomaly - {1}' -f ("$AgentName hook:"), (Get-SubagentStopEnvelopeAnomalyReason -Anomaly $envelope.Anomaly)) } }
117
+ else {
118
+ $agentOutput = $envelope.AssistantText
119
+ $decision = Get-RequiredArtifactOutputBlockDecision -AgentOutput ([string]$agentOutput) -AgentName $AgentName -RequiredArtifact $RequiredArtifact
103
120
  }
104
-
105
- try {
106
- $payload = $RawPayload | ConvertFrom-Json -ErrorAction Stop
107
- }
108
- catch {
109
- return @{ Ok = $false; Message = "$AgentName hook: failed to parse CLAUDE_HOOK_INPUT as JSON: $($_.Exception.Message)" }
121
+ if ($decision.Ok) { return @{ Ok = $true; Message = $null } }
122
+ if (-not $decision.Repairable) {
123
+ if ($envelope.StopHookActive) { return @{ Ok = $true; Message = $null } }
124
+ if (-not $decision.Message.Contains('to the operator and halt.')) { $decision.Message = $decision.Message + "`n" + $script:SubagentStopReportAndHaltLine }
110
125
  }
126
+ return @{ Ok = $false; Message = $decision.Message }
127
+ }
128
+
129
+ function Get-RequiredArtifactOutputBlockDecision {
130
+ <#
131
+ .SYNOPSIS
132
+ Applies the required-artifact checks to the assistant text and returns the block decision.
133
+ .DESCRIPTION
134
+ Returns @{ Ok = $true; Message = $null } to allow, or a hashtable with Ok = $false, a
135
+ Repairable boolean, and Message. A malformed RequiredArtifact spec is a registration
136
+ defect the subagent cannot clear, so it is not repairable; the other blocks are.
137
+ #>
138
+ [CmdletBinding()]
139
+ [OutputType([hashtable])]
140
+ param(
141
+ [Parameter(Mandatory = $true)]
142
+ [AllowEmptyString()]
143
+ [string] $AgentOutput,
144
+ [string] $AgentName,
145
+ [string[]] $RequiredArtifact
146
+ )
111
147
 
112
- $agentOutput = $null
113
- if ($payload.PSObject.Properties.Name -contains 'output') {
114
- $agentOutput = $payload.output
115
- }
116
148
  if ([string]::IsNullOrWhiteSpace($agentOutput)) {
117
- return @{ Ok = $false; Message = "$AgentName hook: agent output is empty; artifact paths must be reported before termination." }
149
+ return @{ Ok = $false; Repairable = $true; Message = "$AgentName hook: agent output is empty (last_assistant_message carries no text); artifact paths must be reported before termination." }
118
150
  }
119
151
 
120
152
  $errors = [System.Collections.Generic.List[string]]::new()
@@ -123,7 +155,7 @@ function Invoke-RequiredArtifactOutputValidation {
123
155
  $spec = ConvertTo-ArtifactSpec -Spec $specText
124
156
  }
125
157
  catch {
126
- return @{ Ok = $false; Message = "$AgentName hook: $($_.Exception.Message)" }
158
+ return @{ Ok = $false; Repairable = $false; Message = "$AgentName hook: $($_.Exception.Message)" }
127
159
  }
128
160
 
129
161
  $path = Get-ArtifactPathFromOutput -AgentOutput $agentOutput -Token $spec.Token
@@ -144,7 +176,7 @@ function Invoke-RequiredArtifactOutputValidation {
144
176
 
145
177
  if ($errors.Count -gt 0) {
146
178
  $message = "$AgentName hook: required artifact validation failed:`n - " + ($errors -join "`n - ")
147
- return @{ Ok = $false; Message = $message }
179
+ return @{ Ok = $false; Repairable = $true; Message = $message }
148
180
  }
149
181
 
150
182
  return @{ Ok = $true; Message = $null }
@@ -154,18 +186,16 @@ if ($MyInvocation.InvocationName -eq '.') {
154
186
  return
155
187
  }
156
188
 
189
+ if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('validate-required-artifact-output: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
190
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'validate-required-artifact-output:'
191
+ if ($null -ne $dependencyDecision) { [Console]::Error.WriteLine($dependencyDecision.Reason); exit $dependencyDecision.ExitCode }
157
192
  if ([string]::IsNullOrWhiteSpace($AgentName) -or $null -eq $RequiredArtifact -or $RequiredArtifact.Count -eq 0) {
158
- Write-Error 'validate-required-artifact-output hook: AgentName and at least one RequiredArtifact spec are required.'
159
- exit 1
160
- }
161
-
162
- $result = Invoke-RequiredArtifactOutputValidation `
163
- -RawPayload $env:CLAUDE_HOOK_INPUT `
164
- -AgentName $AgentName `
165
- -RequiredArtifact $RequiredArtifact
166
- if (-not $result.Ok) {
167
- Write-Error $result.Message
168
- exit 1
193
+ $argumentEnvelope = Read-SubagentStopEnvelope
194
+ if ($argumentEnvelope.StopHookActive) { exit 0 }
195
+ [Console]::Error.WriteLine("validate-required-artifact-output hook: AgentName and at least one RequiredArtifact spec are required.`n$($script:SubagentStopReportAndHaltLine)")
196
+ exit 2
169
197
  }
170
-
198
+ try { $result = Invoke-RequiredArtifactOutputValidation -AgentName $AgentName -RequiredArtifact $RequiredArtifact }
199
+ catch { Exit-SubagentStopBlock -Message ('validate-required-artifact-output: validator error: ' + ([string]$_.Exception.Message -split "`r?`n")[0] + '; the gate fails closed.' + "`n" + $script:SubagentStopReportAndHaltLine) }
200
+ if (-not $result.Ok) { Exit-SubagentStopBlock -Message $result.Message }
171
201
  exit 0
@@ -11,14 +11,22 @@
11
11
  convention, and the file exists on disk.
12
12
 
13
13
  .NOTES
14
- Reads the hook payload from CLAUDE_HOOK_INPUT as JSON. Exits 0 to allow
15
- termination; exits 1 with an error message to block. Filesystem reads go
16
- through Test-ResearchFile so tests can mock the boundary without writing
17
- temporary files.
14
+ Reads the SubagentStop envelope JSON from stdin through the shared
15
+ envelope helper (subagentstop-envelope.ps1) and validates its
16
+ last_assistant_message field, the researcher's last assistant message.
17
+ Exits 0 to allow termination. A block writes the reason to stderr and
18
+ exits 2 through the shared envelope helper; an invalid envelope blocks
19
+ with a payload anomaly reason. A hook dependency that fails to load
20
+ blocks with exit 2 and the validate-task-researcher-output: prefix.
21
+ Filesystem reads go through Test-ResearchFile so tests can mock the
22
+ boundary without writing temporary files.
23
+ An unhandled error during validation blocks with exit 2 and a reason (issue #928).
18
24
  #>
19
25
 
20
26
  [CmdletBinding()]
21
27
  param()
28
+ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
29
+ try { . (Join-Path $PSScriptRoot 'subagentstop-envelope.ps1') } catch { Add-HookDependencyFailure -Name 'subagentstop-envelope.ps1' -ErrorRecord $_ }
22
30
 
23
31
  Set-StrictMode -Version Latest
24
32
  $ErrorActionPreference = 'Stop'
@@ -94,7 +102,7 @@ function Test-IsValidResearchFileName {
94
102
  $fileName = [System.IO.Path]::GetFileName($normalized)
95
103
  return [regex]::IsMatch(
96
104
  $fileName,
97
- '^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-[A-Za-z0-9][A-Za-z0-9-]*-research\.md$'
105
+ '^(?:(?:\d{4}-\d{2}-\d{2}(?:T\d{2}-?\d{2})?|\d{8})-[A-Za-z0-9][A-Za-z0-9-]*[-.]research|research(?:-addendum)?\.\d{4}-\d{2}-\d{2}(?:T\d{2}-\d{2})?)\.md$'
98
106
  )
99
107
  }
100
108
 
@@ -192,7 +200,7 @@ function Test-NumericDerivationEvidence {
192
200
  if (-not $match.Success) { return @{ Ok = $false; Message = "task-researcher hook: numeric derivation evidence is missing $label." } }
193
201
  $values[$label] = $match.Groups['value'].Value.Trim()
194
202
  }
195
- if ($values['Exhaustive Search Scope'] -notmatch '(?i)\b(entire|all|complete)\b.*\b(repository|repo|source tree|tree)\b') {
203
+ if ($values['Exhaustive Search Scope'] -notmatch '(?i)\b(entire|whole|all|complete)\b.*\b(repository|repo|worktree|source tree|tree)\b') {
196
204
  return @{ Ok = $false; Message = 'task-researcher hook: numeric derivation evidence does not declare an exhaustive repository search scope.' }
197
205
  }
198
206
  $primaryStrategy = $values['Primary Search Strategy or Query Expression']
@@ -227,29 +235,18 @@ function Invoke-TaskResearcherOutputValidation {
227
235
  [CmdletBinding()]
228
236
  [OutputType([hashtable])]
229
237
  param(
230
- [string] $RawPayload
238
+ [AllowNull()] [AllowEmptyString()] [string] $InputText
231
239
  )
232
-
233
- if ([string]::IsNullOrWhiteSpace($RawPayload)) {
234
- return @{ Ok = $false; Message = 'task-researcher hook: CLAUDE_HOOK_INPUT is empty; cannot validate task-researcher output.' }
235
- }
236
-
237
- try {
238
- $payload = $RawPayload | ConvertFrom-Json -ErrorAction Stop
239
- }
240
- catch {
241
- return @{ Ok = $false; Message = "task-researcher hook: failed to parse CLAUDE_HOOK_INPUT as JSON: $($_.Exception.Message)" }
242
- }
243
-
244
- $agentOutput = $null
245
- if ($payload.PSObject.Properties.Name -contains 'output') {
246
- $agentOutput = $payload.output
247
- }
248
- if ([string]::IsNullOrWhiteSpace($agentOutput)) {
240
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'validate-task-researcher-output:'
241
+ if ($null -ne $dependencyDecision) { return $dependencyDecision }
242
+ $envelope = if ($PSBoundParameters.ContainsKey('InputText')) { Read-SubagentStopEnvelope -InputText $InputText } else { Read-SubagentStopEnvelope }
243
+ if (-not $envelope.IsValid) { return @{ Ok = $false; Message = "task-researcher hook: payload anomaly - $(Get-SubagentStopEnvelopeAnomalyReason -Anomaly $envelope.Anomaly)" } }
244
+ $assistantMessage = [string]$envelope.AssistantText
245
+ if ([string]::IsNullOrWhiteSpace($assistantMessage)) {
249
246
  return @{ Ok = $false; Message = 'task-researcher hook: agent output is empty; researcher must return research-path before termination.' }
250
247
  }
251
248
 
252
- $researchPath = Get-ResearchPathFromOutput -AgentOutput $agentOutput
249
+ $researchPath = Get-ResearchPathFromOutput -AgentOutput $assistantMessage
253
250
  if ($null -eq $researchPath) {
254
251
  return @{ Ok = $false; Message = 'task-researcher hook: agent output does not advertise a research-path. Researcher must report `research-path: <path>` pointing to docs/features/<feature>/research/ (feature-associated) or docs/research/ (one-off).' }
255
252
  }
@@ -266,13 +263,14 @@ function Invoke-TaskResearcherOutputValidation {
266
263
  return @{ Ok = $false; Message = "task-researcher hook: researcher advertised research-path '$researchPath' but no file exists at that location." }
267
264
  }
268
265
 
269
- $feasibilityResult = Test-AutomationFeasibilitySection -ResearchFilePath $researchPath -AgentOutput $agentOutput
266
+ $feasibilityResult = Test-AutomationFeasibilitySection -ResearchFilePath $researchPath -AgentOutput $assistantMessage
270
267
  if (-not $feasibilityResult.Ok) {
271
268
  return @{ Ok = $false; Message = $feasibilityResult.Message }
272
269
  }
273
270
 
274
271
  if (Test-Path -LiteralPath $researchPath -PathType Leaf) {
275
- $numericEvidenceResult = Test-NumericDerivationEvidence -Content (Get-Content -LiteralPath $researchPath -Raw -ErrorAction Stop)
272
+ try { $researchContent = Get-Content -LiteralPath $researchPath -Raw -ErrorAction Stop } catch { return @{ Ok = $false; Message = "task-researcher hook: failed to read research-path '$researchPath': $($_.Exception.Message)" } }
273
+ $numericEvidenceResult = Test-NumericDerivationEvidence -Content $researchContent
276
274
  if (-not $numericEvidenceResult.Ok) {
277
275
  return $numericEvidenceResult
278
276
  }
@@ -284,11 +282,10 @@ function Invoke-TaskResearcherOutputValidation {
284
282
  if ($MyInvocation.InvocationName -eq '.') {
285
283
  return
286
284
  }
287
-
288
- $result = Invoke-TaskResearcherOutputValidation -RawPayload $env:CLAUDE_HOOK_INPUT
289
- if (-not $result.Ok) {
290
- Write-Error $result.Message
291
- exit 1
292
- }
293
-
285
+ if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('validate-task-researcher-output: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
286
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'validate-task-researcher-output:'
287
+ if ($null -ne $dependencyDecision) { [Console]::Error.WriteLine($dependencyDecision.Reason); exit $dependencyDecision.ExitCode }
288
+ try { $result = Invoke-TaskResearcherOutputValidation }
289
+ catch { Exit-SubagentStopBlock -Message ('validate-task-researcher-output: validator error: ' + ([string]$_.Exception.Message -split "`r?`n")[0] + '; the gate fails closed.' + "`n" + 'This condition is not repairable by the subagent. Report it to the operator and halt.') }
290
+ if (-not $result.Ok) { Exit-SubagentStopBlock -Message $result.Message }
294
291
  exit 0