@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
@@ -30,27 +30,29 @@
30
30
  hookSpecificOutput.permissionDecision='allow' (backward compatibility).
31
31
 
32
32
  Edit tool calls supply only old_string/new_string (a partial patch). An Edit
33
- call is validated by reading the checkpoint at the Edit's
34
- targeted file_path through the injectable CheckpointReader seam and
35
- applying the old_string -> new_string replacement in memory; the resulting
36
- content is then evaluated like a Write. When the patch cannot be resolved
37
- (the targeted file is missing or empty, the Edit has no old_string, or the
38
- old_string is absent from the targeted content), the call is allowed. For
39
- Write calls whose content is not valid JSON, the hook allows the operation
40
- and defers to downstream tools to surface the error.
33
+ call is validated by reading the checkpoint at the Edit's targeted file_path
34
+ through the injectable CheckpointReader seam and applying the old_string ->
35
+ new_string replacement in memory; the resulting content is then evaluated
36
+ like a Write. One occurrence of old_string is replaced unless replace_all is
37
+ true. The hook fails closed: a missing, empty, or unreadable targeted
38
+ checkpoint, an absent or empty old_string, zero or ambiguous occurrences of
39
+ old_string, and a Write with empty content all deny, and the deny reason
40
+ names the cause. Write content (or patched content) that is not valid JSON
41
+ is still allowed, and the hook defers to downstream tools to surface the
42
+ error.
41
43
 
42
44
  .NOTES
43
45
  Compatible with PowerShell 7+. Read-only validation gate.
44
46
  #>
45
47
  [CmdletBinding()]
46
48
  param()
49
+ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
47
50
 
48
51
 
49
- Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
52
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
50
53
  # Dot-source the shared validation helpers. Guarded so a missing file produces a
51
54
  # clear error and so dot-sourcing this hook in tests loads the helpers too.
