@danmoisan/drm-copilot-mcp 1.0.21 → 1.0.22

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 (31) hide show
  1. package/out/mcp-server.js +1624 -190
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/MEMORY.md +5 -1
  4. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_commit_push_memory_before_pr.md +48 -2
  5. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_no_sendmessage_tool.md +35 -0
  6. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_worktree_isolation_branches_from_main.md +45 -0
  7. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +238 -0
  8. package/resources/claude-customizations/.claude/agents/parallel-planner.md +149 -0
  9. package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +23 -11
  10. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +259 -0
  11. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +499 -0
  12. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate-helpers.ps1 +302 -0
  13. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +359 -0
  14. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +244 -0
  15. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +379 -0
  16. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +491 -0
  17. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +490 -0
  18. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusGlob.psm1 +429 -0
  19. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +366 -0
  20. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +184 -0
  21. package/resources/claude-customizations/.claude/settings.json +25 -0
  22. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +148 -0
  23. package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +93 -0
  24. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +960 -0
  25. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +420 -0
  26. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +176 -0
  27. package/resources/claude-customizations/.claude/skills/parallel-run/SKILL.md +56 -0
  28. package/resources/claude-customizations/pack-manifests/core.json +19 -1
  29. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  30. package/resources/config/orchestration-routing.json +22 -0
  31. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +29 -0
