@danmoisan/drm-copilot-mcp 1.1.11 → 1.1.14

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 (66) hide show
  1. package/out/mcp-server.js +368 -209
  2. package/package.json +2 -2
  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-planner.md +18 -1
  6. package/resources/claude-customizations/.claude/agents/pr-author.md +15 -0
  7. package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +16 -8
  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 +33 -37
  10. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +120 -5
  11. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +127 -0
  12. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +172 -24
  13. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +14 -27
  14. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +66 -5
  15. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +134 -10
  16. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +37 -8
  17. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +3 -1
  18. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +332 -0
  19. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +211 -182
  20. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +43 -6
  21. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +486 -0
  22. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +5 -2
  23. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusWriteIntent.psm1 +446 -0
  24. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +1 -0
  25. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeReadiness.psm1 +176 -0
  26. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +369 -0
  27. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +392 -0
  28. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeResolution.psm1 +500 -0
  29. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeTargetResolution.psm1 +347 -0
  30. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +27 -0
  31. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +150 -2
  32. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +5 -5
  33. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +12 -0
  34. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +14 -1
  35. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +15 -0
  36. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +4 -1
  37. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +32 -17
  38. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +39 -6
  39. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +39 -10
  40. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +15 -0
  41. package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +221 -0
  42. package/resources/claude-customizations/config/blast-radius.json +14 -1
  43. package/resources/claude-customizations/pack-manifests/core.json +11 -0
  44. package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +13 -14
  45. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  46. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +193 -0
  47. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +15 -5
  48. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-resolution.ps1 +493 -0
  49. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +189 -0
  50. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +172 -24
  51. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +14 -27
  52. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexDeployment.ps1 +87 -0
  53. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +96 -0
  54. package/resources/codex-and-agents-customizations/.codex/scripts/codex-routing-cli-common.ps1 +370 -0
  55. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-contract.ps1 +475 -0
  56. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-runtime.ps1 +445 -0
  57. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-persistence-runtime.ps1 +118 -0
  58. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-sandbox-preflight.ps1 +69 -0
  59. package/resources/codex-and-agents-customizations/.codex/scripts/launch-epic-child-wave.ps1 +471 -0
  60. package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +280 -0
  61. package/resources/codex-and-agents-customizations/.codex/scripts/resume-epic-child.ps1 +263 -0
  62. package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +2 -1
  63. package/resources/codex-and-agents-customizations/pack-manifests/core.json +20 -1
  64. package/resources/lib/codex-routing/CodexDeployment.psm1 +315 -0
  65. package/resources/lib/codex-routing/CodexTopology.psm1 +394 -0
  66. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +37 -0
@@ -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
- the envelope's tool_input.command and, when matched, allows the merge only when one of three
8
+ the envelope's tool_input.command and, when matched, allows the merge only when one of four
9
9
  checkpoint-only conditions holds:
10
10
 
11
11
  1. Child-feature path: artifacts/orchestration/orchestrator-state.json exists,
@@ -19,12 +19,19 @@
19
19
  entry whose merge_status == "ci_green". A parallel run always names an explicit PR
20
20
  number (each item merges from its own isolated worktree), so a bare command with no
21
21
  PR number cannot satisfy this branch.
