@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
@@ -0,0 +1,343 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Pure cleanup-context predicates shared by the cleanup legs of the enforcement gates (issue #1026).
4
+
5
+ .DESCRIPTION
6
+ The cleanup-merged-worktrees skill writes no per-feature, epic, or parallel checkpoint, so
7
+ the checkpoint-based gates cannot recognize its writes, commits, delegation, and pull
8
+ request. Its integrity record is the sanctioned-removal manifest. This file holds the pure
9
+ decisions the gates' cleanup legs share:
10
+
11
+ Test-CleanupManifestText - the manifest header (tool, schema_version) and,
12
+ with -UtcNow, its freshness and run_id.
13
+ Test-CleanupConsolidationRelativePath - the allowlist: the repository-relative prefixes
14
+ docs/ and .claude/agent-memory/ (ordinal,
15
+ case-sensitive), plus the pr-author receipt
16
+ artifacts/pr_body_<digits>.receipt.json with
17
+ -AllowReceipt.
18
+ Find-CleanupConsolidationRecord - the consolidation worktree, identified by its
19
+ porcelain branch, never by its path.
20
+ Test-CleanupScratchPath - the scratch rule: an absolute path outside every
21
+ registered worktree and the session root, under a
22
+ temporary root.
23
+ Test-CleanupConsolidationBranchLabel - the delegation label rule of the model-routing leg.
24
+
25
+ Every function fails closed: a malformed or ambiguous input answers $false or $null.
26
+
27
+ The literals below restate values owned elsewhere: the branch equals
28
+ CLEANUP_WT_CONSOLIDATION_BRANCH in
29
+ .claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_actions_lib.sh, and the
30
+ manifest tool, schema version, and freshness bound equal $script:ExpectedManifestTool,
31
+ $script:ExpectedManifestSchemaVersion, and $script:ManifestFreshnessBound in
32
+ .claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1. The Codex hooks cannot import
33
+ .claude/lib, so the values are restated here and pinned equal by
34
+ tests/scripts/claude-hooks/hook-cleanup-context.Parity.Tests.ps1. The names avoid the
35
+ removal gates' $script:CleanupWorktreeManifestPath.
36
+
37
+ .NOTES
38
+ PowerShell 7+. Functions and literals only: no file, process, network, clock, or
39
+ environment access, no dot-source, and no import. Byte-identical at .claude/hooks,
40
+ .codex/hooks, and both bundle mirrors under extensions/drm-copilot/resources/.
41
+ #>
42
+
43
+ $script:CleanupContextBranch = 'documentationandmemories'
44
+ $script:CleanupContextManifestRelativePath = 'artifacts/orchestration/cleanup-worktrees-manifest.json'
45
+ $script:CleanupContextManifestTool = 'cleanup-merged-worktrees'
46
+ $script:CleanupContextManifestSchemaVersion = 1
47
+ $script:CleanupContextFreshnessHours = 24
48
+
49
+ function Test-CleanupManifestText {
50
+ <#
51
+ .SYNOPSIS
52
+ Return $true when the text is a cleanup manifest with the expected header.
53
+ .DESCRIPTION
54
+ The text must parse as a JSON object whose tool is the string cleanup-merged-worktrees
55
+ (ordinal) and whose schema_version is the integer 1. When -UtcNow is supplied, the
56
+ manifest must also carry a generated_at that parses, is not after UtcNow, and lies
57
+ within the freshness bound, and a run_id that is a non-blank string. The generated_at
58
+ value is read exactly as CleanupWorktreeManifest.psm1 reads it, so the PR and
59
+ delegation legs agree with the removal gates on freshness.
60
+ .PARAMETER Raw
61
+ The manifest text, or $null when no manifest exists.
62
+ .PARAMETER UtcNow
63
+ The current UTC time from the caller's clock seam.
64
+ .OUTPUTS
65
+ System.Boolean
66
+ #>
67
+ [CmdletBinding()]
68
+ [OutputType([bool])]
69
+ param(
70
+ [AllowNull()][AllowEmptyString()][string] $Raw,
71
+ [datetime] $UtcNow
72
+ )
73
+
74
+ if ([string]::IsNullOrWhiteSpace($Raw)) {
75
+ return $false
76
+ }
77
+ try {
78
+ $manifest = $Raw | ConvertFrom-Json -ErrorAction Stop
79
+ }
80
+ catch {
81
+ return $false
82
+ }
83
+ if ($manifest -isnot [System.Management.Automation.PSCustomObject]) {
84
+ return $false
85
+ }
86
+ $names = @($manifest.PSObject.Properties.Name)
87
+ if ($names -notcontains 'tool' -or $names -notcontains 'schema_version') {
88
+ return $false
89
+ }
90
+ if ($manifest.tool -isnot [string] -or $manifest.tool -cne $script:CleanupContextManifestTool) {
91
+ return $false
92
+ }
93
+ $schemaVersion = $manifest.schema_version
94
+ if (($schemaVersion -isnot [int] -and $schemaVersion -isnot [long]) -or [long]$schemaVersion -ne $script:CleanupContextManifestSchemaVersion) {
95
+ return $false
96
+ }
97
+ if (-not $PSBoundParameters.ContainsKey('UtcNow')) {
98
+ return $true
99
+ }
100
+
101
+ if ($names -notcontains 'generated_at' -or $names -notcontains 'run_id') {
102
+ return $false
103
+ }
104
+ $generatedAt = [datetime]::MinValue
105
+ $styles = [System.Globalization.DateTimeStyles]::AdjustToUniversal -bor [System.Globalization.DateTimeStyles]::AssumeUniversal
106
+ if (-not [datetime]::TryParse([string]$manifest.generated_at, [cultureinfo]::InvariantCulture, $styles, [ref] $generatedAt)) {
107
+ return $false
108
+ }
109
+ if ($generatedAt -gt $UtcNow -or ($UtcNow - $generatedAt) -gt [timespan]::FromHours($script:CleanupContextFreshnessHours)) {
110
+ return $false
111
+ }
112
+ return ($manifest.run_id -is [string] -and -not [string]::IsNullOrWhiteSpace($manifest.run_id))
113
+ }
114
+
115
+ function Test-CleanupConsolidationRelativePath {
116
+ <#
117
+ .SYNOPSIS
118
+ Return $true when a repository-relative path is on the consolidation allowlist.
119
+ .DESCRIPTION
120
+ The path must use only the plain characters A-Z a-z 0-9 . _ / -, must not be rooted,
121
+ must carry no '..' segment, and must start with docs/ or .claude/agent-memory/ under
122
+ ordinal, case-sensitive comparison with at least one character after the prefix. Any
123
+ other Markdown path, such as CLAUDE.md or .claude/rules/x.md, is not admitted. With
124
+ -AllowReceipt the pr-author receipt artifacts/pr_body_<digits>.receipt.json is also
125
+ admitted; it never enters the staging allowlist.
126
+ .PARAMETER RelativePath
127
+ The repository-relative path or operand.
128
+ .PARAMETER AllowReceipt
129
+ Admit the pr-author receipt path (Write and Edit leg only).
130
+ .OUTPUTS
131
+ System.Boolean
132
+ #>
133
+ [CmdletBinding()]
134
+ [OutputType([bool])]
135
+ param(
136
+ [AllowNull()][AllowEmptyString()][string] $RelativePath,
137
+ [switch] $AllowReceipt
138
+ )
139
+
140
+ $path = [string]$RelativePath
141
+ if ($path -cnotmatch '^[A-Za-z0-9._/-]+$' -or $path.StartsWith('/')) {
142
+ return $false
143
+ }
144
+ if (@($path -split '/') -contains '..') {
145
+ return $false
146
+ }
147
+ if ($AllowReceipt -and $path -cmatch '^artifacts/pr_body_[0-9]+\.receipt\.json$') {
148
+ return $true
149
+ }
150
+ foreach ($prefix in @('docs/', '.claude/agent-memory/')) {
151
+ if ($path.Length -gt $prefix.Length -and $path.StartsWith($prefix, [System.StringComparison]::Ordinal)) {
152
+ return $true
153
+ }
154
+ }
155
+ return $false
156
+ }
157
+
158
+ function Find-CleanupConsolidationRecord {
159
+ <#
160
+ .SYNOPSIS
161
+ Return the registered consolidation worktree record, or $null.
162
+ .DESCRIPTION
163
+ Takes the records ConvertFrom-HookTargetWorktreePorcelain emits. The first record (the
164
+ main worktree stanza), prunable records, and detached records are skipped. The single
165
+ remaining record whose Branch is ordinal-equal to documentationandmemories is returned;
166
+ none, or more than one, returns $null. The path is never consulted, because the
167
+ consolidation path can be overridden by CLEANUP_WT_CONSOLIDATION_PATH.
168
+ .PARAMETER Record
169
+ The porcelain records, in listing order.
170
+ .OUTPUTS
171
+ System.Management.Automation.PSCustomObject or $null
172
+ #>
173
+ [CmdletBinding()]
174
+ [OutputType([pscustomobject])]
175
+ param(
176
+ [AllowNull()][AllowEmptyCollection()][object[]] $Record
177
+ )
178
+
179
+ $records = @($Record | Where-Object { $null -ne $_ })
180
+ $found = [System.Collections.Generic.List[object]]::new()
181
+ for ($index = 1; $index -lt $records.Count; $index++) {
182
+ $candidate = $records[$index]
183
+ if ([bool]$candidate.Prunable -or $null -eq $candidate.Branch) {
184
+ continue
185
+ }
186
+ if ([string]$candidate.Branch -ceq $script:CleanupContextBranch) {
187
+ $found.Add($candidate)
188
+ }
189
+ }
190
+ if ($found.Count -ne 1) {
191
+ return $null
192
+ }
193
+ return $found[0]
194
+ }
195
+
196
+ function ConvertTo-CleanupContextPath {
197
+ <#
198
+ .SYNOPSIS
199
+ Normalize a path to forward slashes with one trailing slash removed (bare roots kept).
200
+ .OUTPUTS
201
+ System.String
202
+ #>
203
+ [CmdletBinding()]
204
+ [OutputType([string])]
205
+ param(
206
+ [AllowNull()][AllowEmptyString()][string] $Path
207
+ )
208
+
209
+ $normalized = ([string]$Path).Replace('\', '/')
210
+ if ($normalized.Length -gt 1 -and $normalized.EndsWith('/') -and $normalized -notmatch '^[A-Za-z]:/$') {
211
+ $normalized = $normalized.Substring(0, $normalized.Length - 1)
212
+ }
213
+ return $normalized
214
+ }
215
+
216
+ function Test-CleanupContextPathUnder {
217
+ <#
218
+ .SYNOPSIS
219
+ Return $true when a normalized path equals a root or lies beneath it (OrdinalIgnoreCase).
220
+ .OUTPUTS
221
+ System.Boolean
222
+ #>
223
+ [CmdletBinding()]
224
+ [OutputType([bool])]
225
+ param(
226
+ [Parameter(Mandatory)][AllowEmptyString()][string] $Path,
227
+ [AllowNull()][AllowEmptyString()][string] $Root
228
+ )
229
+
230
+ $normalizedRoot = ConvertTo-CleanupContextPath -Path $Root
231
+ if ([string]::IsNullOrEmpty($normalizedRoot)) {
232
+ return $false
233
+ }
234
+ if ([string]::Equals($Path, $normalizedRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
235
+ return $true
236
+ }
237
+ $prefix = if ($normalizedRoot.EndsWith('/')) { $normalizedRoot } else { $normalizedRoot + '/' }
238
+ return $Path.StartsWith($prefix, [System.StringComparison]::OrdinalIgnoreCase)
239
+ }
240
+
241
+ function Test-CleanupScratchPath {
242
+ <#
243
+ .SYNOPSIS
244
+ Return $true when a path is a scratch path under a temporary root and outside every worktree.
245
+ .DESCRIPTION
246
+ The path must be absolute (drive-rooted or slash-rooted, not UNC), carry no '.' or '..'
247
+ segment and no surrounding whitespace, lie outside every registered worktree root and
248
+ outside the session root, and lie under at least one accepted temporary-root spelling.
249
+ Comparisons use forward-slash normalization and OrdinalIgnoreCase. The rule fails
250
+ closed when no registered root or no temporary root is supplied, and when any
251
+ registered worktree root lies under an accepted temporary root, because a path under
252
+ such a worktree could then be spelled so that it matches no registered root.
253
+ .PARAMETER Path
254
+ The Write or Edit file path.
255
+ .PARAMETER RegisteredRoot
256
+ Every registered worktree root from the listing, prunable ones included.
257
+ .PARAMETER SessionRoot
258
+ The session root.
259
+ .PARAMETER TemporaryRoot
260
+ Every accepted spelling of the operating-system temporary root.
261
+ .OUTPUTS
262
+ System.Boolean
263
+ #>
264
+ [CmdletBinding()]
265
+ [OutputType([bool])]
266
+ param(
267
+ [AllowNull()][AllowEmptyString()][string] $Path,
268
+ [AllowNull()][AllowEmptyCollection()][string[]] $RegisteredRoot,
269
+ [AllowNull()][AllowEmptyString()][string] $SessionRoot,
270
+ [AllowNull()][AllowEmptyCollection()][string[]] $TemporaryRoot
271
+ )
272
+
273
+ $raw = [string]$Path
274
+ if ([string]::IsNullOrEmpty($raw) -or $raw.Trim() -cne $raw) {
275
+ return $false
276
+ }
277
+ $normalized = ConvertTo-CleanupContextPath -Path $raw
278
+ if ($normalized -notmatch '^([A-Za-z]:/|/(?!/))') {
279
+ return $false
280
+ }
281
+ $segments = @($normalized -split '/')
282
+ if ($segments -contains '.' -or $segments -contains '..') {
283
+ return $false
284
+ }
285
+ $roots = @($RegisteredRoot | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
286
+ $temporary = @($TemporaryRoot | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
287
+ if ($roots.Count -eq 0 -or $temporary.Count -eq 0 -or [string]::IsNullOrWhiteSpace($SessionRoot)) {
288
+ return $false
289
+ }
290
+ foreach ($registeredCandidate in $roots) {
291
+ $normalizedRoot = ConvertTo-CleanupContextPath -Path $registeredCandidate
292
+ foreach ($temporaryCandidate in $temporary) {
293
+ if (Test-CleanupContextPathUnder -Path $normalizedRoot -Root $temporaryCandidate) {
294
+ return $false
295
+ }
296
+ }
297
+ if (Test-CleanupContextPathUnder -Path $normalized -Root $registeredCandidate) {
298
+ return $false
299
+ }
300
+ }
301
+ if (Test-CleanupContextPathUnder -Path $normalized -Root $SessionRoot) {
302
+ return $false
303
+ }
304
+ foreach ($temporaryCandidate in $temporary) {
305
+ $normalizedTemporary = ConvertTo-CleanupContextPath -Path $temporaryCandidate
306
+ if (-not [string]::Equals($normalized, $normalizedTemporary, [System.StringComparison]::OrdinalIgnoreCase) -and
307
+ (Test-CleanupContextPathUnder -Path $normalized -Root $normalizedTemporary)) {
308
+ return $true
309
+ }
310
+ }
311
+ return $false
312
+ }
313
+
314
+ function Test-CleanupConsolidationBranchLabel {
315
+ <#
316
+ .SYNOPSIS
317
+ Return $true when a delegation prompt names the consolidation branch by exactly one label.
318
+ .DESCRIPTION
319
+ The prompt must contain exactly one 'branch:' label (case-insensitive count), on its own
320
+ line in the lowercase form 'branch: <value>', with the value ordinal-equal to
321
+ documentationandmemories, and no --head or --branch token anywhere, so the worktree
322
+ resolvers that read the first branch signal cannot select a different branch.
323
+ .PARAMETER PromptText
324
+ The delegation prompt.
325
+ .OUTPUTS
326
+ System.Boolean
327
+ #>
328
+ [CmdletBinding()]
329
+ [OutputType([bool])]
330
+ param(
331
+ [AllowNull()][AllowEmptyString()][string] $PromptText
332
+ )
333
+
334
+ $text = [string]$PromptText
335
+ if ([regex]::Matches($text, '(?i)\bbranch:').Count -ne 1) {
336
+ return $false
337
+ }
338
+ if ([regex]::IsMatch($text, '(?i)(?<![\w-])--(?:head|branch)(?![\w-])')) {
339
+ return $false
340
+ }
341
+ $label = [regex]::Match($text, '(?m)^[ \t]*branch:[ \t]*(?<value>[^\r\n]*?)[ \t]*\r?$')
342
+ return ($label.Success -and $label.Groups['value'].Value -ceq $script:CleanupContextBranch)
343
+ }
@@ -1,12 +1,14 @@
1
1
  <#
2
2
  .SYNOPSIS
3
- Artifact-path rules, changed-language set, and coverage-row rule for the feature-review coverage hook.
3
+ Artifact-path rules, changed-language set, narrowing-line reader, and coverage-row rule for the
4
+ feature-review coverage hook.
4
5
 
5
6
  .DESCRIPTION
6
7
  Dot-sourced by validate-feature-review-coverage.ps1. Defines the advertised-path reader
7
8
  (Get-ReviewArtifactPathFromOutput), the active-feature location rule (Get-ReviewArtifactInfo),
8
- the changed-language reader for the PR context summary (Get-ChangedLanguageSet), and the
9
- per-language coverage-row rule (Test-LanguageCoverageRow).
9
+ the changed-language reader for the PR context summary (Get-ChangedLanguageSet), the
10
+ scope-narrowing line reader (Get-CoverageNarrowingLine), and the per-language coverage-row
11
+ rule (Test-LanguageCoverageRow).
10
12
 
11
13
  The file has no param() block, writes nothing to the output stream, and runs no top-level
12
14
  statement other than function definitions.
@@ -95,6 +97,50 @@ function Get-ChangedLanguageSet {
95
97
  return $langs
96
98
  }
97
99
 
100
+ function Get-CoverageNarrowingLine {
101
+ [CmdletBinding()]
102
+ [OutputType([string])]
103
+ param(
104
+ [Parameter(Mandatory = $true)]
105
+ [AllowEmptyString()]
106
+ [string[]]$Lines,
107
+
108
+ [Parameter(Mandatory = $true)]
109
+ [string]$LabelPattern,
110
+
111
+ [Parameter(Mandatory = $true)]
112
+ [string]$Language
113
+ )
114
+
115
+ # Sanctioned narrowing text is not a scope narrowing by the audit: a line inside a
116
+ # '## Rejected Scope Narrowing' section (caller text recorded verbatim), and a PowerShell
117
+ # line naming branch coverage but not line coverage (Pester measures no branch coverage).
118
+ # The policy-audit template prescribes the no-pre-change-run record in three positions only,
119
+ # so the record is removed there and stays a narrowing form everywhere else.
120
+ $narrowingPattern = '(?i)(informational only|context only|out of plan scope|out of scope|not applicable|\bN/A\b|\bUNVERIFIED\b)'
121
+ $recordPatterns = @(
122
+ '(?<=(?:\bBaseline|\bbaseline coverage artifact):\s*)N/A - no pre-change run'
123
+ '(?<=^\s*\|(?:[^|]*\|){4}\s*)N/A - no pre-change run(?=\s*\|)'
124
+ '(?<!:\s*)`N/A - no pre-change run`'
125
+ )
126
+ $insideRejectedSection = $false
127
+ foreach ($line in $Lines) {
128
+ if ($line -match '^##\s') { $insideRejectedSection = $line -match '^##\s+Rejected Scope Narrowing\b' }
129
+ elseif ($insideRejectedSection) { continue }
130
+ if ($line -notmatch $LabelPattern -or $line -notmatch '(?i)(coverage|lcov|line[s]?\s+hit|pester)') { continue }
131
+ $reducedLine = $line
132
+ foreach ($recordPattern in $recordPatterns) { $reducedLine = [regex]::Replace($reducedLine, $recordPattern, '') }
133
+ # Tested on the original line, because the removals above strip the record it names.
134
+ if ($line.Contains('Baseline: N/A - no pre-change run')) {
135
+ $reducedLine = [regex]::Replace($reducedLine, '(?<![\w/-])Change: N/A', '')
136
+ $reducedLine = $reducedLine.Replace('New/changed-code coverage: N/A - no production file changed', '')
137
+ }
138
+ if ($reducedLine -notmatch $narrowingPattern) { continue }
139
+ if ($Language -eq 'PowerShell' -and $line -match '(?i)\bbranch\b' -and $line -notmatch '(?i)\bline\s+coverage\b') { continue }
140
+ $line
141
+ }
142
+ }
143
+
98
144
  function Test-LanguageCoverageRow {
99
145
  [OutputType([System.Collections.Hashtable])]
100
146
  param(
@@ -141,19 +187,7 @@ function Test-LanguageCoverageRow {
141
187
  }
142
188
  }
143
189
 
144
- # Sanctioned narrowing text is not a scope narrowing by the audit: a line inside a
145
- # '## Rejected Scope Narrowing' section (caller text recorded verbatim), and a PowerShell
146
- # line naming branch coverage but not line coverage (Pester measures no branch coverage).
147
- $narrowingPattern = '(?i)(informational only|context only|out of plan scope|out of scope|not applicable|\bN/A\b|\bUNVERIFIED\b)'
148
- $insideRejectedSection = $false
149
- $narrowing = foreach ($line in $lines) {
150
- if ($line -match '^##\s') { $insideRejectedSection = $line -match '^##\s+Rejected Scope Narrowing\b' }
151
- elseif ($insideRejectedSection) { continue }
152
- if ($line -notmatch $labelPattern -or $line -notmatch '(?i)(coverage|lcov|line[s]?\s+hit|pester)' -or $line -notmatch $narrowingPattern) { continue }
153
- if ($Language -eq 'PowerShell' -and $line -match '(?i)\bbranch\b' -and $line -notmatch '(?i)\bline\s+coverage\b') { continue }
154
- $line
155
- }
156
- $narrowing = @($narrowing)
190
+ $narrowing = @(Get-CoverageNarrowingLine -Lines $lines -LabelPattern $labelPattern -Language $Language)
157
191
  if ($narrowing.Count -gt 0) {
158
192
  $first = ($narrowing | Select-Object -First 1).ToString().Trim()
159
193
  return @{
@@ -297,7 +297,8 @@ function Get-OrchestratorStatePrCreationReadinessError {
297
297
  .DESCRIPTION
298
298
  Private readiness check mirroring validate_orchestrator_state_pr_creation_readiness in
299
299
  _orchestrator_state_pr_creation_readiness.py: steps 5-8 must not be pending, blocked, or
300
- blocked_remediation_loop_limit; blocked_reason must be `none` or absent; and the
300
+ blocked_remediation_loop_limit, except a pending step 7 under review_mode concurrent;
301
+ blocked_reason must be `none` or absent; and the
301
302
  local_execution_overrides / delegation_bypasses lists must be empty when present. It does
302
303
  not enforce completion, CI, PR, or routing-contract gates.
303
304
  .PARAMETER State
@@ -313,11 +314,15 @@ function Get-OrchestratorStatePrCreationReadinessError {
313
314
  )
314
315
 
315
316
  $errors = [System.Collections.Generic.List[string]]::new()
317
+ $stateNames = @($State.PSObject.Properties | ForEach-Object { $_.Name })
318
+ $reviewMode = if ($stateNames -ccontains 'review_mode') { (Get-OrchestratorStateField -State $State -Name 'review_mode').Value } else { $null }
316
319
 
317
320
  # Reject an upstream step recorded as pending, blocked, or blocked_remediation_loop_limit; steps
318
321
  # 5-8 must have finished before the first PR of a branch is created.
319
322
  foreach ($key in $script:PR_CREATION_READY_STEP_KEYS) {
320
323
  $field = Get-OrchestratorStateField -State $State -Name $key
324
+ # Under review_mode concurrent the review runs beside CI and the merge guard holds the merge.
325
+ if ($key -ceq 'step7_status' -and $field.Value -ceq 'pending' -and $reviewMode -is [string] -and $reviewMode -ceq 'concurrent') { continue }
321
326
  if ($field.Present -and (@('pending', 'blocked', 'blocked_remediation_loop_limit') -contains $field.Value)) {
322
327
  $errors.Add("Checkpoint PR-creation readiness validation failed: $key is $($field.Value).")
323
328
  }
@@ -23,6 +23,7 @@
23
23
  C5 mandatory route phases
24
24
  C6 the routing contract, from `OrchestratorStateRoutingContract.psm1`
25
25
  C7 the preparation terminal contract
26
+ C8 the review record under review_mode concurrent
26
27
  M1 model-routing receipt required once delegated
27
28
  M2 a complexity assessment for every matched receipt phase
28
29
  M3 per-entry re-validation of the U6.C and U6.M families
@@ -408,8 +409,8 @@ function Test-OrchestratorStateCompletionReadiness {
408
409
  $errors.AddRange([string[]]@(Get-OrchestratorStateUnconditionalError -State $state))
409
410
 
410
411
  # C family, in the reference's require_complete order: step statuses,
411
- # blocked_reason, pr_gate, ci_gate, phase completeness, routing contract, and
412
- # the preparation terminal contract.
412
+ # blocked_reason, pr_gate, ci_gate, phase completeness, routing contract, the
413
+ # preparation terminal contract, and the concurrent review record.
413
414
  $errors.AddRange([string[]]@(Get-OrchestratorStateCompletionStepStatusError -State $state))
414
415
  $errors.AddRange([string[]]@(Get-OrchestratorStateCompletionBlockedReasonError -State $state))
415
416
  $errors.AddRange([string[]]@(Get-OrchestratorStateCompletionPrGateError -State $state))
@@ -417,6 +418,7 @@ function Test-OrchestratorStateCompletionReadiness {
417
418
  $errors.AddRange([string[]]@(Get-OrchestratorStatePhaseCompletenessError -State $state))
418
419
  $errors.AddRange([string[]]@(Get-OrchestratorStateRoutingContractError -State $state))
419
420
  $errors.AddRange([string[]]@(Get-OrchestratorStatePreparationTerminalError -State $state))
421
+ $errors.AddRange([string[]]@(Get-OrchestratorStateCompletionConcurrentReviewError -State $state))
420
422
 
421
423
  # M family. The gate's M3 leg re-invokes the U6.C and U6.M validators, so its
422
424
  # output is merged under the PD-2 single-emission rule rather than appended
@@ -1,6 +1,6 @@
1
1
  <#
2
2
  .SYNOPSIS
3
- Portable completion-gate checks C1, C2, C3, C4, C5, and C7.
3
+ Portable completion-gate checks C1, C2, C3, C4, C5, C7, and C8.
4
4
 
5
5
  .DESCRIPTION
6
6
  Destination-runtime PowerShell port of the `--require-complete` checks the
@@ -19,6 +19,7 @@
19
19
  C5.1 mandatory route phases, from a static map
20
20
  C7.1 preparation terminal next_step (value-gated, repr rendering)
21
21
  C7.2 preparation terminal step statuses (value-gated, repr rendering)
22
+ C8 last review outcome under review_mode concurrent (value-gated)
22
23
 
23
24
  Route gating for C3 and C4 is resolved through
24
25
  `OrchestratorStateRoutingMatrix.psm1`, implementing deviation PD-1: the gate
@@ -407,6 +408,53 @@ function Get-OrchestratorStatePreparationTerminalError {
407
408
  return $errors.ToArray()
408
409
  }
409
410
 
411
+ function Get-OrchestratorStateCompletionConcurrentReviewError {
412
+ <#
413
+ .SYNOPSIS
414
+ Return the review-record error under review_mode concurrent (row C8).
415
+ .DESCRIPTION
416
+ Mirrors validate_concurrent_review_completion. Only the exact string concurrent
417
+ activates the check, and only the final review_outcomes element counts. The head
418
+ error is reported only when the verdict error does not apply. The check does no
419
+ git work: head currency stays with the merge guard.
420
+ .PARAMETER State
421
+ The parsed checkpoint object.
422
+ .OUTPUTS
423
+ System.String[] - zero or one error string.
424
+ #>
425
+ [CmdletBinding()]
426
+ [OutputType([string[]])]
427
+ param(
428
+ [Parameter(Mandatory = $true)]
429
+ [psobject] $State
430
+ )
431
+
432
+ $mode = $null
433
+ if ((Get-CheckpointObjectMemberName -Owner $State) -ccontains 'review_mode') { $mode = (Get-CheckpointObjectMember -Owner $State -Name 'review_mode').Value }
434
+ if (-not (($mode -is [string]) -and ($mode -ceq 'concurrent'))) {
435
+ return [string[]]@()
436
+ }
437
+
438
+ # A missing loop, a non-list value, an empty list, and a non-object last entry all
439
+ # leave $last unset, so each reports the verdict error.
440
+ $last = $null
441
+ $loop = (Get-CheckpointObjectMember -Owner $State -Name 'remediation_loop').Value
442
+ $outcomes = (Get-CheckpointObjectMember -Owner $loop -Name 'review_outcomes').Value
443
+ if ((Test-CheckpointListValue -Value $outcomes) -and $outcomes.Count -gt 0) {
444
+ $last = $outcomes[$outcomes.Count - 1]
445
+ }
446
+ $verdict = (Get-CheckpointObjectMember -Owner $last -Name 'verdict').Value
447
+ if (-not (($verdict -is [string]) -and ($verdict -ceq 'PASS'))) {
448
+ return [string[]]@('Checkpoint completion validation failed: review_mode concurrent requires a last review_outcomes entry with verdict PASS.')
449
+ }
450
+
451
+ $head = (Get-CheckpointObjectMember -Owner $last -Name 'head_sha').Value
452
+ if (-not (($head -is [string]) -and ($head -cmatch '\A[0-9a-f]{40}([0-9a-f]{24})?\z'))) {
453
+ return [string[]]@('Checkpoint completion validation failed: review_mode concurrent requires head_sha on the last review_outcomes entry.')
454
+ }
455
+ return [string[]]@()
456
+ }
457
+
410
458
  # Each check family is exported individually so the completion entry point can
411
459
  # compose them in the Python reference's order; the gate-key helper stays private.
412
460
  Export-ModuleMember -Function `
@@ -415,4 +463,5 @@ Export-ModuleMember -Function `
415
463
  Get-OrchestratorStateCompletionPrGateError, `
416
464
  Get-OrchestratorStateCompletionCiGateError, `
417
465
  Get-OrchestratorStatePhaseCompletenessError, `
418
- Get-OrchestratorStatePreparationTerminalError
466
+ Get-OrchestratorStatePreparationTerminalError, `
467
+ Get-OrchestratorStateCompletionConcurrentReviewError
@@ -264,9 +264,12 @@ function Get-RemediationCycleError {
264
264
 
265
265
  # Invariant 2: an execution status in the blocked set requires the nested
266
266
  # preflight to report exactly the cleared status; a missing or non-object
267
- # preflight cannot satisfy it.
267
+ # preflight cannot satisfy it. An exact light cycle runs no preflight and is
268
+ # checked by invariant 8 instead.
268
269
  $executionStatus = (Get-CheckpointObjectMember -Owner $Cycle -Name 'execution_status').Value
269
- if ($executionStatus -is [string] -and
270
+ $remediationPath = (Get-CheckpointObjectMember -Owner $Cycle -Name 'remediation_path').Value
271
+ $isLightCycle = ($remediationPath -is [string]) -and ([string]$remediationPath -ceq 'light')
272
+ if (-not $isLightCycle -and $executionStatus -is [string] -and
270
273
  ($script:EXECUTION_STATUSES_REQUIRING_CLEAR_PREFLIGHT -contains [string]$executionStatus)) {
271
274
  $preflight = (Get-CheckpointObjectMember -Owner $Cycle -Name 'preflight').Value
272
275
  $preflightStatus = (Get-CheckpointObjectMember -Owner $preflight -Name 'final_status').Value