@danmoisan/drm-copilot-mcp 1.0.24 → 1.0.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 (68) hide show
  1. package/out/mcp-server.js +1045 -214
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/feature-review.md +5 -3
  4. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +11 -4
  5. package/resources/claude-customizations/.claude/agents/parallel-planner.md +5 -2
  6. package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +28 -8
  7. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +109 -5
  8. package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +390 -0
  9. package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +28 -8
  10. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +117 -46
  11. package/resources/claude-customizations/.claude/lib/bash/parallel-manifest-validate.sh +115 -3
  12. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +105 -2
  13. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +32 -52
  14. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +107 -99
  15. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +295 -0
  16. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +9 -3
  17. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +312 -0
  18. package/resources/claude-customizations/.claude/lib/codex-routing/CodexTopology.psm1 +392 -0
  19. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryValidation.psm1 +500 -0
  20. package/resources/claude-customizations/.claude/lib/mermaid/MermaidGrammar.psm1 +491 -0
  21. package/resources/claude-customizations/.claude/lib/mermaid/MermaidLineScanner.psm1 +488 -0
  22. package/resources/claude-customizations/.claude/lib/mermaid/MermaidMarkdownFences.psm1 +298 -0
  23. package/resources/claude-customizations/.claude/lib/mermaid/MermaidValidation.psm1 +496 -0
  24. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +58 -67
  25. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCheckpointValue.psm1 +383 -0
  26. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCodexModelReceipts.psm1 +297 -0
  27. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCodexTopologyReceipts.psm1 +298 -0
  28. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletion.psm1 +232 -43
  29. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletionChecks.psm1 +416 -0
  30. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateModelReceipts.psm1 +366 -0
  31. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +408 -0
  32. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingContract.psm1 +428 -0
  33. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingMatrix.psm1 +377 -0
  34. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateUnconditional.psm1 +166 -0
  35. package/resources/claude-customizations/.claude/rules/general-unit-test.md +1 -1
  36. package/resources/claude-customizations/.claude/rules/mermaid.md +142 -0
  37. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +88 -3
  38. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +116 -0
  39. package/resources/claude-customizations/.claude/rules/powershell.md +1 -1
  40. package/resources/claude-customizations/.claude/rules/quality-tiers.md +3 -3
  41. package/resources/claude-customizations/.claude/settings.json +5 -0
  42. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +15 -0
  43. package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  44. package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +6 -0
  45. package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +4 -4
  46. package/resources/claude-customizations/.claude/skills/mermaid-diagram/SKILL.md +184 -0
  47. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/c4.md +50 -0
  48. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/class.md +63 -0
  49. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/er.md +56 -0
  50. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/flowchart.md +68 -0
  51. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/gantt.md +51 -0
  52. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/other-types.md +82 -0
  53. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/pie.md +32 -0
  54. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/sequence.md +63 -0
  55. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/state.md +49 -0
  56. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +10 -5
  57. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +116 -41
  58. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +95 -13
  59. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +7 -3
  60. package/resources/claude-customizations/.claude/skills/powershell-qa-gate/SKILL.md +1 -1
  61. package/resources/claude-customizations/config/blast-radius.json +9 -3
  62. package/resources/claude-customizations/pack-manifests/core.json +31 -1
  63. package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  64. package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +1 -1
  65. package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +3 -3
  66. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  67. package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  68. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +37 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danmoisan/drm-copilot-mcp",
3
- "version": "1.0.24",
3
+ "version": "1.0.27",
4
4
  "description": "Stdio MCP server exposing drm-copilot repo-automation tools.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -109,12 +109,14 @@ Coverage metrics are mandatory for every language that has changed files in the
109
109
 
