@danmoisan/drm-copilot-mcp 1.1.17 → 1.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/out/mcp-server.js +1405 -684
  2. package/package.json +6 -4
  3. package/resources/claude-customizations/.claude/agents/atomic-executor.md +11 -0
  4. package/resources/claude-customizations/.claude/agents/atomic-planner.md +10 -0
  5. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +12 -4
  6. package/resources/claude-customizations/.claude/agents/epic-planner.md +5 -4
  7. package/resources/claude-customizations/.claude/agents/feature-review.md +16 -5
  8. package/resources/claude-customizations/.claude/agents/orchestrator.md +21 -7
  9. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +37 -13
  10. package/resources/claude-customizations/.claude/agents/parallel-planner.md +9 -6
  11. package/resources/claude-customizations/.claude/agents/pr-author.md +15 -2
  12. package/resources/claude-customizations/.claude/agents/prd-feature.md +1 -1
  13. package/resources/claude-customizations/.claude/hooks/check-powershell-test-purity.ps1 +7 -1
  14. package/resources/claude-customizations/.claude/hooks/check-python-test-purity.ps1 +7 -1
  15. package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
  16. package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +111 -46
  17. package/resources/claude-customizations/.claude/hooks/enforce-completion-helpers.ps1 +105 -0
  18. package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +8 -1
  19. package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +7 -1
  20. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +116 -59
  21. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +55 -23
  22. package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +74 -75
  23. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +127 -35
  24. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +61 -57
  25. package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +7 -1
  26. package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +129 -39
  27. package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +7 -1
  28. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +9 -3
  29. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +39 -51
  30. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
  31. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
  32. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
  33. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +17 -12
  34. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +9 -3
  35. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +65 -50
  36. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +49 -42
  37. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +54 -72
  38. package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +8 -2
  39. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +279 -0
  40. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +129 -75
  41. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.artifact-root.ps1 +233 -0
  42. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.docs-only.ps1 +224 -0
  43. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +69 -24
  44. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +29 -18
  45. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +10 -4
  46. package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +14 -5
  47. package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +8 -2
  48. package/resources/claude-customizations/.claude/hooks/feature-folder-resolution.ps1 +373 -0
  49. package/resources/claude-customizations/.claude/hooks/feature-review-coverage-thresholds.ps1 +85 -0
  50. package/resources/claude-customizations/.claude/hooks/hook-command-heredoc.ps1 +119 -0
  51. package/resources/claude-customizations/.claude/hooks/hook-command-invocation-operands.ps1 +198 -0
  52. package/resources/claude-customizations/.claude/hooks/hook-command-invocation.ps1 +254 -255
  53. package/resources/claude-customizations/.claude/hooks/hook-command-payload-powershell.ps1 +201 -0
  54. package/resources/claude-customizations/.claude/hooks/hook-command-payload.ps1 +484 -0
  55. package/resources/claude-customizations/.claude/hooks/hook-command-scanner.ps1 +27 -110
  56. package/resources/claude-customizations/.claude/hooks/hook-dependency-guard.ps1 +114 -0
  57. package/resources/claude-customizations/.claude/hooks/hook-docs-only-diff.ps1 +493 -0
  58. package/resources/claude-customizations/.claude/hooks/subagentstop-envelope.ps1 +192 -0
  59. package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +9 -3
  60. package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +40 -42
  61. package/resources/claude-customizations/.claude/hooks/validate-executor-output.ps1 +66 -38
  62. package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-metrics.ps1 +180 -0
  63. package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-rules.ps1 +195 -0
  64. package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage.ps1 +141 -395
  65. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-resolution.ps1 +311 -0
  66. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-routing.ps1 +170 -0
  67. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +115 -190
  68. package/resources/claude-customizations/.claude/hooks/validate-planner-output.ps1 +64 -32
  69. package/resources/claude-customizations/.claude/hooks/validate-pr-author-output.ps1 +39 -44
  70. package/resources/claude-customizations/.claude/hooks/validate-prd-feature-output.ps1 +43 -14
  71. package/resources/claude-customizations/.claude/hooks/validate-required-artifact-output.ps1 +66 -36
  72. package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +31 -34
  73. package/resources/claude-customizations/.claude/lib/bash/mutate-parallel-run.sh +191 -0
  74. package/resources/claude-customizations/.claude/lib/bash/parallel-cohorts.sh +13 -3
  75. package/resources/claude-customizations/.claude/lib/bash/parallel-lane-assertion.sh +4 -2
  76. package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-cli.sh +152 -0
  77. package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-ops.sh +193 -0
  78. package/resources/claude-customizations/.claude/lib/bash/parallel-mutation.sh +322 -0
  79. package/resources/claude-customizations/.claude/lib/bash/remove-parallel-item.sh +146 -0
  80. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +23 -33
  81. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +86 -6
  82. package/resources/claude-customizations/.claude/lib/ci-gate/Invoke-CiGateParser.ps1 +91 -19
  83. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +6 -13
  84. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateEpicWaveBarrier.psm1 +346 -0
  85. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +396 -0
  86. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +22 -15
  87. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRemediationAccounting.psm1 +337 -0
  88. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingContract.psm1 +9 -1
  89. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +79 -2
  90. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +3 -3
  91. package/resources/claude-customizations/.claude/rules/architecture-boundaries.md +5 -5
  92. package/resources/claude-customizations/.claude/rules/csharp.md +2 -2
  93. package/resources/claude-customizations/.claude/rules/general-code-change.md +1 -1
  94. package/resources/claude-customizations/.claude/rules/general-unit-test.md +3 -2
  95. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +153 -4
  96. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +50 -5
  97. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +47 -8
  98. package/resources/claude-customizations/.claude/rules/quality-tiers.md +11 -7
  99. package/resources/claude-customizations/.claude/rules/shell.md +10 -8
  100. package/resources/claude-customizations/.claude/rules/typescript.md +1 -1
  101. package/resources/claude-customizations/.claude/settings.json +3 -4
  102. package/resources/claude-customizations/.claude/skills/acceptance-criteria-tracking/SKILL.md +9 -1
  103. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +7 -1
  104. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +11 -3
  105. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup-worktrees.sh +11 -2
  106. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_detached_lib.sh +1 -1
  107. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_enumerate_lib.sh +171 -5
  108. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_lib.sh +1 -1
  109. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_report_records_lib.sh +6 -43
  110. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_scan_helper.sh +6 -15
  111. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +45 -11
  112. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +1 -1
  113. package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  114. package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +10 -0
  115. package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +7 -4
  116. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +42 -16
  117. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +52 -14
  118. package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +22 -9
  119. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +97 -19
  120. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +63 -7
  121. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +35 -12
  122. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +3 -1
  123. package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +1 -1
  124. package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +7 -3
  125. package/resources/claude-customizations/pack-manifests/core.json +25 -0
  126. package/resources/codex-and-agents-customizations/.agents/skills/acceptance-criteria-tracking/SKILL.md +9 -1
  127. package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +5 -5
  128. package/resources/codex-and-agents-customizations/.agents/skills/atomic-executor/SKILL.md +11 -0
  129. package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +4 -0
  130. package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +10 -0
  131. package/resources/codex-and-agents-customizations/.agents/skills/csharp/SKILL.md +2 -2
  132. package/resources/codex-and-agents-customizations/.agents/skills/csharp-qa-gate/SKILL.md +2 -2
  133. package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +13 -3
  134. package/resources/codex-and-agents-customizations/.agents/skills/epic-plan/SKILL.md +17 -1
  135. package/resources/codex-and-agents-customizations/.agents/skills/epic-run/SKILL.md +2 -1
  136. package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  137. package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +1 -1
  138. package/resources/codex-and-agents-customizations/.agents/skills/feature-review/SKILL.md +30 -6
  139. package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +16 -6
  140. package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +1 -1
  141. package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +3 -2
  142. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +43 -11
  143. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +41 -2
  144. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +19 -4
  145. package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +11 -7
  146. package/resources/codex-and-agents-customizations/.agents/skills/remediation-handoff-atomic-planner/SKILL.md +2 -0
  147. package/resources/codex-and-agents-customizations/.agents/skills/translate-claude-to-codex/SKILL.md +1 -1
  148. package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp/SKILL.md +2 -2
  149. package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +2 -2
  150. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +11 -0
  151. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +11 -0
  152. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +11 -0
  153. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +11 -0
  154. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +11 -0
  155. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +11 -0
  156. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +10 -0
  157. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +10 -0
  158. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +10 -0
  159. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +10 -0
  160. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +10 -0
  161. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +10 -0
  162. package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +2 -1
  163. package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +6 -6
  164. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c1.toml +1 -0
  165. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c2.toml +1 -0
  166. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3-elevated.toml +1 -0
  167. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3.toml +1 -0
  168. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c4.toml +1 -0
  169. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer.toml +1 -0
  170. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +3 -3
  171. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +3 -3
  172. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +3 -3
  173. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +3 -3
  174. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +3 -3
  175. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +3 -3
  176. package/resources/codex-and-agents-customizations/.codex/codex-web-setup.sh +318 -273
  177. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  178. package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +7 -1
  179. package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +7 -1
  180. package/resources/codex-and-agents-customizations/.codex/hooks/codex-epic-child-launch-attestation.ps1 +1 -1
  181. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
  182. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +8 -2
  183. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +97 -44
  184. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-helpers.ps1 +105 -0
  185. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +7 -1
  186. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +37 -5
  187. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +6 -0
  188. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-root-invocation.ps1 +7 -1
  189. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-wave-barrier.ps1 +7 -1
  190. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +54 -41
  191. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +7 -1
  192. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +35 -14
  193. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
  194. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
  195. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
  196. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +24 -11
  197. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +8 -2
  198. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +13 -4
  199. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +8 -2
  200. package/resources/codex-and-agents-customizations/.codex/hooks/feature-folder-resolution.ps1 +373 -0
  201. package/resources/codex-and-agents-customizations/.codex/hooks/feature-review-coverage-thresholds.ps1 +85 -0
  202. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-heredoc.ps1 +119 -0
  203. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation-operands.ps1 +198 -0
  204. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +254 -255
  205. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload-powershell.ps1 +201 -0
  206. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload.ps1 +484 -0
  207. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +27 -110
  208. package/resources/codex-and-agents-customizations/.codex/hooks/hook-dependency-guard.ps1 +114 -0
  209. package/resources/codex-and-agents-customizations/.codex/hooks/hook-docs-only-diff.ps1 +493 -0
  210. package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +8 -2
  211. package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +7 -1
  212. package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +24 -8
  213. package/resources/codex-and-agents-customizations/pack-manifests/core.json +9 -0
  214. package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +2 -2
  215. package/resources/customizations/.github/instructions/csharp-code-change.instructions.md +4 -4
  216. package/resources/customizations/.github/instructions/csharp-unit-test.instructions.md +2 -2
  217. package/resources/customizations/.github/skills/acceptance-criteria-tracking/SKILL.md +9 -1
  218. package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +4 -0
  219. package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  220. package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +1 -1
  221. package/resources/powershell/PoshQC/PoshQC.Coverage.psm1 +314 -0
  222. package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +25 -28
  223. package/resources/powershell/PoshQC/PoshQC.psm1 +1 -0
  224. package/resources/powershell/PoshQC/README.md +8 -2
  225. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +2 -315