@@ -0,0 +1,499 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Pre-tool-use hook that is the Layer 1 per-call deterrent for the parallel cohort barrier.
4
+
5
+ .DESCRIPTION
6
+ Invoked by the Claude Code PreToolUse hook on the "Agent" matcher before any Agent
7
+ (Task) call runs. Activates only when CLAUDE_TOOL_INPUT.subagent_type == "orchestrator"
8
+ and the serialized prompt contains the parallel kickoff marker "Parallel mode: true".
9
+
10
+ Adapted from enforce-epic-wave-barrier.ps1. The parallel surface has no depends_on
11
+ field, so the epic hook's depends_on lookup is replaced by conflict-edge plus
12
+ cohort-index logic: ordering is derived from blast-radius contention recorded as
13
+ conflict_edges[], and position is read from the current cohort coloring.
14
+
15
+ Resolution and decision procedure:
16
+ 1. Resolve the target item's feature folder from the prompt text by scanning for a
17
+ docs/features/active/<token> path, mirroring the epic hook's technique
18
+ (longest match wins; a .md-suffixed match uses its parent directory).
19
+ 2. Read artifacts/orchestration/parallel-orchestrator-state.json and locate the
20
+ items[] record whose feature_folder resolves to the same basename.
21
+ 3. Project the cohort coloring to the cohorts[] rows whose generation equals the
22
+ top-level recolor_generation, and read the target item's cohort index from that
23
+ projection.
24
+ 4. Collect every conflict_edges[] neighbor of the target item.
25
+ 5. Deny with reason PARALLEL_COHORT_BARRIER_BLOCKED unless every neighbor that sits
26
+ in a strictly prior current-generation cohort has merge_status in
27
+ {merged, worktree_removed}. ci_green does NOT satisfy the barrier: an item whose
28
+ CI is green has not merged, so its worktree is still live and its contention is
29
+ unresolved. Same-cohort and later-cohort neighbors do not block Layer 1.
30
+ 6. A missing or unparseable checkpoint, an unresolved feature-folder token, a
31
+ missing items[] record, a target with no current-generation cohort assignment, a
32
+ missing neighbor record, and a missing merge_status all deny (fail-closed).
33
+
34
+ This is the per-call deterrent (Layer 1) of the two-layer cohort-barrier design.
35
+ Neither layer alone closes the gap: a PreToolUse hook fires once per tool call with no
36
+ cross-call or conversation-state visibility, so it cannot validate a batch of
37
+ concurrent Agent calls. The retrospective backstop (Layer 2) is the
38
+ PARALLEL_COHORT_BARRIER_VIOLATION ordering invariant inside
39
+ validate_parallel_orchestrator_state_text, enforced at parallel-orchestrator
40
+ SubagentStop time.
41
+
42
+ .NOTES
43
+ Compatible with PowerShell 7+. No external module dependencies. Filesystem reads go
44
+ through an injectable wrapper function so tests can mock the boundary without writing
45
+ temporary files.
46
+ #>
47
+ [CmdletBinding()]
48
+ param()
49
+
50
+ $script:ParallelCheckpointPath = 'artifacts/orchestration/parallel-orchestrator-state.json'
51
+ $script:AllowedMergeStatuses = @('merged', 'worktree_removed')
52
+ $script:ParallelModeMarker = 'Parallel mode: true'
53
+
54
+ function Get-ParallelCohortBarrierCheckpointContent {
55
+ <#
56
+ .SYNOPSIS
57
+ Read the raw JSON text of the parallel checkpoint. Tests mock this function
58
+ (read seam).
59
+ .OUTPUTS
60
+ System.String or $null
61
+ #>
62
+ [CmdletBinding()]
63
+ [OutputType([string])]
64
+ param()
65
+
66
+ if (-not (Test-Path -LiteralPath $script:ParallelCheckpointPath -PathType Leaf)) {
67
+ return $null
68
+ }
69
+ return (Get-Content -LiteralPath $script:ParallelCheckpointPath -Raw)
70
+ }
71
+
72
+ function Get-ParallelCohortBarrierFolderBasename {
73
+ <#
74
+ .SYNOPSIS
75
+ Normalize a feature-folder path or token to its basename.
76
+ .DESCRIPTION
77
+ Separators are normalized to forward slashes, a trailing slash is trimmed, and a
78
+ .md-suffixed value resolves to its parent directory before the basename is taken.
79
+ Applied to both sides of the target comparison so a checkpoint that records a full
80
+ docs/features/active/<folder> path and one that records a bare basename both match
81
+ the prompt token.
82
+ .PARAMETER FolderPath
83
+ The path or token to reduce.
84
+ .OUTPUTS
85
+ System.String or $null
86
+ #>
87
+ [CmdletBinding()]
88
+ [OutputType([string])]
89
+ param(
90
+ [AllowNull()]
91
+ [AllowEmptyString()]
92
+ [string] $FolderPath
93
+ )
94
+
95
+ if ([string]::IsNullOrWhiteSpace($FolderPath)) {
96
+ return $null
97
+ }
98
+
99
+ $normalized = ($FolderPath -replace '\\', '/').TrimEnd('/')
100
+ if ($normalized -match '\.md$') {
101
+ $normalized = $normalized -replace '/[^/]+\.md$', ''
102
+ }
103
+ return ($normalized -split '/')[-1]
104
+ }
105
+
106
+ function Find-ParallelCohortBarrierFeatureFolderFromPrompt {
107
+ <#
108
+ .SYNOPSIS
109
+ Scans a prompt string for docs/features/active/<...> path tokens and returns the
110
+ longest unique match's basename. Returns $null when no match is found.
111
+ .DESCRIPTION
112
+ Mirrors the epic wave-barrier hook's technique: forward- or backslash-separated
113
+ path tokens are accepted, the longest match wins, and a .md-suffixed match resolves
114
+ to its parent directory before the basename is extracted.
115
+ .PARAMETER Prompt
116
+ The delegation prompt text under evaluation.
117
+ .OUTPUTS
118
+ System.String or $null
119
+ #>
120
+ [CmdletBinding()]
121
+ [OutputType([string])]
122
+ param(
123
+ [Parameter(Mandatory)]
124
+ [AllowEmptyString()]
125
+ [string] $Prompt
126
+ )
127
+
128
+ if (-not $Prompt) {
129
+ return $null
130
+ }
131
+
132
+ $pattern = 'docs[\\/]+features[\\/]+active[\\/]+[^\s"''`]+'
133
+ $matchList = [regex]::Matches($Prompt, $pattern)
134
+ if ($matchList.Count -eq 0) {
135
+ return $null
136
+ }
137
+
138
+ $unique = @{}
139
+ foreach ($m in $matchList) {
140
+ $normalized = ($m.Value -replace '\\', '/').TrimEnd('/')
141
+ $unique[$normalized] = $true
142
+ }
143
+
144
+ $candidates = @(@($unique.Keys) | Sort-Object -Property Length -Descending)
145
+ return (Get-ParallelCohortBarrierFolderBasename -FolderPath $candidates[0])
146
+ }
147
+
148
+ function Find-ParallelCohortBarrierItemRecord {
149
+ <#
150
+ .SYNOPSIS
151
+ Locate the items[] record whose feature_folder resolves to the target basename.
152
+ .PARAMETER Checkpoint
153
+ Parsed parallel checkpoint, or $null when absent/unreadable.
154
+ .PARAMETER FeatureFolder
155
+ The target feature_folder basename resolved from the prompt.
156
+ .OUTPUTS
157
+ System.Object or $null
158
+ #>
159
+ [CmdletBinding()]
160
+ param(
161
+ [AllowNull()]
162
+ $Checkpoint,
163
+
164
+ [AllowNull()]
165
+ [string] $FeatureFolder
166
+ )
167
+
168
+ if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($FeatureFolder)) {
169
+ return $null
170
+ }
171
+ $checkpointProps = @($Checkpoint.PSObject.Properties.Name)
172
+ if ($checkpointProps -notcontains 'items') {
173
+ return $null
174
+ }
175
+
176
+ # Scan every recorded item for a feature_folder whose basename equals the target.
177
+ foreach ($item in @($Checkpoint.items)) {
178
+ $itemProps = @($item.PSObject.Properties.Name)
179
+ if ($itemProps -notcontains 'feature_folder') {
180
+ continue
181
+ }
182
+ $itemBasename = Get-ParallelCohortBarrierFolderBasename -FolderPath ([string]$item.feature_folder)
183
+ if ($itemBasename -eq $FeatureFolder) {
184
+ return $item
185
+ }
186
+ }
187
+ return $null
188
+ }
189
+
190
+ function Find-ParallelCohortBarrierItemByKey {
191
+ <#
192
+ .SYNOPSIS
193
+ Locate the items[] record whose issue_num equals the supplied key.
194
+ .PARAMETER Checkpoint
195
+ Parsed parallel checkpoint, or $null when absent/unreadable.
196
+ .PARAMETER IssueKey
197
+ The item primary key (issue_num) rendered as a string.
198
+ .OUTPUTS
199
+ System.Object or $null
200
+ #>
201
+ [CmdletBinding()]
202
+ param(
203
+ [AllowNull()]
204
+ $Checkpoint,
205
+
206
+ [AllowNull()]
207
+ [string] $IssueKey
208
+ )
209
+
210
+ if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($IssueKey)) {
211
+ return $null
212
+ }
213
+ $checkpointProps = @($Checkpoint.PSObject.Properties.Name)
214
+ if ($checkpointProps -notcontains 'items') {
215
+ return $null
216
+ }
217
+
218
+ foreach ($item in @($Checkpoint.items)) {
219
+ $itemProps = @($item.PSObject.Properties.Name)
220
+ if ($itemProps -notcontains 'issue_num') {
221
+ continue
222
+ }
223
+ if (([string]$item.issue_num) -eq $IssueKey) {
224
+ return $item
225
+ }
226
+ }
227
+ return $null
228
+ }
229
+
230
+ function Find-ParallelCohortBarrierCohortIndex {
231
+ <#
232
+ .SYNOPSIS
233
+ Return the current-generation cohort index that holds the supplied item key.
234
+ .DESCRIPTION
235
+ The cohort projection is restricted to cohorts[] rows whose generation equals the
236
+ top-level recolor_generation, which is the current coloring. Rows from a superseded
237
+ generation are ignored. Returns $null when the checkpoint records no
238
+ recolor_generation, no cohorts, or no current-generation cohort containing the key.
239
+ .PARAMETER Checkpoint
240
+ Parsed parallel checkpoint, or $null when absent/unreadable.
241
+ .PARAMETER IssueKey
242
+ The item primary key (issue_num) rendered as a string.
243
+ .OUTPUTS
244
+ System.Int32 or $null
245
+ #>
246
+ [CmdletBinding()]
247
+ [OutputType([int])]
248
+ param(
249
+ [AllowNull()]
250
+ $Checkpoint,
251
+
252
+ [AllowNull()]
253
+ [string] $IssueKey
254
+ )
255
+
256
+ if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($IssueKey)) {
257
+ return $null
258
+ }
259
+ $checkpointProps = @($Checkpoint.PSObject.Properties.Name)
260
+ if ($checkpointProps -notcontains 'cohorts' -or $checkpointProps -notcontains 'recolor_generation') {
261
+ return $null
262
+ }
263
+
264
+ $currentGeneration = [string]$Checkpoint.recolor_generation
265
+
266
+ foreach ($cohort in @($Checkpoint.cohorts)) {
267
+ $cohortProps = @($cohort.PSObject.Properties.Name)
268
+ if ($cohortProps -notcontains 'index' -or $cohortProps -notcontains 'generation' -or $cohortProps -notcontains 'item_keys') {
269
+ continue
270
+ }
271
+ if (([string]$cohort.generation) -ne $currentGeneration) {
272
+ continue
273
+ }
274
+ $parsedIndex = 0
275
+ if (-not [int]::TryParse(([string]$cohort.index), [ref] $parsedIndex)) {
276
+ continue
277
+ }
278
+ foreach ($key in @($cohort.item_keys)) {
279
+ if (([string]$key) -eq $IssueKey) {
280
+ return $parsedIndex
281
+ }
282
+ }
283
+ }
284
+ return $null
285
+ }
286
+
287
+ function Get-ParallelCohortBarrierConflictNeighborList {
288
+ <#
289
+ .SYNOPSIS
290
+ Return the item keys that share a conflict edge with the supplied item key.
291
+ .PARAMETER Checkpoint
292
+ Parsed parallel checkpoint, or $null when absent/unreadable.
293
+ .PARAMETER IssueKey
294
+ The item primary key (issue_num) rendered as a string.
295
+ .OUTPUTS
296
+ System.Object[] holding each neighbor item key rendered as a string.
297
+ #>
298
+ [CmdletBinding()]
299
+ [OutputType([object[]])]
300
+ param(
301
+ [AllowNull()]
302
+ $Checkpoint,
303
+
304
+ [AllowNull()]
305
+ [string] $IssueKey
306
+ )
307
+
308
+ $neighborList = @()
309
+ if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($IssueKey)) {
310
+ return $neighborList
311
+ }
312
+ $checkpointProps = @($Checkpoint.PSObject.Properties.Name)
313
+ if ($checkpointProps -notcontains 'conflict_edges') {
314
+ return $neighborList
315
+ }
316
+
317
+ # Edges are undirected and canonically normalized to a < b, so both endpoints are
318
+ # inspected and the opposite endpoint is returned as the neighbor.
319
+ foreach ($edge in @($Checkpoint.conflict_edges)) {
320
+ $edgeProps = @($edge.PSObject.Properties.Name)
321
+ if ($edgeProps -notcontains 'a' -or $edgeProps -notcontains 'b') {
322
+ continue
323
+ }
324
+ $endpointA = [string]$edge.a
325
+ $endpointB = [string]$edge.b
326
+ if ($endpointA -eq $IssueKey) {
327
+ $neighborList += $endpointB
328
+ } elseif ($endpointB -eq $IssueKey) {
329
+ $neighborList += $endpointA
330
+ }
331
+ }
332
+ return $neighborList
333
+ }
334
+
335
+ function Test-ParallelCohortBarrierClear {
336
+ <#
337
+ .SYNOPSIS
338
+ Decision logic: true only when every conflicting neighbor in a strictly prior
339
+ current-generation cohort has merge_status merged or worktree_removed.
340
+ .PARAMETER Checkpoint
341
+ Parsed parallel checkpoint, or $null when absent/unreadable.
342
+ .PARAMETER ItemRecord
343
+ The target item's own items[] record, or $null when not found.
344
+ .OUTPUTS
345
+ System.Boolean
346
+ #>
347
+ [CmdletBinding()]
348
+ [OutputType([bool])]
349
+ param(
350
+ [AllowNull()]
351
+ $Checkpoint,
352
+
353
+ [AllowNull()]
354
+ $ItemRecord
355
+ )
356
+
357
+ if ($null -eq $Checkpoint -or $null -eq $ItemRecord) {
358
+ return $false
359
+ }
360
+ $itemProps = @($ItemRecord.PSObject.Properties.Name)
361
+ if ($itemProps -notcontains 'issue_num') {
362
+ return $false
363
+ }
364
+
365
+ $targetKey = [string]$ItemRecord.issue_num
366
+ $targetIndex = Find-ParallelCohortBarrierCohortIndex -Checkpoint $Checkpoint -IssueKey $targetKey
367
+ if ($null -eq $targetIndex) {
368
+ # No current-generation cohort assignment: position is unknown, so fail closed.
369
+ return $false
370
+ }
371
+
372
+ foreach ($neighborKey in @(Get-ParallelCohortBarrierConflictNeighborList -Checkpoint $Checkpoint -IssueKey $targetKey)) {
373
+ $neighborIndex = Find-ParallelCohortBarrierCohortIndex -Checkpoint $Checkpoint -IssueKey $neighborKey
374
+ if ($null -eq $neighborIndex) {
375
+ # Not part of the current coloring, so not a strictly prior cohort neighbor.
376
+ continue
377
+ }
378
+ if ($neighborIndex -ge $targetIndex) {
379
+ # Same-cohort and later-cohort contention is not a Layer 1 concern.
380
+ continue
381
+ }
382
+ $neighborRecord = Find-ParallelCohortBarrierItemByKey -Checkpoint $Checkpoint -IssueKey $neighborKey
383
+ if ($null -eq $neighborRecord) {
384
+ return $false
385
+ }
386
+ $neighborProps = @($neighborRecord.PSObject.Properties.Name)
387
+ if ($neighborProps -notcontains 'merge_status') {
388
+ return $false
389
+ }
390
+ if ($script:AllowedMergeStatuses -notcontains ([string]$neighborRecord.merge_status)) {
391
+ return $false
392
+ }
393
+ }
394
+ return $true
395
+ }
396
+
397
+ function Get-ParallelCohortBarrierAllowDecision {
398
+ [CmdletBinding()]
399
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
400
+ param()
401
+
402
+ return [ordered]@{
403
+ hookSpecificOutput = [ordered]@{
404
+ hookEventName = 'PreToolUse'
405
+ permissionDecision = 'allow'
406
+ }
407
+ }
408
+ }
409
+
410
+ function Get-ParallelCohortBarrierBlockDecision {
411
+ [CmdletBinding()]
412
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
413
+ param(
414
+ [Parameter(Mandatory)]
415
+ [string] $Reason
416
+ )
417
+
418
+ return [ordered]@{
419
+ hookSpecificOutput = [ordered]@{
420
+ hookEventName = 'PreToolUse'
421
+ permissionDecision = 'deny'
422
+ permissionDecisionReason = $Reason
423
+ }
424
+ }
425
+ }
426
+
427
+ function Invoke-ParallelCohortBarrierDecision {
428
+ <#
429
+ .SYNOPSIS
430
+ Parses CLAUDE_TOOL_INPUT and returns an allow-or-block decision.
431
+ .PARAMETER ToolInputRaw
432
+ The raw JSON tool payload supplied by Claude Code.
433
+ .OUTPUTS
434
+ System.Collections.Specialized.OrderedDictionary
435
+ #>
436
+ [CmdletBinding()]
437
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
438
+ param(
439
+ [string] $ToolInputRaw
440
+ )
441
+
442
+ if (-not $ToolInputRaw) {
443
+ return Get-ParallelCohortBarrierAllowDecision
444
+ }
445
+
446
+ try {
447
+ $toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
448
+ } catch {
449
+ throw "enforce-parallel-cohort-barrier hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
450
+ }
451
+
452
+ $subagent = $toolInput.subagent_type
453
+ if (-not $subagent -or $subagent -ne 'orchestrator') {
454
+ return Get-ParallelCohortBarrierAllowDecision
455
+ }
456
+
457
+ $prompt = [string]$toolInput.prompt
458
+ if (-not $prompt -or $prompt -notlike "*$script:ParallelModeMarker*") {
459
+ return Get-ParallelCohortBarrierAllowDecision
460
+ }
461
+
462
+ $featureFolder = Find-ParallelCohortBarrierFeatureFolderFromPrompt -Prompt $prompt
463
+ if (-not $featureFolder) {
464
+ return Get-ParallelCohortBarrierBlockDecision -Reason 'PARALLEL_COHORT_BARRIER_BLOCKED: a parallel-mode orchestrator delegation must reference the target item feature folder path (docs/features/active/<folder>) in the prompt so its conflict edges and cohort position can be verified.'
465
+ }
466
+
467
+ $checkpointRaw = Get-ParallelCohortBarrierCheckpointContent
468
+ $checkpoint = $null
469
+ if (-not [string]::IsNullOrWhiteSpace($checkpointRaw)) {
470
+ try {
471
+ $checkpoint = $checkpointRaw | ConvertFrom-Json -ErrorAction Stop
472
+ } catch {
473
+ $checkpoint = $null
474
+ }
475
+ }
476
+
477
+ $itemRecord = Find-ParallelCohortBarrierItemRecord -Checkpoint $checkpoint -FeatureFolder $featureFolder
478
+ if (Test-ParallelCohortBarrierClear -Checkpoint $checkpoint -ItemRecord $itemRecord) {
479
+ return Get-ParallelCohortBarrierAllowDecision
480
+ }
481
+
482
+ return Get-ParallelCohortBarrierBlockDecision -Reason "PARALLEL_COHORT_BARRIER_BLOCKED: '$featureFolder' cannot start until every conflicting item in a strictly prior current-generation cohort is durably confirmed merged or worktree_removed in the parallel checkpoint. The checkpoint was unreadable, the items[] record was not found, the item has no current-generation cohort assignment, or a conflicting prior-cohort item is not yet safe (ci_green does not satisfy the barrier)."
483
+ }
484
+
485
+ # Guard allows dot-sourcing in tests without executing the entrypoint.
486
+ if ($MyInvocation.InvocationName -eq '.') {
487
+ return
488
+ }
489
+
490
+ try {
491
+ $decision = Invoke-ParallelCohortBarrierDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
492
+ } catch {
493
+ Write-Error $_
494
+ exit 1
495
+ }
496
+
497
+ $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
498
+
499
+ exit 0