110
110
  Coverage thresholds follow the uniform tier rule (Authoritative Decision #2) defined in `.claude/rules/quality-tiers.md`:
111
111
 
112
- - **New code files** (files added in this feature, not previously existing): line coverage >= 85%, branch coverage >= 75%.
113
- - **Modified files** (files that existed before and were changed): line coverage >= 85%, branch coverage >= 75%, and no regression on changed lines relative to baseline.
114
- - **Repo-wide per language**: line coverage >= 85%, branch coverage >= 75%.
112
+ - **New code files** (files added in this feature, not previously existing): line coverage >= 85%, branch coverage >= 75% for branch-capable languages.
113
+ - **Modified files** (files that existed before and were changed): line coverage >= 85%, branch coverage >= 75% for branch-capable languages, and no regression on changed lines relative to baseline.
114
+ - **Repo-wide per language**: line coverage >= 85%, branch coverage >= 75% for branch-capable languages.
115
115
 
116
116
  Tier-specific lower thresholds are not used.
117
117
 
118
+ The branch threshold applies only to branch-capable languages — TypeScript, Python, and C#. PowerShell is a coverage language and is fully subject to the line threshold and the no-regression requirement, but Pester measures command (instruction) coverage and line coverage only, so no branch percentage exists to evaluate and no branch threshold applies to it (see `.claude/rules/powershell.md`). Do not record FAIL for an absent PowerShell branch figure.
119
+
118
120
  ### Verification Procedure
119
121
 
120
122
  For each language that has changed files in the feature branch:
@@ -187,10 +187,17 @@ Read `cohorts[] { index, generation, item_keys[] }` and schedule from it exactly
187
187
 
188
188
  Two scheduling rules govern every launch:
189
189
 
190
- 1. **Cohort barrier.** Cohort `N+1` branches from `main` only after every cohort-`N` item is
191
- `merged` or `worktree_removed`. `current_cohort` increments only on durable confirmation via
192
- `git` and `gh` commands, never from in-memory notifications. A blocked item is neither `merged`
193
- nor `worktree_removed`, so a blocked item holds the barrier.
190
+ 1. **Cohort barrier (per-edge).** An item may start only when every conflicting neighbour
191
+ (`conflict_edges[]`) that sits in a strictly prior current-generation cohort has `merge_status`
192
+ of `merged` or `worktree_removed`. `ci_green` does not satisfy the barrier. Same-cohort and
193
+ later-cohort neighbours do not hold an item back, and an item with no conflicting prior-cohort
194
+ neighbour may start regardless of other cohorts' progress. Evaluate the predicate only against
195
+ durable state confirmed by `git` and `gh` commands, never from in-memory notifications. A
196
+ blocked item is neither `merged` nor `worktree_removed`, so it holds only its own conflicting
197
+ later-cohort neighbours and, transitively, the tail of its own conflict component; unrelated
198
+ lanes keep advancing. `current_cohort` is a PROGRESS INDICATOR — the lowest current-generation
199
+ cohort index still holding a non-terminal, non-withdrawn item, updated only on the same durable
200
+ confirmation — and gates nothing.
194
201
  2. **`max_concurrency` slot filling.** `max_concurrency` caps the number of simultaneously in-flight
195
202
  items independently of cohort size. Fill slots in ascending item-key (`issue_num`) order, and
196
203
  refill each freed slot with the next unstarted item of the current cohort in the same ascending
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: parallel-planner
3
3
  model: opus
4
- description: Planning half of the parallel orchestration surface. It performs item intake over issue numbers and potential-entry paths, drives per-item preparation (promotion, research, spec/user-story, atomic plan, preflight clearance) through concurrent preparation-mode Agent(orchestrator) delegations, computes and validates each item's blast radius, seeds the generation-0 cohort table, writes the parallel run manifest and the planner checkpoint, and emits the parallel-orchestrator kickoff prompt artifact. Performs no atomic execution, PR authoring, or CI monitoring.
4
+ description: Planning half of the parallel orchestration surface. It performs item intake over issue numbers and potential-entry paths, drives per-item preparation (promotion, research, spec/user-story, atomic plan, preflight clearance) through preparation-mode Agent(orchestrator) delegations launched in bounded waves of at most max_concurrency, computes and validates each item's blast radius, seeds the generation-0 cohort table, writes the parallel run manifest and the planner checkpoint, and emits the parallel-orchestrator kickoff prompt artifact. Performs no atomic execution, PR authoring, or CI monitoring.
5
5
  tools:
6
6
  - "Agent(orchestrator)"
7
7
  - Read
@@ -90,7 +90,10 @@ On every invocation:
90
90
  ## Delegation Model
91
91
 
92
92
  You delegate exclusively through `Agent(orchestrator)`, one delegation per item, each carrying the
93
- preparation-mode kickoff line defined in the `parallel-plan` skill. Each child `orchestrator` runs
93
+ preparation-mode kickoff line defined in the `parallel-plan` skill. Delegations are launched in
94
+ BOUNDED WAVES of at most `max_concurrency`, computed with
95
+ `bash .claude/lib/bash/compute-concurrency-batches.sh`, with wave *k+1* launched only after every
96
+ child of wave *k* has terminated. Never launch every item's preparation at once. Each child `orchestrator` runs
94
97
  promotion, research, feature documents, atomic planning, and preflight clearance under
95
98
  `route_id: preparation`, commits and pushes its own branch, then stops before any execution. You do
96
99
  not delegate directly to `atomic-planner`, `atomic-executor`, `task-researcher`, or `prd-feature`;
@@ -25,8 +25,10 @@
25
25
  interprets the CLI's exit code and captured output.
26
26
 
27
27
  .NOTES
28
- Compatible with PowerShell 7+. Read-only validation gate; the validator
29
- subprocess is the only external process invoked.
28
+ Requires PowerShell 7.4+ (the shared validation module uses
29
+ `Test-Json -SchemaFile` Draft 2020-12 support). Read-only validation gate that
30
+ invokes NO external process: validation runs in-process through
31
+ `.claude/lib/discovery-validation/DiscoveryValidation.psm1` (issue #475).
30
32
  #>
31
33
  [CmdletBinding()]
32
34
  param()
@@ -34,11 +36,23 @@ param()
34
36
  function Invoke-DiscoveryValidatorExe {
35
37
  <#
36
38
  .SYNOPSIS
37
- Wrapper around the discovery-artifact validator CLI. Mockable seam.
39
+ Wrapper around the discovery-artifact validator. Mockable seam.
38
40
  .DESCRIPTION
39
- Invokes `python -m scripts.dev_tools.validate_discovery_artifacts` with
40
- the supplied arguments and captures both stdout and stderr. Tests mock
41
- this function directly; production code must never mock `python`.
41
+ Delegates to the portable PowerShell implementation in
42
+ `.claude/lib/discovery-validation/DiscoveryValidation.psm1`, keeping this
43
+ function's name, its `-ValidatorArgs <string[]>` parameter, and its
44
+ `@{ ExitCode; Output }` return shape unchanged so existing mocks and
45
+ `Should -Invoke` assertions continue to bind.
46
+
47
+ This no longer invokes a Python interpreter (issue #475). The `.claude/**`
48
+ payload ships to destinations with no guaranteed Python, Poetry, or
49
+ `scripts/dev_tools`, where the previous `python -m ...` call failed
50
+ obscurely or blocked every operation.
51
+
52
+ Success is SILENT by contract: a passing validation returns `ExitCode = 0`
53
+ with an EMPTY `Output`. The caller denies on a non-zero exit code OR on
54
+ non-empty output, so any success chatter here would deny a passing
55
+ validation (defect D-2).
42
56
  #>
43
57
  [CmdletBinding()]
44
58
  [OutputType([hashtable])]
@@ -47,8 +61,14 @@ function Invoke-DiscoveryValidatorExe {
47
61
  [string[]] $ValidatorArgs
48
62
  )
49
63
 
50
- $output = & python -m scripts.dev_tools.validate_discovery_artifacts @ValidatorArgs 2>&1
51
- return @{ ExitCode = $LASTEXITCODE; Output = ($output | Out-String).Trim() }
64
+ $modulePath = Join-Path -Path $PSScriptRoot `
65
+ -ChildPath '../lib/discovery-validation/DiscoveryValidation.psm1'
66
+ if (-not (Test-Path -LiteralPath $modulePath -PathType Leaf)) {
67
+ return @{ ExitCode = 1; Output = "Discovery-validation module not found: $modulePath" }
68
+ }
69
+
70
+ Import-Module -Name $modulePath -Force -ErrorAction Stop
71
+ return Invoke-DiscoveryArtifactValidation -ValidatorArgs $ValidatorArgs
52
72
  }
53
73
 
54
74
  function Get-DiscoveryArtifactType {
@@ -5,7 +5,7 @@
5
5
  .DESCRIPTION
6
6
  Invoked by the Claude Code PreToolUse hook on the "Bash" matcher before any Bash
7
7
  command runs. Regex-matches gh pr merge with a --merge flag against
8
- CLAUDE_TOOL_INPUT.command and, when matched, allows the merge only when one of two
8
+ CLAUDE_TOOL_INPUT.command and, when matched, allows the merge only when one of three
9
9
  checkpoint-only conditions holds:
10
10
 
11
11
  1. Child-feature path: artifacts/orchestration/orchestrator-state.json exists,
@@ -14,11 +14,17 @@
14
14
  2. Epic-integration path: artifacts/orchestration/epic-orchestrator-state.json exists,
15
15
  epic_merge_pr.ci_gate.conclusion == "success", and, when the command names an
16
16
  explicit PR number, that number matches epic_merge_pr.pr_number.
17
+ 3. Parallel path: artifacts/orchestration/parallel-orchestrator-state.json exists,
18
+ route_id == "parallel", and the command's explicit PR number matches an items[]
19
+ entry whose merge_status == "ci_green". A parallel run always names an explicit PR
20
+ number (each item merges from its own isolated worktree), so a bare command with no
21
+ PR number cannot satisfy this branch.
17
22
 
18
23
  Otherwise the command is denied with reason EPIC_MERGE_GATE_BLOCKED. A missing or
19
- unreadable checkpoint in either branch fails closed (denies); standalone (non-epic)
20
- orchestration never sets epic_mode or populates epic_merge_pr, so it is structurally
21
- prevented from invoking gh pr merge --merge at all.
24
+ unreadable checkpoint in any branch fails closed (denies); standalone (non-epic,
25
+ non-parallel) orchestration never sets epic_mode, populates epic_merge_pr, or writes a
26
+ parallel checkpoint with route_id == "parallel", so it is structurally prevented from
27
+ invoking gh pr merge --merge at all.
22
28
 
23
29
  Design decision: this gate trusts the on-disk checkpoint rather than shelling out live
24
30
  to gh pr view for a real-time head-SHA check, matching the same non-adversarial,
@@ -35,6 +41,7 @@ param()
35
41
 
36
42
  $script:ChildCheckpointPath = 'artifacts/orchestration/orchestrator-state.json'
37
43
  $script:EpicCheckpointPath = 'artifacts/orchestration/epic-orchestrator-state.json'
44
+ $script:ParallelCheckpointPath = 'artifacts/orchestration/parallel-orchestrator-state.json'
38
45
 
39
46
  function Get-ChildOrchestratorCheckpointContent {
40
47
  <#
@@ -72,6 +79,24 @@ function Get-EpicOrchestratorCheckpointContent {
72
79
  return (Get-Content -LiteralPath $script:EpicCheckpointPath -Raw)
73
80
  }
74
81
 
82
+ function Get-ParallelOrchestratorCheckpointContent {
83
+ <#
84
+ .SYNOPSIS
85
+ Read the raw JSON text of the parallel-orchestrator checkpoint. Tests mock
86
+ this function (read seam).
87
+ .OUTPUTS
88
+ System.String or $null
89
+ #>
90
+ [CmdletBinding()]
91
+ [OutputType([string])]
92
+ param()
93
+
94
+ if (-not (Test-Path -LiteralPath $script:ParallelCheckpointPath -PathType Leaf)) {
95
+ return $null
96
+ }
97
+ return (Get-Content -LiteralPath $script:ParallelCheckpointPath -Raw)
98
+ }
99
+
75
100
  function ConvertFrom-EpicMergeGateJson {
76
101
  <#
77
102
  .SYNOPSIS
@@ -113,9 +138,20 @@ function Get-EpicMergeGateCommandPrNumber {
113
138
  [string] $CommandText
114
139
  )
115
140
 
141
+ # Original form: the PR number appears immediately after "merge"
142
+ # (e.g. "gh pr merge 410 --merge"). Preserved verbatim so epic-path outcomes
143
+ # for the forms the epic path uses are unchanged.
116
144
  if ($CommandText -match '(?i)\bgh\s+pr\s+merge\s+(\d+)\b') {
117
145
  return [int]$Matches[1]
118
146
  }
147
+ # Broadened, additive form: the parallel command places the flag before the
148
+ # number (e.g. "gh pr merge --merge 410"). Once "gh pr merge" is confirmed,
149
+ # capture the first standalone run of digits that is not preceded by "-" or a
150
+ # word character, so a flag token such as "--merge" is not treated as a number
151
+ # and a bare "gh pr merge --merge" still yields $null.
152
+ if ($CommandText -match '(?i)\bgh\s+pr\s+merge\b' -and $CommandText -match '(?<![-\w])(\d+)\b') {
153
+ return [int]$Matches[1]
154
+ }
119
155
  return $null
120
156
  }
121
157
 
@@ -206,6 +242,69 @@ function Test-EpicCheckpointAllowsMerge {
206
242
  return $true
207
243
  }
208
244
 
245
+ function Test-ParallelCheckpointAllowsMerge {
246
+ <#
247
+ .SYNOPSIS
248
+ Decision logic for the parallel-orchestrator checkpoint path (branch 3).
249
+ .PARAMETER Checkpoint
250
+ Parsed parallel-orchestrator checkpoint, or $null when absent/unreadable.
251
+ .PARAMETER CommandPrNumber
252
+ The explicit PR number parsed from the command, or $null when the command
253
+ does not name one. A parallel run always names an explicit PR number because
254
+ each item merges from its own isolated worktree, so a $null value denies.
255
+ .OUTPUTS
256
+ System.Boolean
257
+ #>
258
+ [CmdletBinding()]
259
+ [OutputType([bool])]
260
+ param(
261
+ [AllowNull()]
262
+ $Checkpoint,
263
+
264
+ [AllowNull()]
265
+ [Nullable[int]] $CommandPrNumber
266
+ )
267
+
268
+ if ($null -eq $Checkpoint) {
269
+ return $false
270
+ }
271
+ $props = @($Checkpoint.PSObject.Properties.Name)
272
+ if ($props -notcontains 'route_id' -or ([string]$Checkpoint.route_id) -ne 'parallel') {
273
+ return $false
274
+ }
275
+ # A parallel merge always names an explicit PR number; without one the target
276
+ # item cannot be identified, so fail closed.
277
+ if ($null -eq $CommandPrNumber) {
278
+ return $false
279
+ }
280
+ if ($props -notcontains 'items' -or $null -eq $Checkpoint.items) {
281
+ return $false
282
+ }
283
+
284
+ foreach ($item in @($Checkpoint.items)) {
285
+ if ($null -eq $item) {
286
+ continue
287
+ }
288
+ $itemProps = @($item.PSObject.Properties.Name)
289
+ if ($itemProps -notcontains 'pr_number') {
290
+ continue
291
+ }
292
+ $itemPrNumber = 0
293
+ if (-not [int]::TryParse([string]$item.pr_number, [ref] $itemPrNumber)) {
294
+ continue
295
+ }
296
+ if ($itemPrNumber -ne $CommandPrNumber) {
297
+ continue
298
+ }
299
+ if ($itemProps -notcontains 'merge_status') {
300
+ return $false
301
+ }
302
+ return ([string]$item.merge_status) -eq 'ci_green'
303
+ }
304
+
305
+ return $false
306
+ }
307
+
209
308
  function Get-EpicMergeGateAllowDecision {
210
309
  [CmdletBinding()]
211
310
  [OutputType([System.Collections.Specialized.OrderedDictionary])]
@@ -284,7 +383,12 @@ function Invoke-EpicMergeGateDecision {
284
383
  return Get-EpicMergeGateAllowDecision
285
384
  }
286
385
 
287
- return Get-EpicMergeGateBlockDecision -Reason 'EPIC_MERGE_GATE_BLOCKED: gh pr merge --merge requires either a per-feature checkpoint with epic_mode == true and step9_status == "passed", or an epic checkpoint with epic_merge_pr.ci_gate.conclusion == "success" and a matching pr_number. Neither checkpoint satisfied this gate.'
386
+ $parallelCheckpoint = ConvertFrom-EpicMergeGateJson -Raw (Get-ParallelOrchestratorCheckpointContent)
387
+ if (Test-ParallelCheckpointAllowsMerge -Checkpoint $parallelCheckpoint -CommandPrNumber $commandPrNumber) {
388
+ return Get-EpicMergeGateAllowDecision
389
+ }
390
+
391
+ return Get-EpicMergeGateBlockDecision -Reason 'EPIC_MERGE_GATE_BLOCKED: gh pr merge --merge requires either a per-feature checkpoint with epic_mode == true and step9_status == "passed", an epic checkpoint with epic_merge_pr.ci_gate.conclusion == "success" and a matching pr_number, or a parallel-orchestrator checkpoint with route_id == "parallel" whose target item (matched by pr_number) has merge_status == "ci_green". No checkpoint satisfied this gate.'
288
392
  }
289
393
 
290
394
  # Guard allows dot-sourcing in tests without executing the entrypoint.