@danmoisan/drm-copilot-mcp 1.0.9 → 1.0.11
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 +230 -225
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +2 -41
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +26 -5
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +485 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletion.psm1 +243 -0
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +7 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +2 -0
- package/resources/claude-customizations/pack-manifests/core.json +3 -1
- package/resources/config/orchestration-routing.json +1 -1
package/package.json
CHANGED
|
@@ -46,46 +46,7 @@ param()
|
|
|
46
46
|
$script:PrContextArtifactPath = 'artifacts/pr_context.summary.txt'
|
|
47
47
|
$script:OrchestratorStateCheckpointPath = 'artifacts/orchestration/orchestrator-state.json'
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
<#
|
|
51
|
-
.SYNOPSIS
|
|
52
|
-
Runs the orchestrator-state validator against the checkpoint and reports pass/fail.
|
|
53
|
-
.DESCRIPTION
|
|
54
|
-
Mirrors Invoke-RoutingContractValidation (.claude/hooks/validate-orchestrator-output.ps1):
|
|
55
|
-
an injectable subprocess scriptblock seam defaults to ``python -m
|
|
56
|
-
scripts.dev_tools.validate_orchestration_artifacts orchestrator-state <CheckpointPath>
|
|
57
|
-
--require-pr-creation-ready``. A missing checkpoint or --require-pr-creation-ready failure
|
|
58
|
-
both surface via the validator's non-zero exit/stderr text; no separate file-existence check
|
|
59
|
-
is made, validating pre-PR-creation readiness (steps 5-8, blocked_reason) not full completion.
|
|
60
|
-
.OUTPUTS
|
|
61
|
-
System.Collections.Hashtable with keys HasErrors (bool) and ErrorText (string).
|
|
62
|
-
#>
|
|
63
|
-
[CmdletBinding()]
|
|
64
|
-
[OutputType([hashtable])]
|
|
65
|
-
param(
|
|
66
|
-
[Parameter(Mandatory = $false)]
|
|
67
|
-
[string] $CheckpointPath = $script:OrchestratorStateCheckpointPath,
|
|
68
|
-
|
|
69
|
-
[Parameter(Mandatory = $false)]
|
|
70
|
-
[scriptblock] $Invoker = {
|
|
71
|
-
param($Path)
|
|
72
|
-
$output = & python -m scripts.dev_tools.validate_orchestration_artifacts `
|
|
73
|
-
orchestrator-state $Path --require-pr-creation-ready 2>&1
|
|
74
|
-
[pscustomobject]@{
|
|
75
|
-
ExitCode = $LASTEXITCODE
|
|
76
|
-
Output = ($output | Out-String)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
$result = & $Invoker $CheckpointPath
|
|
82
|
-
$exitCode = 0
|
|
83
|
-
if ($null -ne $result -and ($result.PSObject.Properties.Name -contains 'ExitCode')) { $exitCode = [int]$result.ExitCode }
|
|
84
|
-
$outputText = ''
|
|
85
|
-
if ($null -ne $result -and ($result.PSObject.Properties.Name -contains 'Output')) { $outputText = ([string]$result.Output).Trim() }
|
|
86
|
-
|
|
87
|
-
return @{ HasErrors = ($exitCode -ne 0); ErrorText = $outputText }
|
|
88
|
-
}
|
|
49
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorState.psm1') -Force
|
|
89
50
|
|
|
90
51
|
function Get-PrContextArtifactExistence {
|
|
91
52
|
<#
|
|
@@ -359,7 +320,7 @@ function Get-PrAuthorBypassReason {
|
|
|
359
320
|
# Orchestrator-state preflight: runs inside this same PreToolUse hook (so it cannot be
|
|
360
321
|
# bypassed by invoking gh pr create/edit directly) before receipt verification.
|
|
361
322
|
if ($hasBodyFile -and $ContextExists) {
|
|
362
|
-
$preflightResult = Invoke-OrchestratorStatePreflight
|
|
323
|
+
$preflightResult = Invoke-OrchestratorStatePreflight -CheckpointPath $script:OrchestratorStateCheckpointPath
|
|
363
324
|
if ($preflightResult.HasErrors) {
|
|
364
325
|
$preflightSummary = if ([string]::IsNullOrWhiteSpace($preflightResult.ErrorText)) {
|
|
365
326
|
"checkpoint missing at $script:OrchestratorStateCheckpointPath"
|
|
@@ -38,6 +38,8 @@ param(
|
|
|
38
38
|
Set-StrictMode -Version Latest
|
|
39
39
|
$ErrorActionPreference = 'Stop'
|
|
40
40
|
|
|
41
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorState.psm1') -Force
|
|
42
|
+
|
|
41
43
|
function Get-CheckpointFileContent {
|
|
42
44
|
<#
|
|
43
45
|
.SYNOPSIS
|
|
@@ -179,11 +181,30 @@ function Invoke-RoutingContractValidation {
|
|
|
179
181
|
[Parameter(Mandatory = $false)]
|
|
180
182
|
[scriptblock] $Invoker = {
|
|
181
183
|
param($Path, $Type)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
# Capability detection: use the authoritative Python CLI when
|
|
185
|
+
# scripts.dev_tools is importable (drm-copilot); otherwise fall back to
|
|
186
|
+
# the portable PowerShell completion module that travels with the
|
|
187
|
+
# pushed-down pack. The portable path performs the presence-level
|
|
188
|
+
# required-once-delegated existence gate and still fails closed.
|
|
189
|
+
if (Test-PythonOrchestratorValidatorAvailable) {
|
|
190
|
+
$output = & python -m scripts.dev_tools.validate_orchestration_artifacts `
|
|
191
|
+
$Type $Path --require-complete --require-model-routing 2>&1
|
|
192
|
+
[pscustomobject]@{
|
|
193
|
+
ExitCode = $LASTEXITCODE
|
|
194
|
+
Output = ($output | Out-String)
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
# Import the portable completion module only when its function is not
|
|
198
|
+
# already available, so a repeated call (or a test that pre-imports and
|
|
199
|
+
# mocks the function) does not reload the module and reset the seam.
|
|
200
|
+
if (-not (Get-Command -Name Test-OrchestratorStateCompletionReadiness -ErrorAction SilentlyContinue)) {
|
|
201
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorStateCompletion.psm1') -Force
|
|
202
|
+
}
|
|
203
|
+
$portable = Test-OrchestratorStateCompletionReadiness -CheckpointPath $Path
|
|
204
|
+
[pscustomobject]@{
|
|
205
|
+
ExitCode = $portable.ExitCode
|
|
206
|
+
Output = $portable.Output
|
|
207
|
+
}
|
|
187
208
|
}
|
|
188
209
|
}
|
|
189
210
|
)
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Portable orchestrator-state checkpoint checks for pushed-down enforcement hooks.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Provides a self-contained PowerShell implementation of the pushed-down-relevant
|
|
7
|
+
orchestrator-state checkpoint validations so the `.claude` enforcement hooks work
|
|
8
|
+
in consumer repositories that do not ship `scripts/dev_tools` (the authoritative
|
|
9
|
+
Python validator). This module mirrors the portable pattern of
|
|
10
|
+
`.claude/lib/model-routing/ModelRouting.psm1`.
|
|
11
|
+
|
|
12
|
+
It implements PR-creation-readiness parity with
|
|
13
|
+
`scripts/dev_tools/_orchestrator_state_pr_creation_readiness.py` and the base
|
|
14
|
+
checkpoint-presence checks (required keys, step-status validity, blocked_reason
|
|
15
|
+
validity) from `scripts/dev_tools/validate_orchestrator_state.py`. The base
|
|
16
|
+
constants below are pinned to `REQUIRED_STATE_KEYS`, `STEP_STATUS_KEYS`,
|
|
17
|
+
`VALID_STEP_STATUS`, and `VALID_BLOCKED_REASONS` in that validator.
|
|
18
|
+
|
|
19
|
+
Every public function FAILS CLOSED: a missing checkpoint file, invalid JSON, a
|
|
20
|
+
missing required key, an invalid step status, or an unmet readiness condition all
|
|
21
|
+
yield a non-zero ExitCode with a non-empty Output message. The Python validator
|
|
22
|
+
remains the authoritative reference; this module is the destination-runtime
|
|
23
|
+
mirror used only when the Python module is not importable.
|
|
24
|
+
|
|
25
|
+
This module also hosts the capability-detection probe
|
|
26
|
+
(Test-PythonOrchestratorValidatorAvailable), shared by both pushed-down hooks
|
|
27
|
+
(.claude/hooks/enforce-pr-author-skill.ps1 and
|
|
28
|
+
.claude/hooks/validate-orchestrator-output.ps1) so neither hook duplicates it
|
|
29
|
+
locally, and the PR-creation preflight orchestration helper
|
|
30
|
+
(Invoke-OrchestratorStatePreflight) consumed by enforce-pr-author-skill.ps1.
|
|
31
|
+
#>
|
|
32
|
+
|
|
33
|
+
Set-StrictMode -Version Latest
|
|
34
|
+
|
|
35
|
+
# The canonical top-level checkpoint keys required by the primary validator.
|
|
36
|
+
# Pinned to REQUIRED_STATE_KEYS in scripts/dev_tools/validate_orchestrator_state.py.
|
|
37
|
+
$script:REQUIRED_STATE_KEYS = @(
|
|
38
|
+
'objective',
|
|
39
|
+
'change_budget_estimate',
|
|
40
|
+
'path_selected',
|
|
41
|
+
'promotion-type',
|
|
42
|
+
'short-name',
|
|
43
|
+
'relativeFile',
|
|
44
|
+
'long-name',
|
|
45
|
+
'issue-num',
|
|
46
|
+
'feature-folder',
|
|
47
|
+
'work-mode',
|
|
48
|
+
'plan-path',
|
|
49
|
+
'completed_steps',
|
|
50
|
+
'next_step',
|
|
51
|
+
'last_updated',
|
|
52
|
+
'step5_status',
|
|
53
|
+
'step6_status',
|
|
54
|
+
'step7_status',
|
|
55
|
+
'step8_status',
|
|
56
|
+
'step9_status',
|
|
57
|
+
'step10_status',
|
|
58
|
+
'delegation_receipts',
|
|
59
|
+
'blocked_reason'
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# The lifecycle step-status keys whose value, when present, must be a member of
|
|
63
|
+
# VALID_STEP_STATUS. Pinned to STEP_STATUS_KEYS in the primary validator.
|
|
64
|
+
$script:STEP_STATUS_KEYS = @(
|
|
65
|
+
'step5_status',
|
|
66
|
+
'step6_status',
|
|
67
|
+
'step7_status',
|
|
68
|
+
'step8_status',
|
|
69
|
+
'step9_status',
|
|
70
|
+
'step10_status'
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# The allowed step-status vocabulary. Pinned to VALID_STEP_STATUS in the primary
|
|
74
|
+
# validator.
|
|
75
|
+
$script:VALID_STEP_STATUS = @(
|
|
76
|
+
'not-applicable',
|
|
77
|
+
'pending',
|
|
78
|
+
'delegated',
|
|
79
|
+
'verified',
|
|
80
|
+
'blocked',
|
|
81
|
+
'not_started',
|
|
82
|
+
'in_progress',
|
|
83
|
+
'completed'
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# The allowed blocked_reason vocabulary. Pinned to VALID_BLOCKED_REASONS in the
|
|
87
|
+
# primary validator.
|
|
88
|
+
$script:VALID_BLOCKED_REASONS = @(
|
|
89
|
+
'none',
|
|
90
|
+
'spawn_agent_unavailable',
|
|
91
|
+
'delegation_launch_failed',
|
|
92
|
+
'delegate_no_receipt',
|
|
93
|
+
'delegate_contract_incomplete',
|
|
94
|
+
'validator_failed',
|
|
95
|
+
'user_requested_stop'
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# The upstream steps that must not be pending/blocked before the first PR creation
|
|
99
|
+
# of a branch. Pinned to PR_CREATION_READY_STEP_KEYS in
|
|
100
|
+
# _orchestrator_state_pr_creation_readiness.py (deliberately narrower than the full
|
|
101
|
+
# step set: steps 9-10 can only populate after PR creation and CI have run).
|
|
102
|
+
$script:PR_CREATION_READY_STEP_KEYS = @(
|
|
103
|
+
'step5_status',
|
|
104
|
+
'step6_status',
|
|
105
|
+
'step7_status',
|
|
106
|
+
'step8_status'
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# The checkpoint list fields that must be empty (or absent) before the first PR
|
|
110
|
+
# creation. Pinned to PR_CREATION_READY_EMPTY_LIST_KEYS in the Python reference.
|
|
111
|
+
$script:PR_CREATION_READY_EMPTY_LIST_KEYS = @(
|
|
112
|
+
'local_execution_overrides',
|
|
113
|
+
'delegation_bypasses'
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
function Get-OrchestratorStateCheckpoint {
|
|
118
|
+
<#
|
|
119
|
+
.SYNOPSIS
|
|
120
|
+
Load and parse an orchestrator-state checkpoint, failing closed on error.
|
|
121
|
+
.DESCRIPTION
|
|
122
|
+
Private load helper. Reads the checkpoint at -CheckpointPath and parses it as
|
|
123
|
+
JSON. It never throws to the caller: a missing file or unparseable/invalid
|
|
124
|
+
JSON is reported through a structured result with Ok = $false and a non-empty
|
|
125
|
+
Error string, so callers can translate the failure into a fail-closed
|
|
126
|
+
non-zero ExitCode.
|
|
127
|
+
.PARAMETER CheckpointPath
|
|
128
|
+
The path to the orchestrator-state checkpoint JSON file.
|
|
129
|
+
.OUTPUTS
|
|
130
|
+
System.Collections.Hashtable with keys:
|
|
131
|
+
- Ok (bool): $true when the file exists and parsed to a JSON object.
|
|
132
|
+
- State (object): the parsed PSCustomObject on success, otherwise $null.
|
|
133
|
+
- Error (string): a non-empty failure message on failure, otherwise ''.
|
|
134
|
+
#>
|
|
135
|
+
[CmdletBinding()]
|
|
136
|
+
[OutputType([hashtable])]
|
|
137
|
+
param(
|
|
138
|
+
[Parameter(Mandatory = $true)]
|
|
139
|
+
[string] $CheckpointPath
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# A missing checkpoint file is a fail-closed condition: there is no state to
|
|
143
|
+
# validate, so readiness cannot be established.
|
|
144
|
+
if (-not (Test-Path -LiteralPath $CheckpointPath -PathType Leaf)) {
|
|
145
|
+
return @{
|
|
146
|
+
Ok = $false
|
|
147
|
+
State = $null
|
|
148
|
+
Error = "Checkpoint file '$CheckpointPath' does not exist."
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
$raw = Get-Content -LiteralPath $CheckpointPath -Raw -ErrorAction Stop
|
|
153
|
+
|
|
154
|
+
# An empty checkpoint carries no state; treat it as fail-closed rather than
|
|
155
|
+
# letting ConvertFrom-Json return $null silently.
|
|
156
|
+
if ([string]::IsNullOrWhiteSpace($raw)) {
|
|
157
|
+
return @{
|
|
158
|
+
Ok = $false
|
|
159
|
+
State = $null
|
|
160
|
+
Error = "Checkpoint file '$CheckpointPath' is empty."
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Parse the checkpoint text. Invalid JSON is a fail-closed condition, caught here
|
|
165
|
+
# so the caller receives a message instead of a terminating error.
|
|
166
|
+
try {
|
|
167
|
+
$state = $raw | ConvertFrom-Json -ErrorAction Stop
|
|
168
|
+
} catch {
|
|
169
|
+
return @{
|
|
170
|
+
Ok = $false
|
|
171
|
+
State = $null
|
|
172
|
+
Error = "Checkpoint file '$CheckpointPath' is not valid JSON: $($_.Exception.Message)"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# A JSON scalar or array root is not a checkpoint object; require an object so
|
|
177
|
+
# the presence checks can inspect named fields.
|
|
178
|
+
if ($state -isnot [System.Management.Automation.PSCustomObject]) {
|
|
179
|
+
return @{
|
|
180
|
+
Ok = $false
|
|
181
|
+
State = $null
|
|
182
|
+
Error = "Checkpoint file '$CheckpointPath' root must be a JSON object."
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return @{ Ok = $true; State = $state; Error = '' }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function Get-OrchestratorStateField {
|
|
190
|
+
<#
|
|
191
|
+
.SYNOPSIS
|
|
192
|
+
Read a checkpoint field, distinguishing an absent key from a null value.
|
|
193
|
+
.DESCRIPTION
|
|
194
|
+
Private accessor that safely reads a named property from the parsed
|
|
195
|
+
checkpoint object under Set-StrictMode, where accessing an undefined property
|
|
196
|
+
would otherwise throw. Returns whether the key is present and its value,
|
|
197
|
+
mirroring the semantics of Python's ``dict.get`` (absent and null both read
|
|
198
|
+
as "no value" for the readiness checks).
|
|
199
|
+
.PARAMETER State
|
|
200
|
+
The parsed checkpoint PSCustomObject.
|
|
201
|
+
.PARAMETER Name
|
|
202
|
+
The property name to read.
|
|
203
|
+
.OUTPUTS
|
|
204
|
+
System.Collections.Hashtable with keys Present (bool) and Value (object).
|
|
205
|
+
#>
|
|
206
|
+
[CmdletBinding()]
|
|
207
|
+
[OutputType([hashtable])]
|
|
208
|
+
param(
|
|
209
|
+
[Parameter(Mandatory = $true)]
|
|
210
|
+
[psobject] $State,
|
|
211
|
+
|
|
212
|
+
[Parameter(Mandatory = $true)]
|
|
213
|
+
[string] $Name
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
$names = @($State.PSObject.Properties.Name)
|
|
217
|
+
if ($names -contains $Name) {
|
|
218
|
+
return @{ Present = $true; Value = $State.$Name }
|
|
219
|
+
}
|
|
220
|
+
return @{ Present = $false; Value = $null }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function Get-OrchestratorStateBasePresenceError {
|
|
224
|
+
<#
|
|
225
|
+
.SYNOPSIS
|
|
226
|
+
Return the base checkpoint-presence errors, mirroring the primary validator.
|
|
227
|
+
.DESCRIPTION
|
|
228
|
+
Private base check. Emits one error string per missing required key, one per
|
|
229
|
+
step5_status..step10_status value outside VALID_STEP_STATUS, and one when
|
|
230
|
+
blocked_reason is present with a value outside VALID_BLOCKED_REASONS. This
|
|
231
|
+
mirrors the base block of scripts/dev_tools/validate_orchestrator_state.py
|
|
232
|
+
(required keys, step-status validity, blocked_reason validity) that runs
|
|
233
|
+
before any mode-specific gate.
|
|
234
|
+
.PARAMETER State
|
|
235
|
+
The parsed checkpoint PSCustomObject.
|
|
236
|
+
.OUTPUTS
|
|
237
|
+
System.String[] - zero or more error strings; empty when the base shape is valid.
|
|
238
|
+
#>
|
|
239
|
+
[CmdletBinding()]
|
|
240
|
+
[OutputType([string[]])]
|
|
241
|
+
param(
|
|
242
|
+
[Parameter(Mandatory = $true)]
|
|
243
|
+
[psobject] $State
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
$errors = [System.Collections.Generic.List[string]]::new()
|
|
247
|
+
$names = @($State.PSObject.Properties.Name)
|
|
248
|
+
|
|
249
|
+
# Require every canonical top-level field; a missing key is reported individually
|
|
250
|
+
# so the operator sees exactly which fields are absent.
|
|
251
|
+
foreach ($key in $script:REQUIRED_STATE_KEYS) {
|
|
252
|
+
if ($names -notcontains $key) {
|
|
253
|
+
$errors.Add("Checkpoint missing required key: $key")
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# Every present step status must be a member of the allowed vocabulary; an absent
|
|
258
|
+
# step key contributes no error (mirrors the primary validator's None guard).
|
|
259
|
+
foreach ($key in $script:STEP_STATUS_KEYS) {
|
|
260
|
+
$field = Get-OrchestratorStateField -State $State -Name $key
|
|
261
|
+
if ($field.Present -and $null -ne $field.Value -and
|
|
262
|
+
($script:VALID_STEP_STATUS -notcontains [string]$field.Value)) {
|
|
263
|
+
$errors.Add("Checkpoint has invalid $key`: $($field.Value)")
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
# A present, non-null blocked_reason must be a member of the allowed vocabulary.
|
|
268
|
+
$blocked = Get-OrchestratorStateField -State $State -Name 'blocked_reason'
|
|
269
|
+
if ($blocked.Present -and $null -ne $blocked.Value -and
|
|
270
|
+
($script:VALID_BLOCKED_REASONS -notcontains [string]$blocked.Value)) {
|
|
271
|
+
$errors.Add("Checkpoint has invalid blocked_reason: $($blocked.Value)")
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return $errors.ToArray()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function Get-OrchestratorStatePrCreationReadinessError {
|
|
278
|
+
<#
|
|
279
|
+
.SYNOPSIS
|
|
280
|
+
Return the PR-creation-readiness errors, parity with the Python reference.
|
|
281
|
+
.DESCRIPTION
|
|
282
|
+
Private readiness check mirroring
|
|
283
|
+
validate_orchestrator_state_pr_creation_readiness in
|
|
284
|
+
_orchestrator_state_pr_creation_readiness.py: steps 5-8 must not be
|
|
285
|
+
pending/blocked; blocked_reason must be `none` or absent; and the
|
|
286
|
+
local_execution_overrides / delegation_bypasses lists must be empty when
|
|
287
|
+
present. It does not enforce completion, CI, PR, or routing-contract gates.
|
|
288
|
+
.PARAMETER State
|
|
289
|
+
The parsed checkpoint PSCustomObject.
|
|
290
|
+
.OUTPUTS
|
|
291
|
+
System.String[] - zero or more error strings; empty when ready for PR creation.
|
|
292
|
+
#>
|
|
293
|
+
[CmdletBinding()]
|
|
294
|
+
[OutputType([string[]])]
|
|
295
|
+
param(
|
|
296
|
+
[Parameter(Mandatory = $true)]
|
|
297
|
+
[psobject] $State
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
$errors = [System.Collections.Generic.List[string]]::new()
|
|
301
|
+
|
|
302
|
+
# Reject an upstream step recorded as pending or blocked; steps 5-8 must have
|
|
303
|
+
# finished before the first PR of a branch is created.
|
|
304
|
+
foreach ($key in $script:PR_CREATION_READY_STEP_KEYS) {
|
|
305
|
+
$field = Get-OrchestratorStateField -State $State -Name $key
|
|
306
|
+
if ($field.Present -and ($field.Value -eq 'pending' -or $field.Value -eq 'blocked')) {
|
|
307
|
+
$errors.Add("Checkpoint PR-creation readiness validation failed: $key is $($field.Value).")
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
# blocked_reason must read as clear: absent, null, or the literal 'none'. Any
|
|
312
|
+
# other recorded reason means the branch is not ready for PR creation.
|
|
313
|
+
$blocked = Get-OrchestratorStateField -State $State -Name 'blocked_reason'
|
|
314
|
+
if ($blocked.Present -and $null -ne $blocked.Value -and ([string]$blocked.Value -ne 'none')) {
|
|
315
|
+
$errors.Add('Checkpoint PR-creation readiness validation failed: blocked_reason is not `none`.')
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# Each override list must be empty when present: a present non-list value, or a
|
|
319
|
+
# present non-empty list, means overrides/bypasses were recorded (mirrors the
|
|
320
|
+
# Python `value is not None and (not isinstance(value, list) or value)` guard).
|
|
321
|
+
foreach ($key in $script:PR_CREATION_READY_EMPTY_LIST_KEYS) {
|
|
322
|
+
$field = Get-OrchestratorStateField -State $State -Name $key
|
|
323
|
+
if ($field.Present -and $null -ne $field.Value) {
|
|
324
|
+
$isList = $field.Value -is [System.Array]
|
|
325
|
+
if (-not $isList -or @($field.Value).Count -gt 0) {
|
|
326
|
+
$errors.Add("Checkpoint PR-creation readiness validation failed: $key must be an empty list when present.")
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return $errors.ToArray()
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function Test-PythonOrchestratorValidatorAvailable {
|
|
335
|
+
<#
|
|
336
|
+
.SYNOPSIS
|
|
337
|
+
Probe whether the authoritative Python orchestrator-state validator is importable.
|
|
338
|
+
.DESCRIPTION
|
|
339
|
+
Capability-detection seam. Returns $true only when
|
|
340
|
+
``python -c "import scripts.dev_tools.validate_orchestration_artifacts"`` exits 0,
|
|
341
|
+
indicating the authoritative Python validator ships in this repository (drm-copilot).
|
|
342
|
+
Returns $false on any non-zero exit or error, so a consumer repository that received
|
|
343
|
+
only the pushed-down `.claude` pack (no `scripts/dev_tools`) routes to the portable
|
|
344
|
+
PowerShell module. Any probe failure routes to the portable path, which itself fails
|
|
345
|
+
closed on bad checkpoints, preserving fail-closed semantics in both branches. Tests
|
|
346
|
+
mock this seam directly; they never mock `python`.
|
|
347
|
+
.OUTPUTS
|
|
348
|
+
System.Boolean
|
|
349
|
+
#>
|
|
350
|
+
[CmdletBinding()]
|
|
351
|
+
[OutputType([bool])]
|
|
352
|
+
param()
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
& python -c 'import scripts.dev_tools.validate_orchestration_artifacts' 2>&1 | Out-Null
|
|
356
|
+
return ($LASTEXITCODE -eq 0)
|
|
357
|
+
} catch {
|
|
358
|
+
return $false
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function Test-OrchestratorStatePrCreationReadiness {
|
|
363
|
+
<#
|
|
364
|
+
.SYNOPSIS
|
|
365
|
+
Validate a checkpoint is ready for the first `gh pr create` of a branch.
|
|
366
|
+
.DESCRIPTION
|
|
367
|
+
Public entry point used by the pushed-down enforce-pr-author-skill hook when
|
|
368
|
+
the authoritative Python validator is not importable. Loads the checkpoint
|
|
369
|
+
(fail-closed on missing file / invalid JSON), runs the base-presence check
|
|
370
|
+
(required keys, step-status validity, blocked_reason validity), then runs the
|
|
371
|
+
PR-creation-readiness parity check. Returns a hashtable compatible with the
|
|
372
|
+
hook's existing invoker contract: ExitCode is 1 whenever any error is present,
|
|
373
|
+
and Output carries the newline-joined error text (empty on success).
|
|
374
|
+
.PARAMETER CheckpointPath
|
|
375
|
+
The path to the orchestrator-state checkpoint JSON file.
|
|
376
|
+
.OUTPUTS
|
|
377
|
+
System.Collections.Hashtable with keys ExitCode (int, 0 or 1) and Output (string).
|
|
378
|
+
#>
|
|
379
|
+
[CmdletBinding()]
|
|
380
|
+
[OutputType([hashtable])]
|
|
381
|
+
param(
|
|
382
|
+
[Parameter(Mandatory = $true)]
|
|
383
|
+
[string] $CheckpointPath
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# Fail closed when the checkpoint cannot be loaded: the load error is the whole
|
|
387
|
+
# output and ExitCode is 1.
|
|
388
|
+
$loaded = Get-OrchestratorStateCheckpoint -CheckpointPath $CheckpointPath
|
|
389
|
+
if (-not $loaded.Ok) {
|
|
390
|
+
return @{ ExitCode = 1; Output = $loaded.Error }
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
# Accumulate base-presence errors and readiness errors; any error yields a
|
|
394
|
+
# non-zero ExitCode so the hook blocks PR creation.
|
|
395
|
+
$errors = [System.Collections.Generic.List[string]]::new()
|
|
396
|
+
$errors.AddRange([string[]]@(Get-OrchestratorStateBasePresenceError -State $loaded.State))
|
|
397
|
+
$errors.AddRange([string[]]@(Get-OrchestratorStatePrCreationReadinessError -State $loaded.State))
|
|
398
|
+
|
|
399
|
+
if ($errors.Count -gt 0) {
|
|
400
|
+
return @{ ExitCode = 1; Output = ($errors -join [System.Environment]::NewLine) }
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return @{ ExitCode = 0; Output = '' }
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function Invoke-OrchestratorStatePreflight {
|
|
407
|
+
<#
|
|
408
|
+
.SYNOPSIS
|
|
409
|
+
Runs the orchestrator-state validator against the checkpoint and reports pass/fail.
|
|
410
|
+
.DESCRIPTION
|
|
411
|
+
Shared by the pushed-down enforce-pr-author-skill hook. Mirrors
|
|
412
|
+
Invoke-RoutingContractValidation (.claude/hooks/validate-orchestrator-output.ps1):
|
|
413
|
+
an injectable subprocess scriptblock seam defaults to ``python -m
|
|
414
|
+
scripts.dev_tools.validate_orchestration_artifacts orchestrator-state <CheckpointPath>
|
|
415
|
+
--require-pr-creation-ready``. A missing checkpoint or --require-pr-creation-ready failure
|
|
416
|
+
both surface via the validator's non-zero exit/stderr text; no separate file-existence check
|
|
417
|
+
is made, validating pre-PR-creation readiness (steps 5-8, blocked_reason) not full completion.
|
|
418
|
+
.PARAMETER CheckpointPath
|
|
419
|
+
The path to the orchestrator-state checkpoint JSON file. Callers pass their own
|
|
420
|
+
checkpoint-path variable explicitly; the default below is only used when a caller omits
|
|
421
|
+
the parameter.
|
|
422
|
+
.OUTPUTS
|
|
423
|
+
System.Collections.Hashtable with keys HasErrors (bool) and ErrorText (string).
|
|
424
|
+
#>
|
|
425
|
+
[CmdletBinding()]
|
|
426
|
+
[OutputType([hashtable])]
|
|
427
|
+
param(
|
|
428
|
+
[Parameter(Mandatory = $false)]
|
|
429
|
+
[string] $CheckpointPath = 'artifacts/orchestration/orchestrator-state.json',
|
|
430
|
+
|
|
431
|
+
[Parameter(Mandatory = $false)]
|
|
432
|
+
[scriptblock] $Invoker = {
|
|
433
|
+
param($Path)
|
|
434
|
+
# Capability detection: use the authoritative Python CLI when
|
|
435
|
+
# scripts.dev_tools is importable (drm-copilot); otherwise fall back to
|
|
436
|
+
# the portable PowerShell function that lives alongside this one in the
|
|
437
|
+
# pushed-down pack.
|
|
438
|
+
if (Test-PythonOrchestratorValidatorAvailable) {
|
|
439
|
+
$output = & python -m scripts.dev_tools.validate_orchestration_artifacts `
|
|
440
|
+
orchestrator-state $Path --require-pr-creation-ready 2>&1
|
|
441
|
+
[pscustomobject]@{
|
|
442
|
+
ExitCode = $LASTEXITCODE
|
|
443
|
+
Output = ($output | Out-String)
|
|
444
|
+
}
|
|
445
|
+
} else {
|
|
446
|
+
$portable = Test-OrchestratorStatePrCreationReadiness -CheckpointPath $Path
|
|
447
|
+
[pscustomobject]@{
|
|
448
|
+
ExitCode = $portable.ExitCode
|
|
449
|
+
Output = $portable.Output
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
$result = & $Invoker $CheckpointPath
|
|
456
|
+
# Under this module's Set-StrictMode -Version Latest, member-enumerating .Name directly over
|
|
457
|
+
# a zero-property PSCustomObject throws (a PowerShell strict-mode gotcha not present in the
|
|
458
|
+
# hook's un-strict scope this code was moved from), so the property collection is counted
|
|
459
|
+
# before .Name is ever accessed.
|
|
460
|
+
$resultPropertyNames = @()
|
|
461
|
+
if ($null -ne $result -and @($result.PSObject.Properties).Count -gt 0) {
|
|
462
|
+
$resultPropertyNames = @($result.PSObject.Properties.Name)
|
|
463
|
+
}
|
|
464
|
+
$exitCode = 0
|
|
465
|
+
if ($resultPropertyNames -contains 'ExitCode') { $exitCode = [int]$result.ExitCode }
|
|
466
|
+
$outputText = ''
|
|
467
|
+
if ($resultPropertyNames -contains 'Output') { $outputText = ([string]$result.Output).Trim() }
|
|
468
|
+
|
|
469
|
+
return @{ HasErrors = ($exitCode -ne 0); ErrorText = $outputText }
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
# Export the public readiness entry point plus the reusable load, field-accessor,
|
|
473
|
+
# and base-presence primitives so the sibling OrchestratorStateCompletion module can
|
|
474
|
+
# consume them via Import-Module without duplicating the shared parsing and
|
|
475
|
+
# base-check logic. Test-PythonOrchestratorValidatorAvailable and
|
|
476
|
+
# Invoke-OrchestratorStatePreflight are exported so both pushed-down hooks
|
|
477
|
+
# (enforce-pr-author-skill.ps1, validate-orchestrator-output.ps1) can consume them
|
|
478
|
+
# without duplicating the capability probe or the PR-creation preflight orchestration.
|
|
479
|
+
Export-ModuleMember -Function `
|
|
480
|
+
Test-OrchestratorStatePrCreationReadiness, `
|
|
481
|
+
Get-OrchestratorStateCheckpoint, `
|
|
482
|
+
Get-OrchestratorStateField, `
|
|
483
|
+
Get-OrchestratorStateBasePresenceError, `
|
|
484
|
+
Test-PythonOrchestratorValidatorAvailable, `
|
|
485
|
+
Invoke-OrchestratorStatePreflight
|