@danmoisan/drm-copilot-mcp 1.1.10 → 1.1.12

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 (82) hide show
  1. package/out/mcp-server.js +3517 -1104
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +1 -1
  4. package/resources/claude-customizations/.claude/agents/epic-planner.md +4 -1
  5. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +15 -2
  6. package/resources/claude-customizations/.claude/agents/parallel-planner.md +3 -0
  7. package/resources/claude-customizations/.claude/agents/pr-author.md +15 -0
  8. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-authorization.ps1 +443 -0
  9. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +82 -51
  10. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +52 -3
  11. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +120 -5
  12. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +127 -0
  13. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +163 -15
  14. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +21 -28
  15. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +103 -6
  16. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +124 -8
  17. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +171 -15
  18. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +50 -10
  19. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +3 -1
  20. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +332 -0
  21. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +211 -182
  22. package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +35 -7
  23. package/resources/claude-customizations/.claude/hooks/hook-command-invocation.ps1 +483 -0
  24. package/resources/claude-customizations/.claude/hooks/hook-command-scanner.ps1 +483 -0
  25. package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +218 -6
  26. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +18 -75
  27. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +290 -0
  28. package/resources/claude-customizations/.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1 +415 -0
  29. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +1 -0
  30. package/resources/claude-customizations/.claude/lib/project-file-merge/ProjectFileMerge.psm1 +355 -0
  31. package/resources/claude-customizations/.claude/lib/project-file-merge/ProjectFileMergeGrammar.psm1 +318 -0
  32. package/resources/claude-customizations/.claude/lib/project-file-merge/Resolve-MergeableConflict.ps1 +229 -0
  33. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeReadiness.psm1 +176 -0
  34. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +369 -0
  35. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +392 -0
  36. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeResolution.psm1 +500 -0
  37. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeTargetResolution.psm1 +347 -0
  38. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +27 -0
  39. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +66 -2
  40. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +323 -16
  41. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +14 -1
  42. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +4 -0
  43. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +46 -1
  44. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +3 -1
  45. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +75 -8
  46. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +6 -0
  47. package/resources/claude-customizations/.claude/skills/powershell-orchestration-state-machine/SKILL.md +29 -1
  48. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +15 -0
  49. package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +221 -0
  50. package/resources/claude-customizations/config/blast-radius.json +7 -0
  51. package/resources/claude-customizations/pack-manifests/core.json +16 -0
  52. package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +13 -14
  53. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +41 -0
  54. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +41 -0
  55. package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +26 -0
  56. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  57. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +242 -3
  58. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +67 -12
  59. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +34 -8
  60. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +163 -15
  61. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -1
  62. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +34 -7
  63. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +483 -0
  64. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +483 -0
  65. package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +130 -2
  66. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexDeployment.ps1 +87 -0
  67. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +96 -0
  68. package/resources/codex-and-agents-customizations/.codex/scripts/codex-routing-cli-common.ps1 +370 -0
  69. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-contract.ps1 +475 -0
  70. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-runtime.ps1 +445 -0
  71. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-persistence-runtime.ps1 +118 -0
  72. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-sandbox-preflight.ps1 +69 -0
  73. package/resources/codex-and-agents-customizations/.codex/scripts/launch-epic-child-wave.ps1 +471 -0
  74. package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +280 -0
  75. package/resources/codex-and-agents-customizations/.codex/scripts/resume-epic-child.ps1 +263 -0
  76. package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +2 -1
  77. package/resources/codex-and-agents-customizations/pack-manifests/core.json +23 -1
  78. package/resources/config/orchestration-handoff-registry.json +138 -0
  79. package/resources/config/orchestration-handoff.schema.json +472 -0
  80. package/resources/lib/codex-routing/CodexDeployment.psm1 +315 -0
  81. package/resources/lib/codex-routing/CodexTopology.psm1 +394 -0
  82. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +64 -0