@@ -0,0 +1,233 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Artifact-root resolution and the Check 1 body-path binding for enforce-pr-author-skill.ps1 (issue #850).
4
+
5
+ .DESCRIPTION
6
+ Dot-sourced by enforce-pr-author-skill-helpers.ps1 after its module imports. Holds:
7
+
8
+ - Resolve-PrAuthorArtifactRoot, which resolves once per call the worktree whose
9
+ artifacts/ tree the gate reads: the epic checkpoint's worktree under epic scope, or
10
+ the item worktree identity resolution selects. The PR context summary, the body, and
11
+ the receipt are composed beneath that root, so a gate running in the session worktree
12
+ no longer reads another item's artifacts from its own process directory.
13
+ - Get-PrAuthorBodyPathBindingReason, the Check 1 binding: a relative canonical
14
+ --body-file value is accepted only when the resolved worktree is the session worktree,
15
+ because gh reads a relative path from its own process directory; an absolute value is
16
+ accepted only when it equals the canonical path beneath the resolved root.
17
+ - Test-PrAuthorBodyPathEqual and Get-PrAuthorEpicArtifactRoot, the pure path helpers.
18
+ - ConvertTo-PrAuthorBodyNumber, the strict body-number parse (issue #901, #850 note L19).
19
+
20
+ .NOTES
21
+ PowerShell 7+. Depends, at call time, on Get-PrAuthorBodyFileValue and
22
+ Get-PrAuthorTargetCheckpointResolution from the helpers file and on the worktree-resolution
23
+ modules it imports. It reads no file. Mirrored byte-identically under
24
+ extensions/drm-copilot/resources/claude-customizations/.
25
+ #>
26
+ [CmdletBinding()]
27
+ param()
28
+
29
+ function Test-PrAuthorBodyPathEqual {
30
+ <#
31
+ .SYNOPSIS
32
+ Compare two paths: case-insensitive for drive-letter and UNC paths, case-sensitive otherwise.
33
+ .PARAMETER Left
34
+ The first path, in either separator style.
35
+ .PARAMETER Right
36
+ The second path, in either separator style.
37
+ .OUTPUTS
38
+ System.Boolean
39
+ #>
40
+ [CmdletBinding()]
41
+ [OutputType([bool])]
42
+ param(
43
+ [AllowNull()][AllowEmptyString()][string] $Left,
44
+ [AllowNull()][AllowEmptyString()][string] $Right
45
+ )
46
+
47
+ $a = ConvertTo-WorktreeResolutionNormalizedPath -Path $Left
48
+ $b = ConvertTo-WorktreeResolutionNormalizedPath -Path $Right
49
+ if ($null -eq $a -or $null -eq $b) {
50
+ return $false
51
+ }
52
+ $comparison = if ($a -match '^([A-Za-z]:|//)' -or $b -match '^([A-Za-z]:|//)') {
53
+ [System.StringComparison]::OrdinalIgnoreCase
54
+ }
55
+ else {
56
+ [System.StringComparison]::Ordinal
57
+ }
58
+ return [string]::Equals($a, $b, $comparison)
59
+ }
60
+
61
+ function Get-PrAuthorEpicArtifactRoot {
62
+ <#
63
+ .SYNOPSIS
64
+ Return the worktree root that holds an epic checkpoint path, or $null.
65
+ .PARAMETER CheckpointPath
66
+ The absolute epic checkpoint path the epic-scope resolver composed.
67
+ .OUTPUTS
68
+ System.String or $null
69
+ #>
70
+ [CmdletBinding()]
71
+ [OutputType([string])]
72
+ param(
73
+ [AllowNull()][AllowEmptyString()][string] $CheckpointPath
74
+ )
75
+
76
+ $normalized = ConvertTo-WorktreeResolutionNormalizedPath -Path $CheckpointPath
77
+ $suffix = '/' + (Get-EpicScopeCheckpointRelativePath)
78
+ if ($null -eq $normalized -or -not $normalized.EndsWith($suffix, [System.StringComparison]::Ordinal)) {
79
+ return $null
80
+ }
81
+ $root = $normalized.Substring(0, $normalized.Length - $suffix.Length)
82
+ if ($root.Length -eq 0) {
83
+ return $null
84
+ }
85
+ return $root
86
+ }
87
+
88
+ function Resolve-PrAuthorArtifactRoot {
89
+ <#
90
+ .SYNOPSIS
91
+ Resolve, once per call, the worktree whose artifacts the gate reads (issue #850).
92
+ .DESCRIPTION
93
+ Epic scope: the root is the worktree holding the epic checkpoint, and a relative body
94
+ path is allowed only when that root is the session worktree. Otherwise the item target
95
+ is resolved by identity; an unresolved target returns its resolution reason unchanged,
96
+ and a relative body path is allowed only for a SessionRoot target.
97
+ .PARAMETER CommandText
98
+ The Bash command text under evaluation.
99
+ .OUTPUTS
100
+ System.Collections.Specialized.OrderedDictionary with Reason, ArtifactRoot,
101
+ CheckpointPath, EpicScope, Status, and RelativeBodyAllowed.
102
+ #>
103
+ [CmdletBinding()]
104
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
105
+ param(
106
+ [Parameter(Mandatory)]
107
+ [string] $CommandText
108
+ )
109
+
110
+ $epicScope = Resolve-EpicScopeCheckpoint -Text $CommandText -SessionRoot (Get-Location).Path
111
+ if ($null -ne $epicScope -and $epicScope.IsEpicScope) {
112
+ $root = Get-PrAuthorEpicArtifactRoot -CheckpointPath $epicScope.CheckpointPath
113
+ $reason = $null
114
+ $relativeAllowed = $false
115
+ if ($null -eq $root) {
116
+ $reason = "ORCHESTRATOR_STATE_PREFLIGHT_FAILED: the epic checkpoint path '$($epicScope.CheckpointPath)' does not end with '$(Get-EpicScopeCheckpointRelativePath)', so the worktree that holds it cannot be derived."
117
+ }
118
+ else {
119
+ $relativeAllowed = Test-PrAuthorBodyPathEqual -Left $root -Right $epicScope.WorktreeRoot
120
+ }
121
+ return [ordered]@{
122
+ Reason = $reason
123
+ ArtifactRoot = $root
124
+ CheckpointPath = $epicScope.CheckpointPath
125
+ EpicScope = $epicScope
126
+ Status = 'Epic'
127
+ RelativeBodyAllowed = $relativeAllowed
128
+ }
129
+ }
130
+
131
+ $resolution = Get-PrAuthorTargetCheckpointResolution -CommandText $CommandText
132
+ $resolved = -not $resolution.Reason
133
+ return [ordered]@{
134
+ Reason = $resolution.Reason
135
+ ArtifactRoot = if ($resolved) { ConvertTo-WorktreeResolutionNormalizedPath -Path $resolution.WorktreeRoot } else { $null }
136
+ CheckpointPath = $resolution.CheckpointPath
137
+ EpicScope = $null
138
+ Status = $resolution.Status
139
+ RelativeBodyAllowed = ($resolved -and $resolution.Status -eq 'SessionRoot')
140
+ }
141
+ }
142
+
143
+ function ConvertTo-PrAuthorBodyNumber {
144
+ <#
145
+ .SYNOPSIS
146
+ Parse the <N> of artifacts/pr_body_<N>.md, or return $null.
147
+ .DESCRIPTION
148
+ Accepts only ASCII digits that parse as an Int32 and whose canonical invariant spelling
149
+ equals the input, so a value above Int32.MaxValue, a leading zero, and a non-ASCII
150
+ decimal digit all return $null instead of throwing or naming a different file.
151
+ .PARAMETER Digits
152
+ The digit run captured from the body-file name.
153
+ .OUTPUTS
154
+ System.Int32 or $null
155
+ #>
156
+ [CmdletBinding()]
157
+ [OutputType([int])]
158
+ param(
159
+ [AllowNull()][AllowEmptyString()][string] $Digits
160
+ )
161
+
162
+ $number = 0
163
+ $parsed = [int]::TryParse($Digits, [System.Globalization.NumberStyles]::None, [System.Globalization.CultureInfo]::InvariantCulture, [ref] $number)
164
+ if (-not $parsed -or $number.ToString([System.Globalization.CultureInfo]::InvariantCulture) -cne $Digits) {
165
+ return $null
166
+ }
167
+ return $number
168
+ }
169
+
170
+ function Get-PrAuthorBodyPathBindingReason {
171
+ <#
172
+ .SYNOPSIS
173
+ Check 1: bind the --body-file value to the canonical body path beneath the artifact root.
174
+ .DESCRIPTION
175
+ Reads the value once through Get-PrAuthorBodyFileValue. A relative
176
+ artifacts/pr_body_<N>.md (case-sensitive) is accepted only when RelativeBodyAllowed is
177
+ true; otherwise the deny names the absolute canonical path to pass instead. An absolute
178
+ value is accepted only when it equals that canonical path under Test-PrAuthorBodyPathEqual.
179
+ Any other value, including no readable value, denies with the generic text.
180
+ .PARAMETER CommandText
181
+ The Bash command text under evaluation.
182
+ .PARAMETER ArtifactRoot
183
+ The resolved worktree root the artifacts are read beneath.
184
+ .PARAMETER RelativeBodyAllowed
185
+ Whether a relative body path is read from the resolved worktree.
186
+ .OUTPUTS
187
+ System.Collections.Specialized.OrderedDictionary with Reason and BodyNumber.
188
+ #>
189
+ [CmdletBinding()]
190
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
191
+ param(
192
+ [Parameter(Mandatory)]
193
+ [string] $CommandText,
194
+
195
+ [Parameter(Mandatory)]
196
+ [string] $ArtifactRoot,
197
+
198
+ [bool] $RelativeBodyAllowed
199
+ )
200
+
201
+ $generic = "PR_BODY_PATH_NONCANONICAL: ``--body-file`` must reference the canonical ``artifacts/pr_body_<N>.md`` file produced by the pr-author skill beneath the target worktree '$ArtifactRoot' (relative only when that worktree is the session worktree). The path supplied does not match."
202
+ $value = ConvertTo-WorktreeResolutionNormalizedPath -Path (Get-PrAuthorBodyFileValue -CommandText $CommandText)
203
+ if ($null -eq $value) {
204
+ return [ordered]@{ Reason = $generic; BodyNumber = $null }
205
+ }
206
+
207
+ if ($value -cmatch '^artifacts/pr_body_(\d+)\.md$') {
208
+ $number = ConvertTo-PrAuthorBodyNumber -Digits $Matches[1]
209
+ if ($null -eq $number) {
210
+ return [ordered]@{ Reason = $generic; BodyNumber = $null }
211
+ }
212
+ if (-not $RelativeBodyAllowed) {
213
+ $canonical = Join-WorktreeResolutionPath -WorktreeRoot $ArtifactRoot -RepoRelativePath "artifacts/pr_body_$number.md"
214
+ return [ordered]@{
215
+ Reason = "PR_BODY_PATH_NONCANONICAL: the target worktree '$ArtifactRoot' is not the session worktree, so gh would read a relative --body-file from the session worktree. Pass the absolute canonical path '$canonical'."
216
+ BodyNumber = $null
217
+ }
218
+ }
219
+ return [ordered]@{ Reason = $null; BodyNumber = $number }
220
+ }
221
+
222
+ if ($value -match '/artifacts/pr_body_(\d+)\.md$') {
223
+ $number = ConvertTo-PrAuthorBodyNumber -Digits $Matches[1]
224
+ if ($null -ne $number) {
225
+ $canonical = Join-WorktreeResolutionPath -WorktreeRoot $ArtifactRoot -RepoRelativePath "artifacts/pr_body_$number.md"
226
+ if (Test-PrAuthorBodyPathEqual -Left $value -Right $canonical) {
227
+ return [ordered]@{ Reason = $null; BodyNumber = $number }
228
+ }
229
+ }
230
+ }
231
+
232
+ return [ordered]@{ Reason = $generic; BodyNumber = $null }
233
+ }
@@ -0,0 +1,224 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Docs-only exemption from the checkpoint preflight of enforce-pr-author-skill.ps1 (issue #901).
4
+
5
+ .DESCRIPTION
6
+ Dot-sourced by enforce-pr-author-skill-helpers.ps1. Get-PrAuthorBypassReason calls
7
+ Get-PrAuthorDocsOnlyExemption only in its non-epic branch and only after
8
+ Invoke-OrchestratorStatePreflight reports errors. The exemption lets a gh pr create call
9
+ continue to the receipt checks when the branch changes nothing outside docs/. It fails
10
+ closed: every condition below must hold, and the first that does not names the sub-code
11
+ the caller appends to its ORCHESTRATOR_STATE_PREFLIGHT_FAILED denial.
12
+
13
+ E1 DOCS_ONLY_EXEMPTION_NOT_CREATE - the command is a structural gh pr create.
14
+ E2 DOCS_ONLY_EXEMPTION_CHECKPOINT_PRESENT - no checkpoint file exists at the resolved path.
15
+ E3 DOCS_ONLY_EXEMPTION_BASE_UNRESOLVED - exactly one explicit --base, a conservative branch
16
+ spelling without '..', and no base or repository
17
+ override: no single-dash token whose letters
18
+ include B or R (-B, -R, -dB, -Rx), no --repo or
19
+ --repo= token, and no inline GH_REPO= prefix.
20
+ E4 DOCS_ONLY_EXEMPTION_BASE_UNRESOLVED - refs/remotes/origin/<base> resolves to a commit.
21
+ E5 DOCS_ONLY_EXEMPTION_DIFF_FAILED - git diff <commit>...HEAD exits 0 without throwing,
22
+ and non-empty output ends with NUL.
23
+ E6 DOCS_ONLY_EXEMPTION_EMPTY_DIFF - the diff lists at least one path.
24
+ E7 DOCS_ONLY_EXEMPTION_NON_DOCS_PATH - every path starts with docs/ (ordinal).
25
+
26
+ The diff runs with --no-renames so a rename from outside docs/ reports its old path, with -z
27
+ so no path is quoted, and with --no-relative, --no-color, and --no-ext-diff so repository
28
+ configuration cannot change the listing.
29
+
30
+ Residual limit: a GH_REPO value exported by an earlier shell command or inherited from the
31
+ session environment is not visible in the command text, so this file cannot detect it.
32
+
33
+ .NOTES
34
+ PowerShell 7+. Depends, at call time, on Test-CommandLineInvocation,
35
+ Get-CommandLineStructuralMatch, and Get-CommandLineFlagValue from the command parsers the
36
+ helpers file loads. It reads no file content. The git executable runs only through
37
+ Invoke-PrAuthorDocsOnlyGitExe, the single executable seam, which never throws. Mirrored
38
+ byte-identically under extensions/drm-copilot/resources/claude-customizations/.
39
+ Dot-sources hook-docs-only-diff.ps1 (issue #927), whose Get-DocsOnlyDiffTextViolation
40
+ performs the E5 NUL check and the E6 and E7 path classification.
41
+ #>
42
+ [CmdletBinding()]
43
+ param()
44
+ . (Join-Path $PSScriptRoot 'hook-docs-only-diff.ps1')
45
+
46
+ function Invoke-PrAuthorDocsOnlyGitExe {
47
+ <#
48
+ .SYNOPSIS
49
+ Run git -C <root> with the given arguments. The single executable seam; tests mock it.
50
+ .DESCRIPTION
51
+ Never throws: an uncaught error ends the hook with exit 1, which PreToolUse treats as
52
+ non-blocking, so a throw here would allow the call. A root that is not an existing
53
+ directory, or a git that cannot start, returns ExitCode -1. Standard error is discarded
54
+ so that it is never read as path data.
55
+ .PARAMETER WorktreeRoot
56
+ The resolved worktree root git runs in.
57
+ .PARAMETER GitArgs
58
+ The git arguments that follow -C <root>.
59
+ .PARAMETER GitInvoker
60
+ The scriptblock that runs git; a unit test passes a test-local stand-in so that this
61
+ body runs without starting a process.
62
+ .OUTPUTS
63
+ System.Management.Automation.PSCustomObject with ExitCode and Output.
64
+ #>
65
+ [CmdletBinding()]
66
+ [OutputType([pscustomobject])]
67
+ param(
68
+ [AllowNull()][AllowEmptyString()][string] $WorktreeRoot,
69
+ [Parameter(Mandatory)][string[]] $GitArgs,
70
+ [ValidateNotNull()][scriptblock] $GitInvoker = { git @args 2>$null }
71
+ )
72
+
73
+ $failure = [pscustomobject]@{ ExitCode = -1; Output = '' }
74
+ if ([string]::IsNullOrWhiteSpace($WorktreeRoot) -or -not (Test-Path -LiteralPath $WorktreeRoot -PathType Container)) {
75
+ return $failure
76
+ }
77
+ try {
78
+ $lines = @(& $GitInvoker -C $WorktreeRoot @GitArgs 2>$null)
79
+ $code = $LASTEXITCODE
80
+ }
81
+ catch {
82
+ return $failure
83
+ }
84
+ return [pscustomobject]@{ ExitCode = [int]$code; Output = ($lines -join "`n") }
85
+ }
86
+
87
+ function Test-PrAuthorDocsOnlyCheckpointPresence {
88
+ <#
89
+ .SYNOPSIS
90
+ Return $true when a checkpoint file exists at the path, and also for a blank path.
91
+ .DESCRIPTION
92
+ A blank path cannot prove that no checkpoint exists, so it reports presence (fail closed).
93
+ .PARAMETER CheckpointPath
94
+ The absolute checkpoint path identity resolution composed.
95
+ .OUTPUTS
96
+ System.Boolean
97
+ #>
98
+ [CmdletBinding()]
99
+ [OutputType([bool])]
100
+ param(
101
+ [AllowNull()][AllowEmptyString()][string] $CheckpointPath
102
+ )
103
+
104
+ if ([string]::IsNullOrWhiteSpace($CheckpointPath)) {
105
+ return $true
106
+ }
107
+ return (Test-Path -LiteralPath $CheckpointPath -PathType Leaf)
108
+ }
109
+
110
+ function Get-PrAuthorDocsOnlyBaseName {
111
+ <#
112
+ .SYNOPSIS
113
+ Return the single explicit --base value of a gh pr create command, or $null.
114
+ .DESCRIPTION
115
+ Returns $null when the command is not a structural gh pr create; when a single-dash
116
+ token has B or R among its letters (-B base and -R repository, alone or clustered as in
117
+ -dB or -dR); when a token is --repo, starts with --repo=, or starts with GH_REPO=; when
118
+ --base appears other than exactly once; when its value is missing or dash-leading; when
119
+ the value is not a conservative branch spelling; or when it contains '..'.
120
+ .PARAMETER CommandText
121
+ The Bash command text under evaluation.
122
+ .OUTPUTS
123
+ System.String or $null
124
+ #>
125
+ [CmdletBinding()]
126
+ [OutputType([string])]
127
+ param(
128
+ [Parameter(Mandatory)][AllowEmptyString()][string] $CommandText
129
+ )
130
+
131
+ $subcommandPath = @('pr', 'create')
132
+ $match = Get-CommandLineStructuralMatch -CommandText $CommandText -CommandWord 'gh' -SubcommandPath $subcommandPath
133
+ if ($null -eq $match) {
134
+ return $null
135
+ }
136
+ $baseTokenCount = 0
137
+ foreach ($token in @($match.Segment.Tokens)) {
138
+ $text = [string]$token
139
+ if ($text.Length -ge 2 -and $text[0] -eq '-' -and $text[1] -ne '-' -and $text.Substring(1).IndexOfAny([char[]]@('B', 'R')) -ge 0) {
140
+ return $null
141
+ }
142
+ if ($text -eq '--repo' -or $text.StartsWith('--repo=', [System.StringComparison]::OrdinalIgnoreCase) -or $text.StartsWith('GH_REPO=', [System.StringComparison]::OrdinalIgnoreCase)) {
143
+ return $null
144
+ }
145
+ if ($text -eq '--base' -or $text.StartsWith('--base=', [System.StringComparison]::OrdinalIgnoreCase)) {
146
+ $baseTokenCount++
147
+ }
148
+ }
149
+ if ($baseTokenCount -ne 1) {
150
+ return $null
151
+ }
152
+ $base = Get-CommandLineFlagValue -CommandText $CommandText -CommandWord 'gh' -SubcommandPath $subcommandPath -FlagName '--base'
153
+ if ($null -eq $base -or $base -cnotmatch '^[A-Za-z0-9][A-Za-z0-9._/-]*$' -or $base.Contains('..')) {
154
+ return $null
155
+ }
156
+ return $base
157
+ }
158
+
159
+ function Get-PrAuthorDocsOnlyExemption {
160
+ <#
161
+ .SYNOPSIS
162
+ Decide whether a failed preflight is exempt because the branch changes only docs/.
163
+ .DESCRIPTION
164
+ Evaluates E1 to E7 of the file header in order and returns at the first failure.
165
+ Each git call is wrapped so that a throwing seam denies rather than escapes.
166
+ .PARAMETER CommandText
167
+ The Bash command text under evaluation.
168
+ .PARAMETER WorktreeRoot
169
+ The resolved worktree root the diff is computed in.
170
+ .PARAMETER CheckpointPath
171
+ The resolved checkpoint path whose absence E2 requires.
172
+ .OUTPUTS
173
+ System.Collections.Specialized.OrderedDictionary with Exempt and Reason.
174
+ #>
175
+ [CmdletBinding()]
176
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
177
+ param(
178
+ [Parameter(Mandatory)][string] $CommandText,
179
+ [AllowNull()][AllowEmptyString()][string] $WorktreeRoot,
180
+ [AllowNull()][AllowEmptyString()][string] $CheckpointPath
181
+ )
182
+
183
+ if (-not (Test-CommandLineInvocation -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('pr', 'create'))) {
184
+ return [ordered]@{ Exempt = $false; Reason = 'DOCS_ONLY_EXEMPTION_NOT_CREATE: only gh pr create is eligible.' }
185
+ }
186
+ if (Test-PrAuthorDocsOnlyCheckpointPresence -CheckpointPath $CheckpointPath) {
187
+ return [ordered]@{ Exempt = $false; Reason = "DOCS_ONLY_EXEMPTION_CHECKPOINT_PRESENT: a checkpoint file exists at '$CheckpointPath', so its preflight governs." }
188
+ }
189
+ $base = Get-PrAuthorDocsOnlyBaseName -CommandText $CommandText
190
+ if ($null -eq $base) {
191
+ return [ordered]@{ Exempt = $false; Reason = 'DOCS_ONLY_EXEMPTION_BASE_UNRESOLVED: pass exactly one explicit --base <branch>; -B, -R, --repo, a single-dash shorthand cluster containing B or R, and an inline GH_REPO= prefix are not accepted.' }
192
+ }
193
+
194
+ $baseRef = 'refs/remotes/origin/' + $base + '^{commit}'
195
+ try {
196
+ $revParse = Invoke-PrAuthorDocsOnlyGitExe -WorktreeRoot $WorktreeRoot -GitArgs @('rev-parse', '--verify', '--quiet', $baseRef)
197
+ }
198
+ catch {
199
+ $revParse = $null
200
+ }
201
+ $commit = ''
202
+ if ($null -ne $revParse) {
203
+ $commit = ([string]$revParse.Output).Trim()
204
+ }
205
+ if ($null -eq $revParse -or $revParse.ExitCode -ne 0 -or $commit -cnotmatch '^[0-9a-f]{40}([0-9a-f]{24})?$') {
206
+ return [ordered]@{ Exempt = $false; Reason = "DOCS_ONLY_EXEMPTION_BASE_UNRESOLVED: refs/remotes/origin/$base does not resolve to a commit in the target worktree." }
207
+ }
208
+
209
+ try {
210
+ $diff = Invoke-PrAuthorDocsOnlyGitExe -WorktreeRoot $WorktreeRoot -GitArgs @('diff', '--name-only', '-z', '--no-renames', '--no-color', '--no-ext-diff', '--no-relative', ($commit + '...HEAD'))
211
+ }
212
+ catch {
213
+ $diff = $null
214
+ }
215
+ if ($null -eq $diff -or $diff.ExitCode -ne 0) {
216
+ return [ordered]@{ Exempt = $false; Reason = "DOCS_ONLY_EXEMPTION_DIFF_FAILED: git diff against origin/$base could not be computed." }
217
+ }
218
+
219
+ $violation = Get-DocsOnlyDiffTextViolation -DiffText ([string]$diff.Output) -BaseName $base
220
+ if ($violation) {
221
+ return [ordered]@{ Exempt = $false; Reason = $violation }
222
+ }
223
+ return [ordered]@{ Exempt = $true; Reason = $null }
224
+ }
@@ -19,18 +19,49 @@
19
19
  Block cases:
20
20
  Case A - gh pr create or gh pr edit with --body (inline, no --body-file): blocked.
21
21
  Case B - gh pr create with neither --body nor --body-file: blocked.
22
- Case C - gh pr create or gh pr edit with --body-file but context artifact absent: blocked.
22
+ Case C - gh pr create or gh pr edit with --body-file but context artifact absent beneath
23
+ the resolved worktree: blocked.
23
24
  Preflight - --body-file/context present: orchestrator-state checkpoint must pass
24
25
  --require-pr-creation-ready before receipt verification runs, else blocked.
25
- Receipt - preflight passed: the SHA-256 receipt is verified in five ordered checks
26
- (Section below). The first failing check blocks.
26
+ Docs-only - issue #901: when the preflight fails, gh pr create proceeds to receipt
27
+ verification only when every condition holds: no checkpoint file exists
28
+ beneath the resolved worktree; the command carries exactly one explicit
29
+ --base <branch> and no base or repository override (issue #927: no
30
+ single-dash token whose letters include B or R, such as -B, -R, or -dB; no
31
+ --repo or --repo= token; no inline GH_REPO= prefix); refs/remotes/origin/<branch>
32
+ resolves to a commit; git diff --name-only -z --no-renames <commit>...HEAD
33
+ exits 0, ends its non-empty output with NUL, and lists at least one path; and
34
+ every listed path starts with docs/ (ordinal, case-sensitive). Otherwise the
35
+ deny keeps ORCHESTRATOR_STATE_PREFLIGHT_FAILED and appends one sub-code:
36
+ DOCS_ONLY_EXEMPTION_NOT_CREATE - gh pr edit is never exempt.
37
+ DOCS_ONLY_EXEMPTION_CHECKPOINT_PRESENT - a checkpoint file exists.
38
+ DOCS_ONLY_EXEMPTION_BASE_UNRESOLVED - no single explicit --base, a base or
39
+ repository override is present, or
40
+ the base does not resolve on origin.
41
+ DOCS_ONLY_EXEMPTION_DIFF_FAILED - the diff exits non-zero, throws, or
42
+ returns output not ending with NUL.
43
+ DOCS_ONLY_EXEMPTION_EMPTY_DIFF - the diff lists no path.
44
+ DOCS_ONLY_EXEMPTION_NON_DOCS_PATH - a listed path is outside docs/.
45
+ The exemption never applies under epic scope.
46
+ Receipt - preflight passed or docs-only exemption applied: the SHA-256 receipt is
47
+ verified in the ordered checks (Section below). The first failing check blocks.
27
48
 
28
49
  Receipt verification decision order on the --body-file-with-context path:
29
50
  PR_BODY_PATH_NONCANONICAL -> PR_AUTHOR_RECEIPT_MISSING -> PR_AUTHOR_RECEIPT_NUMBER_MISMATCH
30
51
  -> PR_AUTHOR_RECEIPT_HASH_MISMATCH -> PR_AUTHOR_RECEIPT_STALE -> allow.
31
52
 
53
+ Issue #850: the context summary, the body, and the receipt are read beneath the worktree
54
+ the call resolves to (the item worktree, or the epic checkpoint's worktree under epic
55
+ scope), not beneath the process directory, so a gate running in the session worktree
56
+ validates the artifacts of the item it gates.
57
+
32
58
  .NOTES
33
- Compatible with PowerShell 7+. No external module dependencies.
59
+ Compatible with PowerShell 7+. Dependencies are in-repository only: hook-dependency-guard.ps1,
60
+ the HookPayload, OrchestratorState, and OrchestratorStateUnconditional modules, and the
61
+ epic-base-branch and helpers siblings; the helpers load the command parsers, the
62
+ worktree-resolution modules, and the artifact-root and docs-only siblings.
63
+ No PowerShell Gallery module is required.
64
+ The docs-only exemption runs the git executable at call time (issue #901).
34
65
 
35
66
  Enforcement strength: the SHA-256 receipt is a policy-level integrity check that binds the
36
67
  PR body bytes to the receipt the pr-author agent wrote. It is not a cryptographic or security
@@ -42,28 +73,34 @@
42
73
  #>
43
74
  [CmdletBinding()]
44
75
  param()
76
+ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
45
77
 
46
78
 
47
- Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
79
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
48
80
  $script:PrContextArtifactPath = 'artifacts/pr_context.summary.txt'
49
81
  # Null until the resolution step assigns the absolute path of the resolved worktree's
50
82
  # checkpoint (issue #673); a relative default here was the process-directory binding.
51
83
  $script:OrchestratorStateCheckpointPath = $null
52
84
 
53
- Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorState.psm1') -Force
85
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorState.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'OrchestratorState.psm1' -ErrorRecord $_ }
54
86
 
55
87
  function Get-PrContextArtifactExistence {
56
88
  <#
57
89
  .SYNOPSIS
58
90
  Wrapper around Test-Path for the PR context artifact. Tests mock this function.
91
+ .PARAMETER Path
92
+ The absolute path of artifacts/pr_context.summary.txt beneath the resolved worktree.
59
93
  .OUTPUTS
60
94
  System.Boolean
61
95
  #>
62
96
  [CmdletBinding()]
63
97
  [OutputType([bool])]
64
- param()
98
+ param(
99
+ [Parameter(Mandatory)]
100
+ [string] $Path
101
+ )
65
102
 
66
- return [bool](Test-Path -LiteralPath $script:PrContextArtifactPath)
103
+ return [bool](Test-Path -LiteralPath $Path)
67
104
  }
68
105
 
69
106
  function Get-PrBodyFileBytes {
@@ -75,7 +112,8 @@ function Get-PrBodyFileBytes {
75
112
  This is the injectable boundary for body-file bytes in tests; no test writes the body file
76
113
  to disk. The bytes are hashed inline by the receipt verification function.
77
114
  .PARAMETER BodyFilePath
78
- The relative path to the PR body file (for example artifacts/pr_body_5.md).
115
+ The absolute path to the PR body file beneath the resolved worktree (for example
116
+ <worktree>/artifacts/pr_body_5.md).
79
117
  .OUTPUTS
80
118
  System.Byte[] or $null
81
119
  #>
@@ -103,7 +141,8 @@ function Get-PrAuthorReceiptContent {
103
141
  or $null when the receipt file is absent. This is the injectable boundary for receipt
104
142
  content in tests; no test writes the receipt file to disk.
105
143
  .PARAMETER ReceiptFilePath
106
- The relative path to the receipt file (for example artifacts/pr_body_5.receipt.json).
144
+ The absolute path to the receipt file beneath the resolved worktree (for example
145
+ <worktree>/artifacts/pr_body_5.receipt.json).
107
146
  .OUTPUTS
108
147
  System.String or $null
109
148
  #>
@@ -129,25 +168,32 @@ function Get-PrContextSummaryLastWriteUtc {
129
168
  Returns the LastWriteTimeUtc of artifacts/pr_context.summary.txt, or $null when the file is
130
169
  absent. The staleness check compares the receipt's created_at against this value; both are
131
170
  artifact metadata, so no wall-clock seam is required.
171
+ .PARAMETER Path
172
+ The absolute path of artifacts/pr_context.summary.txt beneath the resolved worktree.
132
173
  .OUTPUTS
133
174
  System.DateTime or $null
134
175
  #>
135
176
  [CmdletBinding()]
136
177
  [OutputType([datetime])]
137
- param()
178
+ param(
179
+ [Parameter(Mandatory)]
180
+ [string] $Path
181
+ )
138
182
 
139
- if (-not (Test-Path -LiteralPath $script:PrContextArtifactPath)) {
183
+ if (-not (Test-Path -LiteralPath $Path)) {
140
184
  return $null
141
185
  }
142
186
 
143
- return (Get-Item -LiteralPath $script:PrContextArtifactPath).LastWriteTimeUtc
187
+ return (Get-Item -LiteralPath $Path).LastWriteTimeUtc
144
188
  }
145
189
 
146
- . (Join-Path $PSScriptRoot 'enforce-pr-author-skill.epic-base-branch.ps1')
190
+ try { . (Join-Path $PSScriptRoot 'enforce-pr-author-skill.epic-base-branch.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-pr-author-skill.epic-base-branch.ps1' -ErrorRecord $_ }
147
191
 
148
192
  # Dot-source the receipt-verification and bypass-reason helpers. Guarded so dot-sourcing this
149
193
  # hook in tests loads the helpers too (issue #501 headroom split).
150
- . (Join-Path $PSScriptRoot 'enforce-pr-author-skill-helpers.ps1')
194
+ try { . (Join-Path $PSScriptRoot 'enforce-pr-author-skill-helpers.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-pr-author-skill-helpers.ps1' -ErrorRecord $_ }
195
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorStateUnconditional.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'OrchestratorStateUnconditional.psm1' -ErrorRecord $_ }
196
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorState.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'OrchestratorState.psm1' -ErrorRecord $_ }
151
197
 
152
198
  function Invoke-PrAuthorSkillDecision {
153
199
  <#
@@ -168,6 +214,8 @@ function Invoke-PrAuthorSkillDecision {
168
214
  [AllowEmptyString()]
169
215
  [string] $ToolInputRaw
170
216
  )
217
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'PR_AUTHOR_SKILL_BLOCKED:'
218
+ if ($null -ne $dependencyDecision) { return $dependencyDecision }
171
219
 
172
220
  $payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
173
221
  if (-not $payload.IsValid) {
@@ -182,8 +230,7 @@ function Invoke-PrAuthorSkillDecision {
182
230
  return Get-PrAuthorSkillAllowDecision
183
231
  }
184
232
 
185
- $contextExists = Get-PrContextArtifactExistence
186
- $reason = Get-PrAuthorBypassReason -CommandText $commandText -ContextExists $contextExists
233
+ $reason = Get-PrAuthorBypassReason -CommandText $commandText
187
234
 
188
235
  if ($reason) {
189
236
  return Get-PrAuthorSkillBlockDecision -Reason $reason
@@ -242,8 +289,6 @@ function Test-PrAuthorBypassRequired {
242
289
  Return $true when a Bash command requires the pr-author skill to run first.
243
290
  .PARAMETER CommandText
244
291
  The Bash command text extracted from the envelope's tool_input.
245
- .PARAMETER ContextExists
246
- Whether artifacts/pr_context.summary.txt currently exists on disk.
247
292
  .OUTPUTS
248
293
  System.Boolean
249
294
  #>
@@ -251,13 +296,10 @@ function Test-PrAuthorBypassRequired {
251
296
  [OutputType([bool])]
252
297
  param(
253
298
  [Parameter(Mandatory)]
254
- [string] $CommandText,
255
-
256
- [Parameter(Mandatory)]
257
- [bool] $ContextExists
299
+ [string] $CommandText
258
300
  )
259
301
 
260
- return ($null -ne (Get-PrAuthorBypassReason -CommandText $CommandText -ContextExists $ContextExists))
302
+ return ($null -ne (Get-PrAuthorBypassReason -CommandText $CommandText))
261
303
  }
262
304
 
263
305
  function Invoke-PrAuthorSkillEntryPoint {
@@ -302,6 +344,9 @@ function Invoke-PrAuthorSkillEntryPoint {
302
344
  if ($MyInvocation.InvocationName -eq '.') {
303
345
  return
304
346
  }
347
+ if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('PR_AUTHOR_SKILL_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
348
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'PR_AUTHOR_SKILL_BLOCKED:'
349
+ if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
305
350
 
306
351
  # The entry point returns its [int] exit code as the last pipeline element and the
307
352
  # decision JSON before it. `exit (<call>)` would capture BOTH into the exit