22
-
23
- Otherwise the command is denied with reason EPIC_MERGE_GATE_BLOCKED. A missing or
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
+ 4. Standalone path (issue #670), evaluated last: the command names an explicit PR
23
+ number and one of the three checkpoints above carries a
24
+ standalone_merge_authorizations record whose pr_number equals it, whose fields are
25
+ well formed, and whose session_id equals the live envelope's session_id. The
26
+ dot-sourced helpers file implements this branch.
27
+
28
+ Otherwise the command is denied with reason EPIC_MERGE_GATE_BLOCKED; a denial from
29
+ condition 4 also carries a standalone reason code after that token. A missing or
30
+ unreadable checkpoint in any branch fails closed (denies). A standalone (non-epic,
31
+ non-parallel) run never satisfies conditions 1-3, so its only route to a merge is an
32
+ explicit, PR-specific, session-bound record under condition 4; a bare command with no
33
+ PR number never satisfies condition 4, and a blanket flag is rejected rather than read.
34
+ Trigger scope: --squash is out of scope for this gate and is allowed on both runtimes.
28
35
 
29
36
  Design decision: this gate trusts the on-disk checkpoint rather than shelling out live
30
37
  to gh pr view for a real-time head-SHA check, matching the same non-adversarial,
@@ -35,6 +42,12 @@
35
42
  Compatible with PowerShell 7+. No external module dependencies. Filesystem reads go
36
43
  through injectable wrapper functions so tests can mock the boundary without writing
37
44
  temporary files.
45
+
46
+ Honest disclosure: the standalone-merge authorization record is a policy-level,
47
+ auditable declaration and is not a cryptographic or security control. authorized_by is
48
+ a declaration the hook does not verify. The record is not tamper-proof against a writer
49
+ inside the authorizing session. The session_id cross-check rejects stale or copied
50
+ records from other sessions; it does not stop same-session forgery.
38
51
  #>
39
52
  [CmdletBinding()]
40
53
  param()
@@ -44,6 +57,8 @@ Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -
44
57
  # Invoke-EpicMergeGateDecision and by Get-EpicMergeGateCommandPrNumber.
45
58
  . (Join-Path $PSScriptRoot 'hook-command-scanner.ps1')
46
59
  . (Join-Path $PSScriptRoot 'hook-command-invocation.ps1')
60
+ # Standalone-merge authorization predicates and the two decision-envelope factories (issue #670).
61
+ . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-authorization.ps1')
47
62
 
48
63
  $script:ChildCheckpointPath = 'artifacts/orchestration/orchestrator-state.json'
49
64
  $script:EpicCheckpointPath = 'artifacts/orchestration/epic-orchestrator-state.json'
@@ -324,36 +339,6 @@ function Test-ParallelCheckpointAllowsMerge {
324
339
  return $false
325
340
  }
326
341
 
327
- function Get-EpicMergeGateAllowDecision {
328
- [CmdletBinding()]
329
- [OutputType([System.Collections.Specialized.OrderedDictionary])]
330
- param()
331
-
332
- return [ordered]@{
333
- hookSpecificOutput = [ordered]@{
334
- hookEventName = 'PreToolUse'
335
- permissionDecision = 'allow'
336
- }
337
- }
338
- }
339
-
340
- function Get-EpicMergeGateBlockDecision {
341
- [CmdletBinding()]
342
- [OutputType([System.Collections.Specialized.OrderedDictionary])]
343
- param(
344
- [Parameter(Mandatory)]
345
- [string] $Reason
346
- )
347
-
348
- return [ordered]@{
349
- hookSpecificOutput = [ordered]@{
350
- hookEventName = 'PreToolUse'
351
- permissionDecision = 'deny'
352
- permissionDecisionReason = $Reason
353
- }
354
- }
355
- }
356
-
357
342
  function Invoke-EpicMergeGateDecision {
358
343
  <#
359
344
  .SYNOPSIS
@@ -430,6 +415,17 @@ function Invoke-EpicMergeGateDecision {
430
415
  return Get-EpicMergeGateAllowDecision
431
416
  }
432
417
 
418
+ # Branch 4, evaluated last and only for an explicit PR number: a PR-specific standalone
419
+ # authorization record bound to the live session. Its deny keeps the gate token first.
420
+ if ($null -ne $commandPrNumber) {
421
+ $envelopeSessionId = [string](Get-ClaudeHookEnvelopeValue -Envelope $payload.Envelope -Name 'session_id')
422
+ $standalone = Test-StandaloneCheckpointAllowsMerge -Checkpoints @($childCheckpoint, $epicCheckpoint, $parallelCheckpoint) -CommandPrNumber $commandPrNumber -EnvelopeSessionId $envelopeSessionId
423
+ if ($standalone.Allowed) {
424
+ return Get-EpicMergeGateAllowDecision
425
+ }
426
+ return Get-EpicMergeGateBlockDecision -Reason ('EPIC_MERGE_GATE_BLOCKED: ' + $standalone.ReasonCode + ': ' + $standalone.Message)
427
+ }
428
+
433
429
  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.'
434
430
  }
435
431
 
@@ -5,8 +5,15 @@
5
5
 
6
6
  .DESCRIPTION
7
7
  Invoked by the Claude Code PreToolUse hook on the Agent (Task) tool. Reads
8
- tool input JSON from the the envelope's nested tool_input environment variable and the
9
- orchestrator checkpoint from artifacts/orchestration/orchestrator-state.json.
8
+ tool input JSON from the envelope's nested tool_input, resolves which item the
9
+ delegation is about, and reads that item's orchestrator checkpoint.
10
+
11
+ The item is identified by portable identity only (issue #673): the canonical
12
+ issue-number line in the delegation prompt, and a branch label. A feature-folder
13
+ path never selects the worktree, because a merged folder exists in every checkout
14
+ branched from main. A delegation the gate cannot identify is denied with a named
15
+ reason code rather than checked against whichever checkpoint occupies the calling
16
+ process's directory, which is how a sibling item's state used to produce an allow.
10
17
 
11
18
  The hook enforces presence only: it cannot read the delegate's chosen
12
19
  `model` (no `model` field is exposed in the tool input), so it verifies that
@@ -24,7 +31,14 @@
24
31
  empty or absent tool input, and malformed tool-input JSON.
25
32
 
26
33
  The checkpoint read goes through Get-ModelRoutingCheckpoint so tests can
27
- inject a synthetic checkpoint without touching disk.
34
+ inject a synthetic checkpoint without touching disk. Its path is mandatory and
35
+ carries no default: the caller supplies the absolute path of the resolved
36
+ worktree's checkpoint, so the gate cannot fall back to a relative location.
37
+
38
+ The delegation-identity contract this gate depends on is stated in
39
+ .claude/skills/orchestrate/SKILL.md under `## Issue Number Consistency`: every
40
+ delegation prompt to a receipt-gated subagent type carries the canonical issue
41
+ number line and a `branch:` label naming the item's branch.
28
42
 
29
43
  .NOTES
30
44
  Compatible with PowerShell 7+. Read-only presence-gating deterrent.
@@ -34,6 +48,11 @@ param()
34
48
 
35
49
 
36
50
  Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
51
+ # Portable-identity resolution (issue #673). Unguarded and fail-closed on purpose: a gate
52
+ # that cannot load its resolver must not degrade into the cwd-relative read it replaces.
53
+ Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeItemResolution.psm1') -Force -ErrorAction Stop
54
+ # Epic scope (issue #663): a delegation for the epic integration branch is gated against the epic checkpoint.
55
+ Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/EpicScopeResolution.psm1') -Force -ErrorAction Stop
37
56
  function Get-ModelRoutingCheckpoint {
38
57
  <#
39
58
  .SYNOPSIS
@@ -43,7 +62,8 @@ function Get-ModelRoutingCheckpoint {
43
62
  [CmdletBinding()]
44
63
  [OutputType([object])]
45
64
  param(
46
- [string] $CheckpointPath = 'artifacts/orchestration/orchestrator-state.json'
65
+ [Parameter(Mandatory)]
66
+ [string] $CheckpointPath
47
67
  )
48
68
 
49
69
  if (-not (Test-Path -LiteralPath $CheckpointPath -PathType Leaf)) {
@@ -117,6 +137,66 @@ function Test-ModelRoutingReceiptPresent {
117
137
  return $false
118
138
  }
119
139
 
140
+ function Resolve-ModelRoutingWorktreeTarget {
141
+ <#
142
+ .SYNOPSIS
143
+ Resolve the delegation's target worktree. Tests mock this function (resolution seam).
144
+ .DESCRIPTION
145
+ A seam rather than a direct call because the resolver reads real git state: the
146
+ worktree list, each registration's branch, and the filesystem. A test that drove it
147
+ directly would depend on whichever worktrees exist on the machine running it.
148
+ .PARAMETER PromptText
149
+ The delegation prompt, which is the only field scanned for identity.
150
+ .OUTPUTS
151
+ The target result object from Resolve-WorktreeItemTarget.
152
+ #>
153
+ [CmdletBinding()]
154
+ [OutputType([pscustomobject])]
155
+ param(
156
+ [Parameter(Mandatory)]
157
+ [AllowEmptyString()]
158
+ [string] $PromptText
159
+ )
160
+
161
+ return (Resolve-WorktreeItemTarget -Text $PromptText -SessionRoot (Get-Location).Path)
162
+ }
163
+
164
+ function Get-ModelRoutingTargetCheckpointResolution {
165
+ <#
166
+ .SYNOPSIS
167
+ Resolve the checkpoint path this delegation must be checked against.
168
+ .DESCRIPTION
169
+ Maps the four resolution states onto a checkpoint path or a deny reason. The two
170
+ resolved states share one branch because the path is composed the same way for
171
+ both: identity selects the worktree, and the checkpoint is read beneath it. The two
172
+ unresolved states deny, carrying the code the worktree-resolution accessors supply;
173
+ neither literal appears in this file.
174
+ .PARAMETER PromptText
175
+ The delegation prompt to resolve identity from.
176
+ .OUTPUTS
177
+ System.Collections.Specialized.OrderedDictionary with CheckpointPath and Reason.
178
+ #>
179
+ [CmdletBinding()]
180
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
181
+ param(
182
+ [Parameter(Mandatory)]
183
+ [AllowEmptyString()]
184
+ [string] $PromptText
185
+ )
186
+
187
+ $target = Resolve-ModelRoutingWorktreeTarget -PromptText $PromptText
188
+
189
+ switch ($target.Status) {
190
+ { $_ -in @('SessionRoot', 'OtherWorktree') } {
191
+ return [ordered]@{ CheckpointPath = (Get-WorktreeItemCheckpointPath -WorktreeRoot $target.WorktreeRoot); Reason = $null }
192
+ }
193
+ default {
194
+ $reason = "$($target.ReasonCode): $($target.Detail) The model-routing gate will not check this delegation against a checkpoint that may belong to a different item. Put the line 'Canonical issue number for this feature is <N>.' and a 'branch: <item branch>' label in the delegation prompt so the gate can identify the item."
195
+ return [ordered]@{ CheckpointPath = $null; Reason = $reason }
196
+ }
197
+ }
198
+ }
199
+
120
200
  function Invoke-ModelRoutingReceiptDecision {
121
201
  <#
122
202
  .SYNOPSIS
@@ -154,7 +234,42 @@ function Invoke-ModelRoutingReceiptDecision {
154
234
  return $allow
155
235
  }
156
236
 
157
- $checkpoint = Get-ModelRoutingCheckpoint
237
+ # Identity resolution sits after the scope filter and before the checkpoint read, so a
238
+ # delegation outside the gated set is still allowed however unresolvable its target is,
239
+ # and a gated delegation the gate cannot identify is refused rather than answered from
240
+ # unrelated state. An absent prompt is passed as an empty string, which resolves to no
241
+ # identity and therefore to the same deny.
242
+ $prompt = [string](Get-ClaudeHookToolInputString -ToolInput $envelope.Value -Name 'prompt')
243
+
244
+ # Decision: a prompt whose branch label equals the epic checkpoint's integration_branch is
245
+ # epic scope (issue #663), and the receipt is looked up in the epic checkpoint without
246
+ # running per-feature identity resolution. Any other prompt falls through unchanged.
247
+ $epicScope = Resolve-EpicScopeCheckpoint -Text $prompt -SessionRoot (Get-Location).Path
248
+ if ($epicScope.IsEpicScope) {
249
+ if (Test-ModelRoutingReceiptPresent -Checkpoint $epicScope.Checkpoint -Subagent $subagent) {
250
+ return $allow
251
+ }
252
+ return [ordered]@{
253
+ hookSpecificOutput = [ordered]@{
254
+ hookEventName = 'PreToolUse'
255
+ permissionDecision = 'deny'
256
+ permissionDecisionReason = "MODEL_ROUTING_RECEIPT_BLOCKED: cannot delegate to '$subagent' in epic scope before a model_routing_receipts entry for it is recorded in $($epicScope.CheckpointPath); the failed readiness predicate is 'model_routing_receipts'."
257
+ }
258
+ }
259
+ }
260
+
261
+ $resolution = Get-ModelRoutingTargetCheckpointResolution -PromptText $prompt
262
+ if ($resolution.Reason) {
263
+ return [ordered]@{
264
+ hookSpecificOutput = [ordered]@{
265
+ hookEventName = 'PreToolUse'
266
+ permissionDecision = 'deny'
267
+ permissionDecisionReason = $resolution.Reason
268
+ }
269
+ }
270
+ }
271
+
272
+ $checkpoint = Get-ModelRoutingCheckpoint -CheckpointPath $resolution.CheckpointPath
158
273
  if (Test-ModelRoutingReceiptPresent -Checkpoint $checkpoint -Subagent $subagent) {
159
274
  return $allow
160
275
  }
@@ -0,0 +1,127 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Epic-scope command and path legs of the preimplementation gate (issue #663).
4
+
5
+ .DESCRIPTION
6
+ Dot-sourced by enforce-orchestration-preimplementation-gate.ps1. Holds two groups of
7
+ functions:
8
+
9
+ - The two per-mode read seams of issue #554 (Get-EpicCheckpointContent and
10
+ Get-ParallelCheckpointContent), relocated verbatim from the gate file by issue #663
11
+ so the gate stays inside the 500-line cap. Their names and behaviour are unchanged,
12
+ so tests that mock or shadow them by name are unaffected.
13
+ - The epic-scope decision for the command and path legs. A staging command or a
14
+ Write/Edit call is epic scope when the effective worktree's HEAD (the -C selector
15
+ worktree when present, otherwise the session root) equals the integration_branch of
16
+ artifacts/orchestration/epic-orchestrator-state.json. In epic scope the call is
17
+ decided by the epic command-leg readiness predicate, and under decision D2 an
18
+ implementation-classified operand is allowed only while a merge is in progress in
19
+ that worktree. Outside epic scope the decision function returns $null and the gate's
20
+ single-feature path runs unchanged.
21
+
22
+ .NOTES
23
+ PowerShell 7+. Depends on functions the gate defines or dot-sources before any call:
24
+ Split-OrchestrationCommandLine and ConvertTo-OrchestrationCommandToken (helpers file),
25
+ Get-OrchestrationDelegationCheckpointPath (modes file), and the gate's allow and block
26
+ decision constructors. Mirrored byte-identically under
27
+ extensions/drm-copilot/resources/claude-customizations/.
28
+ #>
29
+
30
+ Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/EpicScopeResolution.psm1') -Force -ErrorAction Stop
31
+ Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/EpicScopeReadiness.psm1') -Force -ErrorAction Stop
32
+
33
+ # The two per-mode read seams (issue #554). Each takes its path from the fixed mode
34
+ # table and never from a delegation's own text; an absent file returns an empty
35
+ # string, which the readiness predicate then treats as a deny.
36
+ function Get-EpicCheckpointContent {
37
+ [CmdletBinding()]
38
+ [OutputType([string])]
39
+ param()
40
+
41
+ $path = Get-OrchestrationDelegationCheckpointPath -Mode 'epic'
42
+ if (-not (Test-Path -LiteralPath $path)) {
43
+ return ''
44
+ }
45
+ return Get-Content -Raw -LiteralPath $path
46
+ }
47
+
48
+ function Get-ParallelCheckpointContent {
49
+ [CmdletBinding()]
50
+ [OutputType([string])]
51
+ param()
52
+
53
+ $path = Get-OrchestrationDelegationCheckpointPath -Mode 'parallel'
54
+ if (-not (Test-Path -LiteralPath $path)) {
55
+ return ''
56
+ }
57
+ return Get-Content -Raw -LiteralPath $path
58
+ }
59
+
60
+ function Get-OrchestrationEpicScopeSelector {
61
+ <#
62
+ .SYNOPSIS
63
+ Returns the value of a leading `git -C <value>` selector, or $null.
64
+ .DESCRIPTION
65
+ Pure. Reads only the first segment of the command line, split and tokenized with
66
+ the helpers-file parser so quoting is handled exactly as the staging exemption
67
+ handles it. An unbalanced command line has no decidable selector and returns $null.
68
+ .PARAMETER Command
69
+ The full command line.
70
+ .OUTPUTS
71
+ System.String
72
+ #>
73
+ [CmdletBinding()]
74
+ [OutputType([string])]
75
+ param([Parameter(Mandatory)][AllowEmptyString()][string] $Command)
76
+
77
+ $split = Split-OrchestrationCommandLine -CommandText $Command
78
+ if (-not $split.Balanced -or @($split.Segments).Count -eq 0) {
79
+ return $null
80
+ }
81
+ $token = @(ConvertTo-OrchestrationCommandToken -Segment ([string]@($split.Segments)[0]).Trim())
82
+ if ($token.Count -ge 3 -and $token[0] -ceq 'git' -and $token[1] -ceq '-C' -and $token[2]) {
83
+ return $token[2]
84
+ }
85
+ return $null
86
+ }
87
+
88
+ function Get-OrchestrationEpicScopeDecision {
89
+ <#
90
+ .SYNOPSIS
91
+ Returns the epic-scope decision for an implementation-classified command or path, or $null.
92
+ .DESCRIPTION
93
+ Resolves epic scope with worktree-HEAD matching and the command's -C selector. When
94
+ the call is not epic scope, returns $null so the caller's single-feature path runs
95
+ unchanged. In epic scope, returns an allow decision when the epic command-leg
96
+ readiness predicate passes, and otherwise a deny naming the epic checkpoint and the
97
+ failed conjunct.
98
+ .PARAMETER Command
99
+ The Bash command line; empty for a path leg.
100
+ .PARAMETER FilePath
101
+ The Write/Edit file path; empty for a command leg.
102
+ .OUTPUTS
103
+ System.Collections.Specialized.OrderedDictionary, or $null outside epic scope.
104
+ #>
105
+ [CmdletBinding()]
106
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
107
+ param(
108
+ [AllowNull()][AllowEmptyString()][string] $Command = '',
109
+ [AllowNull()][AllowEmptyString()][string] $FilePath = ''
110
+ )
111
+
112
+ # A call carrying neither a command nor a path has no leg to decide.
113
+ if (-not $Command -and -not $FilePath) {
114
+ return $null
115
+ }
116
+ $selector = if ($Command) { Get-OrchestrationEpicScopeSelector -Command $Command } else { $null }
117
+ $scope = Resolve-EpicScopeCheckpoint -Text ([string]$Command) -SessionRoot (Get-Location).Path -WorktreeSelector $selector -MatchWorktreeHead
118
+ if (-not $scope.IsEpicScope) {
119
+ return $null
120
+ }
121
+
122
+ $failure = Get-EpicCommandLegReadinessFailure -Checkpoint $scope.Checkpoint -MergeInProgress $scope.MergeInProgress
123
+ if (-not $failure) {
124
+ return Get-OrchestrationPreimplementationGateAllowDecision
125
+ }
126
+ return Get-OrchestrationPreimplementationGateBlockDecision -Reason ("PREIMPLEMENTATION_GATE_BLOCKED: this epic-scope operation was evaluated against $($scope.CheckpointPath), and the failed readiness predicate is '$failure'. Implementation operations in epic scope require that checkpoint to satisfy every readiness predicate, and a production path may be staged or edited only while a merge is in progress.")
127
+ }