@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
|
@@ -12,8 +12,11 @@
|
|
|
12
12
|
docs/features/active/<token> path, mirroring
|
|
13
13
|
enforce-prd-feature-before-planner.ps1's Find-PrdFeatureFolderFromPrompt
|
|
14
14
|
technique (longest match wins; a .md-suffixed match uses its parent directory).
|
|
15
|
-
2.
|
|
16
|
-
|
|
15
|
+
2. Resolve the worktree whose epic checkpoint records the prompt's
|
|
16
|
+
integration_branch: value (issue #690), deny an unresolved or ambiguous target,
|
|
17
|
+
then read artifacts/orchestration/epic-orchestrator-state.json beneath that
|
|
18
|
+
worktree and locate the features[] record whose feature_folder equals the
|
|
19
|
+
resolved basename.
|
|
17
20
|
3. Look up that feature's depends_on list, and for every dependency, locate its own
|
|
18
21
|
features[] record.
|
|
19
22
|
4. Deny with reason EPIC_WAVE_BARRIER_BLOCKED unless every dependency's merge_status
|
|
@@ -26,16 +29,26 @@
|
|
|
26
29
|
SubagentStop time.
|
|
27
30
|
|
|
28
31
|
.NOTES
|
|
29
|
-
Compatible with PowerShell 7+.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
Compatible with PowerShell 7+. Depends on WorktreeRunResolution.psm1 (issue #690),
|
|
33
|
+
imported inside a guard: a failed import is recorded and the decision denies naming
|
|
34
|
+
the module. Filesystem reads go through an injectable wrapper function so tests can
|
|
35
|
+
mock the boundary without writing temporary files.
|
|
32
36
|
#>
|
|
33
37
|
[CmdletBinding()]
|
|
34
38
|
param()
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
38
|
-
|
|
42
|
+
|
|
43
|
+
# Import guard (issue #690): a failed import denies instead of failing open.
|
|
44
|
+
$script:EpicWaveBarrierResolutionImportFailure = $null
|
|
45
|
+
try {
|
|
46
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
$script:EpicWaveBarrierResolutionImportFailure = 'WorktreeRunResolution.psm1'
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
$script:AllowedMergeStatuses = @('merged', 'worktree_removed')
|
|
40
53
|
$script:EpicModeMarker = 'Epic mode: true'
|
|
41
54
|
|
|
@@ -44,17 +57,39 @@ function Get-EpicWaveBarrierCheckpointContent {
|
|
|
44
57
|
.SYNOPSIS
|
|
45
58
|
Read the raw JSON text of the epic checkpoint. Tests mock this function
|
|
46
59
|
(read seam).
|
|
60
|
+
.PARAMETER Path
|
|
61
|
+
The absolute checkpoint path composed beneath the resolved worktree root.
|
|
47
62
|
.OUTPUTS
|
|
48
63
|
System.String or $null
|
|
49
64
|
#>
|
|
50
65
|
[CmdletBinding()]
|
|
51
66
|
[OutputType([string])]
|
|
52
|
-
param()
|
|
67
|
+
param([Parameter(Mandatory)][ValidatePattern('^([A-Za-z]:[\\/]|/)')][string] $Path)
|
|
53
68
|
|
|
54
|
-
if (-not (Test-Path -LiteralPath $
|
|
69
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
|
55
70
|
return $null
|
|
56
71
|
}
|
|
57
|
-
return (Get-Content -LiteralPath $
|
|
72
|
+
return (Get-Content -LiteralPath $Path -Raw)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function Resolve-EpicWaveBarrierTarget {
|
|
76
|
+
<#
|
|
77
|
+
.SYNOPSIS
|
|
78
|
+
Resolve the worktree whose epic checkpoint governs a kickoff (issue #690 seam).
|
|
79
|
+
.DESCRIPTION
|
|
80
|
+
Keyed on the prompt's integration_branch: value, with epic_feature_folder: as a
|
|
81
|
+
cross-check; never on a feature-folder path or the payload cwd.
|
|
82
|
+
.PARAMETER Prompt
|
|
83
|
+
The delegation prompt text.
|
|
84
|
+
.OUTPUTS
|
|
85
|
+
System.Management.Automation.PSCustomObject (the worktree-resolution target result).
|
|
86
|
+
#>
|
|
87
|
+
[CmdletBinding()]
|
|
88
|
+
[OutputType([pscustomobject])]
|
|
89
|
+
param([Parameter(Mandatory)][AllowEmptyString()][string] $Prompt)
|
|
90
|
+
|
|
91
|
+
$signal = Find-WorktreeRunIdentitySignal -Text $Prompt
|
|
92
|
+
return Resolve-WorktreeEpicTarget -IntegrationBranch $signal.IntegrationBranch -EpicSlug $signal.EpicSlug -SessionRoot (Get-Location).Path
|
|
58
93
|
}
|
|
59
94
|
|
|
60
95
|
function Find-EpicWaveBarrierFeatureFolderFromPrompt {
|
|
@@ -246,6 +281,13 @@ function Invoke-EpicWaveBarrierDecision {
|
|
|
246
281
|
[string] $ToolInputRaw
|
|
247
282
|
)
|
|
248
283
|
|
|
284
|
+
# A failed worktree-resolution import denies before any other logic (issue #690).
|
|
285
|
+
if ($script:EpicWaveBarrierResolutionImportFailure) {
|
|
286
|
+
return Get-EpicWaveBarrierBlockDecision -Reason (
|
|
287
|
+
"EPIC_WAVE_BARRIER_BLOCKED: the worktree-resolution module '$($script:EpicWaveBarrierResolutionImportFailure)' " +
|
|
288
|
+
'failed to import, so the epic checkpoint that governs this delegation cannot be located; the gate fails closed.')
|
|
289
|
+
}
|
|
290
|
+
|
|
249
291
|
$envelope = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
250
292
|
if (-not $envelope.IsValid) {
|
|
251
293
|
return Get-EpicWaveBarrierBlockDecision -Reason (
|
|
@@ -264,12 +306,18 @@ function Invoke-EpicWaveBarrierDecision {
|
|
|
264
306
|
return Get-EpicWaveBarrierAllowDecision
|
|
265
307
|
}
|
|
266
308
|
|
|
309
|
+
# The epic checkpoint is located by the kickoff's integration branch (issue #690).
|
|
310
|
+
$target = Resolve-EpicWaveBarrierTarget -Prompt $prompt
|
|
311
|
+
if ($target.Status -eq 'NoTarget' -or $target.Status -eq 'Ambiguous') {
|
|
312
|
+
return Get-EpicWaveBarrierBlockDecision -Reason "EPIC_WAVE_BARRIER_BLOCKED: $($target.ReasonCode): $($target.Detail)"
|
|
313
|
+
}
|
|
314
|
+
|
|
267
315
|
$featureFolder = Find-EpicWaveBarrierFeatureFolderFromPrompt -Prompt $prompt
|
|
268
316
|
if (-not $featureFolder) {
|
|
269
317
|
return Get-EpicWaveBarrierBlockDecision -Reason 'EPIC_WAVE_BARRIER_BLOCKED: an epic-mode orchestrator delegation must reference the target feature folder in the prompt so its dependency edges can be verified.'
|
|
270
318
|
}
|
|
271
319
|
|
|
272
|
-
$checkpointRaw = Get-EpicWaveBarrierCheckpointContent
|
|
320
|
+
$checkpointRaw = Get-EpicWaveBarrierCheckpointContent -Path (Get-WorktreeRunCheckpointPath -Kind epic -WorktreeRoot $target.WorktreeRoot)
|
|
273
321
|
$checkpoint = $null
|
|
274
322
|
if (-not [string]::IsNullOrWhiteSpace($checkpointRaw)) {
|
|
275
323
|
try {
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Checkpoint read seams and run-target resolution for the epic worktree-removal gate (issue #690).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Dot-sourced by enforce-epic-worktree-removal-gate.ps1 after its command-parser
|
|
7
|
+
dot-sources. Holds:
|
|
8
|
+
|
|
9
|
+
- The import guard for WorktreeRunResolution.psm1. A failed import is recorded in
|
|
10
|
+
$script:EpicWorktreeGateResolutionImportFailure, and
|
|
11
|
+
Get-EpicWorktreeGateImportFailureDecision turns it into a deny, so a missing module
|
|
12
|
+
cannot make the gate exit non-zero and fail open.
|
|
13
|
+
- The two checkpoint read seams, relocated from the gate file with their names kept.
|
|
14
|
+
Each takes a mandatory absolute path composed beneath a resolved worktree root.
|
|
15
|
+
- The resolution seam Resolve-EpicWorktreeGateRunTarget, which locates the epic or
|
|
16
|
+
parallel checkpoint that records the removal target under worktree_path.
|
|
17
|
+
- Read-EpicWorktreeGateRunCheckpoint, which returns the target and the parsed
|
|
18
|
+
checkpoint beneath it ($null when the target is unresolved).
|
|
19
|
+
|
|
20
|
+
.NOTES
|
|
21
|
+
PowerShell 7+. Depends on Get-EpicWorktreeGateBlockDecision and
|
|
22
|
+
ConvertFrom-EpicWorktreeGateJson from the gate file, resolved at call time. Mirrored
|
|
23
|
+
byte-identically under extensions/drm-copilot/resources/claude-customizations/.
|
|
24
|
+
#>
|
|
25
|
+
|
|
26
|
+
# Import guard (issue #690): a failed import denies instead of failing open.
|
|
27
|
+
$script:EpicWorktreeGateResolutionImportFailure = $null
|
|
28
|
+
try {
|
|
29
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
$script:EpicWorktreeGateResolutionImportFailure = 'WorktreeRunResolution.psm1'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function Get-EpicWorktreeGateImportFailureDecision {
|
|
36
|
+
<#
|
|
37
|
+
.SYNOPSIS
|
|
38
|
+
Returns the deny for a failed worktree-resolution import, or $null.
|
|
39
|
+
.OUTPUTS
|
|
40
|
+
System.Collections.Specialized.OrderedDictionary, or $null when the import succeeded.
|
|
41
|
+
#>
|
|
42
|
+
[CmdletBinding()]
|
|
43
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
44
|
+
param()
|
|
45
|
+
|
|
46
|
+
if (-not $script:EpicWorktreeGateResolutionImportFailure) {
|
|
47
|
+
return $null
|
|
48
|
+
}
|
|
49
|
+
return Get-EpicWorktreeGateBlockDecision -Reason (
|
|
50
|
+
"EPIC_WORKTREE_REMOVAL_BLOCKED: the worktree-resolution module '$($script:EpicWorktreeGateResolutionImportFailure)' " +
|
|
51
|
+
'failed to import, so the run checkpoint that governs this removal cannot be located; the gate fails closed.')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function Get-EpicWorktreeGateCheckpointContent {
|
|
55
|
+
<#
|
|
56
|
+
.SYNOPSIS
|
|
57
|
+
Read the raw JSON text of the epic checkpoint. Tests mock this function
|
|
58
|
+
(read seam).
|
|
59
|
+
.PARAMETER Path
|
|
60
|
+
The absolute checkpoint path composed beneath the resolved worktree root.
|
|
61
|
+
.OUTPUTS
|
|
62
|
+
System.String or $null
|
|
63
|
+
#>
|
|
64
|
+
[CmdletBinding()]
|
|
65
|
+
[OutputType([string])]
|
|
66
|
+
param([Parameter(Mandatory)][ValidatePattern('^([A-Za-z]:[\\/]|/)')][string] $Path)
|
|
67
|
+
|
|
68
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
|
69
|
+
return $null
|
|
70
|
+
}
|
|
71
|
+
return (Get-Content -LiteralPath $Path -Raw)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function Get-EpicWorktreeGateParallelCheckpointContent {
|
|
75
|
+
<#
|
|
76
|
+
.SYNOPSIS
|
|
77
|
+
Read the raw JSON text of the parallel-orchestrator checkpoint. Tests mock
|
|
78
|
+
this function (read seam).
|
|
79
|
+
.PARAMETER Path
|
|
80
|
+
The absolute checkpoint path composed beneath the resolved worktree root.
|
|
81
|
+
.OUTPUTS
|
|
82
|
+
System.String or $null
|
|
83
|
+
#>
|
|
84
|
+
[CmdletBinding()]
|
|
85
|
+
[OutputType([string])]
|
|
86
|
+
param([Parameter(Mandatory)][ValidatePattern('^([A-Za-z]:[\\/]|/)')][string] $Path)
|
|
87
|
+
|
|
88
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
|
89
|
+
return $null
|
|
90
|
+
}
|
|
91
|
+
return (Get-Content -LiteralPath $Path -Raw)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function Resolve-EpicWorktreeGateRunTarget {
|
|
95
|
+
<#
|
|
96
|
+
.SYNOPSIS
|
|
97
|
+
Resolve the worktree whose run checkpoint records a worktree path (issue #690 seam).
|
|
98
|
+
.PARAMETER Kind
|
|
99
|
+
epic or parallel.
|
|
100
|
+
.PARAMETER WorktreePath
|
|
101
|
+
The removal target the command names.
|
|
102
|
+
.OUTPUTS
|
|
103
|
+
System.Management.Automation.PSCustomObject (the worktree-resolution target result).
|
|
104
|
+
#>
|
|
105
|
+
[CmdletBinding()]
|
|
106
|
+
[OutputType([pscustomobject])]
|
|
107
|
+
param(
|
|
108
|
+
[Parameter(Mandatory)][ValidateSet('epic', 'parallel')][string] $Kind,
|
|
109
|
+
[AllowNull()][AllowEmptyString()][string] $WorktreePath
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return Resolve-WorktreeRunTargetByRecord -Kind $Kind -RecordField worktree_path -Value $WorktreePath -SessionRoot (Get-Location).Path
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function Read-EpicWorktreeGateRunCheckpoint {
|
|
116
|
+
<#
|
|
117
|
+
.SYNOPSIS
|
|
118
|
+
Resolve one run kind and read its checkpoint beneath the resolved root.
|
|
119
|
+
.PARAMETER Kind
|
|
120
|
+
epic or parallel.
|
|
121
|
+
.PARAMETER WorktreePath
|
|
122
|
+
The removal target the command names.
|
|
123
|
+
.OUTPUTS
|
|
124
|
+
System.Management.Automation.PSCustomObject with Target and Checkpoint ($null when unresolved).
|
|
125
|
+
#>
|
|
126
|
+
[CmdletBinding()]
|
|
127
|
+
[OutputType([pscustomobject])]
|
|
128
|
+
param(
|
|
129
|
+
[Parameter(Mandatory)][ValidateSet('epic', 'parallel')][string] $Kind,
|
|
130
|
+
[AllowNull()][AllowEmptyString()][string] $WorktreePath
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
$target = Resolve-EpicWorktreeGateRunTarget -Kind $Kind -WorktreePath $WorktreePath
|
|
134
|
+
$checkpoint = $null
|
|
135
|
+
if ($target.Status -eq 'SessionRoot' -or $target.Status -eq 'OtherWorktree') {
|
|
136
|
+
$path = Get-WorktreeRunCheckpointPath -Kind $Kind -WorktreeRoot $target.WorktreeRoot
|
|
137
|
+
$raw = if ($Kind -eq 'epic') { Get-EpicWorktreeGateCheckpointContent -Path $path } else { Get-EpicWorktreeGateParallelCheckpointContent -Path $path }
|
|
138
|
+
$checkpoint = ConvertFrom-EpicWorktreeGateJson -Raw $raw
|
|
139
|
+
}
|
|
140
|
+
return [pscustomobject]@{
|
|
141
|
+
Target = $target
|
|
142
|
+
Checkpoint = $checkpoint
|
|
143
|
+
}
|
|
144
|
+
}
|
package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1
CHANGED
|
@@ -50,10 +50,16 @@
|
|
|
50
50
|
allow for a removal to proceed; this gate keeps the EPIC_WORKTREE_REMOVAL_BLOCKED
|
|
51
51
|
prefix for both of its branches so transcript attribution stays unambiguous.
|
|
52
52
|
|
|
53
|
+
Issue #690: each checkpoint is read beneath the live worktree whose run checkpoint
|
|
54
|
+
records the removal target under worktree_path, located by
|
|
55
|
+
enforce-epic-worktree-removal-gate-resolution.ps1. An ambiguous target denies before
|
|
56
|
+
any allow; when neither kind resolves, the deny names TARGET_WORKTREE_NOT_DERIVABLE.
|
|
57
|
+
|
|
53
58
|
.NOTES
|
|
54
|
-
Compatible with PowerShell 7+.
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
Compatible with PowerShell 7+. Depends on WorktreeRunResolution.psm1 through the
|
|
60
|
+
dot-sourced resolution sibling, whose import guard denies on a failed import.
|
|
61
|
+
Filesystem reads go through injectable wrapper functions so tests can mock the
|
|
62
|
+
boundary without writing temporary files.
|
|
57
63
|
#>
|
|
58
64
|
[CmdletBinding()]
|
|
59
65
|
param()
|
|
@@ -67,50 +73,13 @@ Import-Module (Join-Path $PSScriptRoot '../lib/cleanup-manifest/CleanupWorktreeM
|
|
|
67
73
|
# Invoke-EpicWorktreeRemovalGateDecision and by Get-EpicWorktreeRemovalCommandPath.
|
|
68
74
|
. (Join-Path $PSScriptRoot 'hook-command-scanner.ps1')
|
|
69
75
|
. (Join-Path $PSScriptRoot 'hook-command-invocation.ps1')
|
|
70
|
-
|
|
71
|
-
$
|
|
76
|
+
# Checkpoint read seams, the import guard, and run-target resolution (issue #690).
|
|
77
|
+
. (Join-Path $PSScriptRoot 'enforce-epic-worktree-removal-gate-resolution.ps1')
|
|
72
78
|
$script:AllowedMergeStatuses = @('merged', 'worktree_removed')
|
|
73
|
-
# Sanctioned-removal manifest location. Recorded here
|
|
74
|
-
#
|
|
75
|
-
# owns the read itself.
|
|
79
|
+
# Sanctioned-removal manifest location. Recorded here so every hook-read document this
|
|
80
|
+
# gate consults is named; the module owns the read itself.
|
|
76
81
|
$script:CleanupWorktreeManifestPath = 'artifacts/orchestration/cleanup-worktrees-manifest.json'
|
|
77
82
|
|
|
78
|
-
function Get-EpicWorktreeGateCheckpointContent {
|
|
79
|
-
<#
|
|
80
|
-
.SYNOPSIS
|
|
81
|
-
Read the raw JSON text of the epic checkpoint. Tests mock this function
|
|
82
|
-
(read seam).
|
|
83
|
-
.OUTPUTS
|
|
84
|
-
System.String or $null
|
|
85
|
-
#>
|
|
86
|
-
[CmdletBinding()]
|
|
87
|
-
[OutputType([string])]
|
|
88
|
-
param()
|
|
89
|
-
|
|
90
|
-
if (-not (Test-Path -LiteralPath $script:EpicCheckpointPath -PathType Leaf)) {
|
|
91
|
-
return $null
|
|
92
|
-
}
|
|
93
|
-
return (Get-Content -LiteralPath $script:EpicCheckpointPath -Raw)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function Get-EpicWorktreeGateParallelCheckpointContent {
|
|
97
|
-
<#
|
|
98
|
-
.SYNOPSIS
|
|
99
|
-
Read the raw JSON text of the parallel-orchestrator checkpoint. Tests mock
|
|
100
|
-
this function (read seam).
|
|
101
|
-
.OUTPUTS
|
|
102
|
-
System.String or $null
|
|
103
|
-
#>
|
|
104
|
-
[CmdletBinding()]
|
|
105
|
-
[OutputType([string])]
|
|
106
|
-
param()
|
|
107
|
-
|
|
108
|
-
if (-not (Test-Path -LiteralPath $script:ParallelCheckpointPath -PathType Leaf)) {
|
|
109
|
-
return $null
|
|
110
|
-
}
|
|
111
|
-
return (Get-Content -LiteralPath $script:ParallelCheckpointPath -Raw)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
83
|
function ConvertFrom-EpicWorktreeGateJson {
|
|
115
84
|
<#
|
|
116
85
|
.SYNOPSIS
|
|
@@ -361,6 +330,12 @@ function Invoke-EpicWorktreeRemovalGateDecision {
|
|
|
361
330
|
[string] $ToolInputRaw
|
|
362
331
|
)
|
|
363
332
|
|
|
333
|
+
# A failed worktree-resolution import denies before any other logic (issue #690).
|
|
334
|
+
$importFailure = Get-EpicWorktreeGateImportFailureDecision
|
|
335
|
+
if ($null -ne $importFailure) {
|
|
336
|
+
return $importFailure
|
|
337
|
+
}
|
|
338
|
+
|
|
364
339
|
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
365
340
|
if (-not $payload.IsValid) {
|
|
366
341
|
return Get-EpicWorktreeGateBlockDecision -Reason (
|
|
@@ -383,14 +358,23 @@ function Invoke-EpicWorktreeRemovalGateDecision {
|
|
|
383
358
|
|
|
384
359
|
$worktreePath = Get-EpicWorktreeRemovalCommandPath -CommandText $commandText
|
|
385
360
|
|
|
386
|
-
|
|
361
|
+
# Each run checkpoint is read beneath the worktree that records this path (issue #690);
|
|
362
|
+
# an ambiguous target denies before any allow or manifest evaluation.
|
|
363
|
+
$epicRead = Read-EpicWorktreeGateRunCheckpoint -Kind epic -WorktreePath $worktreePath
|
|
364
|
+
$parallelRead = Read-EpicWorktreeGateRunCheckpoint -Kind parallel -WorktreePath $worktreePath
|
|
365
|
+
foreach ($read in @($epicRead, $parallelRead)) {
|
|
366
|
+
if ($read.Target.Status -eq 'Ambiguous') {
|
|
367
|
+
return Get-EpicWorktreeGateBlockDecision -Reason "EPIC_WORKTREE_REMOVAL_BLOCKED: $($read.Target.ReasonCode): $($read.Target.Detail)"
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
$checkpoint = $epicRead.Checkpoint
|
|
387
371
|
|
|
388
372
|
$featureRecord = Find-EpicWorktreeFeatureRecord -Checkpoint $checkpoint -WorktreePath $worktreePath
|
|
389
373
|
if (Test-EpicWorktreeRemovalAllowed -FeatureRecord $featureRecord) {
|
|
390
374
|
return Get-EpicWorktreeGateAllowDecision
|
|
391
375
|
}
|
|
392
376
|
|
|
393
|
-
$parallelCheckpoint =
|
|
377
|
+
$parallelCheckpoint = $parallelRead.Checkpoint
|
|
394
378
|
if (Test-ParallelCheckpointAllowsWorktreeRemoval -Checkpoint $parallelCheckpoint -WorktreePath $worktreePath) {
|
|
395
379
|
return Get-EpicWorktreeGateAllowDecision
|
|
396
380
|
}
|
|
@@ -411,7 +395,12 @@ function Invoke-EpicWorktreeRemovalGateDecision {
|
|
|
411
395
|
return Get-EpicWorktreeGateAllowDecision
|
|
412
396
|
}
|
|
413
397
|
|
|
414
|
-
|
|
398
|
+
# When neither kind resolved, the deny names the resolution reason first (issue #690).
|
|
399
|
+
$prefix = ''
|
|
400
|
+
if ($epicRead.Target.Status -eq 'NoTarget' -and $parallelRead.Target.Status -eq 'NoTarget') {
|
|
401
|
+
$prefix = "EPIC_WORKTREE_REMOVAL_BLOCKED: $($epicRead.Target.ReasonCode): $($epicRead.Target.Detail). "
|
|
402
|
+
}
|
|
403
|
+
return Get-EpicWorktreeGateBlockDecision -Reason ($prefix + "EPIC_WORKTREE_REMOVAL_BLOCKED: git worktree remove for '$worktreePath' requires either an epic checkpoint features[] record with merge_status in {merged, worktree_removed}, or a parallel-orchestrator checkpoint with route_id == ""parallel"" whose matching items[] record (matched by worktree_path) has merge_status in {merged, worktree_removed}. No checkpoint authorized this removal.")
|
|
415
404
|
}
|
|
416
405
|
|
|
417
406
|
function Invoke-EpicWorktreeRemovalGateEntryPoint {
|
|
@@ -1,60 +1,212 @@
|
|
|
1
1
|
<#
|
|
2
2
|
.SYNOPSIS
|
|
3
|
-
Epic-scope
|
|
3
|
+
Epic-scope legs, checkpoint read seams, and target resolution of the preimplementation gate.
|
|
4
4
|
|
|
5
5
|
.DESCRIPTION
|
|
6
|
-
Dot-sourced by enforce-orchestration-preimplementation-gate.ps1. Holds
|
|
6
|
+
Dot-sourced by enforce-orchestration-preimplementation-gate.ps1. Holds four groups of
|
|
7
7
|
functions:
|
|
8
8
|
|
|
9
|
-
- The
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- The
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
- The import guard for the worktree-resolution modules (issue #690). A failed import
|
|
10
|
+
is recorded in $script:OrchestrationGateResolutionImportFailure, and
|
|
11
|
+
Get-OrchestrationGateImportFailureDecision turns that record into a deny, so a
|
|
12
|
+
missing module cannot make the gate exit non-zero and fail open.
|
|
13
|
+
- The three checkpoint read seams, Get-CheckpointContent, Get-EpicCheckpointContent,
|
|
14
|
+
and Get-ParallelCheckpointContent. Each takes a mandatory absolute path, so no
|
|
15
|
+
read binds to the calling process's directory. Get-CheckpointContent and
|
|
16
|
+
Get-OrchestrationModeDenyReason were relocated here from the gate file by issue #690
|
|
17
|
+
to keep the gate inside the 500-line cap.
|
|
18
|
+
- The target resolution of issue #690. Resolve-OrchestrationGateTarget selects the
|
|
19
|
+
worktree whose checkpoint governs the call from portable identity only: the
|
|
20
|
+
integration_branch: value for an epic delegation, the parallel_slug: value for a
|
|
21
|
+
parallel delegation, the file_path or git -C operand for the path and command legs,
|
|
22
|
+
and the canonical issue-number line and branch: label for a single-feature
|
|
23
|
+
delegation. Read-OrchestrationGateCheckpoint reads the checkpoint beneath the
|
|
24
|
+
resolved root, or returns the deny for an unresolved target.
|
|
25
|
+
- The epic-scope decision for the command and path legs (issue #663). A staging
|
|
26
|
+
command or a Write/Edit call is epic scope when the effective worktree's HEAD equals
|
|
27
|
+
the integration branch of the epic checkpoint that Resolve-EpicScopeCheckpoint
|
|
28
|
+
locates. Outside epic scope the decision function returns $null and the gate's
|
|
29
|
+
single-feature path runs.
|
|
21
30
|
|
|
22
31
|
.NOTES
|
|
23
32
|
PowerShell 7+. Depends on functions the gate defines or dot-sources before any call:
|
|
24
33
|
Split-OrchestrationCommandLine and ConvertTo-OrchestrationCommandToken (helpers file),
|
|
25
34
|
Get-OrchestrationDelegationCheckpointPath (modes file), and the gate's allow and block
|
|
26
|
-
decision constructors.
|
|
35
|
+
decision constructors. Imports WorktreeItemResolution.psm1 and WorktreeRunResolution.psm1
|
|
36
|
+
before EpicScopeResolution.psm1, so the nested import inside EpicScopeResolution reuses
|
|
37
|
+
the same module instance. Mirrored byte-identically under
|
|
27
38
|
extensions/drm-copilot/resources/claude-customizations/.
|
|
28
39
|
#>
|
|
29
40
|
|
|
30
|
-
Import
|
|
41
|
+
# Import guard (issue #690). Each worktree-resolution import is attempted in order; the
|
|
42
|
+
# first failure is recorded by file name and the decision function denies on it.
|
|
43
|
+
$script:OrchestrationGateResolutionImportFailure = $null
|
|
44
|
+
foreach ($resolutionModule in @('WorktreeItemResolution.psm1', 'WorktreeRunResolution.psm1', 'EpicScopeResolution.psm1')) {
|
|
45
|
+
try {
|
|
46
|
+
Import-Module (Join-Path $PSScriptRoot "../lib/worktree-resolution/$resolutionModule") -Force -ErrorAction Stop
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
if (-not $script:OrchestrationGateResolutionImportFailure) { $script:OrchestrationGateResolutionImportFailure = $resolutionModule }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
31
52
|
Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/EpicScopeReadiness.psm1') -Force -ErrorAction Stop
|
|
32
53
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
54
|
+
function Get-OrchestrationGateImportFailureDecision {
|
|
55
|
+
<#
|
|
56
|
+
.SYNOPSIS
|
|
57
|
+
Returns the deny for a failed worktree-resolution import, or $null.
|
|
58
|
+
.OUTPUTS
|
|
59
|
+
System.Collections.Specialized.OrderedDictionary, or $null when every import succeeded.
|
|
60
|
+
#>
|
|
61
|
+
[CmdletBinding()]
|
|
62
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
63
|
+
param()
|
|
64
|
+
|
|
65
|
+
if (-not $script:OrchestrationGateResolutionImportFailure) {
|
|
66
|
+
return $null
|
|
67
|
+
}
|
|
68
|
+
return Get-OrchestrationPreimplementationGateBlockDecision -Reason (
|
|
69
|
+
"PREIMPLEMENTATION_GATE_BLOCKED: the worktree-resolution module '$($script:OrchestrationGateResolutionImportFailure)' " +
|
|
70
|
+
'failed to import, so the checkpoint that governs this call cannot be located; the gate fails closed.')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# The three checkpoint read seams. Each takes the absolute path composed beneath a
|
|
74
|
+
# resolved worktree root; an absent file returns an empty string, which the readiness
|
|
75
|
+
# predicate then treats as a deny.
|
|
76
|
+
function Get-CheckpointContent {
|
|
77
|
+
[CmdletBinding()]
|
|
78
|
+
[OutputType([string])]
|
|
79
|
+
param([Parameter(Mandatory)][ValidatePattern('^([A-Za-z]:[\\/]|/)')][string] $Path)
|
|
80
|
+
|
|
81
|
+
if (-not (Test-Path -LiteralPath $Path)) {
|
|
82
|
+
return ''
|
|
83
|
+
}
|
|
84
|
+
return Get-Content -Raw -LiteralPath $Path
|
|
85
|
+
}
|
|
86
|
+
|
|
36
87
|
function Get-EpicCheckpointContent {
|
|
37
88
|
[CmdletBinding()]
|
|
38
89
|
[OutputType([string])]
|
|
39
|
-
param()
|
|
90
|
+
param([Parameter(Mandatory)][ValidatePattern('^([A-Za-z]:[\\/]|/)')][string] $Path)
|
|
40
91
|
|
|
41
|
-
|
|
42
|
-
if (-not (Test-Path -LiteralPath $path)) {
|
|
92
|
+
if (-not (Test-Path -LiteralPath $Path)) {
|
|
43
93
|
return ''
|
|
44
94
|
}
|
|
45
|
-
return Get-Content -Raw -LiteralPath $
|
|
95
|
+
return Get-Content -Raw -LiteralPath $Path
|
|
46
96
|
}
|
|
47
97
|
|
|
48
98
|
function Get-ParallelCheckpointContent {
|
|
49
99
|
[CmdletBinding()]
|
|
50
100
|
[OutputType([string])]
|
|
51
|
-
param()
|
|
101
|
+
param([Parameter(Mandatory)][ValidatePattern('^([A-Za-z]:[\\/]|/)')][string] $Path)
|
|
52
102
|
|
|
53
|
-
|
|
54
|
-
if (-not (Test-Path -LiteralPath $path)) {
|
|
103
|
+
if (-not (Test-Path -LiteralPath $Path)) {
|
|
55
104
|
return ''
|
|
56
105
|
}
|
|
57
|
-
return Get-Content -Raw -LiteralPath $
|
|
106
|
+
return Get-Content -Raw -LiteralPath $Path
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Builds a mode-specific deny reason naming the checkpoint actually consulted and the
|
|
110
|
+
# predicate that failed, behind the unchanged PREIMPLEMENTATION_GATE_BLOCKED prefix
|
|
111
|
+
# that downstream reason-matching reads. A resolved absolute path is named when known.
|
|
112
|
+
function Get-OrchestrationModeDenyReason {
|
|
113
|
+
[CmdletBinding()]
|
|
114
|
+
[OutputType([string])]
|
|
115
|
+
param(
|
|
116
|
+
[Parameter(Mandatory)][string] $Mode,
|
|
117
|
+
[Parameter(Mandatory)][string] $Failure,
|
|
118
|
+
[AllowNull()][AllowEmptyString()][string] $CheckpointPath = ''
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
$path = if ($CheckpointPath) { $CheckpointPath } else { Get-OrchestrationDelegationCheckpointPath -Mode $Mode }
|
|
122
|
+
return ("PREIMPLEMENTATION_GATE_BLOCKED: this $Mode-mode delegation was evaluated against " +
|
|
123
|
+
"$path, and the failed readiness predicate is '$Failure'. Implementation operations " +
|
|
124
|
+
'require that checkpoint to satisfy every readiness predicate before implementation begins.')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function Resolve-OrchestrationGateTarget {
|
|
128
|
+
<#
|
|
129
|
+
.SYNOPSIS
|
|
130
|
+
Resolves the worktree whose checkpoint governs a gated call (issue #690 seam).
|
|
131
|
+
.DESCRIPTION
|
|
132
|
+
An epic delegation is keyed on integration_branch: (with epic_feature_folder: as a
|
|
133
|
+
cross-check), a parallel delegation on parallel_slug:, a path leg on its file_path
|
|
134
|
+
operand, a command leg on its git -C selector (none means the session root), and a
|
|
135
|
+
single-feature delegation on its canonical issue-number line and branch: label.
|
|
136
|
+
.OUTPUTS
|
|
137
|
+
System.Management.Automation.PSCustomObject (the worktree-resolution target result).
|
|
138
|
+
#>
|
|
139
|
+
[CmdletBinding()]
|
|
140
|
+
[OutputType([pscustomobject])]
|
|
141
|
+
param(
|
|
142
|
+
[Parameter(Mandatory)][string] $Mode,
|
|
143
|
+
[AllowNull()][AllowEmptyString()][string] $Prompt = '',
|
|
144
|
+
[AllowNull()][AllowEmptyString()][string] $FilePath = '',
|
|
145
|
+
[AllowNull()][AllowEmptyString()][string] $Command = ''
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
$sessionRoot = (Get-Location).Path
|
|
149
|
+
if ($Mode -eq 'epic' -or $Mode -eq 'parallel') {
|
|
150
|
+
$signal = Find-WorktreeRunIdentitySignal -Text $Prompt
|
|
151
|
+
if ($Mode -eq 'epic') {
|
|
152
|
+
return Resolve-WorktreeEpicTarget -IntegrationBranch $signal.IntegrationBranch -EpicSlug $signal.EpicSlug -SessionRoot $sessionRoot
|
|
153
|
+
}
|
|
154
|
+
return Resolve-WorktreeParallelTarget -ParallelSlug $signal.ParallelSlug -SessionRoot $sessionRoot
|
|
155
|
+
}
|
|
156
|
+
if ($FilePath) {
|
|
157
|
+
return Resolve-WorktreeOperandTarget -Path $FilePath -SessionRoot $sessionRoot
|
|
158
|
+
}
|
|
159
|
+
if ($Command) {
|
|
160
|
+
return Resolve-WorktreeOperandTarget -Path (Get-OrchestrationEpicScopeSelector -Command $Command) -SessionRoot $sessionRoot
|
|
161
|
+
}
|
|
162
|
+
return Resolve-WorktreeItemTarget -Text $Prompt -SessionRoot $sessionRoot
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function Read-OrchestrationGateCheckpoint {
|
|
166
|
+
<#
|
|
167
|
+
.SYNOPSIS
|
|
168
|
+
Reads the checkpoint beneath the resolved target worktree, or returns the deny reason.
|
|
169
|
+
.DESCRIPTION
|
|
170
|
+
Resolved is $false, with DenyReason set, when the target is NoTarget or Ambiguous.
|
|
171
|
+
Otherwise Raw holds the checkpoint text (empty when absent) and Path its absolute
|
|
172
|
+
path. Only Status, WorktreeRoot, ReasonCode, and Detail are read from the target.
|
|
173
|
+
.OUTPUTS
|
|
174
|
+
System.Management.Automation.PSCustomObject
|
|
175
|
+
#>
|
|
176
|
+
[CmdletBinding()]
|
|
177
|
+
[OutputType([pscustomobject])]
|
|
178
|
+
param(
|
|
179
|
+
[Parameter(Mandatory)][string] $Mode,
|
|
180
|
+
[AllowNull()][AllowEmptyString()][string] $Prompt = '',
|
|
181
|
+
[AllowNull()][AllowEmptyString()][string] $FilePath = '',
|
|
182
|
+
[AllowNull()][AllowEmptyString()][string] $Command = ''
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
$target = Resolve-OrchestrationGateTarget -Mode $Mode -Prompt $Prompt -FilePath $FilePath -Command $Command
|
|
186
|
+
if ($target.Status -eq 'NoTarget' -or $target.Status -eq 'Ambiguous') {
|
|
187
|
+
return [pscustomobject]@{
|
|
188
|
+
Resolved = $false
|
|
189
|
+
Raw = ''
|
|
190
|
+
Path = $null
|
|
191
|
+
DenyReason = ("PREIMPLEMENTATION_GATE_BLOCKED: $($target.ReasonCode): the target worktree of this $Mode call " +
|
|
192
|
+
"could not be resolved: $($target.Detail). Implementation operations require an identifiable target " +
|
|
193
|
+
'worktree whose checkpoint is ready.')
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
$kind = if ($Mode -eq 'epic' -or $Mode -eq 'parallel') { $Mode } else { 'item' }
|
|
198
|
+
$path = Get-WorktreeRunCheckpointPath -Kind $kind -WorktreeRoot $target.WorktreeRoot
|
|
199
|
+
$raw = switch ($kind) {
|
|
200
|
+
'epic' { Get-EpicCheckpointContent -Path $path }
|
|
201
|
+
'parallel' { Get-ParallelCheckpointContent -Path $path }
|
|
202
|
+
default { Get-CheckpointContent -Path $path }
|
|
203
|
+
}
|
|
204
|
+
return [pscustomobject]@{
|
|
205
|
+
Resolved = $true
|
|
206
|
+
Raw = [string]$raw
|
|
207
|
+
Path = $path
|
|
208
|
+
DenyReason = $null
|
|
209
|
+
}
|
|
58
210
|
}
|
|
59
211
|
|
|
60
212
|
function Get-OrchestrationEpicScopeSelector {
|