@danmoisan/drm-copilot-mcp 0.0.5 → 0.0.6
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 +93 -20
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/orchestrator.md +6 -2
- package/resources/claude-customizations/.claude/agents/pr-author.md +78 -0
- package/resources/claude-customizations/.claude/agents/task-researcher.md +10 -6
- package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +68 -10
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +28 -1
- package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +40 -14
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +210 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +153 -10
- package/resources/claude-customizations/.claude/hooks/validate-pr-author-output.ps1 +136 -0
- package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +22 -6
- package/resources/claude-customizations/.claude/settings.json +22 -0
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +0 -1
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +76 -2
- package/resources/claude-customizations/.claude/skills/research-issue/SKILL.md +5 -3
- package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +69 -0
- package/resources/claude-customizations/.claude-variants/csharp-legacy/rules/csharp.md +96 -0
- package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +77 -0
- package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +64 -0
- package/resources/claude-customizations/pack-manifests/core.json +65 -0
- package/resources/claude-customizations/pack-manifests/csharp-legacy.json +11 -0
- package/resources/claude-customizations/pack-manifests/csharp-modern.json +12 -0
- package/resources/claude-customizations/pack-manifests/powershell.json +14 -0
- package/resources/claude-customizations/pack-manifests/python.json +14 -0
- package/resources/claude-customizations/pack-manifests/typescript.json +9 -0
- package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +30 -13
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +113 -10
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +33 -8
- package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +12 -7
- package/resources/codex-and-agents-customizations/.codex/config.toml +65 -6
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +303 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +300 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +148 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-pr-author-skill.ps1 +336 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +83 -17
- package/resources/config/orchestration-routing.json +2 -10
- package/resources/customizations/.github/agents/pr-author.agent.md +25 -0
- package/resources/customizations/.github/agents/task-researcher.agent.md +4 -4
- package/resources/customizations/.github/prompts/fillout-prd-feature.prompt.md +1 -1
- package/resources/customizations/.github/prompts/research-issue.prompt.md +4 -3
- package/resources/scripts/dev_tools/push_down_claude_customizations.py +253 -224
- package/resources/scripts/dev_tools/push_down_claude_filesystem.py +472 -0
- package/resources/scripts/dev_tools/push_down_claude_pack_selection.py +401 -0
- package/resources/scripts/dev_tools/validate_orchestrator_state.py +106 -29
- package/resources/scripts/dev_tools/validate_policy_audit_artifact.py +24 -0
- package/resources/templates/push_down_claude_customizations.py +128 -304
package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Pre-tool-use hook that blocks Write operations on the orchestrator checkpoint
|
|
4
|
+
file when completed_steps appear out of declared canonical order.
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
Invoked by the Claude Code PreToolUse hook on Write or Edit operations. The
|
|
8
|
+
hook activates only when the target file_path is
|
|
9
|
+
artifacts/orchestration/orchestrator-state.json.
|
|
10
|
+
|
|
11
|
+
For Write tool calls, the content field is parsed as JSON and its
|
|
12
|
+
completed_steps array is validated against the canonical orchestrator
|
|
13
|
+
workflow step prefixes:
|
|
14
|
+
|
|
15
|
+
S0_startup_checks
|
|
16
|
+
S1_change_budget_estimation
|
|
17
|
+
S2_research
|
|
18
|
+
S3_promotion
|
|
19
|
+
S4_atomic_planning
|
|
20
|
+
S5_atomic_execution
|
|
21
|
+
S6_pre_review_commit
|
|
22
|
+
S7_feature_review
|
|
23
|
+
S8_create_pr
|
|
24
|
+
S9_remediation_loop
|
|
25
|
+
S10_post_pr
|
|
26
|
+
S12_complete
|
|
27
|
+
|
|
28
|
+
Entries that do not match any canonical prefix are treated as informational
|
|
29
|
+
and ignored. If any pair (i, j) with i < j has a higher canonical index at
|
|
30
|
+
position i than at position j, the script blocks with a reason listing the
|
|
31
|
+
offending pair. A non-empty rollback_history array suppresses this check
|
|
32
|
+
because rollbacks legitimately reorder steps.
|
|
33
|
+
|
|
34
|
+
Edit tool calls supply only old_string/new_string (a partial patch) and
|
|
35
|
+
cannot be reliably validated without the full target file content, so they
|
|
36
|
+
are allowed by this hook. The next Write call will catch a regression.
|
|
37
|
+
|
|
38
|
+
.NOTES
|
|
39
|
+
Compatible with PowerShell 7+. Read-only validation gate.
|
|
40
|
+
#>
|
|
41
|
+
[CmdletBinding()]
|
|
42
|
+
param()
|
|
43
|
+
|
|
44
|
+
$script:CanonicalStepPrefixes = @(
|
|
45
|
+
'S0_startup_checks',
|
|
46
|
+
'S1_change_budget_estimation',
|
|
47
|
+
'S2_research',
|
|
48
|
+
'S3_promotion',
|
|
49
|
+
'S4_atomic_planning',
|
|
50
|
+
'S5_atomic_execution',
|
|
51
|
+
'S6_pre_review_commit',
|
|
52
|
+
'S7_feature_review',
|
|
53
|
+
'S8_create_pr',
|
|
54
|
+
'S9_remediation_loop',
|
|
55
|
+
'S10_post_pr',
|
|
56
|
+
'S12_complete'
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
function ConvertFrom-CheckpointJson {
|
|
60
|
+
<#
|
|
61
|
+
.SYNOPSIS
|
|
62
|
+
Wrapper around ConvertFrom-Json for the checkpoint content. Mockable.
|
|
63
|
+
#>
|
|
64
|
+
[CmdletBinding()]
|
|
65
|
+
param(
|
|
66
|
+
[Parameter(Mandatory)]
|
|
67
|
+
[string] $Json
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return $Json | ConvertFrom-Json -ErrorAction Stop
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function Get-CanonicalStepIndex {
|
|
74
|
+
<#
|
|
75
|
+
.SYNOPSIS
|
|
76
|
+
Returns the canonical index for a completed_steps entry, or -1 when the
|
|
77
|
+
entry does not match any canonical prefix.
|
|
78
|
+
#>
|
|
79
|
+
[CmdletBinding()]
|
|
80
|
+
[OutputType([int])]
|
|
81
|
+
param(
|
|
82
|
+
[Parameter(Mandatory)]
|
|
83
|
+
[AllowEmptyString()]
|
|
84
|
+
[string] $StepEntry
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
for ($i = 0; $i -lt $script:CanonicalStepPrefixes.Count; $i++) {
|
|
88
|
+
$prefix = $script:CanonicalStepPrefixes[$i]
|
|
89
|
+
if ($StepEntry -eq $prefix -or $StepEntry.StartsWith("$prefix" + '_') -or $StepEntry.StartsWith("$prefix.") -or $StepEntry.StartsWith("$prefix-")) {
|
|
90
|
+
return $i
|
|
91
|
+
}
|
|
92
|
+
# The S3_promotion family of variants (S3_promotion_potential, S3_promotion_issue,
|
|
93
|
+
# S3_promotion_folder, etc.) is matched by the StartsWith branches above.
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return -1
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function Get-OutOfOrderPair {
|
|
100
|
+
<#
|
|
101
|
+
.SYNOPSIS
|
|
102
|
+
Returns the first pair (entry-at-i, entry-at-j, indices) where i<j but the
|
|
103
|
+
canonical index at i is greater than the canonical index at j. Non-canonical
|
|
104
|
+
entries (index -1) are skipped. Returns $null when in order.
|
|
105
|
+
#>
|
|
106
|
+
[CmdletBinding()]
|
|
107
|
+
param(
|
|
108
|
+
[Parameter(Mandatory)]
|
|
109
|
+
[AllowEmptyCollection()]
|
|
110
|
+
[string[]] $CompletedSteps
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
$indexed = @()
|
|
114
|
+
for ($i = 0; $i -lt $CompletedSteps.Count; $i++) {
|
|
115
|
+
$idx = Get-CanonicalStepIndex -StepEntry $CompletedSteps[$i]
|
|
116
|
+
if ($idx -ge 0) {
|
|
117
|
+
$indexed += [pscustomobject]@{ Position = $i; CanonicalIndex = $idx; Entry = $CompletedSteps[$i] }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for ($a = 0; $a -lt $indexed.Count; $a++) {
|
|
122
|
+
for ($b = $a + 1; $b -lt $indexed.Count; $b++) {
|
|
123
|
+
if ($indexed[$a].CanonicalIndex -gt $indexed[$b].CanonicalIndex) {
|
|
124
|
+
return [pscustomobject]@{
|
|
125
|
+
EarlierEntry = $indexed[$a].Entry
|
|
126
|
+
EarlierPos = $indexed[$a].Position
|
|
127
|
+
LaterEntry = $indexed[$b].Entry
|
|
128
|
+
LaterPos = $indexed[$b].Position
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return $null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function Test-StepHasPrefix {
|
|
138
|
+
[CmdletBinding()]
|
|
139
|
+
[OutputType([bool])]
|
|
140
|
+
param(
|
|
141
|
+
[Parameter(Mandatory)]
|
|
142
|
+
[string] $StepEntry,
|
|
143
|
+
|
|
144
|
+
[Parameter(Mandatory)]
|
|
145
|
+
[string] $Prefix
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return $StepEntry -eq $Prefix -or $StepEntry.StartsWith("$Prefix" + '_') -or $StepEntry.StartsWith("$Prefix.") -or $StepEntry.StartsWith("$Prefix-")
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function Get-MissingPrerequisiteForAdvancedStep {
|
|
152
|
+
[CmdletBinding()]
|
|
153
|
+
param(
|
|
154
|
+
[Parameter(Mandatory)]
|
|
155
|
+
[AllowEmptyCollection()]
|
|
156
|
+
[string[]] $CompletedSteps
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
$hasPromotion = $false
|
|
160
|
+
$hasPlanning = $false
|
|
161
|
+
foreach ($step in $CompletedSteps) {
|
|
162
|
+
if (Test-StepHasPrefix -StepEntry $step -Prefix 'S3_promotion') {
|
|
163
|
+
$hasPromotion = $true
|
|
164
|
+
}
|
|
165
|
+
if (Test-StepHasPrefix -StepEntry $step -Prefix 'S4_atomic_planning') {
|
|
166
|
+
$hasPlanning = $true
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
foreach ($step in $CompletedSteps) {
|
|
171
|
+
$index = Get-CanonicalStepIndex -StepEntry $step
|
|
172
|
+
if ($index -ge 5 -and (-not $hasPromotion -or -not $hasPlanning)) {
|
|
173
|
+
return [pscustomobject]@{
|
|
174
|
+
Step = $step
|
|
175
|
+
MissingPromotion = -not $hasPromotion
|
|
176
|
+
MissingPlanning = -not $hasPlanning
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return $null
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function Test-IsCheckpointPath {
|
|
185
|
+
[CmdletBinding()]
|
|
186
|
+
[OutputType([bool])]
|
|
187
|
+
param(
|
|
188
|
+
[Parameter(Mandatory)]
|
|
189
|
+
[string] $NormalizedPath
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
return $NormalizedPath -match '(^|/)artifacts/orchestration/orchestrator-state\.json$'
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function Invoke-CheckpointMonotonicDecision {
|
|
196
|
+
<#
|
|
197
|
+
.SYNOPSIS
|
|
198
|
+
Parses CLAUDE_TOOL_INPUT and returns an allow-or-block decision.
|
|
199
|
+
#>
|
|
200
|
+
[CmdletBinding()]
|
|
201
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
202
|
+
param(
|
|
203
|
+
[string] $ToolInputRaw
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
if (-not $ToolInputRaw) {
|
|
207
|
+
return [ordered]@{ decision = 'allow' }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
$toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
throw "enforce-checkpoint-monotonic hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
$filePath = $toolInput.file_path
|
|
218
|
+
if (-not $filePath) {
|
|
219
|
+
return [ordered]@{ decision = 'allow' }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
$normalized = $filePath -replace '\\', '/'
|
|
223
|
+
if (-not (Test-IsCheckpointPath -NormalizedPath $normalized)) {
|
|
224
|
+
return [ordered]@{ decision = 'allow' }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# Write tool: validate the content payload. Edit tool: partial new_string is
|
|
228
|
+
# not reliable without the full target file content, so allow.
|
|
229
|
+
$content = $toolInput.content
|
|
230
|
+
if (-not $content) {
|
|
231
|
+
return [ordered]@{ decision = 'allow' }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
$payload = ConvertFrom-CheckpointJson -Json $content
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
# The content itself is not valid JSON. Let downstream tools surface the
|
|
239
|
+
# error rather than blocking with a misleading reason here.
|
|
240
|
+
return [ordered]@{ decision = 'allow' }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (-not $payload.PSObject.Properties.Name -contains 'completed_steps') {
|
|
244
|
+
return [ordered]@{ decision = 'allow' }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
$steps = @()
|
|
248
|
+
if ($payload.completed_steps) {
|
|
249
|
+
foreach ($s in $payload.completed_steps) {
|
|
250
|
+
$steps += [string]$s
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
$rollbackHistory = $null
|
|
255
|
+
if ($payload.PSObject.Properties.Name -contains 'rollback_history') {
|
|
256
|
+
$rollbackHistory = $payload.rollback_history
|
|
257
|
+
}
|
|
258
|
+
if ($rollbackHistory -and @($rollbackHistory).Count -gt 0) {
|
|
259
|
+
return [ordered]@{ decision = 'allow' }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
$pair = if ($steps.Count -ge 2) { Get-OutOfOrderPair -CompletedSteps $steps } else { $null }
|
|
263
|
+
if ($null -ne $pair) {
|
|
264
|
+
return [ordered]@{
|
|
265
|
+
decision = 'block'
|
|
266
|
+
reason = "CHECKPOINT_ORDER_BLOCKED: completed_steps lists '$($pair.EarlierEntry)' at position $($pair.EarlierPos) before '$($pair.LaterEntry)' at position $($pair.LaterPos), but the canonical orchestrator workflow requires the later step to follow the earlier one. Reorder completed_steps or, if a rollback occurred, record it in rollback_history."
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
$missingPrerequisite = Get-MissingPrerequisiteForAdvancedStep -CompletedSteps $steps
|
|
271
|
+
if ($null -ne $missingPrerequisite) {
|
|
272
|
+
$missing = @()
|
|
273
|
+
if ($missingPrerequisite.MissingPromotion) {
|
|
274
|
+
$missing += 'S3_promotion'
|
|
275
|
+
}
|
|
276
|
+
if ($missingPrerequisite.MissingPlanning) {
|
|
277
|
+
$missing += 'S4_atomic_planning'
|
|
278
|
+
}
|
|
279
|
+
return [ordered]@{
|
|
280
|
+
decision = 'block'
|
|
281
|
+
reason = "CHECKPOINT_ORDER_BLOCKED: completed_steps lists '$($missingPrerequisite.Step)' before required prerequisite step(s): $($missing -join ', '). Record promotion and planning completion before implementation, review, PR, CI, or DONE steps."
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return [ordered]@{ decision = 'allow' }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
# Guard allows dot-sourcing in tests without executing the entrypoint.
|
|
289
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
$decision = Invoke-CheckpointMonotonicDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
Write-Error $_
|
|
298
|
+
exit 1
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
$decision | ConvertTo-Json -Compress | Write-Output
|
|
302
|
+
|
|
303
|
+
exit 0
|
package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Pre-tool-use hook that blocks Write operations on the orchestrator checkpoint
|
|
4
|
+
file when the checkpoint asserts completion without verifiable completion
|
|
5
|
+
evidence.
|
|
6
|
+
|
|
7
|
+
.DESCRIPTION
|
|
8
|
+
Invoked by the Claude Code PreToolUse hook on Write or Edit operations. The
|
|
9
|
+
hook activates only when the target file_path normalizes to
|
|
10
|
+
artifacts/orchestration/orchestrator-state.json.
|
|
11
|
+
|
|
12
|
+
A written checkpoint asserts completion when any of the following hold:
|
|
13
|
+
|
|
14
|
+
next_step == "complete"
|
|
15
|
+
completed_steps contains "S12_complete"
|
|
16
|
+
step8_status, step9_status, or step10_status == "completed"
|
|
17
|
+
|
|
18
|
+
When completion is asserted, the write is blocked unless ALL of the following
|
|
19
|
+
completion evidence is present:
|
|
20
|
+
|
|
21
|
+
- a non-empty issue-num (top-level, falling back to variables.issue-num);
|
|
22
|
+
- a non-empty feature-folder (top-level, falling back to
|
|
23
|
+
variables.feature-folder);
|
|
24
|
+
- a ci_gate object with conclusion == "success" and a non-empty head_sha.
|
|
25
|
+
|
|
26
|
+
The block reason names the specific missing evidence so the caller can
|
|
27
|
+
remediate. When completion is not asserted, the write is allowed
|
|
28
|
+
(backward compatibility).
|
|
29
|
+
|
|
30
|
+
Edit tool calls supply only old_string/new_string (a partial patch) and
|
|
31
|
+
cannot be reliably validated without the full target file content, so they
|
|
32
|
+
are allowed by this hook, matching enforce-checkpoint-monotonic.ps1. For
|
|
33
|
+
Write calls whose content is not valid JSON, the hook allows the operation
|
|
34
|
+
and defers to downstream tools to surface the error.
|
|
35
|
+
|
|
36
|
+
.NOTES
|
|
37
|
+
Compatible with PowerShell 7+. Read-only validation gate.
|
|
38
|
+
#>
|
|
39
|
+
[CmdletBinding()]
|
|
40
|
+
param()
|
|
41
|
+
|
|
42
|
+
function ConvertFrom-CheckpointJson {
|
|
43
|
+
<#
|
|
44
|
+
.SYNOPSIS
|
|
45
|
+
Wrapper around ConvertFrom-Json for the checkpoint content. Mockable.
|
|
46
|
+
#>
|
|
47
|
+
[CmdletBinding()]
|
|
48
|
+
param(
|
|
49
|
+
[Parameter(Mandatory)]
|
|
50
|
+
[string] $Json
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return $Json | ConvertFrom-Json -ErrorAction Stop
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function Test-IsCheckpointPath {
|
|
57
|
+
[CmdletBinding()]
|
|
58
|
+
[OutputType([bool])]
|
|
59
|
+
param(
|
|
60
|
+
[Parameter(Mandatory)]
|
|
61
|
+
[string] $NormalizedPath
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return $NormalizedPath -match '(^|/)artifacts/orchestration/orchestrator-state\.json$'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function Get-CheckpointStringValue {
|
|
68
|
+
<#
|
|
69
|
+
.SYNOPSIS
|
|
70
|
+
Returns a trimmed string for a payload property, or an empty string when
|
|
71
|
+
the property is absent or its value is not a non-empty string.
|
|
72
|
+
#>
|
|
73
|
+
[CmdletBinding()]
|
|
74
|
+
[OutputType([string])]
|
|
75
|
+
param(
|
|
76
|
+
[Parameter(Mandatory)]
|
|
77
|
+
[AllowNull()]
|
|
78
|
+
$Payload,
|
|
79
|
+
|
|
80
|
+
[Parameter(Mandatory)]
|
|
81
|
+
[string] $Name
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if ($null -eq $Payload) {
|
|
85
|
+
return ''
|
|
86
|
+
}
|
|
87
|
+
if (-not ($Payload.PSObject.Properties.Name -contains $Name)) {
|
|
88
|
+
return ''
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
$value = $Payload.$Name
|
|
92
|
+
if ($null -eq $value) {
|
|
93
|
+
return ''
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return ([string]$value).Trim()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function Test-CompletionAsserted {
|
|
100
|
+
<#
|
|
101
|
+
.SYNOPSIS
|
|
102
|
+
Returns $true when the checkpoint payload asserts completion via
|
|
103
|
+
next_step, completed_steps, or step8/9/10 status fields.
|
|
104
|
+
#>
|
|
105
|
+
[CmdletBinding()]
|
|
106
|
+
[OutputType([bool])]
|
|
107
|
+
param(
|
|
108
|
+
[Parameter(Mandatory)]
|
|
109
|
+
[AllowNull()]
|
|
110
|
+
$Payload
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if ($null -eq $Payload) {
|
|
114
|
+
return $false
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
$nextStep = Get-CheckpointStringValue -Payload $Payload -Name 'next_step'
|
|
118
|
+
if ($nextStep -eq 'complete') {
|
|
119
|
+
return $true
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if ($Payload.PSObject.Properties.Name -contains 'completed_steps' -and $Payload.completed_steps) {
|
|
123
|
+
foreach ($step in $Payload.completed_steps) {
|
|
124
|
+
if (([string]$step) -eq 'S12_complete') {
|
|
125
|
+
return $true
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
foreach ($statusField in @('step8_status', 'step9_status', 'step10_status')) {
|
|
131
|
+
$status = Get-CheckpointStringValue -Payload $Payload -Name $statusField
|
|
132
|
+
if ($status -eq 'completed') {
|
|
133
|
+
return $true
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return $false
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function Get-MissingCompletionEvidence {
|
|
141
|
+
<#
|
|
142
|
+
.SYNOPSIS
|
|
143
|
+
Returns the list of missing completion-evidence descriptions for a
|
|
144
|
+
completion-asserting checkpoint. An empty list means all evidence is
|
|
145
|
+
present.
|
|
146
|
+
#>
|
|
147
|
+
[CmdletBinding()]
|
|
148
|
+
[OutputType([string[]])]
|
|
149
|
+
param(
|
|
150
|
+
[Parameter(Mandatory)]
|
|
151
|
+
[AllowNull()]
|
|
152
|
+
$Payload
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
$missing = @()
|
|
156
|
+
|
|
157
|
+
$issueNum = Get-CheckpointStringValue -Payload $Payload -Name 'issue-num'
|
|
158
|
+
if (-not $issueNum -and $null -ne $Payload -and ($Payload.PSObject.Properties.Name -contains 'variables')) {
|
|
159
|
+
$issueNum = Get-CheckpointStringValue -Payload $Payload.variables -Name 'issue-num'
|
|
160
|
+
}
|
|
161
|
+
if (-not $issueNum) {
|
|
162
|
+
$missing += 'issue-num'
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
$featureFolder = Get-CheckpointStringValue -Payload $Payload -Name 'feature-folder'
|
|
166
|
+
if (-not $featureFolder -and $null -ne $Payload -and ($Payload.PSObject.Properties.Name -contains 'variables')) {
|
|
167
|
+
$featureFolder = Get-CheckpointStringValue -Payload $Payload.variables -Name 'feature-folder'
|
|
168
|
+
}
|
|
169
|
+
if (-not $featureFolder) {
|
|
170
|
+
$missing += 'feature-folder'
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
$ciGate = $null
|
|
174
|
+
if ($null -ne $Payload -and ($Payload.PSObject.Properties.Name -contains 'ci_gate')) {
|
|
175
|
+
$ciGate = $Payload.ci_gate
|
|
176
|
+
}
|
|
177
|
+
if ($null -eq $ciGate -or $ciGate -isnot [System.Management.Automation.PSCustomObject]) {
|
|
178
|
+
$missing += 'ci_gate (object with conclusion == "success" and non-empty head_sha)'
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
$conclusion = Get-CheckpointStringValue -Payload $ciGate -Name 'conclusion'
|
|
182
|
+
if ($conclusion -ne 'success') {
|
|
183
|
+
$missing += 'ci_gate.conclusion == "success"'
|
|
184
|
+
}
|
|
185
|
+
$headSha = Get-CheckpointStringValue -Payload $ciGate -Name 'head_sha'
|
|
186
|
+
if (-not $headSha) {
|
|
187
|
+
$missing += 'ci_gate.head_sha'
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if ($issueNum -eq '232') {
|
|
192
|
+
$prGate = $null
|
|
193
|
+
if ($null -ne $Payload -and ($Payload.PSObject.Properties.Name -contains 'pr_gate')) {
|
|
194
|
+
$prGate = $Payload.pr_gate
|
|
195
|
+
}
|
|
196
|
+
if ($null -eq $prGate -or $prGate -isnot [System.Management.Automation.PSCustomObject]) {
|
|
197
|
+
$missing += 'pr_gate (object with pr_number, pr_url, head_branch, and head_sha)'
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
foreach ($field in @('pr_number', 'pr_url', 'head_branch', 'head_sha')) {
|
|
201
|
+
if (-not (Get-CheckpointStringValue -Payload $prGate -Name $field)) {
|
|
202
|
+
$missing += "pr_gate.$field"
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
$prHeadSha = Get-CheckpointStringValue -Payload $prGate -Name 'head_sha'
|
|
206
|
+
$ciHeadSha = Get-CheckpointStringValue -Payload $ciGate -Name 'head_sha'
|
|
207
|
+
if ($prHeadSha -and $ciHeadSha -and $prHeadSha -ne $ciHeadSha) {
|
|
208
|
+
$missing += 'ci_gate.head_sha matching pr_gate.head_sha'
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return [string[]]$missing
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function Invoke-CompletionConsistencyDecision {
|
|
217
|
+
<#
|
|
218
|
+
.SYNOPSIS
|
|
219
|
+
Parses CLAUDE_TOOL_INPUT and returns an allow-or-block decision based on
|
|
220
|
+
completion-evidence consistency.
|
|
221
|
+
#>
|
|
222
|
+
[CmdletBinding()]
|
|
223
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
224
|
+
param(
|
|
225
|
+
[string] $ToolInputRaw
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if (-not $ToolInputRaw) {
|
|
229
|
+
return [ordered]@{ decision = 'allow' }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
$toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
throw "enforce-completion-consistency hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
$filePath = $toolInput.file_path
|
|
240
|
+
if (-not $filePath) {
|
|
241
|
+
return [ordered]@{ decision = 'allow' }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
$normalized = $filePath -replace '\\', '/'
|
|
245
|
+
if (-not (Test-IsCheckpointPath -NormalizedPath $normalized)) {
|
|
246
|
+
return [ordered]@{ decision = 'allow' }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
# Write tool: validate the content payload. Edit tool: partial new_string is
|
|
250
|
+
# not reliable without the full target file content, so allow.
|
|
251
|
+
$content = $toolInput.content
|
|
252
|
+
if (-not $content) {
|
|
253
|
+
return [ordered]@{ decision = 'allow' }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
$payload = ConvertFrom-CheckpointJson -Json $content
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
# The content itself is not valid JSON. Let downstream tools surface the
|
|
261
|
+
# error rather than blocking with a misleading reason here.
|
|
262
|
+
return [ordered]@{ decision = 'allow' }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (-not (Test-CompletionAsserted -Payload $payload)) {
|
|
266
|
+
return [ordered]@{ decision = 'allow' }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
$missing = Get-MissingCompletionEvidence -Payload $payload
|
|
270
|
+
if ($missing.Count -eq 0) {
|
|
271
|
+
return [ordered]@{ decision = 'allow' }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
$issueContext = ''
|
|
275
|
+
if ((Get-CheckpointStringValue -Payload $payload -Name 'issue-num') -eq '232') {
|
|
276
|
+
$issueContext = ' Issue #232 requires pr_gate evidence and current-head ci_gate evidence.'
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return [ordered]@{
|
|
280
|
+
decision = 'block'
|
|
281
|
+
reason = "COMPLETION_CONSISTENCY_BLOCKED: the checkpoint asserts completion but is missing required completion evidence: $($missing -join ', ').$issueContext A completion-asserting checkpoint must include a non-empty issue-num, a non-empty feature-folder, and a ci_gate object with conclusion == 'success' and a non-empty head_sha. Supply the missing evidence or remove the completion assertion."
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
# Guard allows dot-sourcing in tests without executing the entrypoint.
|
|
286
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
$decision = Invoke-CompletionConsistencyDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
Write-Error $_
|
|
295
|
+
exit 1
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
$decision | ConvertTo-Json -Compress | Write-Output
|
|
299
|
+
|
|
300
|
+
exit 0
|