@danmoisan/drm-copilot-mcp 1.0.1 → 1.0.8
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 +896 -202
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/atomic-executor.md +1 -0
- package/resources/claude-customizations/.claude/agents/atomic-planner.md +1 -0
- package/resources/claude-customizations/.claude/agents/commit-message.md +42 -0
- package/resources/claude-customizations/.claude/agents/csharp-typed-engineer.md +1 -0
- package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +128 -0
- package/resources/claude-customizations/.claude/agents/epic-review.md +1 -0
- package/resources/claude-customizations/.claude/agents/feature-review.md +1 -0
- package/resources/claude-customizations/.claude/agents/human-exception-runbook.md +48 -0
- package/resources/claude-customizations/.claude/agents/orchestrator.md +80 -2
- package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +1 -0
- package/resources/claude-customizations/.claude/agents/pr-author.md +37 -21
- package/resources/claude-customizations/.claude/agents/prd-feature.md +1 -0
- package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +1 -0
- package/resources/claude-customizations/.claude/agents/staged-review.md +1 -0
- package/resources/claude-customizations/.claude/agents/status-updater.md +1 -0
- package/resources/claude-customizations/.claude/agents/typescript-engineer.md +1 -0
- package/resources/claude-customizations/.claude/hooks/check-powershell-test-purity.ps1 +103 -65
- package/resources/claude-customizations/.claude/hooks/check-python-test-purity.ps1 +12 -9
- package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +24 -16
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +19 -13
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +304 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +304 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +237 -0
- package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +16 -12
- package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +14 -10
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +182 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +35 -8
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +17 -14
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +104 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +268 -93
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +19 -11
- package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +28 -6
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +17 -14
- package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +146 -36
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +27 -7
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +53 -0
- package/resources/claude-customizations/.claude/settings.json +38 -1
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +252 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +59 -13
- package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +28 -0
- package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +22 -18
- package/resources/claude-customizations/pack-manifests/core.json +9 -0
- package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +8 -2
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +24 -3
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +13 -0
- package/resources/codex-and-agents-customizations/.agents/skills/remediation-handoff-atomic-planner/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp/SKILL.md +65 -0
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +79 -0
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +66 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +15 -14
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -25
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +144 -28
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-helpers.ps1 +163 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-pr-author-skill.ps1 +257 -93
- package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +98 -0
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +24 -0
- package/resources/codex-and-agents-customizations/pack-manifests/csharp-legacy.json +11 -0
- package/resources/codex-and-agents-customizations/pack-manifests/csharp-modern.json +10 -0
- package/resources/codex-and-agents-customizations/pack-manifests/powershell.json +15 -0
- package/resources/codex-and-agents-customizations/pack-manifests/python.json +15 -0
- package/resources/codex-and-agents-customizations/pack-manifests/typescript.json +9 -0
- package/resources/config/orchestration-routing.json +92 -0
- package/resources/customizations/.github/agents/pr-author.agent.md +24 -18
- package/resources/icon.png +0 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +11 -0
- package/resources/codex-and-agents-customizations/.github/workflows/_validate-orchestrator-state.yml +0 -68
- package/resources/codex-and-agents-customizations/.github/workflows/validate-orchestrator-state.yml +0 -15
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
- subprocess execution (Start-Process with raw executables)
|
|
21
21
|
- time-based flakiness (Start-Sleep)
|
|
22
22
|
|
|
23
|
-
If the content contains any forbidden pattern, the script writes a JSON
|
|
24
|
-
to stdout with
|
|
25
|
-
the reason.
|
|
23
|
+
If the content contains any forbidden pattern, the script writes a PreToolUse JSON
|
|
24
|
+
response to stdout with hookSpecificOutput.permissionDecision = 'deny' and exits with
|
|
25
|
+
code 0 to let Claude Code surface the reason.
|
|
26
26
|
|
|
27
27
|
.NOTES
|
|
28
28
|
Compatible with PowerShell 7+.
|
|
@@ -32,80 +32,118 @@
|
|
|
32
32
|
[CmdletBinding()]
|
|
33
33
|
param()
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
function Get-PowerShellTestPurityBlockDecision {
|
|
36
|
+
[CmdletBinding()]
|
|
37
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
38
|
+
param(
|
|
39
|
+
[Parameter(Mandatory)]
|
|
40
|
+
[string] $Reason
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
[ordered]@{
|
|
44
|
+
hookSpecificOutput = [ordered]@{
|
|
45
|
+
hookEventName = 'PreToolUse'
|
|
46
|
+
permissionDecision = 'deny'
|
|
47
|
+
permissionDecisionReason = $Reason
|
|
48
|
+
}
|
|
49
|
+
}
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
function Test-PowerShellTestFilePath {
|
|
53
|
+
[CmdletBinding()]
|
|
54
|
+
[OutputType([bool])]
|
|
55
|
+
param(
|
|
56
|
+
[Parameter(Mandatory)]
|
|
57
|
+
[string] $FilePath
|
|
58
|
+
)
|
|
46
59
|
|
|
47
|
-
$
|
|
48
|
-
|
|
49
|
-
exit 0
|
|
60
|
+
$normalized = $FilePath -replace '\\', '/'
|
|
61
|
+
return (($normalized -match '(^|/)tests/.*\.ps1$') -or ($normalized -match '\.Tests\.ps1$'))
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
function Invoke-PowerShellTestPurityDecision {
|
|
65
|
+
[CmdletBinding()]
|
|
66
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
67
|
+
param(
|
|
68
|
+
[string] $ToolInputRaw
|
|
69
|
+
)
|
|
57
70
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
elseif ($null -ne $toolInput.new_string) {
|
|
63
|
-
$content = [string]$toolInput.new_string
|
|
64
|
-
}
|
|
71
|
+
if (-not $ToolInputRaw) {
|
|
72
|
+
return $null
|
|
73
|
+
}
|
|
65
74
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
75
|
+
try {
|
|
76
|
+
$toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return $null
|
|
80
|
+
}
|
|
69
81
|
|
|
70
|
-
$
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
@{ Pattern = '\[System\.IO\.Path\]::GetTempFileName'; Reason = 'temporary files forbidden in Pester unit tests' },
|
|
78
|
-
@{ Pattern = '\[System\.IO\.Path\]::GetTempPath'; Reason = 'temp path usage forbidden in Pester unit tests' },
|
|
79
|
-
@{ Pattern = '\$env:TEMP\b'; Reason = '$env:TEMP usage forbidden in Pester unit tests' },
|
|
80
|
-
@{ Pattern = '\$env:TMP\b'; Reason = '$env:TMP usage forbidden in Pester unit tests' },
|
|
81
|
-
@{ Pattern = '\bInvoke-WebRequest\b'; Reason = 'network access (Invoke-WebRequest) forbidden in Pester unit tests' },
|
|
82
|
-
@{ Pattern = '\bInvoke-RestMethod\b'; Reason = 'network access (Invoke-RestMethod) forbidden in Pester unit tests' },
|
|
83
|
-
@{ Pattern = '\[System\.Net\.Http\.'; Reason = 'System.Net.Http usage forbidden in Pester unit tests' },
|
|
84
|
-
@{ Pattern = '\[System\.Net\.WebRequest\]'; Reason = 'System.Net.WebRequest usage forbidden in Pester unit tests' },
|
|
85
|
-
@{ Pattern = '\[System\.Net\.Sockets\.'; Reason = 'raw socket access forbidden in Pester unit tests' },
|
|
86
|
-
@{ Pattern = '\bStart-Process\b'; Reason = 'Start-Process forbidden in Pester unit tests; mock the wrapper seam instead' },
|
|
87
|
-
@{ Pattern = '\bStart-Sleep\b'; Reason = 'Start-Sleep forbidden in Pester unit tests; avoid timing hacks' }
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
$violations = @()
|
|
91
|
-
foreach ($entry in $forbiddenPatterns) {
|
|
92
|
-
if ($content -match $entry.Pattern) {
|
|
93
|
-
$violations += $entry.Reason
|
|
82
|
+
$filePath = $toolInput.file_path
|
|
83
|
+
if (-not $filePath) {
|
|
84
|
+
return $null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (-not (Test-PowerShellTestFilePath -FilePath $filePath)) {
|
|
88
|
+
return $null
|
|
94
89
|
}
|
|
95
|
-
}
|
|
96
90
|
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
$content = $null
|
|
92
|
+
if ($null -ne $toolInput.content) {
|
|
93
|
+
$content = [string]$toolInput.content
|
|
94
|
+
}
|
|
95
|
+
elseif ($null -ne $toolInput.new_string) {
|
|
96
|
+
$content = [string]$toolInput.new_string
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (-not $content) {
|
|
100
|
+
return $null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
$forbiddenPatterns = @(
|
|
104
|
+
@{ Pattern = '(?m)^\s*Mock\s+git\b'; Reason = 'direct Mock git forbidden in Pester tests; mock the Invoke-GitExe wrapper instead' },
|
|
105
|
+
@{ Pattern = '(?m)^\s*Mock\s+gh\b'; Reason = 'direct Mock gh forbidden in Pester tests; mock the Invoke-GhExe wrapper instead' },
|
|
106
|
+
@{ Pattern = '(?m)^\s*Mock\s+actionlint\b'; Reason = 'direct Mock actionlint forbidden in Pester tests; mock the Invoke-ActionlintExe wrapper instead' },
|
|
107
|
+
@{ Pattern = "(?m)^\s*Mock\s+['""]git['""]"; Reason = 'direct Mock ''git'' forbidden in Pester tests; mock the Invoke-GitExe wrapper instead' },
|
|
108
|
+
@{ Pattern = "(?m)^\s*Mock\s+['""]gh['""]"; Reason = 'direct Mock ''gh'' forbidden in Pester tests; mock the Invoke-GhExe wrapper instead' },
|
|
109
|
+
@{ Pattern = '\bNew-TemporaryFile\b'; Reason = 'New-TemporaryFile forbidden in Pester unit tests' },
|
|
110
|
+
@{ Pattern = '\[System\.IO\.Path\]::GetTempFileName'; Reason = 'temporary files forbidden in Pester unit tests' },
|
|
111
|
+
@{ Pattern = '\[System\.IO\.Path\]::GetTempPath'; Reason = 'temp path usage forbidden in Pester unit tests' },
|
|
112
|
+
@{ Pattern = '\$env:TEMP\b'; Reason = '$env:TEMP usage forbidden in Pester unit tests' },
|
|
113
|
+
@{ Pattern = '\$env:TMP\b'; Reason = '$env:TMP usage forbidden in Pester unit tests' },
|
|
114
|
+
@{ Pattern = '\bInvoke-WebRequest\b'; Reason = 'network access (Invoke-WebRequest) forbidden in Pester unit tests' },
|
|
115
|
+
@{ Pattern = '\bInvoke-RestMethod\b'; Reason = 'network access (Invoke-RestMethod) forbidden in Pester unit tests' },
|
|
116
|
+
@{ Pattern = '\[System\.Net\.Http\.'; Reason = 'System.Net.Http usage forbidden in Pester unit tests' },
|
|
117
|
+
@{ Pattern = '\[System\.Net\.WebRequest\]'; Reason = 'System.Net.WebRequest usage forbidden in Pester unit tests' },
|
|
118
|
+
@{ Pattern = '\[System\.Net\.Sockets\.'; Reason = 'raw socket access forbidden in Pester unit tests' },
|
|
119
|
+
@{ Pattern = '\bStart-Process\b'; Reason = 'Start-Process forbidden in Pester unit tests; mock the wrapper seam instead' },
|
|
120
|
+
@{ Pattern = '\bStart-Sleep\b'; Reason = 'Start-Sleep forbidden in Pester unit tests; avoid timing hacks' }
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
$violations = @()
|
|
124
|
+
foreach ($entry in $forbiddenPatterns) {
|
|
125
|
+
if ($content -match $entry.Pattern) {
|
|
126
|
+
$violations += $entry.Reason
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if ($violations.Count -eq 0) {
|
|
131
|
+
return $null
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
$uniqueViolations = $violations | Select-Object -Unique
|
|
135
|
+
$reason = "PowerShell unit test purity violations in '$filePath': " + ($uniqueViolations -join '; ') + ". Replace with wrapper-seam mocks, in-memory fakes, or pure code paths per .claude/rules/powershell.md."
|
|
136
|
+
|
|
137
|
+
return Get-PowerShellTestPurityBlockDecision -Reason $reason
|
|
99
138
|
}
|
|
100
139
|
|
|
101
|
-
|
|
102
|
-
|
|
140
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
103
143
|
|
|
104
|
-
$
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
144
|
+
$decision = Invoke-PowerShellTestPurityDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
|
|
145
|
+
if ($null -ne $decision -and $decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
146
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
147
|
+
}
|
|
108
148
|
|
|
109
|
-
Write-Output $response
|
|
110
149
|
exit 0
|
|
111
|
-
|
|
@@ -38,8 +38,11 @@ function Get-PythonTestPurityBlockDecision {
|
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
[ordered]@{
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
hookSpecificOutput = [ordered]@{
|
|
42
|
+
hookEventName = 'PreToolUse'
|
|
43
|
+
permissionDecision = 'deny'
|
|
44
|
+
permissionDecisionReason = $Reason
|
|
45
|
+
}
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -63,7 +66,7 @@ function Invoke-PythonTestPurityDecision {
|
|
|
63
66
|
)
|
|
64
67
|
|
|
65
68
|
if (-not $ToolInputRaw) {
|
|
66
|
-
return
|
|
69
|
+
return $null
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
try {
|
|
@@ -74,11 +77,11 @@ function Invoke-PythonTestPurityDecision {
|
|
|
74
77
|
|
|
75
78
|
$filePath = $toolInput.file_path
|
|
76
79
|
if (-not $filePath) {
|
|
77
|
-
return
|
|
80
|
+
return $null
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
if (-not (Test-PythonTestFilePath -FilePath $filePath)) {
|
|
81
|
-
return
|
|
84
|
+
return $null
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
$content = $null
|
|
@@ -89,7 +92,7 @@ function Invoke-PythonTestPurityDecision {
|
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
if (-not $content) {
|
|
92
|
-
return
|
|
95
|
+
return $null
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
$forbiddenPatterns = @(
|
|
@@ -125,7 +128,7 @@ function Invoke-PythonTestPurityDecision {
|
|
|
125
128
|
}
|
|
126
129
|
|
|
127
130
|
if ($violations.Count -eq 0) {
|
|
128
|
-
return
|
|
131
|
+
return $null
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
$uniqueViolations = $violations | Select-Object -Unique
|
|
@@ -139,8 +142,8 @@ if ($MyInvocation.InvocationName -eq '.') {
|
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
$decision = Invoke-PythonTestPurityDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
|
|
142
|
-
if ($decision.
|
|
143
|
-
$decision | ConvertTo-Json -Compress | Write-Output
|
|
145
|
+
if ($null -ne $decision -and $decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
146
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
144
147
|
}
|
|
145
148
|
|
|
146
149
|
exit 0
|
|
@@ -27,9 +27,11 @@
|
|
|
27
27
|
|
|
28
28
|
Entries that do not match any canonical prefix are treated as informational
|
|
29
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
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
position i than at position j, the script denies via a PreToolUse JSON
|
|
31
|
+
response with hookSpecificOutput.permissionDecision='deny' and a reason
|
|
32
|
+
listing the offending pair. A non-empty rollback_history array suppresses
|
|
33
|
+
this check because rollbacks legitimately reorder steps. All allow paths
|
|
34
|
+
emit hookSpecificOutput.permissionDecision='allow'.
|
|
33
35
|
|
|
34
36
|
Edit tool calls supply only old_string/new_string (a partial patch) and
|
|
35
37
|
cannot be reliably validated without the full target file content, so they
|
|
@@ -204,7 +206,7 @@ function Invoke-CheckpointMonotonicDecision {
|
|
|
204
206
|
)
|
|
205
207
|
|
|
206
208
|
if (-not $ToolInputRaw) {
|
|
207
|
-
return [ordered]@{
|
|
209
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
208
210
|
}
|
|
209
211
|
|
|
210
212
|
try {
|
|
@@ -216,19 +218,19 @@ function Invoke-CheckpointMonotonicDecision {
|
|
|
216
218
|
|
|
217
219
|
$filePath = $toolInput.file_path
|
|
218
220
|
if (-not $filePath) {
|
|
219
|
-
return [ordered]@{
|
|
221
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
220
222
|
}
|
|
221
223
|
|
|
222
224
|
$normalized = $filePath -replace '\\', '/'
|
|
223
225
|
if (-not (Test-IsCheckpointPath -NormalizedPath $normalized)) {
|
|
224
|
-
return [ordered]@{
|
|
226
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
225
227
|
}
|
|
226
228
|
|
|
227
229
|
# Write tool: validate the content payload. Edit tool: partial new_string is
|
|
228
230
|
# not reliable without the full target file content, so allow.
|
|
229
231
|
$content = $toolInput.content
|
|
230
232
|
if (-not $content) {
|
|
231
|
-
return [ordered]@{
|
|
233
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
232
234
|
}
|
|
233
235
|
|
|
234
236
|
try {
|
|
@@ -237,11 +239,11 @@ function Invoke-CheckpointMonotonicDecision {
|
|
|
237
239
|
catch {
|
|
238
240
|
# The content itself is not valid JSON. Let downstream tools surface the
|
|
239
241
|
# error rather than blocking with a misleading reason here.
|
|
240
|
-
return [ordered]@{
|
|
242
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
241
243
|
}
|
|
242
244
|
|
|
243
245
|
if (-not $payload.PSObject.Properties.Name -contains 'completed_steps') {
|
|
244
|
-
return [ordered]@{
|
|
246
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
245
247
|
}
|
|
246
248
|
|
|
247
249
|
$steps = @()
|
|
@@ -256,14 +258,17 @@ function Invoke-CheckpointMonotonicDecision {
|
|
|
256
258
|
$rollbackHistory = $payload.rollback_history
|
|
257
259
|
}
|
|
258
260
|
if ($rollbackHistory -and @($rollbackHistory).Count -gt 0) {
|
|
259
|
-
return [ordered]@{
|
|
261
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
260
262
|
}
|
|
261
263
|
|
|
262
264
|
$pair = if ($steps.Count -ge 2) { Get-OutOfOrderPair -CompletedSteps $steps } else { $null }
|
|
263
265
|
if ($null -ne $pair) {
|
|
264
266
|
return [ordered]@{
|
|
265
|
-
|
|
266
|
-
|
|
267
|
+
hookSpecificOutput = [ordered]@{
|
|
268
|
+
hookEventName = 'PreToolUse'
|
|
269
|
+
permissionDecision = 'deny'
|
|
270
|
+
permissionDecisionReason = "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."
|
|
271
|
+
}
|
|
267
272
|
}
|
|
268
273
|
}
|
|
269
274
|
|
|
@@ -277,12 +282,15 @@ function Invoke-CheckpointMonotonicDecision {
|
|
|
277
282
|
$missing += 'S4_atomic_planning'
|
|
278
283
|
}
|
|
279
284
|
return [ordered]@{
|
|
280
|
-
|
|
281
|
-
|
|
285
|
+
hookSpecificOutput = [ordered]@{
|
|
286
|
+
hookEventName = 'PreToolUse'
|
|
287
|
+
permissionDecision = 'deny'
|
|
288
|
+
permissionDecisionReason = "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."
|
|
289
|
+
}
|
|
282
290
|
}
|
|
283
291
|
}
|
|
284
292
|
|
|
285
|
-
return [ordered]@{
|
|
293
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
286
294
|
}
|
|
287
295
|
|
|
288
296
|
# Guard allows dot-sourcing in tests without executing the entrypoint.
|
|
@@ -298,6 +306,6 @@ catch {
|
|
|
298
306
|
exit 1
|
|
299
307
|
}
|
|
300
308
|
|
|
301
|
-
$decision | ConvertTo-Json -Compress | Write-Output
|
|
309
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
302
310
|
|
|
303
311
|
exit 0
|
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
variables.feature-folder);
|
|
24
24
|
- a ci_gate object with conclusion == "success" and a non-empty head_sha.
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
When completion evidence is missing the hook emits a PreToolUse JSON
|
|
27
|
+
response with hookSpecificOutput.permissionDecision='deny' and a reason that
|
|
28
|
+
names the specific missing evidence so the caller can remediate. When
|
|
29
|
+
completion is not asserted, the write is allowed via
|
|
30
|
+
hookSpecificOutput.permissionDecision='allow' (backward compatibility).
|
|
29
31
|
|
|
30
32
|
Edit tool calls supply only old_string/new_string (a partial patch) and
|
|
31
33
|
cannot be reliably validated without the full target file content, so they
|
|
@@ -331,7 +333,7 @@ function Invoke-CompletionConsistencyDecision {
|
|
|
331
333
|
)
|
|
332
334
|
|
|
333
335
|
if (-not $ToolInputRaw) {
|
|
334
|
-
return [ordered]@{
|
|
336
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
335
337
|
}
|
|
336
338
|
|
|
337
339
|
try {
|
|
@@ -343,12 +345,12 @@ function Invoke-CompletionConsistencyDecision {
|
|
|
343
345
|
|
|
344
346
|
$filePath = $toolInput.file_path
|
|
345
347
|
if (-not $filePath) {
|
|
346
|
-
return [ordered]@{
|
|
348
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
347
349
|
}
|
|
348
350
|
|
|
349
351
|
$normalized = $filePath -replace '\\', '/'
|
|
350
352
|
if (-not (Test-IsCheckpointPath -NormalizedPath $normalized)) {
|
|
351
|
-
return [ordered]@{
|
|
353
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
352
354
|
}
|
|
353
355
|
|
|
354
356
|
# Write tool: validate the content payload directly. Edit tool: no content is
|
|
@@ -360,7 +362,7 @@ function Invoke-CompletionConsistencyDecision {
|
|
|
360
362
|
if (-not $content) {
|
|
361
363
|
# No content, and the Edit could not be resolved against on-disk
|
|
362
364
|
# state (missing file or non-matching patch): defer and allow.
|
|
363
|
-
return [ordered]@{
|
|
365
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
364
366
|
}
|
|
365
367
|
}
|
|
366
368
|
|
|
@@ -370,11 +372,11 @@ function Invoke-CompletionConsistencyDecision {
|
|
|
370
372
|
catch {
|
|
371
373
|
# The content itself is not valid JSON. Let downstream tools surface the
|
|
372
374
|
# error rather than blocking with a misleading reason here.
|
|
373
|
-
return [ordered]@{
|
|
375
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
374
376
|
}
|
|
375
377
|
|
|
376
378
|
if (-not (Test-CompletionAsserted -Payload $payload)) {
|
|
377
|
-
return [ordered]@{
|
|
379
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
378
380
|
}
|
|
379
381
|
|
|
380
382
|
$missingArgs = @{ Payload = $payload; FolderExistsCheck = $FolderExistsCheck }
|
|
@@ -383,12 +385,16 @@ function Invoke-CompletionConsistencyDecision {
|
|
|
383
385
|
}
|
|
384
386
|
$missing = Get-MissingCompletionEvidence @missingArgs
|
|
385
387
|
if ($missing.Count -eq 0) {
|
|
386
|
-
return [ordered]@{
|
|
388
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
387
389
|
}
|
|
388
390
|
|
|
391
|
+
$reason = "COMPLETION_CONSISTENCY_BLOCKED: the checkpoint asserts completion but is missing required completion evidence: $($missing -join ', '). 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; routes whose requires_pr_gate is true must also include pr_gate evidence with a matching head_sha. Supply the missing evidence or remove the completion assertion."
|
|
389
392
|
return [ordered]@{
|
|
390
|
-
|
|
391
|
-
|
|
393
|
+
hookSpecificOutput = [ordered]@{
|
|
394
|
+
hookEventName = 'PreToolUse'
|
|
395
|
+
permissionDecision = 'deny'
|
|
396
|
+
permissionDecisionReason = $reason
|
|
397
|
+
}
|
|
392
398
|
}
|
|
393
399
|
}
|
|
394
400
|
|
|
@@ -405,6 +411,6 @@ catch {
|
|
|
405
411
|
exit 1
|
|
406
412
|
}
|
|
407
413
|
|
|
408
|
-
$decision | ConvertTo-Json -Compress | Write-Output
|
|
414
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
409
415
|
|
|
410
416
|
exit 0
|