@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
@@ -0,0 +1,471 @@
1
+ # Launch a bounded wave of worktree-bound Codex processes with sealed receipts and status.
2
+ [CmdletBinding()]
3
+ param(
4
+ [AllowEmptyString()][string] $LaunchSpecPath = '',
5
+ [ValidateRange(1, 8)][int] $MaxParallel = 4,
6
+ [switch] $Supervisor,
7
+ [switch] $Wait,
8
+ [AllowEmptyString()][string] $RepositoryRoot = ''
9
+ )
10
+
11
+ $ErrorActionPreference = 'Stop'
12
+ . (Join-Path $PSScriptRoot 'epic-child-launch-contract.ps1')
13
+ . (Join-Path $PSScriptRoot 'epic-child-launch-runtime.ps1')
14
+
15
+ function Get-CodexChildSessionId {
16
+ [OutputType([string])]
17
+ param([Parameter(Mandatory)][AllowEmptyCollection()][string[]] $JsonLines)
18
+ foreach ($line in $JsonLines) {
19
+ if ([string]::IsNullOrWhiteSpace($line)) {
20
+ continue
21
+ }
22
+ try {
23
+ $codexEvent = $line | ConvertFrom-Json -Depth 16 -ErrorAction Stop
24
+ } catch {
25
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: codex --json emitted malformed JSON: $_"
26
+ }
27
+ foreach ($candidate in @(
28
+ $codexEvent.thread_id, $codexEvent.session_id, $codexEvent.thread.id,
29
+ $codexEvent.payload.thread_id, $codexEvent.payload.session_id
30
+ )) {
31
+ if (-not [string]::IsNullOrWhiteSpace([string]$candidate)) {
32
+ return [string]$candidate
33
+ }
34
+ }
35
+ }
36
+ return ''
37
+ }
38
+
39
+ function Get-CodexChildSealedJsonFile {
40
+ [CmdletBinding()]
41
+ param(
42
+ [Parameter(Mandatory)][string] $Path,
43
+ [Parameter(Mandatory)][string] $Name,
44
+ [switch] $HoldOpen,
45
+ [scriptblock] $OpenFile = {
46
+ param([string] $FilePath)
47
+ [System.IO.File]::Open($FilePath, [System.IO.FileMode]::Open,
48
+ [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)
49
+ }
50
+ )
51
+ $stream = & $OpenFile $Path
52
+ try {
53
+ $memory = [System.IO.MemoryStream]::new()
54
+ try { $stream.CopyTo($memory); $bytes = $memory.ToArray() } finally { $memory.Dispose() }
55
+ $offset = if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { 3 } else { 0 }
56
+ try {
57
+ $raw = [System.Text.UTF8Encoding]::new($false, $true).GetString($bytes, $offset, $bytes.Length - $offset)
58
+ } catch {
59
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: $Name is not valid UTF-8."
60
+ }
61
+ $value = ConvertFrom-CodexChildLaunchJson -Raw $raw -Name $Name
62
+ $sha256 = [Convert]::ToHexString([System.Security.Cryptography.SHA256]::HashData($bytes)).ToLowerInvariant()
63
+ if (-not $HoldOpen) { $stream.Dispose(); $stream = $null }
64
+ return [pscustomobject]@{ Value = $value; Sha256 = $sha256; Stream = $stream }
65
+ } catch {
66
+ $stream.Dispose()
67
+ throw
68
+ }
69
+ }
70
+
71
+ function Get-CodexChildLaunchReceipt {
72
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
73
+ param(
74
+ [Parameter(Mandatory)] $Spec,
75
+ [Parameter(Mandatory)] $Entry,
76
+ [Parameter(Mandatory)] $AgentProfile,
77
+ [Parameter(Mandatory)] $CodexRuntime,
78
+ [Parameter(Mandatory)][string] $SpecPath,
79
+ [Parameter(Mandatory)][string] $SpecSha256,
80
+ [Parameter(Mandatory)][string] $CheckpointPath,
81
+ [Parameter(Mandatory)][string] $CheckpointSha256,
82
+ [Parameter(Mandatory)][string] $ReceiptPath,
83
+ [Parameter(Mandatory)][string] $StatusPath,
84
+ [Parameter(Mandatory)][string] $CodexHomePath,
85
+ [Parameter(Mandatory)][string] $WaveLockPath
86
+ )
87
+ $authorizedAt = [datetimeoffset]::UtcNow
88
+ return [ordered]@{
89
+ schema_version = 2; state = 'launching'; codex_session_id = ''
90
+ launch_id = [string]$Entry.launch_id; wave_id = [string]$Spec.wave_id
91
+ checkpoint_kind = [string]$Spec.checkpoint_kind; wave_lock_path = $WaveLockPath
92
+ wave_number = [int]$Spec.wave_number; max_parallel_features = [int]$Spec.max_parallel_features
93
+ feature_folder = [string]$Entry.feature_folder; issue_num = $Entry.issue_num
94
+ delegation_id = [string]$Entry.delegation_id; deployment_agent = [string]$Entry.deployment_agent
95
+ model = [string]$Entry.model; model_reasoning_effort = [string]$Entry.model_reasoning_effort
96
+ permissions = [string]$Entry.permissions; runtime_permissions = 'epic-child-workspace'
97
+ execution_context = [string]$Entry.execution_context
98
+ worktree_path = [string]$AgentProfile.worktree_path; branch_name = [string]$Entry.branch_name
99
+ integration_branch = [string]$Spec.integration_branch; integration_head = [string]$AgentProfile.integration_head
100
+ child_head = [string]$AgentProfile.child_head
101
+ trusted_repository_root = [string]$AgentProfile.trusted_repository_root
102
+ trusted_repository_head = [string]$AgentProfile.trusted_repository_head
103
+ git_common_directory = [string]$AgentProfile.git_common_directory
104
+ trusted_surface_objects = $AgentProfile.trusted_surface_objects
105
+ trusted_surface_sha256 = [string]$AgentProfile.trusted_surface_sha256
106
+ prompt_sha256 = Get-CodexChildSha256 -Value ([string]$Entry.prompt)
107
+ profile_path = [string]$AgentProfile.profile_path
108
+ profile_sha256 = [string]$AgentProfile.profile_sha256
109
+ developer_instructions_sha256 = Get-CodexChildSha256 -Value ([string]$AgentProfile.developer_instructions)
110
+ skills_config_sha256 = Get-CodexChildSha256 -Value ([string]$AgentProfile.skills_config)
111
+ spec_path = $SpecPath; spec_sha256 = $SpecSha256
112
+ checkpoint_path = $CheckpointPath; checkpoint_sha256 = $CheckpointSha256
113
+ receipt_path = $ReceiptPath; status_path = $StatusPath; codex_home_path = $CodexHomePath
114
+ codex_command_path = [string]$CodexRuntime.CommandPath
115
+ codex_denied_paths = [string[]]$CodexRuntime.DeniedPaths
116
+ authorized_at = $authorizedAt.ToString('o'); session_bound_at = ''
117
+ expires_at = $authorizedAt.AddDays(7).ToString('o')
118
+ }
119
+ }
120
+
121
+ function Get-CodexChildProcessStartInfo {
122
+ [CmdletBinding()]
123
+ param(
124
+ [Parameter(Mandatory)] $Entry,
125
+ [Parameter(Mandatory)] $AgentProfile,
126
+ [Parameter(Mandatory)] $Receipt,
127
+ [Parameter(Mandatory)][string] $LastMessagePath
128
+ )
129
+ $codexCommandPath = [string]$Receipt.codex_command_path
130
+ $isPowerShellShim = $codexCommandPath.EndsWith('.ps1', [System.StringComparison]::OrdinalIgnoreCase)
131
+ $info = [System.Diagnostics.ProcessStartInfo]::new($(if ($isPowerShellShim) { 'pwsh' } else { $codexCommandPath }))
132
+ $info.UseShellExecute = $false; $info.CreateNoWindow = $true
133
+ $info.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
134
+ $info.RedirectStandardInput = $true; $info.RedirectStandardOutput = $true
135
+ $info.RedirectStandardError = $true; $info.WorkingDirectory = [string]$Entry.worktree_path
136
+ if ($isPowerShellShim) {
137
+ foreach ($argument in @('-NoProfile', '-File', $codexCommandPath)) { $info.ArgumentList.Add($argument) }
138
+ }
139
+ $permissionOverride = Get-CodexChildPermissionOverride -DeniedPaths ([string[]]$Receipt.codex_denied_paths)
140
+ $projectsOverride = Get-CodexChildProjectsOverride -WorktreePath ([string]$Entry.worktree_path)
141
+ $shellOverrides = Get-CodexChildShellEnvironmentOverrideList -WorktreePath ([string]$Entry.worktree_path)
142
+ foreach ($argument in @(
143
+ 'exec', '--ignore-user-config', '-C', [string]$Entry.worktree_path,
144
+ '-c', $projectsOverride, '-c', $permissionOverride,
145
+ '-c', $shellOverrides[0], '-c', $shellOverrides[1],
146
+ '-m', [string]$AgentProfile.model,
147
+ '-c', "model_reasoning_effort=$($AgentProfile.model_reasoning_effort)",
148
+ '-c', 'default_permissions="epic-child-workspace"', '-c', 'approval_policy="never"',
149
+ '-c', ('developer_instructions=' + ([string]$AgentProfile.developer_instructions | ConvertTo-Json -Compress)),
150
+ '-c', "skills.config=$($AgentProfile.skills_config)", '--strict-config',
151
+ '--dangerously-bypass-hook-trust', '--json', '-o', $LastMessagePath, '-'
152
+ )) { $info.ArgumentList.Add($argument) }
153
+ if ($IsWindows) { $info.ArgumentList.Insert(1, 'windows.sandbox="elevated"'); $info.ArgumentList.Insert(1, '-c') }
154
+ $environment = @{
155
+ CODEX_EPIC_CHILD_LAUNCH_ID = [string]$Receipt.launch_id
156
+ CODEX_EPIC_CHILD_LAUNCH_RECEIPT = [string]$Receipt.receipt_path
157
+ CODEX_EPIC_CHILD_LAUNCH_SPEC = [string]$Receipt.spec_path
158
+ CODEX_EPIC_CHILD_EXPECTED_WORKTREE = [string]$Receipt.worktree_path
159
+ CODEX_EPIC_CHILD_DELEGATION_ID = [string]$Receipt.delegation_id
160
+ CODEX_EPIC_CHILD_EXECUTION_CONTEXT = [string]$Receipt.execution_context
161
+ CODEX_EPIC_CHILD_AGENT = [string]$Receipt.deployment_agent
162
+ CODEX_EPIC_CHILD_MODEL = [string]$Receipt.model
163
+ CODEX_EPIC_CHILD_REASONING_EFFORT = [string]$Receipt.model_reasoning_effort
164
+ CODEX_EPIC_CHILD_PROFILE_SHA256 = [string]$Receipt.profile_sha256
165
+ }
166
+ foreach ($item in $environment.GetEnumerator()) { $info.Environment[$item.Key] = $item.Value }
167
+ $info.Environment['CODEX_HOME'] = [string]$Receipt.codex_home_path
168
+ return $info
169
+ }
170
+
171
+ function Start-CodexChildProcess {
172
+ [CmdletBinding(SupportsShouldProcess)]
173
+ param(
174
+ [Parameter(Mandatory)] $Entry,
175
+ [Parameter(Mandatory)] $AgentProfile,
176
+ [Parameter(Mandatory)] $Receipt,
177
+ [Parameter(Mandatory)][string] $ArtifactRoot
178
+ )
179
+ $base = Join-Path $ArtifactRoot ([string]$Entry.launch_id)
180
+ if (-not $PSCmdlet.ShouldProcess([string]$Entry.worktree_path, "Start Codex epic child $($Entry.launch_id)")) { return $null }
181
+ $process = [System.Diagnostics.Process]::new()
182
+ $process.StartInfo = Get-CodexChildProcessStartInfo -Entry $Entry -AgentProfile $AgentProfile `
183
+ -Receipt $Receipt -LastMessagePath "$base.last-message.txt"
184
+ $started = $false
185
+ try {
186
+ if (-not $process.Start()) { throw "failed to start $($Entry.launch_id)." }
187
+ $started = $true
188
+ $errorTask = $process.StandardError.ReadToEndAsync()
189
+ $process.StandardInput.Write([string]$Entry.prompt); $process.StandardInput.Close()
190
+ $prefixLines = [System.Collections.Generic.List[string]]::new()
191
+ $sessionId = ''; $deadline = [datetimeoffset]::UtcNow.AddSeconds(30)
192
+ while ([string]::IsNullOrWhiteSpace($sessionId) -and $prefixLines.Count -lt 16) {
193
+ $remaining = [Math]::Max(1, [int]($deadline - [datetimeoffset]::UtcNow).TotalMilliseconds)
194
+ $readTask = $process.StandardOutput.ReadLineAsync()
195
+ if (-not $readTask.Wait($remaining)) { throw 'session id timeout.' }
196
+ if ($null -eq $readTask.Result) { break }
197
+ $prefixLines.Add($readTask.Result)
198
+ $sessionId = Get-CodexChildSessionId -JsonLines @($readTask.Result)
199
+ }
200
+ if ([string]::IsNullOrWhiteSpace($sessionId)) { throw 'no usable Codex session id was emitted.' }
201
+ Set-CodexChildReceiptState -Receipt $Receipt -State active -SessionId $sessionId
202
+ return [pscustomobject]@{
203
+ Entry = $Entry; Process = $process; ErrorTask = $errorTask; SessionId = $sessionId
204
+ OutputTask = $process.StandardOutput.ReadToEndAsync(); ExitTask = $process.WaitForExitAsync()
205
+ OutputPrefix = [string]::Join([Environment]::NewLine, $prefixLines) + [Environment]::NewLine
206
+ BasePath = $base; StartedAt = [datetimeoffset]::UtcNow.ToString('o'); Receipt = $Receipt
207
+ }
208
+ } catch {
209
+ if ($started -and -not $process.HasExited) { $process.Kill($true) }
210
+ Set-CodexChildReceiptState -Receipt $Receipt -State failed -FailureReason ([string]$_)
211
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: $($Entry.launch_id) failed during startup: $_"
212
+ }
213
+ }
214
+
215
+ function Get-CodexChildTerminalStatusEntry {
216
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
217
+ param([Parameter(Mandatory)] $Child, [Parameter(Mandatory)][string] $ReceiptPath)
218
+ $wrapper = Join-Path ([string]$Child.Entry.worktree_path) '.codex/scripts/resume-epic-child.ps1'
219
+ $quotedWrapper = "'" + $wrapper.Replace("'", "''") + "'"
220
+ $quotedReceipt = "'" + $ReceiptPath.Replace("'", "''") + "'"
221
+ $status = [ordered]@{
222
+ state = $(if ($Child.Process.ExitCode -eq 0) { 'completed' } else { 'failed' })
223
+ pid = $Child.Process.Id; exit_code = $Child.Process.ExitCode
224
+ codex_session_id = $Child.SessionId; receipt_path = $ReceiptPath
225
+ resume_script_path = $wrapper
226
+ resume_command = "& $quotedWrapper -ReceiptPath $quotedReceipt"
227
+ stdout_path = "$($Child.BasePath).stdout.jsonl"; stderr_path = "$($Child.BasePath).stderr.log"
228
+ }
229
+ if ($Child.Process.ExitCode -eq 0) {
230
+ $status.completed_at = [string]$Child.Receipt.completed_at
231
+ } else {
232
+ $status.failed_at = [string]$Child.Receipt.failed_at
233
+ }
234
+ return $status
235
+ }
236
+
237
+ function Get-CodexChildResumeStatus {
238
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
239
+ param([Parameter(Mandatory)] $Child, [Parameter(Mandatory)][string] $ReceiptPath)
240
+ [System.IO.File]::WriteAllText("$($Child.BasePath).stdout.jsonl", $Child.OutputPrefix + $Child.OutputTask.Result)
241
+ [System.IO.File]::WriteAllText("$($Child.BasePath).stderr.log", $Child.ErrorTask.Result)
242
+ if ($Child.Process.ExitCode -eq 0) {
243
+ Set-CodexChildReceiptState -Receipt $Child.Receipt -State completed -ExitCode 0
244
+ } else {
245
+ Set-CodexChildReceiptState -Receipt $Child.Receipt -State failed `
246
+ -ExitCode $Child.Process.ExitCode -FailureReason 'Codex child process returned a nonzero exit code.'
247
+ }
248
+ Remove-CodexChildIsolatedAuth -HomePath ([string]$Child.Receipt.codex_home_path) -Confirm:$false
249
+ return Get-CodexChildTerminalStatusEntry -Child $Child -ReceiptPath $ReceiptPath
250
+ }
251
+
252
+ function Write-CodexChildWaveStatus {
253
+ param([string] $Path, [string] $WaveId, [string] $State, $Statuses, [AllowEmptyString()][string] $Failure = '')
254
+ Write-CodexChildJsonAtomic -Path $Path -Value ([ordered]@{
255
+ schema_version = 2; wave_id = $WaveId; supervisor_pid = $PID
256
+ state = $State; failure = $Failure; launches = $Statuses
257
+ updated_at = [datetimeoffset]::UtcNow.ToString('o')
258
+ })
259
+ }
260
+
261
+ function Invoke-CodexChildWaveSupervisor {
262
+ [CmdletBinding()]
263
+ param(
264
+ [Parameter(Mandatory)] $Spec, [Parameter(Mandatory)][System.Collections.IDictionary] $Profiles,
265
+ [Parameter(Mandatory)] $CodexRuntime, [Parameter(Mandatory)][string] $SpecPath,
266
+ [Parameter(Mandatory)][string] $CheckpointPath, [Parameter(Mandatory)][string] $ArtifactRoot,
267
+ [Parameter(Mandatory)][string] $SpecSha256, [Parameter(Mandatory)][string] $CheckpointSha256,
268
+ [Parameter(Mandatory)][string] $RepositoryRoot,
269
+ [Parameter(Mandatory)][int] $MaxParallel
270
+ )
271
+ [System.IO.Directory]::CreateDirectory($ArtifactRoot) | Out-Null
272
+ $statusPath = Join-Path $ArtifactRoot "wave.$($Spec.wave_id).status.json"
273
+ $lockPath = Get-CodexChildSemanticWaveLockPath -Spec $Spec -ArtifactRoot $ArtifactRoot
274
+ [System.IO.Directory]::CreateDirectory((Split-Path $lockPath -Parent)) | Out-Null
275
+ $lock = Enter-CodexChildWaveLock -Path $lockPath
276
+ $statuses = [ordered]@{}; $running = [System.Collections.Generic.List[object]]::new()
277
+ $queue = [System.Collections.Generic.Queue[object]]::new(); $failure = $null
278
+ $currentEntry = $null; $currentHome = ''; $currentReceipt = $null
279
+ $specSeal = $null; $checkpointSeal = $null
280
+ try {
281
+ $specSeal = Get-CodexChildSealedJsonFile -Path $SpecPath -Name 'launch spec' -HoldOpen
282
+ $checkpointSeal = Get-CodexChildSealedJsonFile -Path $CheckpointPath -Name 'epic checkpoint' -HoldOpen
283
+ if ([string]$specSeal.Sha256 -cne $SpecSha256 -or
284
+ [string]$checkpointSeal.Sha256 -cne $CheckpointSha256) {
285
+ throw 'EPIC_CHILD_LAUNCH_BLOCKED: launch spec or checkpoint changed after validation.'
286
+ }
287
+ $Spec = $specSeal.Value; $checkpoint = $checkpointSeal.Value
288
+ $lockedRuntime = Get-CodexChildRuntimeContext -Spec $Spec -RepositoryRoot $RepositoryRoot
289
+ $lockedErrors = Test-CodexChildLaunchSpec -Spec $Spec -Checkpoint $checkpoint -RepositoryRoot $RepositoryRoot `
290
+ -ProfilesByKey $lockedRuntime.Profiles -LiveBranchesByWorktree $lockedRuntime.Branches
291
+ if ($lockedErrors.Count -gt 0) {
292
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: $($lockedErrors -join ' ')"
293
+ }
294
+ $Profiles = $lockedRuntime.Profiles
295
+ foreach ($entry in @($Spec.launches)) {
296
+ $queue.Enqueue($entry); $statuses[[string]$entry.launch_id] = [ordered]@{ state = 'queued' }
297
+ }
298
+ $specHash = [string]$specSeal.Sha256; $checkpointHash = [string]$checkpointSeal.Sha256
299
+ Write-CodexChildWaveStatus -Path $statusPath -WaveId $Spec.wave_id -State running -Statuses $statuses
300
+ while ($queue.Count -gt 0 -or $running.Count -gt 0) {
301
+ while ($queue.Count -gt 0 -and $running.Count -lt $MaxParallel) {
302
+ $entry = $queue.Dequeue(); $currentEntry = $entry
303
+ $receiptPath = Join-Path $ArtifactRoot "$($entry.launch_id).receipt.json"
304
+ $profileKey = Get-CodexChildProfileKey -WorktreePath ([string]$entry.worktree_path) `
305
+ -AgentName ([string]$entry.deployment_agent) -RepositoryRoot $RepositoryRoot
306
+ $agentProfile = $Profiles[$profileKey]
307
+ $currentHome = New-CodexChildIsolatedHome -RepositoryRoot $RepositoryRoot `
308
+ -WaveId ([string]$Spec.wave_id) -LaunchId ([string]$entry.launch_id) `
309
+ -OriginalAuthPath ([string]$CodexRuntime.OriginalAuthPath) -Confirm:$false
310
+ $launchRuntime = [pscustomobject]@{
311
+ CommandPath = [string]$CodexRuntime.CommandPath
312
+ DeniedPaths = [string[]]@($CodexRuntime.DeniedPaths) + @($currentHome)
313
+ OriginalAuthPath = [string]$CodexRuntime.OriginalAuthPath
314
+ }
315
+ $probeInfo = Get-CodexChildSandboxProbeStartInfo -WorktreePath ([string]$entry.worktree_path) `
316
+ -CodexHomePath $currentHome -CommandPath ([string]$launchRuntime.CommandPath) `
317
+ -DeniedPaths ([string[]]$launchRuntime.DeniedPaths) `
318
+ -DeniedProbePath ([string]$launchRuntime.OriginalAuthPath)
319
+ Assert-CodexChildSandboxPreflight -StartInfo $probeInfo
320
+ $receipt = Get-CodexChildLaunchReceipt -Spec $Spec -Entry $entry -AgentProfile $agentProfile `
321
+ -CodexRuntime $launchRuntime -SpecPath $SpecPath -SpecSha256 $specHash `
322
+ -CheckpointPath $CheckpointPath -CheckpointSha256 $checkpointHash `
323
+ -ReceiptPath $receiptPath -StatusPath $statusPath -CodexHomePath $currentHome `
324
+ -WaveLockPath $lockPath
325
+ Write-CodexChildJsonCreateNew -Path $receiptPath -Value $receipt
326
+ $currentReceipt = $receipt
327
+ $child = Start-CodexChildProcess -Entry $entry -AgentProfile $agentProfile -Receipt $receipt -ArtifactRoot $ArtifactRoot
328
+ $running.Add($child)
329
+ $currentEntry = $null; $currentHome = ''; $currentReceipt = $null
330
+ $statuses[[string]$entry.launch_id] = [ordered]@{
331
+ state = 'running'; pid = $child.Process.Id; codex_session_id = $child.SessionId
332
+ receipt_path = $receiptPath; started_at = $child.StartedAt
333
+ }
334
+ Write-CodexChildWaveStatus -Path $statusPath -WaveId $Spec.wave_id -State running -Statuses $statuses
335
+ }
336
+ if ($running.Count -eq 0) { continue }
337
+ [System.Threading.Tasks.Task]::WaitAny([System.Threading.Tasks.Task[]]@($running | ForEach-Object { $_.ExitTask })) | Out-Null
338
+ foreach ($child in @($running | Where-Object { $_.ExitTask.IsCompleted })) {
339
+ $receiptPath = Join-Path $ArtifactRoot "$($child.Entry.launch_id).receipt.json"
340
+ $statuses[[string]$child.Entry.launch_id] = Get-CodexChildResumeStatus -Child $child -ReceiptPath $receiptPath
341
+ $running.Remove($child) | Out-Null
342
+ Write-CodexChildWaveStatus -Path $statusPath -WaveId $Spec.wave_id -State running -Statuses $statuses
343
+ }
344
+ }
345
+ } catch {
346
+ $failure = $_
347
+ } finally {
348
+ $cleanupErrors = [System.Collections.Generic.List[string]]::new()
349
+ if ($null -ne $currentEntry) {
350
+ $failedStatus = [ordered]@{ state = 'failed'; failure = [string]$failure }
351
+ if ($null -ne $currentReceipt -and [string]$currentReceipt.state -ceq 'failed') {
352
+ $failedStatus.receipt_path = [string]$currentReceipt.receipt_path
353
+ $failedStatus.codex_session_id = [string]$currentReceipt.codex_session_id
354
+ $failedStatus.exit_code = [int]$currentReceipt.exit_code
355
+ $failedStatus.failed_at = [string]$currentReceipt.failed_at
356
+ }
357
+ $statuses[[string]$currentEntry.launch_id] = $failedStatus
358
+ if (-not [string]::IsNullOrWhiteSpace($currentHome)) {
359
+ try { Remove-CodexChildIsolatedHome -HomePath $currentHome -Confirm:$false } catch {
360
+ $cleanupErrors.Add("failed to remove unlaunched isolated home: $_")
361
+ }
362
+ }
363
+ }
364
+ foreach ($child in @($running)) {
365
+ $childExitCode = -1
366
+ try {
367
+ if (-not $child.Process.HasExited) { $child.Process.Kill($true); $child.Process.WaitForExit() }
368
+ if ($child.Process.HasExited) { $childExitCode = [int]$child.Process.ExitCode }
369
+ } catch {
370
+ $cleanupErrors.Add("failed to terminate child $($child.Entry.launch_id): $_")
371
+ }
372
+ try {
373
+ Set-CodexChildReceiptState -Receipt $child.Receipt -State failed -ExitCode $childExitCode `
374
+ -FailureReason 'Wave supervisor aborted the running child.'
375
+ } catch {
376
+ $cleanupErrors.Add("failed to seal child receipt $($child.Entry.launch_id): $_")
377
+ }
378
+ try {
379
+ Remove-CodexChildIsolatedAuth -HomePath ([string]$child.Receipt.codex_home_path) -Confirm:$false
380
+ } catch {
381
+ $cleanupErrors.Add("failed to remove child auth $($child.Entry.launch_id): $_")
382
+ }
383
+ $statuses[[string]$child.Entry.launch_id] = [ordered]@{
384
+ state = 'failed'; outcome = 'aborted'; pid = $child.Process.Id
385
+ exit_code = [int]$child.Receipt.exit_code; failed_at = [string]$child.Receipt.failed_at
386
+ codex_session_id = $child.SessionId
387
+ receipt_path = [string]$child.Receipt.receipt_path; failure = [string]$failure
388
+ }
389
+ }
390
+ while ($queue.Count -gt 0) {
391
+ $entry = $queue.Dequeue(); $statuses[[string]$entry.launch_id] = [ordered]@{ state = 'cancelled' }
392
+ }
393
+ $failed = @($statuses.Values | Where-Object { $_.state -in @('failed', 'cancelled') }).Count -gt 0
394
+ $finalState = if ($null -ne $failure -or $failed -or $cleanupErrors.Count -gt 0) { 'failed' } else { 'completed' }
395
+ $failureParts = [System.Collections.Generic.List[string]]::new()
396
+ if ($null -ne $failure) { $failureParts.Add([string]$failure) }
397
+ foreach ($cleanupError in $cleanupErrors) { $failureParts.Add($cleanupError) }
398
+ try {
399
+ Write-CodexChildWaveStatus -Path $statusPath -WaveId $Spec.wave_id -State $finalState `
400
+ -Statuses $statuses -Failure ($failureParts -join ' | ')
401
+ } catch {
402
+ $cleanupErrors.Add("failed to write final wave status: $_")
403
+ }
404
+ try {
405
+ if ($null -ne $checkpointSeal -and $null -ne $checkpointSeal.Stream) { $checkpointSeal.Stream.Dispose() }
406
+ } catch { $cleanupErrors.Add("failed to release checkpoint seal: $_") }
407
+ try {
408
+ if ($null -ne $specSeal -and $null -ne $specSeal.Stream) { $specSeal.Stream.Dispose() }
409
+ } catch { $cleanupErrors.Add("failed to release spec seal: $_") }
410
+ try { $lock.Dispose() } catch { $cleanupErrors.Add("failed to release semantic wave lock: $_") }
411
+ if ($cleanupErrors.Count -gt 0) {
412
+ $cleanupFailure = [System.Exception]::new("EPIC_CHILD_LAUNCH_BLOCKED: cleanup failures: $($cleanupErrors -join ' | ')")
413
+ if ($null -eq $failure) { $failure = $cleanupFailure } else {
414
+ $failure = [System.AggregateException]::new('Epic-child wave and cleanup failed.',
415
+ [System.Exception[]]@($failure.Exception, $cleanupFailure))
416
+ }
417
+ }
418
+ }
419
+ if ($null -ne $failure) { throw $failure }
420
+ return $statusPath
421
+ }
422
+
423
+ if ($MyInvocation.InvocationName -eq '.') { return }
424
+ if ([string]::IsNullOrWhiteSpace($LaunchSpecPath)) { throw 'EPIC_CHILD_LAUNCH_BLOCKED: LaunchSpecPath is required.' }
425
+ $scriptRepositoryRoot = Get-CodexChildCanonicalPath -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -BasePath $PSScriptRoot
426
+ if (-not [string]::IsNullOrWhiteSpace($RepositoryRoot) -and
427
+ (Get-CodexChildCanonicalPath -Path $RepositoryRoot -BasePath (Get-Location).Path) -ne $scriptRepositoryRoot) {
428
+ throw 'EPIC_CHILD_LAUNCH_BLOCKED: RepositoryRoot must equal the repository containing this launcher.'
429
+ }
430
+ $repositoryRootPath = $scriptRepositoryRoot
431
+ $specPath = Get-CodexChildCanonicalPath -Path $LaunchSpecPath -BasePath $repositoryRootPath
432
+ $launchBase = Get-CodexChildCanonicalPath -Path 'artifacts/orchestration/epic-child-launches' -BasePath $repositoryRootPath
433
+ if (-not $specPath.StartsWith($launchBase + [System.IO.Path]::DirectorySeparatorChar, [System.StringComparison]::OrdinalIgnoreCase)) {
434
+ throw 'EPIC_CHILD_LAUNCH_BLOCKED: launch spec must be under the repository launch-artifact root.'
435
+ }
436
+ $specSeal = Get-CodexChildSealedJsonFile -Path $specPath -Name 'launch spec'
437
+ $spec = $specSeal.Value
438
+ $artifactRoot = Get-CodexChildCanonicalPath -Path (Join-Path $launchBase ([string]$spec.wave_id)) -BasePath $repositoryRootPath
439
+ if ((Split-Path $specPath -Parent) -ne $artifactRoot) { throw 'EPIC_CHILD_LAUNCH_BLOCKED: launch spec parent must match wave_id.' }
440
+ $checkpointPath = Get-CodexChildCanonicalPath -Path ([string]$spec.checkpoint_path) -BasePath $repositoryRootPath
441
+ $expectedCheckpoint = Get-CodexChildCanonicalPath -Path "artifacts/orchestration/$($spec.checkpoint_kind)-state.json" -BasePath $repositoryRootPath
442
+ if ($checkpointPath -ne $expectedCheckpoint) { throw 'EPIC_CHILD_LAUNCH_BLOCKED: checkpoint_path must be the canonical repository epic checkpoint.' }
443
+ $checkpointSeal = Get-CodexChildSealedJsonFile -Path $checkpointPath -Name 'epic checkpoint'
444
+ $checkpoint = $checkpointSeal.Value
445
+ $runtime = Get-CodexChildRuntimeContext -Spec $spec -RepositoryRoot $repositoryRootPath
446
+ $errors = Test-CodexChildLaunchSpec -Spec $spec -Checkpoint $checkpoint -RepositoryRoot $repositoryRootPath `
447
+ -ProfilesByKey $runtime.Profiles -LiveBranchesByWorktree $runtime.Branches
448
+ if ($errors.Count -gt 0) { throw "EPIC_CHILD_LAUNCH_BLOCKED: $($errors -join ' ')" }
449
+ $effectiveMaximum = Get-CodexChildEffectiveMaximum -RequestedMaximum $MaxParallel -SpecMaximum ([int]$spec.max_parallel_features)
450
+ $codexRuntime = Get-CodexChildCommandContext
451
+ if ($Supervisor -or $Wait) {
452
+ Invoke-CodexChildWaveSupervisor -Spec $spec -Profiles $runtime.Profiles -CodexRuntime $codexRuntime `
453
+ -SpecPath $specPath -CheckpointPath $checkpointPath -ArtifactRoot $artifactRoot `
454
+ -SpecSha256 ([string]$specSeal.Sha256) -CheckpointSha256 ([string]$checkpointSeal.Sha256) `
455
+ -RepositoryRoot $repositoryRootPath -MaxParallel $effectiveMaximum | Write-Output
456
+ exit 0
457
+ }
458
+ $supervisorInfo = [System.Diagnostics.ProcessStartInfo]::new('pwsh')
459
+ $supervisorInfo.UseShellExecute = $false; $supervisorInfo.CreateNoWindow = $true
460
+ $supervisorInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
461
+ foreach ($argument in @('-NoProfile', '-File', $PSCommandPath, '-LaunchSpecPath', $specPath,
462
+ '-RepositoryRoot', $repositoryRootPath, '-MaxParallel', [string]$effectiveMaximum, '-Supervisor')) {
463
+ $supervisorInfo.ArgumentList.Add($argument)
464
+ }
465
+ $supervisorProcess = [System.Diagnostics.Process]::Start($supervisorInfo)
466
+ if ($null -eq $supervisorProcess) { throw 'EPIC_CHILD_LAUNCH_BLOCKED: failed to start the background supervisor.' }
467
+ [ordered]@{
468
+ wave_id = [string]$spec.wave_id; supervisor_pid = $supervisorProcess.Id
469
+ effective_max_parallel = $effectiveMaximum
470
+ status_path = Join-Path $artifactRoot "wave.$($spec.wave_id).status.json"
471
+ } | ConvertTo-Json -Compress | Write-Output