@danmoisan/drm-copilot-mcp 1.1.15 → 1.1.17
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.
- package/out/mcp-server.js +639 -27
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +12 -6
- package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +4 -5
- package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +3 -3
- package/resources/claude-customizations/.claude/hooks/enforce-batch-budget-route.ps1 +139 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +221 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +42 -66
- package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +58 -10
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +144 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +36 -47
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +179 -27
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +21 -38
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +4 -4
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +58 -10
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +59 -9
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +114 -37
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +95 -66
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +99 -64
- package/resources/claude-customizations/.claude/lib/bash/abandon-parallel-item.sh +176 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +193 -14
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusGlob.psm1 +51 -28
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +5 -8
- package/resources/claude-customizations/.claude/lib/parallel-drift/Invoke-ParallelDriftDetection.ps1 +336 -0
- package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDrift.psm1 +467 -0
- package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDriftHalt.psm1 +388 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +53 -32
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +19 -4
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +497 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +21 -0
- package/resources/claude-customizations/.claude/rules/powershell.md +3 -3
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +2 -0
- package/resources/claude-customizations/.claude/skills/invoke-csharp-engineer/SKILL.md +4 -0
- package/resources/claude-customizations/.claude/skills/invoke-powershell-engineer/SKILL.md +7 -4
- package/resources/claude-customizations/.claude/skills/invoke-python-engineer/SKILL.md +6 -3
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +3 -1
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +32 -18
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +10 -10
- package/resources/claude-customizations/.claude/skills/powershell-change-budget-router/SKILL.md +6 -6
- package/resources/claude-customizations/.claude/skills/python-change-budget-router/SKILL.md +11 -35
- package/resources/claude-customizations/pack-manifests/core.json +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +1 -2
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-python-engineer/SKILL.md +2 -3
- package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +1 -2
- package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/SKILL.md +10 -34
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-orchestrator.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-batch-budget-route.ps1 +139 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +165 -80
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +169 -79
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +1 -0
- package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +7 -7
- package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +8 -10
- package/resources/customizations/.github/agents/python-orchestrator.agent.md +4 -4
- package/resources/customizations/.github/agents/python-typed-engineer.agent.md +7 -8
- package/resources/customizations/.github/prompts/orchestrate-powershell-work.prompt.md +2 -2
- package/resources/customizations/.github/prompts/orchestrate-python-work.prompt.md +3 -3
- package/resources/customizations/.github/skills/powershell-change-budget-router/SKILL.md +3 -3
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +12 -0
|
@@ -19,17 +19,15 @@ Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -
|
|
|
19
19
|
# proof the issue #539 exemption is behaviourally unchanged.
|
|
20
20
|
. (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-modes.ps1')
|
|
21
21
|
|
|
22
|
-
# Epic-scope command and path legs (issue #663, decision D2),
|
|
23
|
-
# seams
|
|
22
|
+
# Epic-scope command and path legs (issue #663, decision D2), the three checkpoint read
|
|
23
|
+
# seams, the mode deny reason, and the target resolution with its import guard (issue
|
|
24
|
+
# #690), all relocated to this sibling to keep the gate inside the 500-line cap.
|
|
24
25
|
. (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-epic-scope.ps1')
|
|
25
26
|
|
|
26
27
|
# Shared command-line parser (issue #545): per-segment scan text and structural matching.
|
|
27
28
|
. (Join-Path $PSScriptRoot 'hook-command-scanner.ps1')
|
|
28
29
|
. (Join-Path $PSScriptRoot 'hook-command-invocation.ps1')
|
|
29
30
|
|
|
30
|
-
# The readiness checkpoint this gate reads and names in its block message.
|
|
31
|
-
$script:CheckpointPath = 'artifacts/orchestration/orchestrator-state.json'
|
|
32
|
-
|
|
33
31
|
# Every orchestration checkpoint a planner or orchestrator surface writes. Writing one
|
|
34
32
|
# of these is orchestration bookkeeping, not implementation, so the gate must not
|
|
35
33
|
# require a ready checkpoint before the checkpoint itself can be created. The set is a
|
|
@@ -254,17 +252,6 @@ function Test-OrchestrationReady {
|
|
|
254
252
|
)
|
|
255
253
|
}
|
|
256
254
|
|
|
257
|
-
function Get-CheckpointContent {
|
|
258
|
-
[CmdletBinding()]
|
|
259
|
-
[OutputType([string])]
|
|
260
|
-
param()
|
|
261
|
-
|
|
262
|
-
if (-not (Test-Path -LiteralPath $script:CheckpointPath)) {
|
|
263
|
-
return ''
|
|
264
|
-
}
|
|
265
|
-
return Get-Content -Raw -LiteralPath $script:CheckpointPath
|
|
266
|
-
}
|
|
267
|
-
|
|
268
255
|
function Get-OrchestrationPreimplementationGateAllowDecision {
|
|
269
256
|
[CmdletBinding()]
|
|
270
257
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
@@ -295,23 +282,6 @@ function Get-OrchestrationPreimplementationGateBlockDecision {
|
|
|
295
282
|
}
|
|
296
283
|
}
|
|
297
284
|
|
|
298
|
-
# Builds a mode-specific deny reason naming the checkpoint actually consulted and the
|
|
299
|
-
# predicate that failed, behind the unchanged PREIMPLEMENTATION_GATE_BLOCKED prefix
|
|
300
|
-
# that downstream reason-matching reads.
|
|
301
|
-
function Get-OrchestrationModeDenyReason {
|
|
302
|
-
[CmdletBinding()]
|
|
303
|
-
[OutputType([string])]
|
|
304
|
-
param(
|
|
305
|
-
[Parameter(Mandatory)][string] $Mode,
|
|
306
|
-
[Parameter(Mandatory)][string] $Failure
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
$path = Get-OrchestrationDelegationCheckpointPath -Mode $Mode
|
|
310
|
-
return ("PREIMPLEMENTATION_GATE_BLOCKED: this $Mode-mode delegation was evaluated against " +
|
|
311
|
-
"$path, and the failed readiness predicate is '$Failure'. Implementation operations " +
|
|
312
|
-
'require that checkpoint to satisfy every readiness predicate before implementation begins.')
|
|
313
|
-
}
|
|
314
|
-
|
|
315
285
|
function Invoke-OrchestrationPreimplementationGateDecision {
|
|
316
286
|
[CmdletBinding()]
|
|
317
287
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
@@ -337,6 +307,9 @@ function Invoke-OrchestrationPreimplementationGateDecision {
|
|
|
337
307
|
[string] $ParallelCheckpointRaw
|
|
338
308
|
)
|
|
339
309
|
|
|
310
|
+
# A failed worktree-resolution import denies before any other logic (issue #690).
|
|
311
|
+
$importFailure = Get-OrchestrationGateImportFailureDecision; if ($null -ne $importFailure) { return $importFailure }
|
|
312
|
+
|
|
340
313
|
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
341
314
|
if (-not $payload.IsValid) {
|
|
342
315
|
return Get-OrchestrationPreimplementationGateBlockDecision -Reason (
|
|
@@ -396,9 +369,16 @@ function Invoke-OrchestrationPreimplementationGateDecision {
|
|
|
396
369
|
if ($mode -eq 'epic' -or $mode -eq 'parallel') {
|
|
397
370
|
$isEpic = ($mode -eq 'epic')
|
|
398
371
|
$injected = if ($isEpic) { 'EpicCheckpointRaw' } else { 'ParallelCheckpointRaw' }
|
|
399
|
-
$
|
|
400
|
-
|
|
401
|
-
|
|
372
|
+
$modePath = ''
|
|
373
|
+
if ($PSBoundParameters.ContainsKey($injected)) {
|
|
374
|
+
$modeRaw = [string]$PSBoundParameters[$injected]
|
|
375
|
+
} else {
|
|
376
|
+
# The run checkpoint is read beneath the worktree the kickoff identifies (#690).
|
|
377
|
+
$read = Read-OrchestrationGateCheckpoint -Mode $mode -Prompt $prompt
|
|
378
|
+
if (-not $read.Resolved) { return Get-OrchestrationPreimplementationGateBlockDecision -Reason $read.DenyReason }
|
|
379
|
+
$modeRaw = $read.Raw
|
|
380
|
+
$modePath = $read.Path
|
|
381
|
+
}
|
|
402
382
|
try {
|
|
403
383
|
$modeCheckpoint = ConvertFrom-CheckpointJson -Json ([string]$modeRaw)
|
|
404
384
|
} catch { $modeCheckpoint = $null }
|
|
@@ -411,11 +391,14 @@ function Invoke-OrchestrationPreimplementationGateDecision {
|
|
|
411
391
|
}
|
|
412
392
|
if (-not $failure) { return Get-OrchestrationPreimplementationGateAllowDecision }
|
|
413
393
|
return Get-OrchestrationPreimplementationGateBlockDecision -Reason (
|
|
414
|
-
Get-OrchestrationModeDenyReason -Mode $mode -Failure $failure)
|
|
394
|
+
Get-OrchestrationModeDenyReason -Mode $mode -Failure $failure -CheckpointPath $modePath)
|
|
415
395
|
}
|
|
416
396
|
|
|
397
|
+
# Truthiness fall-through is kept: an empty -CheckpointRaw resolves like an unbound one.
|
|
417
398
|
if (-not $CheckpointRaw) {
|
|
418
|
-
$
|
|
399
|
+
$read = Read-OrchestrationGateCheckpoint -Mode 'single-feature' -Prompt $prompt -FilePath $filePath -Command $command
|
|
400
|
+
if (-not $read.Resolved) { return Get-OrchestrationPreimplementationGateBlockDecision -Reason $read.DenyReason }
|
|
401
|
+
$CheckpointRaw = $read.Raw
|
|
419
402
|
}
|
|
420
403
|
try {
|
|
421
404
|
$checkpoint = ConvertFrom-CheckpointJson -Json $CheckpointRaw
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
.claude/hooks/enforce-epic-worktree-removal-gate.ps1.
|
|
26
26
|
|
|
27
27
|
The two token values are declared ONCE EACH below and every match in this file reads
|
|
28
|
-
those variables rather than repeating a literal. The producer
|
|
29
|
-
|
|
30
|
-
tests/scripts/dev_tools/test_parallel_abandon_token_seam.py parses
|
|
31
|
-
time so a rename on one side without the
|
|
28
|
+
those variables rather than repeating a literal. The pushed-down producer of the same pair
|
|
29
|
+
is .claude/lib/bash/abandon-parallel-item.sh (the Python CLI it ports remains the parity
|
|
30
|
+
reference), and tests/scripts/dev_tools/test_parallel_abandon_token_seam.py parses every
|
|
31
|
+
side at run time so a rename on one side without the others fails.
|
|
32
32
|
#>
|
|
33
33
|
[CmdletBinding()]
|
|
34
34
|
param()
|
|
@@ -16,8 +16,11 @@
|
|
|
16
16
|
1. Resolve the target item's feature folder from the prompt text by scanning for a
|
|
17
17
|
docs/features/active/<token> path, mirroring the epic hook's technique
|
|
18
18
|
(longest match wins; a .md-suffixed match uses its parent directory).
|
|
19
|
-
2.
|
|
20
|
-
|
|
19
|
+
2. Resolve the worktree whose parallel checkpoint records the prompt's
|
|
20
|
+
parallel_slug: value (issue #690), deny an unresolved or ambiguous target, then
|
|
21
|
+
read artifacts/orchestration/parallel-orchestrator-state.json beneath that
|
|
22
|
+
worktree and locate the items[] record whose feature_folder resolves to the same
|
|
23
|
+
basename.
|
|
21
24
|
3. Project the cohort coloring to the cohorts[] rows whose generation equals the
|
|
22
25
|
top-level recolor_generation, and read the target item's cohort index from that
|
|
23
26
|
projection.
|
|
@@ -40,16 +43,26 @@
|
|
|
40
43
|
SubagentStop time.
|
|
41
44
|
|
|
42
45
|
.NOTES
|
|
43
|
-
Compatible with PowerShell 7+.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
Compatible with PowerShell 7+. Depends on WorktreeRunResolution.psm1 (issue #690),
|
|
47
|
+
imported inside a guard: a failed import is recorded and the decision denies naming
|
|
48
|
+
the module. Filesystem reads go through an injectable wrapper function so tests can
|
|
49
|
+
mock the boundary without writing temporary files.
|
|
46
50
|
#>
|
|
47
51
|
[CmdletBinding()]
|
|
48
52
|
param()
|
|
49
53
|
|
|
50
54
|
|
|
51
55
|
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
52
|
-
|
|
56
|
+
|
|
57
|
+
# Import guard (issue #690): a failed import denies instead of failing open.
|
|
58
|
+
$script:ParallelCohortBarrierResolutionImportFailure = $null
|
|
59
|
+
try {
|
|
60
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
$script:ParallelCohortBarrierResolutionImportFailure = 'WorktreeRunResolution.psm1'
|
|
64
|
+
}
|
|
65
|
+
|
|
53
66
|
$script:AllowedMergeStatuses = @('merged', 'worktree_removed')
|
|
54
67
|
$script:ParallelModeMarker = 'Parallel mode: true'
|
|
55
68
|
|
|
@@ -62,17 +75,39 @@ function Get-ParallelCohortBarrierCheckpointContent {
|
|
|
62
75
|
.SYNOPSIS
|
|
63
76
|
Read the raw JSON text of the parallel checkpoint. Tests mock this function
|
|
64
77
|
(read seam).
|
|
78
|
+
.PARAMETER Path
|
|
79
|
+
The absolute checkpoint path composed beneath the resolved worktree root.
|
|
65
80
|
.OUTPUTS
|
|
66
81
|
System.String or $null
|
|
67
82
|
#>
|
|
68
83
|
[CmdletBinding()]
|
|
69
84
|
[OutputType([string])]
|
|
70
|
-
param()
|
|
85
|
+
param([Parameter(Mandatory)][ValidatePattern('^([A-Za-z]:[\\/]|/)')][string] $Path)
|
|
71
86
|
|
|
72
|
-
if (-not (Test-Path -LiteralPath $
|
|
87
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
|
73
88
|
return $null
|
|
74
89
|
}
|
|
75
|
-
return (Get-Content -LiteralPath $
|
|
90
|
+
return (Get-Content -LiteralPath $Path -Raw)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function Resolve-ParallelCohortBarrierTarget {
|
|
94
|
+
<#
|
|
95
|
+
.SYNOPSIS
|
|
96
|
+
Resolve the worktree whose parallel checkpoint governs a kickoff (issue #690 seam).
|
|
97
|
+
.DESCRIPTION
|
|
98
|
+
Keyed on the prompt's parallel_slug: value; never on a feature-folder path or the
|
|
99
|
+
payload cwd.
|
|
100
|
+
.PARAMETER Prompt
|
|
101
|
+
The delegation prompt text.
|
|
102
|
+
.OUTPUTS
|
|
103
|
+
System.Management.Automation.PSCustomObject (the worktree-resolution target result).
|
|
104
|
+
#>
|
|
105
|
+
[CmdletBinding()]
|
|
106
|
+
[OutputType([pscustomobject])]
|
|
107
|
+
param([Parameter(Mandatory)][AllowEmptyString()][string] $Prompt)
|
|
108
|
+
|
|
109
|
+
$signal = Find-WorktreeRunIdentitySignal -Text $Prompt
|
|
110
|
+
return Resolve-WorktreeParallelTarget -ParallelSlug $signal.ParallelSlug -SessionRoot (Get-Location).Path
|
|
76
111
|
}
|
|
77
112
|
|
|
78
113
|
function Get-ParallelCohortBarrierFolderBasename {
|
|
@@ -196,6 +231,13 @@ function Invoke-ParallelCohortBarrierDecision {
|
|
|
196
231
|
[string] $ToolInputRaw
|
|
197
232
|
)
|
|
198
233
|
|
|
234
|
+
# A failed worktree-resolution import denies before any other logic (issue #690).
|
|
235
|
+
if ($script:ParallelCohortBarrierResolutionImportFailure) {
|
|
236
|
+
return Get-ParallelCohortBarrierBlockDecision -Reason (
|
|
237
|
+
"PARALLEL_COHORT_BARRIER_BLOCKED: the worktree-resolution module '$($script:ParallelCohortBarrierResolutionImportFailure)' " +
|
|
238
|
+
'failed to import, so the parallel checkpoint that governs this delegation cannot be located; the gate fails closed.')
|
|
239
|
+
}
|
|
240
|
+
|
|
199
241
|
$envelope = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
200
242
|
if (-not $envelope.IsValid) {
|
|
201
243
|
return Get-ParallelCohortBarrierBlockDecision -Reason (
|
|
@@ -214,12 +256,18 @@ function Invoke-ParallelCohortBarrierDecision {
|
|
|
214
256
|
return Get-ParallelCohortBarrierAllowDecision
|
|
215
257
|
}
|
|
216
258
|
|
|
259
|
+
# The parallel checkpoint is located by the kickoff's parallel_slug (issue #690).
|
|
260
|
+
$target = Resolve-ParallelCohortBarrierTarget -Prompt $prompt
|
|
261
|
+
if ($target.Status -eq 'NoTarget' -or $target.Status -eq 'Ambiguous') {
|
|
262
|
+
return Get-ParallelCohortBarrierBlockDecision -Reason "PARALLEL_COHORT_BARRIER_BLOCKED: $($target.ReasonCode): $($target.Detail)"
|
|
263
|
+
}
|
|
264
|
+
|
|
217
265
|
$featureFolder = Find-ParallelCohortBarrierFeatureFolderFromPrompt -Prompt $prompt
|
|
218
266
|
if (-not $featureFolder) {
|
|
219
267
|
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.'
|
|
220
268
|
}
|
|
221
269
|
|
|
222
|
-
$checkpointRaw = Get-ParallelCohortBarrierCheckpointContent
|
|
270
|
+
$checkpointRaw = Get-ParallelCohortBarrierCheckpointContent -Path (Get-WorktreeRunCheckpointPath -Kind parallel -WorktreeRoot $target.WorktreeRoot)
|
|
223
271
|
$checkpoint = $null
|
|
224
272
|
if (-not [string]::IsNullOrWhiteSpace($checkpointRaw)) {
|
|
225
273
|
try {
|
|
@@ -45,10 +45,15 @@
|
|
|
45
45
|
retrospective backstop, is the PARALLEL_DRIFT_GATE_VIOLATION invariant in
|
|
46
46
|
scripts/dev_tools/_parallel_orchestrator_state_drift.py.
|
|
47
47
|
|
|
48
|
+
Issue #690: the parallel checkpoint is read beneath the worktree that the prompt's
|
|
49
|
+
parallel_slug: value resolves to, never beneath the payload cwd. An unresolved or
|
|
50
|
+
ambiguous target denies after the subagent and marker filter and before the folder check.
|
|
51
|
+
|
|
48
52
|
.NOTES
|
|
49
|
-
PowerShell 7
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
PowerShell 7+. Depends on WorktreeRunResolution.psm1 (issue #690), imported inside a
|
|
54
|
+
guard: a failed import is recorded and the decision denies naming the module. Both read
|
|
55
|
+
boundaries -- the checkpoint read and the finding-file existence check -- are injectable
|
|
56
|
+
wrapper functions, so tests mock them without writing temporary files.
|
|
52
57
|
|
|
53
58
|
The eight shape-and-derivation helpers this hook calls live in the dot-sourced sibling
|
|
54
59
|
module .claude/hooks/enforce-parallel-drift-gate-helpers.ps1, together with the
|
|
@@ -63,12 +68,21 @@ param()
|
|
|
63
68
|
|
|
64
69
|
|
|
65
70
|
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
71
|
+
|
|
72
|
+
# Import guard (issue #690): a failed import denies instead of failing open.
|
|
73
|
+
$script:ParallelDriftGateResolutionImportFailure = $null
|
|
74
|
+
try {
|
|
75
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
$script:ParallelDriftGateResolutionImportFailure = 'WorktreeRunResolution.psm1'
|
|
79
|
+
}
|
|
80
|
+
|
|
66
81
|
# Dot-source the shape-and-derivation helpers. Guarded so a missing file produces a clear error
|
|
67
82
|
# and so dot-sourcing this hook in tests loads the helpers too.
|
|
68
83
|
$script:ParallelDriftGateHelpersPath = Join-Path $PSScriptRoot 'enforce-parallel-drift-gate-helpers.ps1'
|
|
69
84
|
. $script:ParallelDriftGateHelpersPath
|
|
70
85
|
|
|
71
|
-
$script:ParallelCheckpointPath = 'artifacts/orchestration/parallel-orchestrator-state.json'
|
|
72
86
|
$script:ParallelModeMarker = 'Parallel mode: true'
|
|
73
87
|
$script:ReviewSubagentType = 'feature-review'
|
|
74
88
|
$script:ActiveFeatureRoot = 'docs/features/active'
|
|
@@ -85,15 +99,38 @@ function Get-ParallelDriftGateCheckpointContent {
|
|
|
85
99
|
.SYNOPSIS
|
|
86
100
|
Read the raw JSON text of the parallel checkpoint, or $null when the file is absent.
|
|
87
101
|
Tests mock this function (checkpoint-read seam).
|
|
102
|
+
.PARAMETER Path
|
|
103
|
+
The absolute checkpoint path composed beneath the resolved worktree root.
|
|
88
104
|
#>
|
|
89
105
|
[CmdletBinding()]
|
|
90
106
|
[OutputType([string])]
|
|
91
|
-
param()
|
|
107
|
+
param([Parameter(Mandatory)][ValidatePattern('^([A-Za-z]:[\\/]|/)')][string] $Path)
|
|
92
108
|
|
|
93
|
-
if (-not (Test-Path -LiteralPath $
|
|
109
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
|
94
110
|
return $null
|
|
95
111
|
}
|
|
96
|
-
return (Get-Content -LiteralPath $
|
|
112
|
+
return (Get-Content -LiteralPath $Path -Raw)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function Resolve-ParallelDriftGateTarget {
|
|
116
|
+
<#
|
|
117
|
+
.SYNOPSIS
|
|
118
|
+
Resolve the worktree whose parallel checkpoint governs a review delegation (issue #690
|
|
119
|
+
seam).
|
|
120
|
+
.DESCRIPTION
|
|
121
|
+
Keyed on the prompt's parallel_slug: value; never on a feature-folder path or the
|
|
122
|
+
payload cwd.
|
|
123
|
+
.PARAMETER Prompt
|
|
124
|
+
The delegation prompt text.
|
|
125
|
+
.OUTPUTS
|
|
126
|
+
System.Management.Automation.PSCustomObject (the worktree-resolution target result).
|
|
127
|
+
#>
|
|
128
|
+
[CmdletBinding()]
|
|
129
|
+
[OutputType([pscustomobject])]
|
|
130
|
+
param([Parameter(Mandatory)][AllowEmptyString()][string] $Prompt)
|
|
131
|
+
|
|
132
|
+
$signal = Find-WorktreeRunIdentitySignal -Text $Prompt
|
|
133
|
+
return Resolve-WorktreeParallelTarget -ParallelSlug $signal.ParallelSlug -SessionRoot (Get-Location).Path
|
|
97
134
|
}
|
|
98
135
|
|
|
99
136
|
function Test-ParallelDriftFindingPresent {
|
|
@@ -282,6 +319,13 @@ function Invoke-ParallelDriftGateDecision {
|
|
|
282
319
|
[string] $ToolInputRaw
|
|
283
320
|
)
|
|
284
321
|
|
|
322
|
+
# A failed worktree-resolution import denies before any other logic (issue #690).
|
|
323
|
+
if ($script:ParallelDriftGateResolutionImportFailure) {
|
|
324
|
+
return Get-ParallelDriftGateBlockDecision -Reason (
|
|
325
|
+
"PARALLEL_DRIFT_GATE_BLOCKED: the worktree-resolution module '$($script:ParallelDriftGateResolutionImportFailure)' " +
|
|
326
|
+
'failed to import, so the parallel checkpoint that governs this review cannot be located; the gate fails closed.')
|
|
327
|
+
}
|
|
328
|
+
|
|
285
329
|
$envelope = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
286
330
|
if (-not $envelope.IsValid) {
|
|
287
331
|
return Get-ParallelDriftGateBlockDecision -Reason (
|
|
@@ -302,12 +346,18 @@ function Invoke-ParallelDriftGateDecision {
|
|
|
302
346
|
return Get-ParallelDriftGateAllowDecision
|
|
303
347
|
}
|
|
304
348
|
|
|
349
|
+
# The parallel checkpoint is located by the delegation's parallel_slug (issue #690).
|
|
350
|
+
$target = Resolve-ParallelDriftGateTarget -Prompt $prompt
|
|
351
|
+
if ($target.Status -eq 'NoTarget' -or $target.Status -eq 'Ambiguous') {
|
|
352
|
+
return Get-ParallelDriftGateBlockDecision -Reason "PARALLEL_DRIFT_GATE_BLOCKED: $($target.ReasonCode): $($target.Detail)"
|
|
353
|
+
}
|
|
354
|
+
|
|
305
355
|
$featureFolder = Find-ParallelDriftGateFeatureFolderFromPrompt -Prompt $prompt
|
|
306
356
|
if (-not $featureFolder) {
|
|
307
357
|
return Get-ParallelDriftGateBlockDecision -Reason 'PARALLEL_DRIFT_GATE_BLOCKED: a parallel-mode feature-review delegation must reference the target item feature folder in the prompt so its drift state can be verified.'
|
|
308
358
|
}
|
|
309
359
|
|
|
310
|
-
$checkpointRaw = Get-ParallelDriftGateCheckpointContent
|
|
360
|
+
$checkpointRaw = Get-ParallelDriftGateCheckpointContent -Path (Get-WorktreeRunCheckpointPath -Kind parallel -WorktreeRoot $target.WorktreeRoot)
|
|
311
361
|
$checkpoint = $null
|
|
312
362
|
if (-not [string]::IsNullOrWhiteSpace($checkpointRaw)) {
|
|
313
363
|
try {
|
|
@@ -317,7 +367,7 @@ function Invoke-ParallelDriftGateDecision {
|
|
|
317
367
|
}
|
|
318
368
|
}
|
|
319
369
|
if ($null -eq $checkpoint) {
|
|
320
|
-
return Get-ParallelDriftGateBlockDecision -Reason "PARALLEL_DRIFT_GATE_BLOCKED: the parallel checkpoint '$
|
|
370
|
+
return Get-ParallelDriftGateBlockDecision -Reason "PARALLEL_DRIFT_GATE_BLOCKED: the parallel checkpoint 'artifacts/orchestration/parallel-orchestrator-state.json' in worktree '$($target.WorktreeRoot)' is missing or unreadable, so the drift state of '$featureFolder' cannot be verified."
|
|
321
371
|
}
|
|
322
372
|
|
|
323
373
|
$item = Find-ParallelDriftGateItemRecord -Checkpoint $checkpoint -FeatureFolder $featureFolder
|
package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1
CHANGED
|
@@ -20,10 +20,15 @@
|
|
|
20
20
|
opens its own pull request against main, so a removed worktree is unrecoverable work
|
|
21
21
|
unless that item's own merge has been durably confirmed.
|
|
22
22
|
|
|
23
|
+
Issue #690: each checkpoint is read beneath the live worktree whose run checkpoint
|
|
24
|
+
records the removal target under worktree_path. An ambiguous target denies before any
|
|
25
|
+
allow; when neither kind resolves, the deny names TARGET_WORKTREE_NOT_DERIVABLE.
|
|
26
|
+
|
|
23
27
|
.NOTES
|
|
24
|
-
Compatible with PowerShell 7+.
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
Compatible with PowerShell 7+. Depends on WorktreeRunResolution.psm1 (issue #690),
|
|
29
|
+
imported inside a guard: a failed import is recorded and the decision denies naming
|
|
30
|
+
the module. Filesystem reads go through injectable wrapper functions so tests can
|
|
31
|
+
mock the boundary without writing temporary files.
|
|
27
32
|
#>
|
|
28
33
|
[CmdletBinding()]
|
|
29
34
|
param()
|
|
@@ -39,17 +44,19 @@ Import-Module (Join-Path $PSScriptRoot '../lib/cleanup-manifest/CleanupWorktreeM
|
|
|
39
44
|
# concern now has one implementation.
|
|
40
45
|
. (Join-Path $PSScriptRoot 'hook-command-scanner.ps1')
|
|
41
46
|
. (Join-Path $PSScriptRoot 'hook-command-invocation.ps1')
|
|
42
|
-
|
|
47
|
+
|
|
48
|
+
# Import guard (issue #690): a failed import denies instead of failing open.
|
|
49
|
+
$script:ParallelWorktreeGateResolutionImportFailure = $null
|
|
50
|
+
try {
|
|
51
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
$script:ParallelWorktreeGateResolutionImportFailure = 'WorktreeRunResolution.psm1'
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
$script:AllowedMergeStatuses = @('merged', 'worktree_removed')
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# merged epic child worktree reached the deny below even though
|
|
47
|
-
# enforce-epic-worktree-removal-gate.ps1 authorized the same call. Both gates run on
|
|
48
|
-
# the same Bash call, so that deny won and epic worktrees could never be removed.
|
|
49
|
-
$script:EpicCheckpointPath = 'artifacts/orchestration/epic-orchestrator-state.json'
|
|
50
|
-
# Sanctioned-removal manifest location. Recorded here beside the checkpoint path so
|
|
51
|
-
# every hook-read document this gate consults is named in one place; the module owns
|
|
52
|
-
# the read itself.
|
|
58
|
+
# Sanctioned-removal manifest location. Recorded here so every hook-read document this
|
|
59
|
+
# gate consults is named; the module owns the read itself.
|
|
53
60
|
$script:CleanupWorktreeManifestPath = 'artifacts/orchestration/cleanup-worktrees-manifest.json'
|
|
54
61
|
|
|
55
62
|
function Get-ParallelWorktreeRemovalGateCheckpointContent {
|
|
@@ -57,17 +64,19 @@ function Get-ParallelWorktreeRemovalGateCheckpointContent {
|
|
|
57
64
|
.SYNOPSIS
|
|
58
65
|
Read the raw JSON text of the parallel checkpoint. Tests mock this function
|
|
59
66
|
(read seam).
|
|
67
|
+
.PARAMETER Path
|
|
68
|
+
The absolute checkpoint path composed beneath the resolved worktree root.
|
|
60
69
|
.OUTPUTS
|
|
61
70
|
System.String or $null
|
|
62
71
|
#>
|
|
63
72
|
[CmdletBinding()]
|
|
64
73
|
[OutputType([string])]
|
|
65
|
-
param()
|
|
74
|
+
param([Parameter(Mandatory)][ValidatePattern('^([A-Za-z]:[\\/]|/)')][string] $Path)
|
|
66
75
|
|
|
67
|
-
if (-not (Test-Path -LiteralPath $
|
|
76
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
|
68
77
|
return $null
|
|
69
78
|
}
|
|
70
|
-
return (Get-Content -LiteralPath $
|
|
79
|
+
return (Get-Content -LiteralPath $Path -Raw)
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
function Get-ParallelWorktreeRemovalGateEpicCheckpointContent {
|
|
@@ -78,18 +87,81 @@ function Get-ParallelWorktreeRemovalGateEpicCheckpointContent {
|
|
|
78
87
|
.DESCRIPTION
|
|
79
88
|
A separate seam from the parallel checkpoint reader so a test can drive the
|
|
80
89
|
two documents independently: the epic branch must be exercised with the
|
|
81
|
-
parallel checkpoint absent, present-but-not-covering, and covering.
|
|
90
|
+
parallel checkpoint absent, present-but-not-covering, and covering. An epic run
|
|
91
|
+
records per-child state in features[] and never writes the parallel checkpoint,
|
|
92
|
+
so without this read every merged epic child worktree reached the deny below.
|
|
93
|
+
.PARAMETER Path
|
|
94
|
+
The absolute checkpoint path composed beneath the resolved worktree root.
|
|
82
95
|
.OUTPUTS
|
|
83
96
|
System.String or $null
|
|
84
97
|
#>
|
|
85
98
|
[CmdletBinding()]
|
|
86
99
|
[OutputType([string])]
|
|
87
|
-
param()
|
|
100
|
+
param([Parameter(Mandatory)][ValidatePattern('^([A-Za-z]:[\\/]|/)')][string] $Path)
|
|
88
101
|
|
|
89
|
-
if (-not (Test-Path -LiteralPath $
|
|
102
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
|
90
103
|
return $null
|
|
91
104
|
}
|
|
92
|
-
return (Get-Content -LiteralPath $
|
|
105
|
+
return (Get-Content -LiteralPath $Path -Raw)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function Resolve-ParallelWorktreeGateRunTarget {
|
|
109
|
+
<#
|
|
110
|
+
.SYNOPSIS
|
|
111
|
+
Resolve the worktree whose run checkpoint records a worktree path (issue #690 seam).
|
|
112
|
+
.PARAMETER Kind
|
|
113
|
+
epic or parallel.
|
|
114
|
+
.PARAMETER WorktreePath
|
|
115
|
+
The removal target the command names.
|
|
116
|
+
.OUTPUTS
|
|
117
|
+
System.Management.Automation.PSCustomObject (the worktree-resolution target result).
|
|
118
|
+
#>
|
|
119
|
+
[CmdletBinding()]
|
|
120
|
+
[OutputType([pscustomobject])]
|
|
121
|
+
param(
|
|
122
|
+
[Parameter(Mandatory)][ValidateSet('epic', 'parallel')][string] $Kind,
|
|
123
|
+
[AllowNull()][AllowEmptyString()][string] $WorktreePath
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return Resolve-WorktreeRunTargetByRecord -Kind $Kind -RecordField worktree_path -Value $WorktreePath -SessionRoot (Get-Location).Path
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function Read-ParallelWorktreeGateRunCheckpoint {
|
|
130
|
+
<#
|
|
131
|
+
.SYNOPSIS
|
|
132
|
+
Resolve one run kind and read its checkpoint beneath the resolved root.
|
|
133
|
+
.PARAMETER Kind
|
|
134
|
+
epic or parallel.
|
|
135
|
+
.PARAMETER WorktreePath
|
|
136
|
+
The removal target the command names.
|
|
137
|
+
.OUTPUTS
|
|
138
|
+
System.Management.Automation.PSCustomObject with Target and Checkpoint ($null when
|
|
139
|
+
unresolved, absent, or unparseable).
|
|
140
|
+
#>
|
|
141
|
+
[CmdletBinding()]
|
|
142
|
+
[OutputType([pscustomobject])]
|
|
143
|
+
param(
|
|
144
|
+
[Parameter(Mandatory)][ValidateSet('epic', 'parallel')][string] $Kind,
|
|
145
|
+
[AllowNull()][AllowEmptyString()][string] $WorktreePath
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
$target = Resolve-ParallelWorktreeGateRunTarget -Kind $Kind -WorktreePath $WorktreePath
|
|
149
|
+
$checkpoint = $null
|
|
150
|
+
if ($target.Status -eq 'SessionRoot' -or $target.Status -eq 'OtherWorktree') {
|
|
151
|
+
$path = Get-WorktreeRunCheckpointPath -Kind $Kind -WorktreeRoot $target.WorktreeRoot
|
|
152
|
+
$raw = if ($Kind -eq 'parallel') { Get-ParallelWorktreeRemovalGateCheckpointContent -Path $path } else { Get-ParallelWorktreeRemovalGateEpicCheckpointContent -Path $path }
|
|
153
|
+
if (-not [string]::IsNullOrWhiteSpace($raw)) {
|
|
154
|
+
try {
|
|
155
|
+
$checkpoint = $raw | ConvertFrom-Json -ErrorAction Stop
|
|
156
|
+
} catch {
|
|
157
|
+
$checkpoint = $null
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return [pscustomobject]@{
|
|
162
|
+
Target = $target
|
|
163
|
+
Checkpoint = $checkpoint
|
|
164
|
+
}
|
|
93
165
|
}
|
|
94
166
|
|
|
95
167
|
function Get-ParallelWorktreeRemovalCommandPath {
|
|
@@ -263,6 +335,13 @@ function Invoke-ParallelWorktreeRemovalGateDecision {
|
|
|
263
335
|
[string] $ToolInputRaw
|
|
264
336
|
)
|
|
265
337
|
|
|
338
|
+
# A failed worktree-resolution import denies before any other logic (issue #690).
|
|
339
|
+
if ($script:ParallelWorktreeGateResolutionImportFailure) {
|
|
340
|
+
return Get-ParallelWorktreeGateBlockDecision -Reason (
|
|
341
|
+
"PARALLEL_WORKTREE_REMOVAL_BLOCKED: the worktree-resolution module '$($script:ParallelWorktreeGateResolutionImportFailure)' " +
|
|
342
|
+
'failed to import, so the run checkpoint that governs this removal cannot be located; the gate fails closed.')
|
|
343
|
+
}
|
|
344
|
+
|
|
266
345
|
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
267
346
|
if (-not $payload.IsValid) {
|
|
268
347
|
return Get-ParallelWorktreeGateBlockDecision -Reason (
|
|
@@ -285,15 +364,16 @@ function Invoke-ParallelWorktreeRemovalGateDecision {
|
|
|
285
364
|
|
|
286
365
|
$worktreePath = Get-ParallelWorktreeRemovalCommandPath -CommandText $commandText
|
|
287
366
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
367
|
+
# Each run checkpoint is read beneath the worktree that records this path (issue #690),
|
|
368
|
+
# parallel kind first; an ambiguous target denies before any allow or manifest check.
|
|
369
|
+
$parallelRead = Read-ParallelWorktreeGateRunCheckpoint -Kind parallel -WorktreePath $worktreePath
|
|
370
|
+
$epicRead = Read-ParallelWorktreeGateRunCheckpoint -Kind epic -WorktreePath $worktreePath
|
|
371
|
+
foreach ($read in @($parallelRead, $epicRead)) {
|
|
372
|
+
if ($read.Target.Status -eq 'Ambiguous') {
|
|
373
|
+
return Get-ParallelWorktreeGateBlockDecision -Reason "PARALLEL_WORKTREE_REMOVAL_BLOCKED: $($read.Target.ReasonCode): $($read.Target.Detail)"
|
|
295
374
|
}
|
|
296
375
|
}
|
|
376
|
+
$checkpoint = $parallelRead.Checkpoint
|
|
297
377
|
|
|
298
378
|
$itemRecord = Find-ParallelWorktreeItemRecord -Checkpoint $checkpoint -WorktreePath $worktreePath
|
|
299
379
|
if (Test-ParallelWorktreeRemovalAllowed -ItemRecord $itemRecord) {
|
|
@@ -310,15 +390,7 @@ function Invoke-ParallelWorktreeRemovalGateDecision {
|
|
|
310
390
|
#
|
|
311
391
|
# Fail-closed is preserved: an absent, unreadable, or malformed epic checkpoint parses to
|
|
312
392
|
# $null, which yields no record, which is not allowed.
|
|
313
|
-
$
|
|
314
|
-
$epicCheckpoint = $null
|
|
315
|
-
if (-not [string]::IsNullOrWhiteSpace($epicCheckpointRaw)) {
|
|
316
|
-
try {
|
|
317
|
-
$epicCheckpoint = $epicCheckpointRaw | ConvertFrom-Json -ErrorAction Stop
|
|
318
|
-
} catch {
|
|
319
|
-
$epicCheckpoint = $null
|
|
320
|
-
}
|
|
321
|
-
}
|
|
393
|
+
$epicCheckpoint = $epicRead.Checkpoint
|
|
322
394
|
|
|
323
395
|
$featureRecord = Find-ParallelWorktreeItemRecord -Checkpoint $epicCheckpoint -WorktreePath $worktreePath -RecordArrayName 'features'
|
|
324
396
|
if (Test-ParallelWorktreeRemovalAllowed -ItemRecord $featureRecord) {
|
|
@@ -340,7 +412,12 @@ function Invoke-ParallelWorktreeRemovalGateDecision {
|
|
|
340
412
|
return Get-ParallelWorktreeGateAllowDecision
|
|
341
413
|
}
|
|
342
414
|
|
|
343
|
-
|
|
415
|
+
# When neither kind resolved, the deny names the resolution reason first (issue #690).
|
|
416
|
+
$prefix = ''
|
|
417
|
+
if ($parallelRead.Target.Status -eq 'NoTarget' -and $epicRead.Target.Status -eq 'NoTarget') {
|
|
418
|
+
$prefix = "PARALLEL_WORKTREE_REMOVAL_BLOCKED: $($parallelRead.Target.ReasonCode): $($parallelRead.Target.Detail). "
|
|
419
|
+
}
|
|
420
|
+
return Get-ParallelWorktreeGateBlockDecision -Reason ($prefix + "PARALLEL_WORKTREE_REMOVAL_BLOCKED: git worktree remove for '$worktreePath' requires a matching parallel checkpoint items[] record with merge_status in {merged, worktree_removed}. The checkpoint was unreadable, no matching record was found, or merge_status was not yet safe for removal.")
|
|
344
421
|
}
|
|
345
422
|
|
|
346
423
|
function Invoke-ParallelWorktreeRemovalGateEntryPoint {
|