@danmoisan/drm-copilot-mcp 1.1.25 → 1.1.27

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 (185) hide show
  1. package/out/mcp-server.js +170 -52
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/atomic-executor.md +3 -3
  4. package/resources/claude-customizations/.claude/agents/atomic-planner.md +2 -2
  5. package/resources/claude-customizations/.claude/agents/csharp-typed-engineer.md +7 -7
  6. package/resources/claude-customizations/.claude/agents/feature-review.md +6 -3
  7. package/resources/claude-customizations/.claude/agents/orchestrator.md +11 -7
  8. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +7 -3
  9. package/resources/claude-customizations/.claude/agents/parallel-planner.md +6 -2
  10. package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +7 -7
  11. package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +6 -6
  12. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-review.ps1 +699 -0
  13. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +16 -1
  14. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +41 -0
  15. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-cleanup.ps1 +481 -0
  16. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +3 -1
  17. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier-helpers.ps1 +69 -3
  18. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +2 -2
  19. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.cleanup.ps1 +246 -0
  20. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.docs-only.ps1 +1 -0
  21. package/resources/claude-customizations/.claude/hooks/hook-cleanup-context.ps1 +343 -0
  22. package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-rules.ps1 +50 -16
  23. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +6 -1
  24. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletion.psm1 +4 -2
  25. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletionChecks.psm1 +51 -2
  26. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +5 -2
  27. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRemediationAccounting.psm1 +116 -4
  28. package/resources/claude-customizations/.claude/lib/parallel-admission/Invoke-ParallelAdmission.ps1 +626 -0
  29. package/resources/claude-customizations/.claude/lib/parallel-admission/ParallelAdmission.psm1 +508 -0
  30. package/resources/claude-customizations/.claude/lib/parallel-admission/ParallelAdmissionJson.psm1 +141 -0
  31. package/resources/claude-customizations/.claude/lib/parallel-admission/ParallelAdmissionPrecedence.psm1 +508 -0
  32. package/resources/claude-customizations/.claude/rules/csharp.md +8 -8
  33. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +28 -2
  34. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +17 -1
  35. package/resources/claude-customizations/.claude/rules/powershell.md +2 -2
  36. package/resources/claude-customizations/.claude/settings.json +1 -0
  37. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +9 -13
  38. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +93 -14
  39. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup-worktrees.sh +23 -10
  40. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_lib.sh +54 -46
  41. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_merge_lib.sh +234 -0
  42. package/resources/claude-customizations/.claude/skills/csharp-qa-gate/SKILL.md +14 -15
  43. package/resources/claude-customizations/.claude/skills/epic-progress-report/SKILL.md +338 -0
  44. package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.Logic.ps1 +248 -0
  45. package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.Metrics.ps1 +232 -0
  46. package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.Render.ps1 +196 -0
  47. package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.State.ps1 +286 -0
  48. package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.ps1 +475 -0
  49. package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +31 -16
  50. package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +21 -9
  51. package/resources/claude-customizations/.claude/skills/invoke-csharp-engineer/SKILL.md +5 -5
  52. package/resources/claude-customizations/.claude/skills/invoke-powershell-engineer/SKILL.md +4 -4
  53. package/resources/claude-customizations/.claude/skills/invoke-python-engineer/SKILL.md +4 -4
  54. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +24 -2
  55. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +71 -7
  56. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +34 -2
  57. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +82 -12
  58. package/resources/claude-customizations/.claude/skills/powershell-qa-gate/SKILL.md +16 -17
  59. package/resources/claude-customizations/.claude/skills/python-qa-gate/SKILL.md +15 -16
  60. package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +1 -1
  61. package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +6 -6
  62. package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +13 -14
  63. package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +4 -4
  64. package/resources/claude-customizations/pack-manifests/core.json +15 -0
  65. package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +8 -12
  66. package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +1 -1
  67. package/resources/codex-and-agents-customizations/.agents/skills/csharp-qa-gate/SKILL.md +13 -14
  68. package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +31 -16
  69. package/resources/codex-and-agents-customizations/.agents/skills/feature-review/SKILL.md +5 -1
  70. package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +18 -5
  71. package/resources/codex-and-agents-customizations/.agents/skills/invoke-csharp-engineer/SKILL.md +4 -4
  72. package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +4 -4
  73. package/resources/codex-and-agents-customizations/.agents/skills/invoke-python-engineer/SKILL.md +4 -4
  74. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +24 -2
  75. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +16 -1
  76. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +3 -3
  77. package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +2 -2
  78. package/resources/codex-and-agents-customizations/.agents/skills/powershell-qa-gate/SKILL.md +15 -16
  79. package/resources/codex-and-agents-customizations/.agents/skills/python-qa-gate/SKILL.md +14 -15
  80. package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +2 -0
  81. package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +13 -14
  82. package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +4 -4
  83. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +3 -3
  84. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +3 -3
  85. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +3 -3
  86. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +3 -3
  87. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +3 -3
  88. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +3 -3
  89. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +2 -2
  90. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +2 -2
  91. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +2 -2
  92. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +2 -2
  93. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +2 -2
  94. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +2 -2
  95. package/resources/codex-and-agents-customizations/.codex/agents/csharp-atomic-executor.toml +2 -2
  96. package/resources/codex-and-agents-customizations/.codex/agents/csharp-atomic-planning.toml +1 -1
  97. package/resources/codex-and-agents-customizations/.codex/agents/csharp-orchestrator.toml +1 -1
  98. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c1.toml +6 -6
  99. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c2.toml +6 -6
  100. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3-elevated.toml +6 -6
  101. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3.toml +6 -6
  102. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c4.toml +6 -6
  103. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer.toml +6 -6
  104. package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +2 -2
  105. package/resources/codex-and-agents-customizations/.codex/agents/powershell-atomic-executor.toml +2 -2
  106. package/resources/codex-and-agents-customizations/.codex/agents/powershell-atomic-planning.toml +1 -1
  107. package/resources/codex-and-agents-customizations/.codex/agents/powershell-di-unit-test-engineer.toml +1 -1
  108. package/resources/codex-and-agents-customizations/.codex/agents/powershell-orchestrator.toml +1 -1
  109. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +7 -7
  110. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +7 -7
  111. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +7 -7
  112. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +7 -7
  113. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +7 -7
  114. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +7 -7
  115. package/resources/codex-and-agents-customizations/.codex/agents/pytest-unit-test-coding.toml +1 -1
  116. package/resources/codex-and-agents-customizations/.codex/agents/python-atomic-executor.toml +2 -2
  117. package/resources/codex-and-agents-customizations/.codex/agents/python-atomic-planning.toml +1 -1
  118. package/resources/codex-and-agents-customizations/.codex/agents/python-execution-only-typed.toml +1 -1
  119. package/resources/codex-and-agents-customizations/.codex/agents/python-orchestrator.toml +1 -1
  120. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +6 -6
  121. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +6 -6
  122. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +6 -6
  123. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +6 -6
  124. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +6 -6
  125. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +6 -6
  126. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  127. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate-review.ps1 +699 -0
  128. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +29 -2
  129. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-cleanup.ps1 +481 -0
  130. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +4 -2
  131. package/resources/codex-and-agents-customizations/.codex/hooks/hook-cleanup-context.ps1 +343 -0
  132. package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +51 -4
  133. package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +6 -6
  134. package/resources/codex-and-agents-customizations/pack-manifests/core.json +3 -0
  135. package/resources/customizations/.github/agents/5.1-Beast-adjusted.agent.md +1 -1
  136. package/resources/customizations/.github/agents/Powershell DI Unit Test Engineer.agent.md +20 -26
  137. package/resources/customizations/.github/agents/atomic_executor.agent.md +4 -4
  138. package/resources/customizations/.github/agents/atomic_planning.agent.md +4 -4
  139. package/resources/customizations/.github/agents/commentary-remediation.agent.md +1 -1
  140. package/resources/customizations/.github/agents/commit-steward.agent.md +1 -1
  141. package/resources/customizations/.github/agents/csharp-atomic-executor.agent.md +14 -14
  142. package/resources/customizations/.github/agents/csharp-atomic-planning.agent.md +2 -2
  143. package/resources/customizations/.github/agents/csharp-orchestrator.agent.md +4 -4
  144. package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +19 -22
  145. package/resources/customizations/.github/agents/epic-review.agent.md +1 -1
  146. package/resources/customizations/.github/agents/gpt-5-beast-mode.agent.md +3 -3
  147. package/resources/customizations/.github/agents/orchestrator.agent.md +8 -8
  148. package/resources/customizations/.github/agents/powershell-atomic-executor.agent.md +12 -12
  149. package/resources/customizations/.github/agents/powershell-atomic-planning.agent.md +4 -4
  150. package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +6 -6
  151. package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +21 -24
  152. package/resources/customizations/.github/agents/pytest-unit-test-coding.agent.md +13 -17
  153. package/resources/customizations/.github/agents/python-atomic-executor.agent.md +11 -11
  154. package/resources/customizations/.github/agents/python-atomic-planning.agent.md +2 -2
  155. package/resources/customizations/.github/agents/python-execution-only-typed.agent.md +14 -17
  156. package/resources/customizations/.github/agents/python-orchestrator.agent.md +7 -7
  157. package/resources/customizations/.github/agents/python-typed-engineer.agent.md +17 -20
  158. package/resources/customizations/.github/agents/staged-review.agent.md +2 -2
  159. package/resources/customizations/.github/agents/tdd-green.agent.md +1 -1
  160. package/resources/customizations/.github/agents/tdd-red.agent.md +3 -3
  161. package/resources/customizations/.github/agents/tdd-refactor.agent.md +2 -2
  162. package/resources/customizations/.github/agents/typescript-engineer.agent.md +3 -3
  163. package/resources/customizations/.github/codex/execute-hard-lock.prompt.md +3 -3
  164. package/resources/customizations/.github/instructions/general-code-change.instructions.md +1 -1
  165. package/resources/customizations/.github/instructions/powershell-unit-test.instructions.md +1 -1
  166. package/resources/customizations/.github/instructions/python-code-change.instructions.md +5 -5
  167. package/resources/customizations/.github/instructions/python-suppressions.instructions.md +3 -3
  168. package/resources/customizations/.github/instructions/python-unit-test.instructions.md +1 -1
  169. package/resources/customizations/.github/prompts/execute-hard-lock.prompt.md +2 -2
  170. package/resources/customizations/.github/prompts/generate-commit-message-repo.prompt.md +2 -2
  171. package/resources/customizations/.github/prompts/remediate-comments.prompt.md +2 -2
  172. package/resources/customizations/.github/prompts/review-feature.prompt.md +1 -1
  173. package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +8 -12
  174. package/resources/customizations/.github/skills/execution-environment/SKILL.md +31 -16
  175. package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +15 -2
  176. package/resources/feature-templates/bug/plan.yyyy-MM-ddTHH-mm.md +4 -4
  177. package/resources/feature-templates/feature/plan.yyyy-MM-ddTHH-mm.md +3 -3
  178. package/resources/feature-templates/refactor/plan.yyyy-MM-ddTHH-mm.md +2 -2
  179. package/resources/powershell/PoshQC/PoshQC.ScanConfig.psm1 +3 -1
  180. package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +14 -12
  181. package/resources/powershell/PoshQC/PoshQC.psm1 +3 -3
  182. package/resources/powershell/PoshQC/README.md +8 -8
  183. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +2 -2
  184. package/resources/templates/policy_audit/AGENTS.md +9 -10
  185. package/resources/templates/policy_audit/policy-audit.yyyy-MM-ddTHH-mm.md +19 -20
