@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
@@ -6,53 +6,36 @@
6
6
  Dot-sourced by enforce-epic-merge-gate.ps1 immediately after the authorization sibling.
7
7
  Holds:
8
8
 
9
- - The import guard for WorktreeRunResolution.psm1. A failed import is recorded in
10
- $script:EpicMergeGateResolutionImportFailure, and
11
- Get-EpicMergeGateImportFailureDecision turns it into a deny, so a missing module
9
+ - The import guards for WorktreeRunResolution.psm1 and, after it,
10
+ WorktreeItemResolution.psm1 (issue #850). A failed import is recorded in
11
+ $script:HookDependencyFailures, and the gate's call to
12
+ Get-HookDependencyFailureDecision turns it into a deny, so a missing module
12
13
  cannot make the gate exit non-zero and fail open.
13
14
  - The three checkpoint read seams, relocated from the gate file with their names kept.
14
15
  Each takes a mandatory absolute path composed beneath a resolved worktree root.
15
- - The session worktree root, used for the child branch and for a bare command.
16
+ - The session worktree root, used for a bare command.
16
17
  - The resolution seam Resolve-EpicMergeGateRunTarget, which locates the epic or
17
18
  parallel checkpoint that records the command's pull request number.
19
+ - The item seam Resolve-EpicMergeGateItemTarget (issue #850), which locates the live
20
+ worktree whose per-feature checkpoint records the command's pull request number in
21
+ pr_gate.pr_number or a standalone_merge_authorizations entry.
18
22
  - Test-ChildCheckpointPrGateBinding, which binds the command's pull request number to
19
- the per-feature checkpoint's pr_gate.pr_number when that field is recorded.
20
- - Get-EpicMergeGateUnresolvedReason, the reason prefix used when neither run branch
21
- resolves.
23
+ the per-feature checkpoint: pr_gate.pr_number must equal it when recorded, and
24
+ otherwise a positive-integer standalone record must name it (issue #788).
25
+ - Get-EpicMergeGateUnresolvedReason, the reason prefix used when neither the item
26
+ target nor either run branch resolves.
27
+ - Test-EpicMergeGateNoRecordingTarget, the precondition of the docs-only merge branch:
28
+ the item, epic, and parallel targets all resolve NoTarget (issue #927).
22
29
 
23
30
  .NOTES
24
- PowerShell 7+. Depends on Get-EpicMergeGateBlockDecision from the authorization
25
- sibling, dot-sourced by the gate before this file. Mirrored byte-identically under
31
+ PowerShell 7+. Depends on Get-EpicMergeGateBlockDecision and
32
+ Test-StandalonePositiveJsonInteger from the authorization sibling, dot-sourced by the
33
+ gate before this file. Mirrored byte-identically under
26
34
  extensions/drm-copilot/resources/claude-customizations/.
27
35
  #>
28
36
 
29
- # Import guard (issue #690): a failed import denies instead of failing open.
30
- $script:EpicMergeGateResolutionImportFailure = $null
31
- try {
32
- Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop
33
- }
34
- catch {
35
- $script:EpicMergeGateResolutionImportFailure = 'WorktreeRunResolution.psm1'
36
- }
37
-
38
- function Get-EpicMergeGateImportFailureDecision {
39
- <#
40
- .SYNOPSIS
41
- Returns the deny for a failed worktree-resolution import, or $null.
42
- .OUTPUTS
43
- System.Collections.Specialized.OrderedDictionary, or $null when the import succeeded.
44
- #>
45
- [CmdletBinding()]
46
- [OutputType([System.Collections.Specialized.OrderedDictionary])]
47
- param()
48
-
49
- if (-not $script:EpicMergeGateResolutionImportFailure) {
50
- return $null
51
- }
52
- return Get-EpicMergeGateBlockDecision -Reason (
53
- "EPIC_MERGE_GATE_BLOCKED: the worktree-resolution module '$($script:EpicMergeGateResolutionImportFailure)' " +
54
- 'failed to import, so the run checkpoint that governs this merge cannot be located; the gate fails closed.')
55
- }
37
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeRunResolution.psm1' -ErrorRecord $_ }
38
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeItemResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeItemResolution.psm1' -ErrorRecord $_ }
56
39
 
57
40
  function Get-ChildOrchestratorCheckpointContent {
58
41
  <#
@@ -60,7 +43,7 @@ function Get-ChildOrchestratorCheckpointContent {
60
43
  Read the raw JSON text of the per-feature orchestrator checkpoint. Tests mock
61
44
  this function (read seam).
62
45
  .PARAMETER Path
63
- The absolute checkpoint path composed beneath the session worktree root.
46
+ The absolute checkpoint path composed beneath the item or session worktree root.
64
47
  .OUTPUTS
65
48
  System.String or $null
66
49
  #>
@@ -117,7 +100,7 @@ function Get-ParallelOrchestratorCheckpointContent {
117
100
  function Get-EpicMergeGateSessionWorktreeRoot {
118
101
  <#
119
102
  .SYNOPSIS
120
- Return the worktree root the session runs in (the child branch and bare-command root).
103
+ Return the worktree root the session runs in (the bare-command root).
121
104
  .OUTPUTS
122
105
  System.String
123
106
  #>
@@ -149,14 +132,35 @@ function Resolve-EpicMergeGateRunTarget {
149
132
  return Resolve-WorktreeRunTargetByRecord -Kind $Kind -RecordField pr_number -Value ([string]$PrNumber) -SessionRoot (Get-Location).Path
150
133
  }
151
134
 
135
+ function Resolve-EpicMergeGateItemTarget {
136
+ <#
137
+ .SYNOPSIS
138
+ Resolve the worktree whose per-feature checkpoint records a pull request number (issue #850 seam).
139
+ .PARAMETER PrNumber
140
+ The explicit pull request number the command names.
141
+ .OUTPUTS
142
+ System.Management.Automation.PSCustomObject (the worktree-resolution target result).
143
+ #>
144
+ [CmdletBinding()]
145
+ [OutputType([pscustomobject])]
146
+ param(
147
+ [Parameter(Mandatory)][int] $PrNumber
148
+ )
149
+
150
+ return Resolve-WorktreeItemTargetByPrNumber -PrNumber $PrNumber -SessionRoot (Get-Location).Path
151
+ }
152
+
152
153
  function Test-ChildCheckpointPrGateBinding {
153
154
  <#
154
155
  .SYNOPSIS
155
- True unless the per-feature checkpoint records a pr_gate.pr_number that differs from the command's.
156
+ True when the per-feature checkpoint binds the command's pull request number (issue #788).
156
157
  .DESCRIPTION
157
- A bare command, an absent checkpoint, and a checkpoint without pr_gate.pr_number
158
- keep today's unbound child-branch decision. When both numbers are present they must
159
- parse as integers and be equal.
158
+ Pure. A bare command is unbound and returns $true. An explicit number requires a
159
+ checkpoint: when it records pr_gate.pr_number, that value must parse as an integer
160
+ equal to the number; otherwise a standalone_merge_authorizations entry whose
161
+ pr_number is a positive JSON integer equal to the number must exist. Every other
162
+ case returns $false, so a checkpoint that names neither field cannot authorize an
163
+ explicit-number merge through the child branch.
160
164
  .PARAMETER Checkpoint
161
165
  Parsed per-feature checkpoint, or $null.
162
166
  .PARAMETER CommandPrNumber
@@ -171,30 +175,45 @@ function Test-ChildCheckpointPrGateBinding {
171
175
  [AllowNull()][Nullable[int]] $CommandPrNumber
172
176
  )
173
177
 
174
- if ($null -eq $CommandPrNumber -or $null -eq $Checkpoint) {
178
+ if ($null -eq $CommandPrNumber) {
175
179
  return $true
176
180
  }
177
- if (@($Checkpoint.PSObject.Properties.Name) -notcontains 'pr_gate' -or $null -eq $Checkpoint.pr_gate) {
178
- return $true
181
+ if ($null -eq $Checkpoint) {
182
+ return $false
179
183
  }
180
- $prGate = $Checkpoint.pr_gate
181
- if (@($prGate.PSObject.Properties.Name) -notcontains 'pr_number' -or $null -eq $prGate.pr_number) {
182
- return $true
184
+ $props = @($Checkpoint.PSObject.Properties.Name)
185
+ if ($props -contains 'pr_gate' -and $null -ne $Checkpoint.pr_gate -and
186
+ @($Checkpoint.pr_gate.PSObject.Properties.Name) -contains 'pr_number' -and $null -ne $Checkpoint.pr_gate.pr_number) {
187
+ $recorded = 0
188
+ if (-not [int]::TryParse([string]$Checkpoint.pr_gate.pr_number, [ref] $recorded)) {
189
+ return $false
190
+ }
191
+ return ($recorded -eq $CommandPrNumber)
183
192
  }
184
- $recorded = 0
185
- if (-not [int]::TryParse([string]$prGate.pr_number, [ref] $recorded)) {
193
+ if ($props -notcontains 'standalone_merge_authorizations') {
186
194
  return $false
187
195
  }
188
- return ($recorded -eq $CommandPrNumber)
196
+ foreach ($entry in @($Checkpoint.standalone_merge_authorizations)) {
197
+ if ($null -eq $entry -or @($entry.PSObject.Properties.Name) -notcontains 'pr_number') {
198
+ continue
199
+ }
200
+ if ((Test-StandalonePositiveJsonInteger -Value $entry.pr_number) -and [long]$entry.pr_number -eq $CommandPrNumber) {
201
+ return $true
202
+ }
203
+ }
204
+ return $false
189
205
  }
190
206
 
191
207
  function Get-EpicMergeGateUnresolvedReason {
192
208
  <#
193
209
  .SYNOPSIS
194
- Returns '<code>: <epic detail>; <parallel detail>' when neither run branch resolved, or $null.
210
+ Returns '<code>: <item detail>; <epic detail>; <parallel detail>' when no target resolved, or $null.
195
211
  .DESCRIPTION
196
- The ambiguity code wins when either target is Ambiguous. Returns $null when either
197
- target is absent (a bare command) or either target resolved.
212
+ The ambiguity code of the first Ambiguous target among item, epic, and parallel
213
+ wins; otherwise the item target's code is used. Returns $null when any target is
214
+ absent (a bare command) or any target resolved.
215
+ .PARAMETER ItemTarget
216
+ The item target, or $null.
198
217
  .PARAMETER EpicTarget
199
218
  The epic run target, or $null.
200
219
  .PARAMETER ParallelTarget
@@ -205,17 +224,55 @@ function Get-EpicMergeGateUnresolvedReason {
205
224
  [CmdletBinding()]
206
225
  [OutputType([string])]
207
226
  param(
227
+ [AllowNull()] $ItemTarget,
208
228
  [AllowNull()] $EpicTarget,
209
229
  [AllowNull()] $ParallelTarget
210
230
  )
211
231
 
212
- if ($null -eq $EpicTarget -or $null -eq $ParallelTarget) {
232
+ if ($null -eq $ItemTarget -or $null -eq $EpicTarget -or $null -eq $ParallelTarget) {
213
233
  return $null
214
234
  }
235
+ $targets = @($ItemTarget, $EpicTarget, $ParallelTarget)
215
236
  $unresolved = @('NoTarget', 'Ambiguous')
216
- if ($unresolved -notcontains $EpicTarget.Status -or $unresolved -notcontains $ParallelTarget.Status) {
217
- return $null
237
+ foreach ($target in $targets) {
238
+ if ($unresolved -notcontains $target.Status) {
239
+ return $null
240
+ }
241
+ }
242
+ $ambiguous = @($targets | Where-Object { $_.Status -eq 'Ambiguous' })
243
+ $code = if ($ambiguous.Count -gt 0) { $ambiguous[0].ReasonCode } else { $ItemTarget.ReasonCode }
244
+ return ('{0}: {1}; {2}; {3}' -f $code, $ItemTarget.Detail, $EpicTarget.Detail, $ParallelTarget.Detail)
245
+ }
246
+
247
+ function Test-EpicMergeGateNoRecordingTarget {
248
+ <#
249
+ .SYNOPSIS
250
+ True only when the item, epic, and parallel targets all have status NoTarget (issue #927).
251
+ .DESCRIPTION
252
+ Pure. That combination means no checkpoint records the command's pull request number,
253
+ which is the only case the docs-only merge branch evaluates. A $null target (a bare
254
+ command), an Ambiguous target, and any resolved target return $false.
255
+ .PARAMETER ItemTarget
256
+ The item target, or $null.
257
+ .PARAMETER EpicTarget
258
+ The epic run target, or $null.
259
+ .PARAMETER ParallelTarget
260
+ The parallel run target, or $null.
261
+ .OUTPUTS
262
+ System.Boolean
263
+ #>
264
+ [CmdletBinding()]
265
+ [OutputType([bool])]
266
+ param(
267
+ [AllowNull()] $ItemTarget,
268
+ [AllowNull()] $EpicTarget,
269
+ [AllowNull()] $ParallelTarget
270
+ )
271
+
272
+ foreach ($target in @($ItemTarget, $EpicTarget, $ParallelTarget)) {
273
+ if ($null -eq $target -or [string]$target.Status -cne 'NoTarget') {
274
+ return $false
275
+ }
218
276
  }
219
- $code = if ($EpicTarget.Status -eq 'Ambiguous') { $EpicTarget.ReasonCode } elseif ($ParallelTarget.Status -eq 'Ambiguous') { $ParallelTarget.ReasonCode } else { $EpicTarget.ReasonCode }
220
- return ('{0}: {1}; {2}' -f $code, $EpicTarget.Detail, $ParallelTarget.Detail)
277
+ return $true
221
278
  }
@@ -33,15 +33,25 @@
33
33
  PR number never satisfies condition 4, and a blanket flag is rejected rather than read.
34
34
  Trigger scope: --squash is out of scope for this gate and is allowed on both runtimes.
35
35
 
36
- Design decision: this gate trusts the on-disk checkpoint rather than shelling out live
37
- to gh pr view for a real-time head-SHA check, matching the same non-adversarial,
38
- policy-level-not-cryptographic posture already accepted for
39
- enforce-pr-author-skill.ps1's own receipt mechanism. It is not a cryptographic control.
36
+ Design decision: branches 1-4 trust the on-disk checkpoint and never call gh. Issue #927
37
+ adds a docs-only branch after branch 4 fails, only for an explicit PR number whose item,
38
+ epic, and parallel targets are all NoTarget (no checkpoint records the PR): it calls
39
+ Get-DocsOnlyMergeExemption (hook-docs-only-diff.ps1), which runs gh pr view and git in the
40
+ session worktree and allows the merge when conditions M1-M9 of that file hold. Otherwise
41
+ the deny gains " (docs-only exemption not applied: <Reason>)", where the reason may be
42
+ DOCS_ONLY_EXEMPTION_PR_UNRESOLVED. The hook never fetches; --match-head-commit, when
43
+ present, must equal headRefOid (OD-2). Residual limits: an ambient GH_REPO or GH_HOST is
44
+ not visible in the command text; gh and git run in the session worktree root, so a cd
45
+ made by an earlier Bash call is not visible either; and a push after the lookup can
46
+ change the head unless --match-head-commit is passed. Like the receipt mechanism of
47
+ enforce-pr-author-skill.ps1, this is a policy-level control, not a cryptographic one.
40
48
 
41
49
  Issue #690: with an explicit PR number, the epic and parallel checkpoints are read
42
- beneath the live worktree whose run checkpoint records that number, and the child
43
- branch binds the number to pr_gate.pr_number when the per-feature checkpoint records
44
- it. A bare command reads every checkpoint beneath the session worktree.
50
+ beneath the live worktree whose run checkpoint records that number. Issue #850: with
51
+ an explicit PR number, the per-feature checkpoint is read beneath the live worktree
52
+ whose checkpoint records the number in pr_gate.pr_number or a standalone record, and
53
+ the child branch requires that checkpoint to bind the number (issue #788). A bare
54
+ command reads every checkpoint beneath the session worktree.
45
55
 
46
56
  .NOTES
47
57
  Compatible with PowerShell 7+. Depends on WorktreeRunResolution.psm1 through the
@@ -57,16 +67,19 @@
57
67
  #>
58
68
  [CmdletBinding()]
59
69
  param()
70
+ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
60
71
 
61
- Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
72
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
62
73
  # Shared command-line parser (issue #545), consumed by the scope filter in
63
74
  # Invoke-EpicMergeGateDecision and by Get-EpicMergeGateCommandPrNumber.
64
- . (Join-Path $PSScriptRoot 'hook-command-scanner.ps1')
65
- . (Join-Path $PSScriptRoot 'hook-command-invocation.ps1')
75
+ try { . (Join-Path $PSScriptRoot 'hook-command-scanner.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-scanner.ps1' -ErrorRecord $_ }
76
+ try { . (Join-Path $PSScriptRoot 'hook-command-invocation.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-invocation.ps1' -ErrorRecord $_ }
66
77
  # Standalone-merge authorization predicates and the two decision-envelope factories (issue #670).
67
- . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-authorization.ps1')
78
+ try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-authorization.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-authorization.ps1' -ErrorRecord $_ }
68
79
  # Checkpoint read seams, the import guard, and run-target resolution (issue #690).
69
- . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-resolution.ps1')
80
+ try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-resolution.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-resolution.ps1' -ErrorRecord $_ }
81
+ # Shared docs-only diff classification and the merge-side docs-only exemption (issue #927).
82
+ try { . (Join-Path $PSScriptRoot 'hook-docs-only-diff.ps1') } catch { Add-HookDependencyFailure -Name 'hook-docs-only-diff.ps1' -ErrorRecord $_ }
70
83
 
71
84
  function ConvertFrom-EpicMergeGateJson {
72
85
  <#
@@ -312,10 +325,8 @@ function Invoke-EpicMergeGateDecision {
312
325
  )
313
326
 
314
327
  # A failed worktree-resolution import denies before any other logic (issue #690).
315
- $importFailure = Get-EpicMergeGateImportFailureDecision
316
- if ($null -ne $importFailure) {
317
- return $importFailure
318
- }
328
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'EPIC_MERGE_GATE_BLOCKED:'
329
+ if ($null -ne $dependencyDecision) { return $dependencyDecision }
319
330
 
320
331
  $payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
321
332
  if (-not $payload.IsValid) {
@@ -357,9 +368,17 @@ function Invoke-EpicMergeGateDecision {
357
368
  $commandPrNumber = Get-EpicMergeGateCommandPrNumber -CommandText $commandText
358
369
  $sessionRoot = Get-EpicMergeGateSessionWorktreeRoot
359
370
 
360
- # The child merges from its own worktree, so its checkpoint is the session worktree's;
361
- # a recorded pr_gate.pr_number must match an explicit command PR number (issue #690).
362
- $childCheckpoint = ConvertFrom-EpicMergeGateJson -Raw (Get-ChildOrchestratorCheckpointContent -Path (Get-WorktreeRunCheckpointPath -Kind item -WorktreeRoot $sessionRoot))
371
+ # With an explicit PR number, the per-feature checkpoint is read beneath the live
372
+ # worktree whose checkpoint records the number in pr_gate.pr_number or a standalone
373
+ # record (issue #850); an unresolved item target reads none. A bare command reads the
374
+ # session worktree. The checkpoint read must still bind the number (issue #788).
375
+ $itemTarget = $null
376
+ $childRoot = $sessionRoot
377
+ if ($null -ne $commandPrNumber) {
378
+ $itemTarget = Resolve-EpicMergeGateItemTarget -PrNumber $commandPrNumber
379
+ $childRoot = if (@('SessionRoot', 'OtherWorktree') -contains $itemTarget.Status) { $itemTarget.WorktreeRoot } else { $null }
380
+ }
381
+ $childCheckpoint = if ($childRoot) { ConvertFrom-EpicMergeGateJson -Raw (Get-ChildOrchestratorCheckpointContent -Path (Get-WorktreeRunCheckpointPath -Kind item -WorktreeRoot $childRoot)) } else { $null }
363
382
  if ((Test-ChildCheckpointAllowsEpicMerge -Checkpoint $childCheckpoint) -and
364
383
  (Test-ChildCheckpointPrGateBinding -Checkpoint $childCheckpoint -CommandPrNumber $commandPrNumber)) {
365
384
  return Get-EpicMergeGateAllowDecision
@@ -396,10 +415,20 @@ function Invoke-EpicMergeGateDecision {
396
415
  if ($standalone.Allowed) {
397
416
  return Get-EpicMergeGateAllowDecision
398
417
  }
399
- # When neither run branch resolved, its reason code and detail precede the standalone reason.
400
- $unresolved = Get-EpicMergeGateUnresolvedReason -EpicTarget $epicTarget -ParallelTarget $parallelTarget
418
+ # Docs-only branch (issue #927): evaluated only when no checkpoint records the PR.
419
+ $docsOnlySuffix = ''
420
+ if (Test-EpicMergeGateNoRecordingTarget -ItemTarget $itemTarget -EpicTarget $epicTarget -ParallelTarget $parallelTarget) {
421
+ $docsOnly = Get-DocsOnlyMergeExemption -CommandText $commandText -PrNumber $commandPrNumber -WorktreeRoot $sessionRoot
422
+ if ($docsOnly.Exempt) {
423
+ return Get-EpicMergeGateAllowDecision
424
+ }
425
+ $docsOnlySuffix = ' (docs-only exemption not applied: ' + $docsOnly.Reason + ')'
426
+ }
427
+ # When neither the item target nor a run branch resolved, the reason code and the
428
+ # three details precede the standalone reason (issue #850).
429
+ $unresolved = Get-EpicMergeGateUnresolvedReason -ItemTarget $itemTarget -EpicTarget $epicTarget -ParallelTarget $parallelTarget
401
430
  $prefix = if ($unresolved) { $unresolved + '; ' } else { '' }
402
- return Get-EpicMergeGateBlockDecision -Reason ('EPIC_MERGE_GATE_BLOCKED: ' + $prefix + $standalone.ReasonCode + ': ' + $standalone.Message)
431
+ return Get-EpicMergeGateBlockDecision -Reason ('EPIC_MERGE_GATE_BLOCKED: ' + $prefix + $standalone.ReasonCode + ': ' + $standalone.Message + $docsOnlySuffix)
403
432
  }
404
433
 
405
434
  return Get-EpicMergeGateBlockDecision -Reason 'EPIC_MERGE_GATE_BLOCKED: gh pr merge --merge requires either a per-feature checkpoint with epic_mode == true and step9_status == "passed", an epic checkpoint with epic_merge_pr.ci_gate.conclusion == "success" and a matching pr_number, or a parallel-orchestrator checkpoint with route_id == "parallel" whose target item (matched by pr_number) has merge_status == "ci_green". No checkpoint satisfied this gate.'
@@ -447,6 +476,9 @@ function Invoke-EpicMergeGateEntryPoint {
447
476
  if ($MyInvocation.InvocationName -eq '.') {
448
477
  return
449
478
  }
479
+ if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('EPIC_MERGE_GATE_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
480
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'EPIC_MERGE_GATE_BLOCKED:'
481
+ if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
450
482
 
451
483
  # The entry point returns its [int] exit code as the last pipeline element and the
452
484
  # decision JSON before it. `exit (<call>)` would capture BOTH into the exit
@@ -456,4 +488,4 @@ if ($entryPointResult.Count -gt 1) {
456
488
  $entryPointResult[0..($entryPointResult.Count - 2)] | Write-Output
457
489
  }
458
490
 
459
- exit ([int]$entryPointResult[-1])
491
+ exit ([int]$entryPointResult[-1])
@@ -8,45 +8,59 @@
8
8
  and the serialized prompt contains the epic-mode kickoff marker "Epic mode: true".
9
9
 
10
10
  Resolution and decision procedure:
11
- 1. Resolve the target child feature_folder from the prompt text by scanning for a
12
- docs/features/active/<token> path, mirroring
13
- enforce-prd-feature-before-planner.ps1's Find-PrdFeatureFolderFromPrompt
14
- technique (longest match wins; a .md-suffixed match uses its parent directory).
15
- 2. Resolve the worktree whose epic checkpoint records the prompt's
16
- integration_branch: value (issue #690), deny an unresolved or ambiguous target,
17
- then read artifacts/orchestration/epic-orchestrator-state.json beneath that
18
- worktree and locate the features[] record whose feature_folder equals the
19
- resolved basename.
20
- 3. Look up that feature's depends_on list, and for every dependency, locate its own
21
- features[] record.
22
- 4. Deny with reason EPIC_WAVE_BARRIER_BLOCKED unless every dependency's merge_status
11
+ 1. Resolve the worktree whose epic checkpoint records the prompt's
12
+ integration_branch: value (issue #690) and deny an unresolved or ambiguous target.
13
+ 2. Collect the cited feature-folder candidates with the shared resolver
14
+ (feature-folder-resolution.ps1, issue #565): every docs/features/active/<token>
15
+ path is truncated to its feature-folder segment, so a nested research/ or
16
+ evidence/ citation names its own folder. No candidate denies.
17
+ 3. Read artifacts/orchestration/epic-orchestrator-state.json beneath that worktree
18
+ and select the target among the candidates with Select-FeatureFolderTarget
19
+ -DependencyAware: a cited upstream dependency of another cited candidate is
20
+ pruned, and two or more remaining candidates deny as ambiguous. Neither string
21
+ length nor prompt position takes part in the selection.
22
+ 4. Locate the target's features[] record and, for every depends_on entry, its own
23
+ record. An entry resolves through the union index, so an issue number matches
24
+ issue_num and a folder value matches the normalized feature_folder.
25
+ 5. Deny with reason EPIC_WAVE_BARRIER_BLOCKED unless every dependency's merge_status
23
26
  is merged or worktree_removed. A missing/unreadable checkpoint, an unresolved
24
27
  target feature_folder, or a missing dependency record also denies (fail-closed).
25
28
 
26
- This is the per-call deterrent (Layer 1) of the two-layer wave-barrier design; the
27
- retrospective backstop (Layer 2) is the wave-barrier ordering invariant inside
28
- validate_epic_orchestrator_state_text, enforced separately at epic-orchestrator
29
- SubagentStop time.
29
+ This is the per-call deterrent (Layer 1) of the two-layer wave-barrier design. The
30
+ retrospective backstop (Layer 2) is the wave-barrier ordering invariant of
31
+ validate_epic_orchestrator_state_text, run at epic-orchestrator SubagentStop time by
32
+ validate-orchestrator-output.ps1 through its PowerShell port
33
+ OrchestratorStateEpicWaveBarrier.psm1, with parity pinned by tests/fixtures/epic_wave_barrier/.
34
+ The SubagentStop transport and exit-code defect that hook depended on was resolved by epic #904
35
+ and issue #928: a violation blocks the stop with exit 2 and a stderr reason.
36
+ The two layers share no code.
30
37
 
31
38
  .NOTES
32
- Compatible with PowerShell 7+. Depends on WorktreeRunResolution.psm1 (issue #690),
33
- imported inside a guard: a failed import is recorded and the decision denies naming
34
- the module. Filesystem reads go through an injectable wrapper function so tests can
35
- mock the boundary without writing temporary files.
39
+ Compatible with PowerShell 7+. Depends on WorktreeRunResolution.psm1 (issue #690) and
40
+ the pure sibling feature-folder-resolution.ps1 (issue #565), each loaded inside a
41
+ guard: a failure is recorded and the decision denies naming the dependency. Filesystem
42
+ reads go through an injectable wrapper function so tests can mock the boundary without
43
+ writing temporary files.
36
44
  #>
37
45
  [CmdletBinding()]
38
46
  param()
47
+ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
39
48
 
40
49
 
41
- Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
50
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
42
51
 
43
- # Import guard (issue #690): a failed import denies instead of failing open.
52
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeRunResolution.psm1' -ErrorRecord $_ }
44
53
  $script:EpicWaveBarrierResolutionImportFailure = $null
54
+
55
+ # Shared feature-folder resolution (issue #565). Guarded so a failed dot-source denies
56
+ # rather than failing open; an earlier recorded failure is kept.
45
57
  try {
46
- Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop
58
+ . (Join-Path $PSScriptRoot 'feature-folder-resolution.ps1')
47
59
  }
48
60
  catch {
49
- $script:EpicWaveBarrierResolutionImportFailure = 'WorktreeRunResolution.psm1'
61
+ if (-not $script:EpicWaveBarrierResolutionImportFailure) {
62
+ $script:EpicWaveBarrierResolutionImportFailure = 'feature-folder-resolution.ps1'
63
+ }
50
64
  }
51
65
 
52
66
  $script:AllowedMergeStatuses = @('merged', 'worktree_removed')
@@ -95,56 +109,35 @@ function Resolve-EpicWaveBarrierTarget {
95
109
  function Find-EpicWaveBarrierFeatureFolderFromPrompt {
96
110
  <#
97
111
  .SYNOPSIS
98
- Scans a prompt string for docs/features/active/<...> path tokens and returns the
99
- longest unique match's basename. Returns $null when no match is found.
112
+ Returns the distinct feature-folder basenames cited in a prompt, in first-occurrence
113
+ order. Returns no output when no folder is cited.
100
114
  .DESCRIPTION
101
- Mirrors enforce-prd-feature-before-planner.ps1's
102
- Find-PrdFeatureFolderFromPrompt technique: forward- or backslash-separated path
103
- tokens are accepted, the longest match wins, and a .md-suffixed match resolves to
104
- its parent directory before the basename is extracted.
115
+ Delegates to Find-FeatureFolderCandidate in the shared resolver (issue #565): each
116
+ docs/features/active/<token> path, in either separator style, is truncated to its
117
+ feature-folder segment, so a nested research/ or evidence/ citation names its own
118
+ folder. Selection among several candidates is made by Select-FeatureFolderTarget.
105
119
  .PARAMETER Prompt
106
120
  The delegation prompt text under evaluation.
107
121
  .OUTPUTS
108
- System.String or $null
122
+ System.String[]
109
123
  #>
110
124
  [CmdletBinding()]
111
- [OutputType([string])]
125
+ [OutputType([string[]])]
112
126
  param(
113
127
  [Parameter(Mandatory)]
114
128
  [AllowEmptyString()]
115
129
  [string] $Prompt
116
130
  )
117
131
 
118
- if (-not $Prompt) {
119
- return $null
120
- }
121
-
122
- $pattern = 'docs[\\/]+features[\\/]+active[\\/]+[^\s"''`]+'
123
- $matchList = [regex]::Matches($Prompt, $pattern)
124
- if ($matchList.Count -eq 0) {
125
- return $null
126
- }
127
-
128
- $unique = @{}
129
- foreach ($m in $matchList) {
130
- $normalized = ($m.Value -replace '\\', '/').TrimEnd('/')
131
- $unique[$normalized] = $true
132
- }
133
-
134
- $candidates = @(@($unique.Keys) | Sort-Object -Property Length -Descending)
135
- $best = $candidates[0]
136
-
137
- if ($best -match '\.md$') {
138
- $best = $best -replace '/[^/]+\.md$', ''
139
- }
140
-
141
- return ($best -split '/')[-1]
132
+ return [string[]]@(Find-FeatureFolderCandidate -Text $Prompt)
142
133
  }
143
134
 
144
135
  function Find-EpicWaveBarrierFeatureRecord {
145
136
  <#
146
137
  .SYNOPSIS
147
- Locate the features[] record whose feature_folder equals the target basename.
138
+ Locate the features[] record the target basename identifies, through the shared
139
+ union index (Find-FeatureFolderRecord). A lifecycle-prefixed feature_folder value
140
+ such as active/<folder> matches its basename; zero or several matches return $null.
148
141
  .PARAMETER Checkpoint
149
142
  Parsed epic checkpoint, or $null when absent/unreadable.
150
143
  .PARAMETER FeatureFolder
@@ -169,17 +162,7 @@ function Find-EpicWaveBarrierFeatureRecord {
169
162
  return $null
170
163
  }
171
164
 
172
- # Scan every recorded feature for a feature_folder that equals the target basename.
173
- foreach ($feature in @($Checkpoint.features)) {
174
- $featureProps = @($feature.PSObject.Properties.Name)
175
- if ($featureProps -notcontains 'feature_folder') {
176
- continue
177
- }
178
- if (([string]$feature.feature_folder) -eq $FeatureFolder) {
179
- return $feature
180
- }
181
- }
182
- return $null
165
+ return Find-FeatureFolderRecord -Records @($Checkpoint.features) -Reference $FeatureFolder
183
166
  }
184
167
 
185
168
  function Test-EpicWaveBarrierDependenciesMerged {
@@ -219,9 +202,11 @@ function Test-EpicWaveBarrierDependenciesMerged {
219
202
  }
220
203
 
221
204
  # Every dependency edge must be durably confirmed merged or worktree_removed before
222
- # this wave's feature is allowed to start.
223
- foreach ($dependencyFolder in $dependsOn) {
224
- $dependencyRecord = Find-EpicWaveBarrierFeatureRecord -Checkpoint $Checkpoint -FeatureFolder ([string]$dependencyFolder)
205
+ # this wave's feature is allowed to start. Each raw entry resolves through the union
206
+ # index, so an issue-number edge matches issue_num (issue #565).
207
+ $features = if (@($Checkpoint.PSObject.Properties.Name) -contains 'features') { @($Checkpoint.features) } else { @() }
208
+ foreach ($dependency in $dependsOn) {
209
+ $dependencyRecord = Find-FeatureFolderRecord -Records $features -Reference $dependency
225
210
  if ($null -eq $dependencyRecord) {
226
211
  return $false
227
212
  }
@@ -280,11 +265,13 @@ function Invoke-EpicWaveBarrierDecision {
280
265
  param(
281
266
  [string] $ToolInputRaw
282
267
  )
268
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'EPIC_WAVE_BARRIER_BLOCKED:'
269
+ if ($null -ne $dependencyDecision) { return $dependencyDecision }
283
270
 
284
- # A failed worktree-resolution import denies before any other logic (issue #690).
271
+ # A failed dependency import denies before any other logic (issue #565).
285
272
  if ($script:EpicWaveBarrierResolutionImportFailure) {
286
273
  return Get-EpicWaveBarrierBlockDecision -Reason (
287
- "EPIC_WAVE_BARRIER_BLOCKED: the worktree-resolution module '$($script:EpicWaveBarrierResolutionImportFailure)' " +
274
+ "EPIC_WAVE_BARRIER_BLOCKED: the dependency '$($script:EpicWaveBarrierResolutionImportFailure)' " +
288
275
  'failed to import, so the epic checkpoint that governs this delegation cannot be located; the gate fails closed.')
289
276
  }
290
277
 
@@ -312,8 +299,8 @@ function Invoke-EpicWaveBarrierDecision {
312
299
  return Get-EpicWaveBarrierBlockDecision -Reason "EPIC_WAVE_BARRIER_BLOCKED: $($target.ReasonCode): $($target.Detail)"
313
300
  }
314
301
 
315
- $featureFolder = Find-EpicWaveBarrierFeatureFolderFromPrompt -Prompt $prompt
316
- if (-not $featureFolder) {
302
+ $candidates = @(Find-EpicWaveBarrierFeatureFolderFromPrompt -Prompt $prompt)
303
+ if ($candidates.Count -eq 0) {
317
304
  return Get-EpicWaveBarrierBlockDecision -Reason 'EPIC_WAVE_BARRIER_BLOCKED: an epic-mode orchestrator delegation must reference the target feature folder in the prompt so its dependency edges can be verified.'
318
305
  }
319
306
 
@@ -327,6 +314,15 @@ function Invoke-EpicWaveBarrierDecision {
327
314
  }
328
315
  }
329
316
 
317
+ # The target is selected among the cited candidates (issue #565): a cited upstream
318
+ # dependency of another candidate is pruned, and an unresolved tie denies.
319
+ $records = if ($null -ne $checkpoint -and @($checkpoint.PSObject.Properties.Name) -contains 'features') { @($checkpoint.features) } else { @() }
320
+ $selection = Select-FeatureFolderTarget -Records $records -Candidate $candidates -DependencyAware
321
+ if ($selection.Status -eq 'Ambiguous') {
322
+ return Get-EpicWaveBarrierBlockDecision -Reason "EPIC_WAVE_BARRIER_BLOCKED: $($selection.Detail)."
323
+ }
324
+ $featureFolder = $selection.Basename
325
+
330
326
  $featureRecord = Find-EpicWaveBarrierFeatureRecord -Checkpoint $checkpoint -FeatureFolder $featureFolder
331
327
  if (Test-EpicWaveBarrierDependenciesMerged -Checkpoint $checkpoint -FeatureRecord $featureRecord) {
332
328
  return Get-EpicWaveBarrierAllowDecision
@@ -377,5 +373,8 @@ function Invoke-EpicWaveBarrierEntryPoint {
377
373
  if ($MyInvocation.InvocationName -eq '.') {
378
374
  return
379
375
  }
376
+ if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('EPIC_WAVE_BARRIER_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
377
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'EPIC_WAVE_BARRIER_BLOCKED:'
378
+ if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
380
379
 
381
380
  exit (Invoke-EpicWaveBarrierEntryPoint)