@@ -0,0 +1,369 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Epic-scope resolution: decides whether a gated call is an epic-level operation (issue #663).
4
+
5
+ .DESCRIPTION
6
+ An epic run keeps its state in artifacts/orchestration/epic-orchestrator-state.json at
7
+ the coordinating worktree root, not in a per-feature checkpoint. This module decides,
8
+ for one gated call, whether the call is an operation of that epic and, if so, returns
9
+ the absolute path and parsed content of the governing epic checkpoint.
10
+
11
+ A call is epic scope only when the session root's epic checkpoint parses, carries
12
+ route_id "epic" and a non-empty integration_branch, and that branch equals either the
13
+ call's branch signal (--head, --branch, or a branch: label) or, for a command or path
14
+ leg, the HEAD branch of the effective worktree (the -C selector worktree when present,
15
+ otherwise the session root). A command or path leg ignores any text branch signal, so
16
+ its merge probe inspects the worktree it operates on. Anything else is not epic scope,
17
+ so the caller's existing per-feature resolution runs unchanged.
18
+
19
+ The checkpoint path is always composed from the resolved session root and is never
20
+ read from command or prompt text (the issue #554 posture and the issue #673
21
+ invariant). Exactly one checkpoint read happens per resolution.
22
+
23
+ .NOTES
24
+ PowerShell 7+. Every filesystem contact passes through the exported seams of the
25
+ sibling WorktreeResolution.psm1, so tests model any topology by mocking those seams
26
+ inside this module and create no file. No subprocess, no network, no clock read, and
27
+ no environment read. Mirrored byte-identically under
28
+ extensions/drm-copilot/resources/claude-customizations/.
29
+ AUTHORITY: PowerShell-authoritative. The epic readiness predicates have no Python reference implementation, and no enforcement hook invokes Python (issue #663, D5).
30
+ CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
31
+ #>
32
+
33
+ Set-StrictMode -Version Latest
34
+ $ErrorActionPreference = 'Stop'
35
+
36
+ Import-Module (Join-Path $PSScriptRoot 'WorktreeResolution.psm1') -ErrorAction Stop
37
+ Import-Module (Join-Path $PSScriptRoot 'WorktreeTargetResolution.psm1') -ErrorAction Stop
38
+
39
+ # The one definition of the epic checkpoint location relative to a worktree root.
40
+ $script:EpicCheckpointRelativePath = 'artifacts/orchestration/epic-orchestrator-state.json'
41
+
42
+ # The only route id that makes an epic checkpoint scope-defining. Compared case-sensitively.
43
+ $script:EpicRouteId = 'epic'
44
+
45
+ # Git layout names read from a worktree: the root marker, the linked-worktree pointer
46
+ # line, the HEAD file, the branch reference prefix, and the in-progress merge marker.
47
+ $script:GitEntryName = '.git'
48
+ $script:GitDirLinePattern = '^gitdir:\s*(?<target>.+)$'
49
+ $script:HeadFileName = 'HEAD'
50
+ $script:BranchReferencePrefix = 'ref: refs/heads/'
51
+ $script:MergeHeadFileName = 'MERGE_HEAD'
52
+
53
+ # A drive-rooted or slash-rooted normalised path.
54
+ $script:AbsolutePathPattern = '^([A-Za-z]:(/|$)|/)'
55
+
56
+ function Get-EpicScopePropertyValue {
57
+ # Pure: the named property of an object, or $null when the object or the property
58
+ # is absent. StrictMode forbids reading a missing property directly.
59
+ [OutputType([object])]
60
+ param([AllowNull()][object] $InputObject, [string] $Name)
61
+
62
+ if ($null -eq $InputObject) {
63
+ return $null
64
+ }
65
+ $property = $InputObject.PSObject.Properties[$Name]
66
+ if ($null -eq $property) {
67
+ return $null
68
+ }
69
+ return $property.Value
70
+ }
71
+
72
+ function Get-EpicScopeFirstLine {
73
+ # Pure: the trimmed first line of a text, or $null when there is none.
74
+ [OutputType([string])]
75
+ param([AllowNull()][string] $Text)
76
+
77
+ if ([string]::IsNullOrWhiteSpace($Text)) {
78
+ return $null
79
+ }
80
+ return ($Text -split "`r?`n", 2)[0].Trim()
81
+ }
82
+
83
+ function New-EpicScopeResult {
84
+ # Pure: the single constructor of the resolution result shape.
85
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Pure in-memory factory that changes no system state.')]
86
+ [OutputType([pscustomobject])]
87
+ param(
88
+ [bool] $IsEpicScope,
89
+ [AllowNull()][string] $CheckpointPath,
90
+ [AllowNull()][object] $Checkpoint,
91
+ [AllowNull()][string] $WorktreeRoot,
92
+ [AllowNull()][string] $Branch,
93
+ [bool] $MergeInProgress,
94
+ [string] $Reason
95
+ )
96
+
97
+ return [pscustomobject]@{
98
+ IsEpicScope = $IsEpicScope
99
+ CheckpointPath = $CheckpointPath
100
+ Checkpoint = $Checkpoint
101
+ WorktreeRoot = $WorktreeRoot
102
+ Branch = $Branch
103
+ MergeInProgress = $MergeInProgress
104
+ Reason = $Reason
105
+ }
106
+ }
107
+
108
+ function Get-EpicScopeCheckpointRelativePath {
109
+ <#
110
+ .SYNOPSIS
111
+ Return the repository-relative path of the epic checkpoint.
112
+ #>
113
+ [CmdletBinding()]
114
+ [OutputType([string])]
115
+ param()
116
+
117
+ return $script:EpicCheckpointRelativePath
118
+ }
119
+
120
+ function Get-EpicScopeCheckpointText {
121
+ <#
122
+ .SYNOPSIS
123
+ Read the epic checkpoint text, or $null when no such file exists (seam).
124
+ .PARAMETER Path
125
+ The absolute path of the epic checkpoint.
126
+ #>
127
+ [CmdletBinding()]
128
+ [OutputType([string])]
129
+ param(
130
+ [Parameter(Mandatory = $true)]
131
+ [string] $Path
132
+ )
133
+
134
+ if ((Get-WorktreeResolutionGitEntryKind -Path $Path) -ne 'File') {
135
+ return $null
136
+ }
137
+ return (Get-WorktreeResolutionGitFileText -Path $Path)
138
+ }
139
+
140
+ function ConvertFrom-EpicScopeCheckpointText {
141
+ <#
142
+ .SYNOPSIS
143
+ Parse epic checkpoint text into an object, or $null. Never throws.
144
+ .DESCRIPTION
145
+ Pure. Null, empty, unparseable, and non-object JSON (an array or a scalar) all
146
+ return $null, so an unreadable checkpoint is treated as absent.
147
+ .PARAMETER Text
148
+ The raw checkpoint text.
149
+ #>
150
+ [CmdletBinding()]
151
+ [OutputType([pscustomobject])]
152
+ param(
153
+ [Parameter(Mandatory = $true)]
154
+ [AllowNull()]
155
+ [AllowEmptyString()]
156
+ [string] $Text
157
+ )
158
+
159
+ if ([string]::IsNullOrWhiteSpace($Text)) {
160
+ return $null
161
+ }
162
+ try {
163
+ $parsed = ConvertFrom-Json -InputObject $Text -ErrorAction Stop
164
+ } catch {
165
+ # Unparseable text is a fail-closed "not epic scope", never a hook error.
166
+ Write-Debug "EPIC_SCOPE_CHECKPOINT_UNPARSEABLE: $($_.Exception.Message)"
167
+ return $null
168
+ }
169
+ if ($parsed -isnot [System.Management.Automation.PSCustomObject]) {
170
+ return $null
171
+ }
172
+ return $parsed
173
+ }
174
+
175
+ function Get-EpicScopeWorktreeGitDirectory {
176
+ <#
177
+ .SYNOPSIS
178
+ Return a worktree's git directory, or $null.
179
+ .DESCRIPTION
180
+ For a main checkout the git entry is a directory and is returned as-is. For a
181
+ linked worktree the git entry is a file whose first line is a gitdir line; its
182
+ target is returned, joined to the worktree root when it is relative. Anything
183
+ else returns $null.
184
+ .PARAMETER WorktreeRoot
185
+ The absolute worktree root.
186
+ #>
187
+ [CmdletBinding()]
188
+ [OutputType([string])]
189
+ param(
190
+ [Parameter(Mandatory = $true)]
191
+ [AllowEmptyString()]
192
+ [string] $WorktreeRoot
193
+ )
194
+
195
+ $root = ConvertTo-WorktreeResolutionNormalizedPath -Path $WorktreeRoot
196
+ if ($null -eq $root -or $root -notmatch $script:AbsolutePathPattern) {
197
+ return $null
198
+ }
199
+ $gitEntry = Join-WorktreeResolutionPath -WorktreeRoot $root -RepoRelativePath $script:GitEntryName
200
+ $kind = Get-WorktreeResolutionGitEntryKind -Path $gitEntry
201
+ # Decide by entry kind: a directory is the git directory itself, a file points at it,
202
+ # and a missing entry means the root carries no git state.
203
+ if ($kind -eq 'Directory') {
204
+ return $gitEntry
205
+ } elseif ($kind -ne 'File') {
206
+ return $null
207
+ }
208
+ $firstLine = Get-EpicScopeFirstLine -Text (Get-WorktreeResolutionGitFileText -Path $gitEntry)
209
+ if ($null -eq $firstLine -or $firstLine -notmatch $script:GitDirLinePattern) {
210
+ return $null
211
+ }
212
+ $target = ConvertTo-WorktreeResolutionNormalizedPath -Path $Matches['target']
213
+ if ($target -match $script:AbsolutePathPattern) {
214
+ return $target
215
+ }
216
+ return (Join-WorktreeResolutionPath -WorktreeRoot $root -RepoRelativePath $target)
217
+ }
218
+
219
+ function Get-EpicScopeWorktreeHeadBranch {
220
+ <#
221
+ .SYNOPSIS
222
+ Return the branch checked out in a worktree, or $null for a detached or unreadable HEAD (seam).
223
+ .PARAMETER WorktreeRoot
224
+ The absolute worktree root.
225
+ #>
226
+ [CmdletBinding()]
227
+ [OutputType([string])]
228
+ param(
229
+ [Parameter(Mandatory = $true)]
230
+ [AllowEmptyString()]
231
+ [string] $WorktreeRoot
232
+ )
233
+
234
+ $gitDirectory = Get-EpicScopeWorktreeGitDirectory -WorktreeRoot $WorktreeRoot
235
+ if ($null -eq $gitDirectory) {
236
+ return $null
237
+ }
238
+ $headPath = Join-WorktreeResolutionPath -WorktreeRoot $gitDirectory -RepoRelativePath $script:HeadFileName
239
+ $firstLine = Get-EpicScopeFirstLine -Text (Get-WorktreeResolutionGitFileText -Path $headPath)
240
+ if ($null -eq $firstLine -or -not $firstLine.StartsWith($script:BranchReferencePrefix)) {
241
+ return $null
242
+ }
243
+ $branch = $firstLine.Substring($script:BranchReferencePrefix.Length).Trim()
244
+ if ($branch.Length -eq 0) {
245
+ return $null
246
+ }
247
+ return $branch
248
+ }
249
+
250
+ function Test-EpicScopeMergeInProgress {
251
+ <#
252
+ .SYNOPSIS
253
+ Report whether a merge is in progress in a worktree (MERGE_HEAD exists) (seam).
254
+ .PARAMETER WorktreeRoot
255
+ The absolute worktree root.
256
+ #>
257
+ [CmdletBinding()]
258
+ [OutputType([bool])]
259
+ param(
260
+ [Parameter(Mandatory = $true)]
261
+ [AllowEmptyString()]
262
+ [string] $WorktreeRoot
263
+ )
264
+
265
+ $gitDirectory = Get-EpicScopeWorktreeGitDirectory -WorktreeRoot $WorktreeRoot
266
+ if ($null -eq $gitDirectory) {
267
+ return $false
268
+ }
269
+ $mergeHeadPath = Join-WorktreeResolutionPath -WorktreeRoot $gitDirectory -RepoRelativePath $script:MergeHeadFileName
270
+ return ((Get-WorktreeResolutionGitEntryKind -Path $mergeHeadPath) -eq 'File')
271
+ }
272
+
273
+ function Resolve-EpicScopeCheckpoint {
274
+ <#
275
+ .SYNOPSIS
276
+ Decide whether a gated call is an epic-level operation and return the governing epic checkpoint.
277
+ .DESCRIPTION
278
+ Returns an object with IsEpicScope, CheckpointPath, Checkpoint, WorktreeRoot,
279
+ Branch, MergeInProgress, and Reason. The first matching step decides: no branch
280
+ signal without head matching (no-branch-signal, and no checkpoint read); an
281
+ unresolvable session root (session-root-unresolved); an absent or unparseable epic
282
+ checkpoint (epic-checkpoint-absent-or-unparseable); a route_id other than epic
283
+ (route_id); an empty integration_branch (integration_branch); an unresolvable
284
+ selector (selector-unresolved); a branch that does not match (branch-mismatch).
285
+ .PARAMETER Text
286
+ The command or prompt text; only its branch signal is read.
287
+ .PARAMETER SessionRoot
288
+ The session directory; defaults to the current location.
289
+ .PARAMETER WorktreeSelector
290
+ The -C selector value of a command leg, when present.
291
+ .PARAMETER MatchWorktreeHead
292
+ Match the effective worktree's HEAD branch, ignoring any text branch signal, and
293
+ probe that worktree for a merge in progress.
294
+ #>
295
+ [CmdletBinding()]
296
+ [OutputType([pscustomobject])]
297
+ param(
298
+ [Parameter(Mandatory = $true)]
299
+ [AllowNull()]
300
+ [AllowEmptyString()]
301
+ [string] $Text,
302
+
303
+ [AllowNull()]
304
+ [AllowEmptyString()]
305
+ [string] $SessionRoot = (Get-Location).ProviderPath,
306
+
307
+ [AllowNull()]
308
+ [AllowEmptyString()]
309
+ [string] $WorktreeSelector,
310
+
311
+ [switch] $MatchWorktreeHead
312
+ )
313
+
314
+ $branch = Find-WorktreeResolutionBranchSignal -Text $Text
315
+ if ($null -eq $branch -and -not $MatchWorktreeHead) {
316
+ return (New-EpicScopeResult -IsEpicScope $false -Reason 'no-branch-signal')
317
+ }
318
+ $root = Find-WorktreeResolutionRoot -Path $SessionRoot
319
+ if ($null -eq $root) {
320
+ return (New-EpicScopeResult -IsEpicScope $false -Reason 'session-root-unresolved')
321
+ }
322
+ $path = Join-WorktreeResolutionPath -WorktreeRoot $root -RepoRelativePath (Get-EpicScopeCheckpointRelativePath)
323
+ $checkpoint = ConvertFrom-EpicScopeCheckpointText -Text (Get-EpicScopeCheckpointText -Path $path)
324
+ if ($null -eq $checkpoint) {
325
+ return (New-EpicScopeResult -IsEpicScope $false -CheckpointPath $path -Reason 'epic-checkpoint-absent-or-unparseable')
326
+ }
327
+ if ([string](Get-EpicScopePropertyValue -InputObject $checkpoint -Name 'route_id') -cne $script:EpicRouteId) {
328
+ return (New-EpicScopeResult -IsEpicScope $false -CheckpointPath $path -Reason 'route_id')
329
+ }
330
+ $integrationBranch = [string](Get-EpicScopePropertyValue -InputObject $checkpoint -Name 'integration_branch')
331
+ if ([string]::IsNullOrWhiteSpace($integrationBranch)) {
332
+ return (New-EpicScopeResult -IsEpicScope $false -CheckpointPath $path -Reason 'integration_branch')
333
+ }
334
+
335
+ # Decide the matched branch. A command or path leg (-MatchWorktreeHead) matches the
336
+ # effective worktree's HEAD, the selector worktree taking precedence, and ignores any
337
+ # text branch signal so the merge probe inspects the worktree the call operates on
338
+ # (issue #663 remediation CR-2); otherwise the explicit branch signal decides.
339
+ $effectiveRoot = $root
340
+ if (-not $MatchWorktreeHead) {
341
+ $candidate = $branch
342
+ } else {
343
+ if (-not [string]::IsNullOrWhiteSpace($WorktreeSelector)) {
344
+ $effectiveRoot = Find-WorktreeResolutionRoot -Path $WorktreeSelector
345
+ }
346
+ if ($null -eq $effectiveRoot) {
347
+ return (New-EpicScopeResult -IsEpicScope $false -CheckpointPath $path -Reason 'selector-unresolved')
348
+ }
349
+ $candidate = Get-EpicScopeWorktreeHeadBranch -WorktreeRoot $effectiveRoot
350
+ }
351
+ if ($null -eq $candidate -or $candidate -cne $integrationBranch) {
352
+ return (New-EpicScopeResult -IsEpicScope $false -CheckpointPath $path -Reason 'branch-mismatch')
353
+ }
354
+
355
+ $mergeInProgress = $false
356
+ if ($MatchWorktreeHead) {
357
+ $mergeInProgress = [bool](Test-EpicScopeMergeInProgress -WorktreeRoot $effectiveRoot)
358
+ }
359
+ return (New-EpicScopeResult -IsEpicScope $true -CheckpointPath $path -Checkpoint $checkpoint -WorktreeRoot $effectiveRoot -Branch $candidate -MergeInProgress $mergeInProgress -Reason 'epic-scope')
360
+ }
361
+
362
+ Export-ModuleMember -Function `
363
+ Get-EpicScopeCheckpointRelativePath, `
364
+ Get-EpicScopeCheckpointText, `
365
+ ConvertFrom-EpicScopeCheckpointText, `
366
+ Get-EpicScopeWorktreeGitDirectory, `
367
+ Get-EpicScopeWorktreeHeadBranch, `
368
+ Test-EpicScopeMergeInProgress, `
369
+ Resolve-EpicScopeCheckpoint