@@ -45,7 +45,11 @@
45
45
  enqueues the pull request; only a pure --disable-auto is out of scope. The predicate and the
46
46
  PR selector reader live in enforce-epic-merge-gate-command.ps1.
47
47
 
48
- Design decision: branches 1-4 trust the on-disk checkpoint and never call gh. The green-CI
48
+ Design decision: without review_mode concurrent, branches 1-4 trust the on-disk checkpoint and
49
+ never call gh. Under that key the review guard (enforce-epic-merge-gate-review.ps1, issue #1097)
50
+ runs before branch 1 and makes one bounded gh pr view call and at most two git calls, and it
51
+ denies a merge with no passing review at a current head. Its shape check precedes the
52
+ out-of-scope allow and denies text that could be a merge and cannot be bound. The green-CI
49
53
  branch makes at most one bounded gh pr view call, only after its pure stages A to C pass.
50
54
  Its parallel exclusion is best-effort: the Parallel mode: true marker is in the delegation
51
55
  prompt, not in a Bash payload, so a parallel child that no checkpoint records yet is not
@@ -100,6 +104,8 @@ try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-command.ps1') } catch
100
104
  try { . (Join-Path $PSScriptRoot 'hook-docs-only-diff.ps1') } catch { Add-HookDependencyFailure -Name 'hook-docs-only-diff.ps1' -ErrorRecord $_ }
101
105
  # Green-CI standalone merge branch: pure checks, the live-head seam, and the stage orchestrator (issue #997).
102
106
  try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-green-ci.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-green-ci.ps1' -ErrorRecord $_ }
107
+ # Review guard under review_mode concurrent, byte-identical to the Codex copy (issue #1097).
108
+ try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-review.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-review.ps1' -ErrorRecord $_ }
103
109
 
104
110
  function ConvertFrom-EpicMergeGateJson {
105
111
  <#
@@ -349,6 +355,9 @@ function Invoke-EpicMergeGateDecision {
349
355
  # Scope (issue #952): every gh pr merge invocation except a pure --disable-auto, and every gh api
350
356
  # merge or enqueue mutation, is gated; Test-EpicMergeGateCommandInScope holds the predicate.
351
357
  if (-not (Test-EpicMergeGateCommandInScope -CommandText $commandText)) {
358
+ if (-not (Test-EpicMergeGateReviewMergeText -CommandText $commandText)) { return Get-EpicMergeGateAllowDecision }
359
+ $unscopedDecision = Get-EpicMergeGateReviewUnscopedDecision -CommandText $commandText
360
+ if ($unscopedDecision.Denied) { return Get-EpicMergeGateBlockDecision -Reason $unscopedDecision.Reason }
352
361
  return Get-EpicMergeGateAllowDecision
353
362
  }
354
363
 
@@ -366,6 +375,12 @@ function Invoke-EpicMergeGateDecision {
366
375
  $childRoot = if (@('SessionRoot', 'OtherWorktree') -contains $itemTarget.Status) { $itemTarget.WorktreeRoot } else { $null }
367
376
  }
368
377
  $childCheckpoint = if ($childRoot) { ConvertFrom-EpicMergeGateJson -Raw (Get-ChildOrchestratorCheckpointContent -Path (Get-WorktreeRunCheckpointPath -Kind item -WorktreeRoot $childRoot)) } else { $null }
378
+
379
+ # Review guard (issue #1097): inert without review_mode concurrent, so it precedes every allow branch.
380
+ $reviewDecision = Get-EpicMergeGateReviewShapeDecision -CommandText $commandText -KnownCheckpoint $childCheckpoint -KnownPrNumber $commandPrNumber -ReadCheckpoint ${function:Read-EpicMergeGateReviewItemCheckpoint}
381
+ if (-not $reviewDecision.Denied) { $reviewDecision = Get-EpicMergeGateReviewDecision -Checkpoint $childCheckpoint -CommandPrNumber $commandPrNumber -GhRoot $sessionRoot -GitRoot $childRoot }
382
+ if ($reviewDecision.Denied) { return Get-EpicMergeGateBlockDecision -Reason $reviewDecision.Reason }
383
+
369
384
  if ((Test-ChildCheckpointAllowsEpicMerge -Checkpoint $childCheckpoint) -and
370
385
  (Test-ChildCheckpointPrGateBinding -Checkpoint $childCheckpoint -CommandPrNumber $commandPrNumber)) {
371
386
  return Get-EpicMergeGateAllowDecision
@@ -62,6 +62,9 @@ try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.ps
62
62
  try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeItemResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeItemResolution.psm1' -ErrorRecord $_ }
63
63
  # Epic scope (issue #663): a delegation for the epic integration branch is gated against the epic checkpoint.
64
64
  try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/EpicScopeResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'EpicScopeResolution.psm1' -ErrorRecord $_ }
65
+ # Cleanup consolidation delegation (issue #1026): the session-root manifest and the shared predicates.
66
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/cleanup-manifest/CleanupWorktreeManifest.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'CleanupWorktreeManifest.psm1' -ErrorRecord $_ }
67
+ try { . (Join-Path $PSScriptRoot 'hook-cleanup-context.ps1') } catch { Add-HookDependencyFailure -Name 'hook-cleanup-context.ps1' -ErrorRecord $_ }
65
68
  function Get-ModelRoutingCheckpoint {
66
69
  <#
67
70
  .SYNOPSIS
@@ -244,6 +247,38 @@ function Get-ModelRoutingAuthorAssertedReason {
244
247
  return "MODEL_ROUTING_RECEIPT_BLOCKED: AUTHOR_ASSERTED_AUTOCLOSE_MISSING: the checkpoint records issue-num $issue, so the Agent(pr-author) delegation prompt must carry the line '$line' so the PR body emits '- Closes #$issue'. Add that line on its own line, or omit it only for an epic child (epic_mode true)."
245
248
  }
246
249
 
250
+ function Test-ModelRoutingCleanupDelegation {
251
+ <#
252
+ .SYNOPSIS
253
+ Return $true when a pr-author delegation is the cleanup consolidation delegation (issue #1026).
254
+ .DESCRIPTION
255
+ The prompt must carry exactly one 'branch: documentationandmemories' label and no --head
256
+ or --branch token (Test-CleanupConsolidationBranchLabel), checked first so no other
257
+ prompt reaches the manifest read. Then the session-root manifest read through
258
+ Get-CleanupWorktreeManifestContent must pass Test-CleanupManifestText with the
259
+ Get-CleanupWorktreeManifestUtcNow clock. Never throws: any error answers $false.
260
+ .PARAMETER PromptText
261
+ The delegation prompt.
262
+ .OUTPUTS
263
+ System.Boolean
264
+ #>
265
+ [CmdletBinding()]
266
+ [OutputType([bool])]
267
+ param(
268
+ [Parameter(Mandatory)][AllowEmptyString()][string] $PromptText
269
+ )
270
+
271
+ try {
272
+ if (-not (Test-CleanupConsolidationBranchLabel -PromptText $PromptText)) {
273
+ return $false
274
+ }
275
+ return (Test-CleanupManifestText -Raw (Get-CleanupWorktreeManifestContent) -UtcNow (Get-CleanupWorktreeManifestUtcNow))
276
+ }
277
+ catch {
278
+ return $false
279
+ }
280
+ }
281
+
247
282
  function Invoke-ModelRoutingReceiptDecision {
248
283
  <#
249
284
  .SYNOPSIS
@@ -328,6 +363,12 @@ function Invoke-ModelRoutingReceiptDecision {
328
363
  return $allow
329
364
  }
330
365
 
366
+ # Issue #1026: the cleanup consolidation delegation has no checkpoint by design; a fresh valid
367
+ # session-root manifest and the exact consolidation branch label stand in for the receipt.
368
+ if ($subagent -eq 'pr-author' -and $null -eq $checkpoint -and (Test-ModelRoutingCleanupDelegation -PromptText $prompt)) {
369
+ return $allow
370
+ }
371
+
331
372
  return [ordered]@{
332
373
  hookSpecificOutput = [ordered]@{
333
374
  hookEventName = 'PreToolUse'
@@ -0,0 +1,481 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Cleanup-context allow legs of the preimplementation gate (issue #1026).
4
+
5
+ .DESCRIPTION
6
+ Dot-sourced by enforce-orchestration-preimplementation-gate.ps1 on the Claude and the Codex
7
+ surface. The gate calls Get-OrchestrationCleanupContextResult only where it would otherwise
8
+ return its final PREIMPLEMENTATION_GATE_BLOCKED deny, after the epic-scope decision, the
9
+ delegation-mode branch, and the unresolved-target and ambiguous-resolution denies, so every
10
+ existing deny path, ordering, and wording is unchanged when no leg applies. Four legs:
11
+
12
+ Manifest Write - the session-root spelling of
13
+ artifacts/orchestration/cleanup-worktrees-manifest.json (the relative
14
+ literal or its join with the session root), with content that passes
15
+ Test-CleanupManifestText. An Edit carries no content and is denied.
16
+ With -AllowPatchManifest (Codex only) a patch whose single marker is
17
+ '*** Add File:' naming the manifest is admitted on its '+' lines.
18
+ Scratch Write/Edit - an absolute local path under a temporary root, outside every
19
+ registered worktree and the session root (Test-CleanupScratchPath).
20
+ Consolidation Write - an absolute path inside the consolidation worktree (porcelain branch
21
+ documentationandmemories) whose relative remainder is allowlisted,
22
+ including the pr-author receipt artifacts/pr_body_<digits>.receipt.json.
23
+ Consolidation git - every chained segment is git -C <absolute root> add or commit, every
24
+ target is the consolidation root; add takes only '--' and allowlisted
25
+ operands; commit takes only -m, --message, -F, --file, and --trailer
26
+ with values, no pathspec, and a non-empty, entirely allowlisted staged
27
+ index read through Get-OrchestrationGateStagedIndex.
28
+
29
+ Pure prefilters run before any process: the manifest leg compares the path literal; the
30
+ scratch leg requires an absolute local path under a temporary root; the consolidation Write
31
+ leg requires a /docs/, /.claude/agent-memory/, or receipt tail; the command leg requires
32
+ every segment to be the git -C add or commit shape. Only then is the listing seam
33
+ Get-OrchestrationGateWorktreeList called, and the staged-index seam only after every target
34
+ matches the consolidation root. Get-OrchestrationCleanupContextResult never throws: any
35
+ error answers Allowed $false, which keeps the gate's deny. Test-OrchestrationCleanupContextAllowed
36
+ is its Boolean wrapper.
37
+
38
+ DenySuffix, which the gate appends to its deny reason, is empty unless a leg has identified
39
+ the consolidation worktree as the target (the Write leg: the enclosing root is the
40
+ consolidation root; the git leg: every resolved target is that root) and then fails. It is
41
+ then ' Cleanup leg not applied: ' followed by one sub-code and a short detail:
42
+
43
+ CLEANUP_LEG_PATH_NOT_ALLOWLISTED - Write leg: the relative remainder is not allowlisted.
44
+ CLEANUP_LEG_STAGED_INDEX_UNREADABLE - commit: the staged-index read failed, exited
45
+ non-zero, or is not NUL-terminated.
46
+ CLEANUP_LEG_STAGED_INDEX_EMPTY - commit: the staged index lists no path.
47
+ CLEANUP_LEG_STAGED_PATH_NOT_ALLOWLISTED - commit: a staged path is not allowlisted.
48
+
49
+ A pure-prefilter rejection, the manifest and scratch legs, an empty or failed listing, a
50
+ missing consolidation record, an unresolved or other target, and any error leave DenySuffix
51
+ empty, so every other deny text is unchanged.
52
+
53
+ .NOTES
54
+ PowerShell 7+. Byte-identical at .claude/hooks, .codex/hooks, and both bundle mirrors.
55
+ Call-time dependencies loaded by each gate: Get-OrchestrationGateWorktreeList (epic-scope
56
+ sibling), ConvertFrom-HookTargetWorktreePorcelain, Get-HookTargetWorktreeEnclosingRoot, and
57
+ Get-HookTargetWorktreeRelativePath (HookTargetWorktree.psm1 on Claude, hook-target-worktree.ps1
58
+ on Codex), Get-OrchestrationCommandTarget and $script:OrchestrationTargetNonLocalPattern
59
+ (targets file), and Split-OrchestrationCommandLine, Test-OrchestrationCommandTextUnresolvable,
60
+ and ConvertTo-OrchestrationCommandToken (helpers file).
61
+ #>
62
+
63
+ try { . (Join-Path $PSScriptRoot 'hook-cleanup-context.ps1') } catch { Add-HookDependencyFailure -Name 'hook-cleanup-context.ps1' -ErrorRecord $_ }
64
+ try { . (Join-Path $PSScriptRoot 'hook-docs-only-diff.ps1') } catch { Add-HookDependencyFailure -Name 'hook-docs-only-diff.ps1' -ErrorRecord $_ }
65
+
66
+ function Get-OrchestrationGateTemporaryRoot {
67
+ <#
68
+ .SYNOPSIS
69
+ Return every accepted spelling of the operating-system temporary root (test seam).
70
+ .DESCRIPTION
71
+ The distinct non-empty values of [System.IO.Path]::GetTempPath(), TEMP, TMP, and TMPDIR.
72
+ GetTempPath() supplies the long-name spelling on Windows, while the TEMP value the
73
+ runtime hands to the Write tool can carry the 8.3 short-name spelling, so both are
74
+ accepted. Tests mock this function.
75
+ .OUTPUTS
76
+ System.String[]
77
+ #>
78
+ [CmdletBinding()]
79
+ [OutputType([string[]])]
80
+ param()
81
+
82
+ $values = [System.Collections.Generic.List[string]]::new()
83
+ foreach ($value in @([System.IO.Path]::GetTempPath(), $env:TEMP, $env:TMP, $env:TMPDIR)) {
84
+ if (-not [string]::IsNullOrWhiteSpace($value) -and $values -cnotcontains $value) {
85
+ $values.Add($value)
86
+ }
87
+ }
88
+ return [string[]]$values.ToArray()
89
+ }
90
+
91
+ function Get-OrchestrationGateStagedIndex {
92
+ <#
93
+ .SYNOPSIS
94
+ Read the staged paths of a worktree as NUL-terminated names (test seam).
95
+ .DESCRIPTION
96
+ Runs git diff --cached --name-only -z --no-renames --no-color --no-ext-diff --no-relative
97
+ in the worktree through the bounded Invoke-DocsOnlyGitExe seam, which never throws and
98
+ reports ExitCode -1 for a root that is not a directory. Tests mock this function.
99
+ .PARAMETER WorktreeRoot
100
+ The consolidation worktree root.
101
+ .OUTPUTS
102
+ System.Management.Automation.PSCustomObject with ExitCode and Output.
103
+ #>
104
+ [CmdletBinding()]
105
+ [OutputType([pscustomobject])]
106
+ param([Parameter(Mandatory)][string] $WorktreeRoot)
107
+
108
+ return (Invoke-DocsOnlyGitExe -WorktreeRoot $WorktreeRoot -GitArgs @('diff', '--cached', '--name-only', '-z', '--no-renames', '--no-color', '--no-ext-diff', '--no-relative'))
109
+ }
110
+
111
+ function Get-OrchestrationCleanupListingRecord {
112
+ <#
113
+ .SYNOPSIS
114
+ Return the porcelain records of the session root's worktree listing, or an empty array.
115
+ .OUTPUTS
116
+ System.Object[]
117
+ #>
118
+ [CmdletBinding()]
119
+ [OutputType([object[]])]
120
+ param([Parameter(Mandatory)][string] $SessionRoot)
121
+
122
+ $text = [string](Get-OrchestrationGateWorktreeList -SessionRoot $SessionRoot)
123
+ return @(ConvertFrom-HookTargetWorktreePorcelain -Text $text)
124
+ }
125
+
126
+ function ConvertTo-OrchestrationCleanupComparablePath {
127
+ <#
128
+ .SYNOPSIS
129
+ Normalize separators and upper-case a leading drive letter, so only the drive letter
130
+ is compared without case.
131
+ .OUTPUTS
132
+ System.String
133
+ #>
134
+ [CmdletBinding()]
135
+ [OutputType([string])]
136
+ param([AllowNull()][AllowEmptyString()][string] $Path)
137
+
138
+ $normalized = ConvertTo-CleanupContextPath -Path $Path
139
+ if ($normalized -match '^[A-Za-z]:') {
140
+ $normalized = $normalized.Substring(0, 1).ToUpperInvariant() + $normalized.Substring(1)
141
+ }
142
+ return $normalized
143
+ }
144
+
145
+ function Test-OrchestrationCleanupManifestPath {
146
+ <#
147
+ .SYNOPSIS
148
+ Return $true when a path is the session-root spelling of the cleanup manifest.
149
+ .OUTPUTS
150
+ System.Boolean
151
+ #>
152
+ [CmdletBinding()]
153
+ [OutputType([bool])]
154
+ param([AllowNull()][AllowEmptyString()][string] $Path, [Parameter(Mandatory)][string] $SessionRoot)
155
+
156
+ $relative = $script:CleanupContextManifestRelativePath
157
+ $candidate = ConvertTo-OrchestrationCleanupComparablePath -Path $Path
158
+ if ($candidate -ceq $relative) {
159
+ return $true
160
+ }
161
+ $root = ConvertTo-OrchestrationCleanupComparablePath -Path $SessionRoot
162
+ $joined = if ($root.EndsWith('/')) { $root + $relative } else { $root + '/' + $relative }
163
+ return ($candidate -ceq $joined)
164
+ }
165
+
166
+ function Get-OrchestrationCleanupPatchManifestContent {
167
+ <#
168
+ .SYNOPSIS
169
+ Return the added text of a patch whose single marker adds the manifest, or $null.
170
+ .DESCRIPTION
171
+ The first non-empty line must be '*** Begin Patch' and the patch must carry exactly one
172
+ file marker, an '*** Add File:' naming the manifest. The content is the '+' lines that
173
+ follow the marker, with the '+' removed, up to the next '***' line.
174
+ .OUTPUTS
175
+ System.String or $null
176
+ #>
177
+ [CmdletBinding()]
178
+ [OutputType([string])]
179
+ param([Parameter(Mandatory)][AllowEmptyString()][string] $PatchText, [Parameter(Mandatory)][string] $SessionRoot)
180
+
181
+ $markers = @(Get-OrchestrationPatchMarkerPath -PatchText $PatchText)
182
+ $lines = @($PatchText -split "`r?`n")
183
+ $markerLines = @($lines | Where-Object { $_ -cmatch '^\*\*\* (Add File|Update File|Delete File|Move to):' })
184
+ if ($markers.Count -ne 1 -or $markerLines.Count -ne 1 -or -not $markerLines[0].StartsWith('*** Add File:', [System.StringComparison]::Ordinal)) {
185
+ return $null
186
+ }
187
+ if (-not (Test-OrchestrationCleanupManifestPath -Path $markers[0] -SessionRoot $SessionRoot)) {
188
+ return $null
189
+ }
190
+ $added = [System.Collections.Generic.List[string]]::new()
191
+ $inside = $false
192
+ foreach ($line in $lines) {
193
+ if ($line.StartsWith('*** Add File:', [System.StringComparison]::Ordinal)) { $inside = $true; continue }
194
+ if (-not $inside) { continue }
195
+ if ($line.StartsWith('***', [System.StringComparison]::Ordinal)) { break }
196
+ if (-not $line.StartsWith('+', [System.StringComparison]::Ordinal)) { return $null }
197
+ $added.Add($line.Substring(1))
198
+ }
199
+ return ($added -join "`n")
200
+ }
201
+
202
+ function Test-OrchestrationCleanupScratchWrite {
203
+ <#
204
+ .SYNOPSIS
205
+ Scratch leg: an absolute local path under a temporary root and outside every worktree.
206
+ .OUTPUTS
207
+ System.Boolean
208
+ #>
209
+ [CmdletBinding()]
210
+ [OutputType([bool])]
211
+ param([Parameter(Mandatory)][string] $FilePath, [Parameter(Mandatory)][string] $SessionRoot)
212
+
213
+ if ($FilePath -match $script:OrchestrationTargetNonLocalPattern) {
214
+ return $false
215
+ }
216
+ $normalized = ConvertTo-CleanupContextPath -Path $FilePath
217
+ if ($normalized -notmatch '^([A-Za-z]:/|/(?!/))') {
218
+ return $false
219
+ }
220
+ $temporary = [string[]]@(Get-OrchestrationGateTemporaryRoot | ForEach-Object { $_ })
221
+ if (@($temporary | Where-Object { Test-CleanupContextPathUnder -Path $normalized -Root $_ }).Count -eq 0) {
222
+ return $false
223
+ }
224
+ $records = @(Get-OrchestrationCleanupListingRecord -SessionRoot $SessionRoot)
225
+ if ($records.Count -eq 0) {
226
+ return $false
227
+ }
228
+ $roots = [string[]]@($records | ForEach-Object { [string]$_.Root })
229
+ if ($null -ne (Get-HookTargetWorktreeEnclosingRoot -Path $normalized -Roots $roots)) {
230
+ return $false
231
+ }
232
+ return (Test-CleanupScratchPath -Path $FilePath -RegisteredRoot $roots -SessionRoot $SessionRoot -TemporaryRoot $temporary)
233
+ }
234
+
235
+ function Get-OrchestrationCleanupConsolidationWriteResult {
236
+ <#
237
+ .SYNOPSIS
238
+ Consolidation Write/Edit leg: an allowlisted path inside the consolidation worktree.
239
+ .DESCRIPTION
240
+ DenySuffix names CLEANUP_LEG_PATH_NOT_ALLOWLISTED only when the enclosing root is the
241
+ consolidation root; every earlier rejection leaves it empty.
242
+ .OUTPUTS
243
+ System.Management.Automation.PSCustomObject with Allowed and DenySuffix.
244
+ #>
245
+ [CmdletBinding()]
246
+ [OutputType([pscustomobject])]
247
+ param([Parameter(Mandatory)][string] $FilePath, [Parameter(Mandatory)][string] $SessionRoot)
248
+
249
+ $denied = [pscustomobject]@{ Allowed = $false; DenySuffix = '' }
250
+ if ($FilePath -match $script:OrchestrationTargetNonLocalPattern) {
251
+ return $denied
252
+ }
253
+ $normalized = ConvertTo-CleanupContextPath -Path $FilePath
254
+ if ($normalized -notmatch '^([A-Za-z]:/|/(?!/))' -or $normalized -cnotmatch '/docs/|/\.claude/agent-memory/|/artifacts/pr_body_[0-9]+\.receipt\.json$') {
255
+ return $denied
256
+ }
257
+ $records = @(Get-OrchestrationCleanupListingRecord -SessionRoot $SessionRoot)
258
+ $record = Find-CleanupConsolidationRecord -Record $records
259
+ if ($null -eq $record) {
260
+ return $denied
261
+ }
262
+ $enclosing = Get-HookTargetWorktreeEnclosingRoot -Path $normalized -Roots ([string[]]@($records | ForEach-Object { [string]$_.Root }))
263
+ if (-not [string]::Equals([string]$enclosing, [string]$record.Root, [System.StringComparison]::OrdinalIgnoreCase)) {
264
+ return $denied
265
+ }
266
+ $relative = Get-HookTargetWorktreeRelativePath -Path $normalized -WorktreeRoot $record.Root
267
+ if (Test-CleanupConsolidationRelativePath -RelativePath $relative -AllowReceipt) {
268
+ return [pscustomobject]@{ Allowed = $true; DenySuffix = '' }
269
+ }
270
+ return [pscustomobject]@{ Allowed = $false; DenySuffix = " Cleanup leg not applied: CLEANUP_LEG_PATH_NOT_ALLOWLISTED: '$relative' is not an allowlisted path in the consolidation worktree." }
271
+ }
272
+
273
+ function Test-OrchestrationCleanupSegmentShape {
274
+ <#
275
+ .SYNOPSIS
276
+ Return the subcommand of a git -C <absolute> add|commit segment whose options the leg
277
+ admits, or $null.
278
+ .DESCRIPTION
279
+ add admits only '--' and at least one allowlisted operand. commit admits only -m,
280
+ --message, -F, --file, and --trailer, each with a separate value or an '=' value, and no
281
+ pathspec.
282
+ .OUTPUTS
283
+ System.String or $null
284
+ #>
285
+ [CmdletBinding()]
286
+ [OutputType([string])]
287
+ param([Parameter(Mandatory)][AllowEmptyCollection()][AllowEmptyString()][string[]] $Token)
288
+
289
+ if ($Token.Count -lt 4 -or $Token[0] -cne 'git' -or $Token[1] -cne '-C' -or $Token[2] -notmatch '^([A-Za-z]:/|/(?!/))') {
290
+ return $null
291
+ }
292
+ $subcommand = $Token[3]
293
+ $rest = if ($Token.Count -gt 4) { @($Token[4..($Token.Count - 1)]) } else { @() }
294
+ if ($subcommand -ceq 'add') {
295
+ $operands = @($rest | Where-Object { $_ -cne '--' })
296
+ if (($rest.Count - $operands.Count) -gt 1 -or $operands.Count -eq 0) {
297
+ return $null
298
+ }
299
+ foreach ($operand in $operands) {
300
+ if ($operand.StartsWith('-') -or -not (Test-CleanupConsolidationRelativePath -RelativePath $operand)) {
301
+ return $null
302
+ }
303
+ }
304
+ return 'add'
305
+ }
306
+ if ($subcommand -cne 'commit') {
307
+ return $null
308
+ }
309
+ $valueOptions = @('-m', '--message', '-F', '--file', '--trailer')
310
+ for ($index = 0; $index -lt $rest.Count; $index++) {
311
+ $item = $rest[$index]
312
+ if ($valueOptions -ccontains $item) {
313
+ $index++
314
+ if ($index -ge $rest.Count) {
315
+ return $null
316
+ }
317
+ continue
318
+ }
319
+ $name = if ($item.IndexOf('=') -gt 0) { $item.Substring(0, $item.IndexOf('=')) } else { '' }
320
+ if (@('--message', '--file', '--trailer') -ccontains $name) {
321
+ continue
322
+ }
323
+ return $null
324
+ }
325
+ return 'commit'
326
+ }
327
+
328
+ function Get-OrchestrationCleanupConsolidationCommandResult {
329
+ <#
330
+ .SYNOPSIS
331
+ Consolidation git leg: every segment stages or commits allowlisted paths in the
332
+ consolidation worktree.
333
+ .DESCRIPTION
334
+ DenySuffix names a CLEANUP_LEG_STAGED_* sub-code only after every target matched the
335
+ consolidation root; every earlier rejection leaves it empty.
336
+ .OUTPUTS
337
+ System.Management.Automation.PSCustomObject with Allowed and DenySuffix.
338
+ #>
339
+ [CmdletBinding()]
340
+ [OutputType([pscustomobject])]
341
+ param([Parameter(Mandatory)][string] $Command, [Parameter(Mandatory)][string] $SessionRoot)
342
+
343
+ $denied = [pscustomobject]@{ Allowed = $false; DenySuffix = '' }
344
+ if (Test-OrchestrationCommandTextUnresolvable -CommandText $Command) {
345
+ return $denied
346
+ }
347
+ $split = Split-OrchestrationCommandLine -CommandText $Command
348
+ $segments = @($split.Segments)
349
+ if (-not $split.Balanced -or $segments.Count -eq 0) {
350
+ return $denied
351
+ }
352
+ $commitRoots = [System.Collections.Generic.List[string]]::new()
353
+ foreach ($segment in $segments) {
354
+ $tokens = [string[]]@(ConvertTo-OrchestrationCommandToken -Segment $segment)
355
+ $shape = Test-OrchestrationCleanupSegmentShape -Token $tokens
356
+ if ($null -eq $shape) {
357
+ return $denied
358
+ }
359
+ if ($shape -ceq 'commit') {
360
+ $commitRoots.Add($tokens[2])
361
+ }
362
+ }
363
+
364
+ $target = Get-OrchestrationCommandTarget -Command $Command -SessionRoot $SessionRoot
365
+ if (-not $target.Resolved -or @($target.Targets).Count -eq 0) {
366
+ return $denied
367
+ }
368
+ $record = Find-CleanupConsolidationRecord -Record @(Get-OrchestrationCleanupListingRecord -SessionRoot $SessionRoot)
369
+ if ($null -eq $record) {
370
+ return $denied
371
+ }
372
+ $consolidationRoot = ConvertTo-OrchestrationCleanupComparablePath -Path $record.Root
373
+ foreach ($candidate in @($target.Targets)) {
374
+ if (-not [string]::Equals((ConvertTo-OrchestrationCleanupComparablePath -Path $candidate), $consolidationRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
375
+ return $denied
376
+ }
377
+ }
378
+ if ($commitRoots.Count -gt 0) {
379
+ $prefix = ' Cleanup leg not applied: '
380
+ $unreadable = [pscustomobject]@{ Allowed = $false; DenySuffix = "${prefix}CLEANUP_LEG_STAGED_INDEX_UNREADABLE: the staged index of the consolidation worktree could not be read as NUL-terminated names." }
381
+ $empty = [pscustomobject]@{ Allowed = $false; DenySuffix = "${prefix}CLEANUP_LEG_STAGED_INDEX_EMPTY: the staged index of the consolidation worktree lists no path." }
382
+ $staged = Get-OrchestrationGateStagedIndex -WorktreeRoot $record.Root
383
+ if ($null -eq $staged -or $staged.ExitCode -ne 0) {
384
+ return $unreadable
385
+ }
386
+ $text = [string]$staged.Output
387
+ if ($text.Length -eq 0) {
388
+ return $empty
389
+ }
390
+ if (-not $text.EndsWith([char]0)) {
391
+ return $unreadable
392
+ }
393
+ $paths = @(ConvertFrom-DocsOnlyNameList -Text $text)
394
+ if ($paths.Count -eq 0) {
395
+ return $empty
396
+ }
397
+ foreach ($path in $paths) {
398
+ if (-not (Test-CleanupConsolidationRelativePath -RelativePath $path)) {
399
+ return [pscustomobject]@{ Allowed = $false; DenySuffix = "${prefix}CLEANUP_LEG_STAGED_PATH_NOT_ALLOWLISTED: the staged path '$path' is not under docs/ or .claude/agent-memory/." }
400
+ }
401
+ }
402
+ }
403
+ return [pscustomobject]@{ Allowed = $true; DenySuffix = '' }
404
+ }
405
+
406
+ function Get-OrchestrationCleanupContextResult {
407
+ <#
408
+ .SYNOPSIS
409
+ Decide whether a call that the gate would deny is a sanctioned cleanup operation.
410
+ .DESCRIPTION
411
+ Composes the manifest, scratch, consolidation Write/Edit, and consolidation git legs of
412
+ the file header and returns Allowed with the DenySuffix rule of the file header. Never
413
+ throws: any error answers Allowed $false with an empty DenySuffix.
414
+ .PARAMETER FilePath
415
+ The Write or Edit file path; empty for a command.
416
+ .PARAMETER Content
417
+ The Write content; empty for an Edit.
418
+ .PARAMETER Command
419
+ The Bash command, or the Codex apply_patch text; empty for a path leg.
420
+ .PARAMETER AllowPatchManifest
421
+ Admit a Codex apply_patch that adds the manifest (Codex gate only).
422
+ .OUTPUTS
423
+ System.Management.Automation.PSCustomObject with Allowed and DenySuffix.
424
+ #>
425
+ [CmdletBinding()]
426
+ [OutputType([pscustomobject])]
427
+ param(
428
+ [AllowNull()][AllowEmptyString()][string] $FilePath,
429
+ [AllowNull()][AllowEmptyString()][string] $Content,
430
+ [AllowNull()][AllowEmptyString()][string] $Command,
431
+ [switch] $AllowPatchManifest
432
+ )
433
+
434
+ $denied = [pscustomobject]@{ Allowed = $false; DenySuffix = '' }
435
+ try {
436
+ $sessionRoot = (Get-Location).Path
437
+ if ($FilePath) {
438
+ if (Test-OrchestrationCleanupManifestPath -Path $FilePath -SessionRoot $sessionRoot) {
439
+ return [pscustomobject]@{ Allowed = [bool](Test-CleanupManifestText -Raw $Content); DenySuffix = '' }
440
+ }
441
+ if (Test-OrchestrationCleanupScratchWrite -FilePath $FilePath -SessionRoot $sessionRoot) {
442
+ return [pscustomobject]@{ Allowed = $true; DenySuffix = '' }
443
+ }
444
+ return (Get-OrchestrationCleanupConsolidationWriteResult -FilePath $FilePath -SessionRoot $sessionRoot)
445
+ }
446
+ if (-not $Command) {
447
+ return $denied
448
+ }
449
+ if ($AllowPatchManifest -and $Command.TrimStart().StartsWith('*** Begin Patch', [System.StringComparison]::Ordinal)) {
450
+ $patchContent = Get-OrchestrationCleanupPatchManifestContent -PatchText $Command -SessionRoot $sessionRoot
451
+ return [pscustomobject]@{ Allowed = ($null -ne $patchContent -and (Test-CleanupManifestText -Raw $patchContent)); DenySuffix = '' }
452
+ }
453
+ return (Get-OrchestrationCleanupConsolidationCommandResult -Command $Command -SessionRoot $sessionRoot)
454
+ }
455
+ catch {
456
+ Write-Debug "PREIMPL_CLEANUP_LEG_ERROR: $($_.Exception.Message)"
457
+ return $denied
458
+ }
459
+ }
460
+
461
+ function Test-OrchestrationCleanupContextAllowed {
462
+ <#
463
+ .SYNOPSIS
464
+ Return $true when a call that the gate would deny is a sanctioned cleanup operation.
465
+ .DESCRIPTION
466
+ Boolean wrapper that returns the Allowed value of Get-OrchestrationCleanupContextResult.
467
+ Never throws.
468
+ .OUTPUTS
469
+ System.Boolean
470
+ #>
471
+ [CmdletBinding()]
472
+ [OutputType([bool])]
473
+ param(
474
+ [AllowNull()][AllowEmptyString()][string] $FilePath,
475
+ [AllowNull()][AllowEmptyString()][string] $Content,
476
+ [AllowNull()][AllowEmptyString()][string] $Command,
477
+ [switch] $AllowPatchManifest
478
+ )
479
+
480
+ return [bool](Get-OrchestrationCleanupContextResult -FilePath $FilePath -Content $Content -Command $Command -AllowPatchManifest:$AllowPatchManifest).Allowed
481
+ }
@@ -24,6 +24,7 @@ try { . (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-m
24
24
  # seams, the mode deny reason, and the target resolution with its import guard (issue
25
25
  # #690), all relocated to this sibling to keep the gate inside the 500-line cap.
26
26
  try { . (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-epic-scope.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-orchestration-preimplementation-gate-epic-scope.ps1' -ErrorRecord $_ }
27
+ try { . (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-cleanup.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-orchestration-preimplementation-gate-cleanup.ps1' -ErrorRecord $_ }
27
28
 
28
29
  # Shared command-line parser (issue #545): per-segment scan text and structural matching.
29
30
  try { . (Join-Path $PSScriptRoot 'hook-command-scanner.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-scanner.ps1' -ErrorRecord $_ }
@@ -411,7 +412,8 @@ function Invoke-OrchestrationPreimplementationGateDecision {
411
412
  if (Test-OrchestrationReady -Payload $checkpoint) {
412
413
  return Get-OrchestrationPreimplementationGateAllowDecision
413
414
  }
414
- return Get-OrchestrationPreimplementationGateBlockDecision -Reason 'PREIMPLEMENTATION_GATE_BLOCKED: Implementation operations require artifacts/orchestration/orchestrator-state.json to contain issue number, feature folder, route metadata, lifecycle readiness, and checkpoint state before implementation begins.'
415
+ if (($cleanup = Get-OrchestrationCleanupContextResult -FilePath $filePath -Content (Get-ClaudeHookToolInputString -ToolInput $toolInput -Name 'content') -Command $command).Allowed) { return Get-OrchestrationPreimplementationGateAllowDecision } # Cleanup-context legs (issue #1026).
416
+ return Get-OrchestrationPreimplementationGateBlockDecision -Reason ('PREIMPLEMENTATION_GATE_BLOCKED: Implementation operations require artifacts/orchestration/orchestrator-state.json to contain issue number, feature folder, route metadata, lifecycle readiness, and checkpoint state before implementation begins.' + $cleanup.DenySuffix)
415
417
  }
416
418
 
417
419
  function Invoke-OrchestrationPreimplementationGateEntryPoint {