52
- $script:CompletionHelpersPath = Join-Path $PSScriptRoot 'enforce-completion-helpers.ps1'
53
- . $script:CompletionHelpersPath
55
+ try { . (Join-Path $PSScriptRoot 'enforce-completion-helpers.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-completion-helpers.ps1' -ErrorRecord $_ }
54
56
 
55
57
  function ConvertFrom-CheckpointJson {
56
58
  <#
@@ -72,9 +74,10 @@ function Get-CheckpointFileContent {
72
74
  Reads the on-disk checkpoint content for the read-then-validate Edit path.
73
75
  .DESCRIPTION
74
76
  Returns the full file text when the path resolves to a file on disk, or
75
- $null when the file does not exist. Tests inject a CheckpointReader
76
- scriptblock instead of mocking this function so no temporary files are
77
- required.
77
+ $null when the file does not exist. An existing empty file yields an
78
+ empty string, never $null, so a missing checkpoint and an empty
79
+ checkpoint stay distinct. Tests inject a CheckpointReader scriptblock
80
+ instead of mocking this function so no temporary files are required.
78
81
  #>
79
82
  [CmdletBinding()]
80
83
  [OutputType([string])]
@@ -86,7 +89,9 @@ function Get-CheckpointFileContent {
86
89
  if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
87
90
  return $null
88
91
  }
89
- return Get-Content -LiteralPath $Path -Raw -ErrorAction Stop
92
+ $text = Get-Content -LiteralPath $Path -Raw -ErrorAction Stop
93
+ if ($null -eq $text) { return '' }
94
+ return [string]$text
90
95
  }
91
96
 
92
97
  function Test-IsCheckpointPath {
@@ -266,20 +271,24 @@ function Get-MissingCompletionEvidence {
266
271
  function Resolve-EditedCheckpointContent {
267
272
  <#
268
273
  .SYNOPSIS
269
- Returns the patched checkpoint content for an Edit-tool call, or $null
270
- when the patch cannot be applied against the on-disk checkpoint.
274
+ Returns an object with Content and Failure for an Edit-tool call: the
275
+ patched checkpoint content on success, or a failure cause when the patch
276
+ cannot be applied against the targeted checkpoint.
271
277
  .DESCRIPTION
272
- Implements the read-then-validate Edit path. When the tool input carries
273
- an old_string (an Edit patch), the checkpoint at the caller-supplied
274
- CheckpointPath (the Edit's targeted file_path) is read through the
275
- injectable CheckpointReader seam and the old_string -> new_string
276
- replacement is applied in memory (no on-disk mutation). Returns $null
277
- when there is no old_string, the targeted file does not exist or is empty, or the
278
- old_string is not present in the on-disk content, signalling the caller
279
- to allow (defer).
278
+ Implements the read-then-validate Edit path. The checkpoint at the
279
+ caller-supplied CheckpointPath (the Edit's targeted file_path, passed to
280
+ the reader unchanged) is read through the injectable CheckpointReader seam
281
+ and the old_string -> new_string replacement is applied in memory (no
282
+ on-disk mutation). One occurrence of old_string is replaced unless
283
+ replace_all is true. Failure is one of no-old_string (absent or empty
284
+ old_string), checkpoint-unreadable (the reader threw), checkpoint-missing
285
+ (the reader returned $null), checkpoint-empty (the reader returned an
286
+ empty string), old_string-not-found, or old_string-ambiguous. Content is
287
+ $null on failure and Failure is $null on success. The caller denies on
288
+ any Failure.
280
289
  #>
281
290
  [CmdletBinding()]
282
- [OutputType([string])]
291
+ [OutputType([pscustomobject])]
283
292
  param(
284
293
  [Parameter(Mandatory)]
285
294
  [AllowNull()]
@@ -297,7 +306,7 @@ function Resolve-EditedCheckpointContent {
297
306
  $oldString = [string]$ToolInput.old_string
298
307
  }
299
308
  if ([string]::IsNullOrEmpty($oldString)) {
300
- return $null
309
+ return [pscustomobject]@{ Content = $null; Failure = 'no-old_string' }
301
310
  }
302
311
 
303
312
  $newString = ''
@@ -305,20 +314,46 @@ function Resolve-EditedCheckpointContent {
305
314
  $newString = [string]$ToolInput.new_string
306
315
  }
307
316
 
308
- $onDisk = & $CheckpointReader $CheckpointPath
309
- if ([string]::IsNullOrEmpty([string]$onDisk)) {
310
- # The on-disk checkpoint does not exist (or is empty); cannot patch.
311
- return $null
317
+ try {
318
+ $onDisk = & $CheckpointReader $CheckpointPath
319
+ }
320
+ catch {
321
+ # A reader exception must not escape the gate; report it as a failure cause.
322
+ return [pscustomobject]@{ Content = $null; Failure = 'checkpoint-unreadable' }
323
+ }
324
+ if ($null -eq $onDisk) {
325
+ return [pscustomobject]@{ Content = $null; Failure = 'checkpoint-missing' }
312
326
  }
313
327
 
314
328
  $onDiskText = [string]$onDisk
315
- if (-not $onDiskText.Contains($oldString)) {
316
- # The old_string is not present, so the patch does not apply here.
317
- return $null
329
+ if ($onDiskText.Length -eq 0) {
330
+ return [pscustomobject]@{ Content = $null; Failure = 'checkpoint-empty' }
318
331
  }
319
332
 
320
- # Apply the patch in memory using a literal (non-regex) replacement.
321
- return $onDiskText.Replace($oldString, $newString)
333
+ # Apply the patch in memory; one occurrence unless replace_all is true.
334
+ $replaceAll = Get-EditReplaceAllFlag -ToolInput $ToolInput
335
+ return Invoke-SingleOccurrenceEdit -Text $onDiskText -OldString $oldString -NewString $newString -ReplaceAll $replaceAll
336
+ }
337
+
338
+ function ConvertTo-CompletionDenyDecision {
339
+ <#
340
+ .SYNOPSIS
341
+ Builds the PreToolUse deny decision for the given reason.
342
+ #>
343
+ [CmdletBinding()]
344
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
345
+ param(
346
+ [Parameter(Mandatory)]
347
+ [string] $Reason
348
+ )
349
+
350
+ return [ordered]@{
351
+ hookSpecificOutput = [ordered]@{
352
+ hookEventName = 'PreToolUse'
353
+ permissionDecision = 'deny'
354
+ permissionDecisionReason = $Reason
355
+ }
356
+ }
322
357
  }
323
358
 
324
359
  function Invoke-CompletionConsistencyDecision {
@@ -341,6 +376,8 @@ function Invoke-CompletionConsistencyDecision {
341
376
  [Parameter(Mandatory = $false)]
342
377
  [scriptblock] $RoutingMatrixReader
343
378
  )
379
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'COMPLETION_CONSISTENCY_BLOCKED:'
380
+ if ($null -ne $dependencyDecision) { return $dependencyDecision }
344
381
 
345
382
  $envelope = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
346
383
  if (-not $envelope.IsValid) {
@@ -366,18 +403,25 @@ function Invoke-CompletionConsistencyDecision {
366
403
  return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
367
404
  }
368
405
 
369
- # Write tool: validate the content payload directly. Edit tool: no content is
370
- # supplied, so read the on-disk checkpoint through the injectable seam and
371
- # apply the old_string -> new_string patch in memory (read-then-validate).
372
- $content = Get-ClaudeHookToolInputString -ToolInput $toolInput -Name 'content'
373
- if (-not $content) {
374
- $content = Resolve-EditedCheckpointContent -ToolInput $toolInput -CheckpointReader $CheckpointReader -CheckpointPath $filePath
406
+ # Write tool: a present content property is validated directly, and empty
407
+ # content is denied. Edit tool: no content property is supplied, so read the
408
+ # targeted checkpoint through the injectable seam and apply the old_string ->
409
+ # new_string patch in memory (read-then-validate). Any failure cause denies.
410
+ $cause = $null
411
+ if ($toolInput.PSObject.Properties.Name -contains 'content') {
412
+ $content = Get-ClaudeHookToolInputString -ToolInput $toolInput -Name 'content'
375
413
  if (-not $content) {
376
- # No content, and the Edit could not be resolved against on-disk
377
- # state (missing file or non-matching patch): defer and allow.
378
- return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
414
+ $cause = 'write-content-empty'
379
415
  }
380
416
  }
417
+ else {
418
+ $edit = Resolve-EditedCheckpointContent -ToolInput $toolInput -CheckpointReader $CheckpointReader -CheckpointPath $filePath
419
+ $content = $edit.Content
420
+ $cause = $edit.Failure
421
+ }
422
+ if ($cause) {
423
+ return ConvertTo-CompletionDenyDecision -Reason "COMPLETION_CONSISTENCY_BLOCKED: the canonical checkpoint cannot be deleted, emptied, or replaced through an unresolved patch ($cause)."
424
+ }
381
425
 
382
426
  try {
383
427
  $payload = ConvertFrom-CheckpointJson -Json $content
@@ -411,12 +455,33 @@ function Invoke-CompletionConsistencyDecision {
411
455
  }
412
456
  }
413
457
 
458
+ function Invoke-CompletionConsistencyEntryPoint {
459
+ <#
460
+ .SYNOPSIS
461
+ Reads the payload and returns the gate decision, failing closed when the read or evaluation throws
462
+ (issue #903, #736 O-4).
463
+ #>
464
+ [CmdletBinding()]
465
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
466
+ param([scriptblock] $ReadPayload = { Read-ClaudeHookRawPayload })
467
+
468
+ try {
469
+ return Invoke-CompletionConsistencyDecision -ToolInputRaw ([string](& $ReadPayload))
470
+ }
471
+ catch {
472
+ return ConvertTo-CompletionDenyDecision -Reason "COMPLETION_CONSISTENCY_BLOCKED: the gate could not evaluate the payload ($($_.Exception.Message)) and fails closed."
473
+ }
474
+ }
475
+
414
476
  # Guard allows dot-sourcing in tests without executing the entrypoint.
415
477
  if ($MyInvocation.InvocationName -eq '.') {
416
478
  return
417
479
  }
480
+ if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('COMPLETION_CONSISTENCY_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
481
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'COMPLETION_CONSISTENCY_BLOCKED:'
482
+ if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
418
483
 
419
- $decision = Invoke-CompletionConsistencyDecision -ToolInputRaw (Read-ClaudeHookRawPayload)
484
+ $decision = Invoke-CompletionConsistencyEntryPoint
420
485
 
421
486
  $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
422
487
 
@@ -11,6 +11,12 @@
11
11
  - Test-IsValidFeatureFolder: rejects sentinel/placeholder feature folders
12
12
  and folders not anchored under docs/features/active/<segment>; optionally
13
13
  verifies on-disk existence through an injectable scriptblock seam.
14
+ - Get-EditReplaceAllFlag: returns the boolean value of the replace_all
15
+ property of an Edit tool input; the string 'true' is honoured and the
16
+ string 'false' is not.
17
+ - Invoke-SingleOccurrenceEdit: pure single-occurrence patch of a text;
18
+ reports a failure cause instead of replacing when old_string is empty,
19
+ absent, or ambiguous.
14
20
 
15
21
  This script is dot-sourced by enforce-completion-consistency.ps1. It contains
16
22
  no entrypoint logic, so dot-sourcing it in tests has no side effects.
@@ -161,3 +167,102 @@ function Test-RouteRequiresPrGate {
161
167
  }
162
168
  return ([bool]$route.requires_pr_gate -eq $true)
163
169
  }
170
+
171
+ function Get-EditReplaceAllFlag {
172
+ <#
173
+ .SYNOPSIS
174
+ Returns the boolean value of the replace_all property of an Edit tool input.
175
+ .DESCRIPTION
176
+ Returns $false when the tool input is null or has no replace_all property.
177
+ Returns the value when it is already a boolean. Returns $true for a string
178
+ whose trimmed, case-insensitive value is 'true'. Returns $false for every
179
+ other value. No [bool] cast is used because [bool]'false' is $true in
180
+ PowerShell.
181
+ #>
182
+ [CmdletBinding()]
183
+ [OutputType([bool])]
184
+ param(
185
+ [Parameter(Mandatory)]
186
+ [AllowNull()]
187
+ $ToolInput
188
+ )
189
+
190
+ if ($null -eq $ToolInput -or -not ($ToolInput.PSObject.Properties.Name -contains 'replace_all')) {
191
+ return $false
192
+ }
193
+
194
+ $value = $ToolInput.replace_all
195
+ if ($value -is [bool]) {
196
+ return $value
197
+ }
198
+ if ($value -is [string]) {
199
+ return $value.Trim() -ieq 'true'
200
+ }
201
+ return $false
202
+ }
203
+
204
+ function Invoke-SingleOccurrenceEdit {
205
+ <#
206
+ .SYNOPSIS
207
+ Applies an old_string to new_string patch to a text in memory and reports
208
+ a failure cause when the patch cannot be applied unambiguously.
209
+ .DESCRIPTION
210
+ Pure function with no I/O. Normalizes CRLF to LF in all three strings. An
211
+ empty OldString yields Failure 'no-old_string'. Zero ordinal occurrences
212
+ yield Failure 'old_string-not-found'. When ReplaceAll is true every
213
+ occurrence is replaced by ordinal String.Replace. Otherwise a second
214
+ ordinal search that starts after the end of the first match yields Failure
215
+ 'old_string-ambiguous' when it finds a match (overlapping candidates are
216
+ counted once), and no match yields a single splice by Substring. No regular
217
+ expression is used, so a '$' in NewString stays literal. Returns an object
218
+ with Content and Failure; Content is $null on failure and Failure is $null
219
+ on success.
220
+ #>
221
+ [CmdletBinding()]
222
+ [OutputType([pscustomobject])]
223
+ param(
224
+ [Parameter(Mandatory)]
225
+ [AllowNull()]
226
+ [AllowEmptyString()]
227
+ [string] $Text,
228
+
229
+ [Parameter(Mandatory)]
230
+ [AllowNull()]
231
+ [AllowEmptyString()]
232
+ [string] $OldString,
233
+
234
+ [Parameter(Mandatory)]
235
+ [AllowNull()]
236
+ [AllowEmptyString()]
237
+ [string] $NewString,
238
+
239
+ [Parameter(Mandatory = $false)]
240
+ [bool] $ReplaceAll = $false
241
+ )
242
+
243
+ $normalizedText = $Text.Replace("`r`n", "`n")
244
+ $normalizedOld = $OldString.Replace("`r`n", "`n")
245
+ $normalizedNew = $NewString.Replace("`r`n", "`n")
246
+
247
+ if ($normalizedOld.Length -eq 0) {
248
+ return [pscustomobject]@{ Content = $null; Failure = 'no-old_string' }
249
+ }
250
+
251
+ $comparison = [System.StringComparison]::Ordinal
252
+ $firstIndex = $normalizedText.IndexOf($normalizedOld, $comparison)
253
+ if ($firstIndex -lt 0) {
254
+ return [pscustomobject]@{ Content = $null; Failure = 'old_string-not-found' }
255
+ }
256
+
257
+ if ($ReplaceAll) {
258
+ return [pscustomobject]@{ Content = $normalizedText.Replace($normalizedOld, $normalizedNew); Failure = $null }
259
+ }
260
+
261
+ $afterFirst = $firstIndex + $normalizedOld.Length
262
+ if ($normalizedText.IndexOf($normalizedOld, $afterFirst, $comparison) -ge 0) {
263
+ return [pscustomobject]@{ Content = $null; Failure = 'old_string-ambiguous' }
264
+ }
265
+
266
+ $spliced = $normalizedText.Substring(0, $firstIndex) + $normalizedNew + $normalizedText.Substring($afterFirst)
267
+ return [pscustomobject]@{ Content = $spliced; Failure = $null }
268
+ }
@@ -32,9 +32,11 @@
32
32
  #>
33
33
  [CmdletBinding()]
34
34
  param()
35
+ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
35
36
 
36
37
 
37
- Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
38
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
39
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryValidation.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryValidation.psm1' -ErrorRecord $_ }
38
40
  function Invoke-DiscoveryValidatorExe {
39
41
  <#
40
42
  .SYNOPSIS
@@ -164,6 +166,8 @@ function Invoke-DiscoveryArtifactGateDecision {
164
166
  [Parameter(Mandatory = $false)]
165
167
  [scriptblock] $RequiredArtifactReader = { Get-RequiredDiscoveryArtifactDeclaration }
166
168
  )
169
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'DISCOVERY_ARTIFACT_GATE_BLOCKED:'
170
+ if ($null -ne $dependencyDecision) { return $dependencyDecision }
167
171
 
168
172
  $envelope = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
169
173
  if (-not $envelope.IsValid) {
@@ -224,6 +228,9 @@ function Invoke-DiscoveryArtifactGateDecision {
224
228
  if ($MyInvocation.InvocationName -eq '.') {
225
229
  return
226
230
  }
231
+ if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('DISCOVERY_ARTIFACT_GATE_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
232
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'DISCOVERY_ARTIFACT_GATE_BLOCKED:'
233
+ if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
227
234
 
228
235
  $decision = Invoke-DiscoveryArtifactGateDecision -ToolInputRaw (Read-ClaudeHookRawPayload)
229
236
 
@@ -42,9 +42,10 @@
42
42
  #>
43
43
  [CmdletBinding()]
44
44
  param()
45
+ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
45
46
 
46
47
 
47
- Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
48
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
48
49
  $script:GatedSubagentTypes = @('epic-planner', 'epic-orchestrator', 'parallel-planner', 'parallel-orchestrator')
49
50
  $script:ParallelSubagentTypes = @('parallel-planner', 'parallel-orchestrator')
50
51
  $script:ProhibitedCallerAgentType = 'orchestrator'
@@ -214,6 +215,8 @@ function Invoke-EpicInvocationOriginDecision {
214
215
  [AllowEmptyString()]
215
216
  [string] $ToolInputRaw
216
217
  )
218
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'EPIC_INVOCATION_ORIGIN_BLOCKED:'
219
+ if ($null -ne $dependencyDecision) { return $dependencyDecision }
217
220
 
218
221
  # The envelope carries both halves: the caller's agent_type at the root and the
219
222
  # delegation target's subagent_type inside tool_input.
@@ -272,6 +275,9 @@ function Invoke-EpicInvocationOriginDecision {
272
275
  if ($MyInvocation.InvocationName -eq '.') {
273
276
  return
274
277
  }
278
+ if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('EPIC_INVOCATION_ORIGIN_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
279
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'EPIC_INVOCATION_ORIGIN_BLOCKED:'
280
+ if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
275
281
 
276
282
  $decision = Invoke-EpicInvocationOriginDecision -HookInputRaw (Read-ClaudeHookRawPayload)
277
283