@danmoisan/drm-copilot-mcp 1.0.27 → 1.1.1
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 +0 -1
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/hooks/check-powershell-test-purity.ps1 +17 -21
- package/resources/claude-customizations/.claude/hooks/check-python-test-purity.ps1 +17 -19
- package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +17 -19
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +18 -19
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +18 -19
- package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +54 -32
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +64 -20
- package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +51 -22
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +56 -19
- package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +71 -28
- package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +68 -23
- package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +36 -24
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +20 -22
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +349 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +177 -20
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +17 -20
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier-helpers.ps1 +278 -0
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +55 -271
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +57 -22
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +56 -19
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +70 -27
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +228 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +68 -225
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +161 -34
- package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +59 -22
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +70 -27
- package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +32 -18
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +34 -60
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +187 -0
- package/resources/claude-customizations/.claude/lib/hook-payload/HookPayload.psm1 +494 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +125 -4
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +27 -0
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +28 -0
- package/resources/claude-customizations/config/blast-radius.json +9 -3
- package/resources/claude-customizations/pack-manifests/core.json +5 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +349 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +123 -6
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +1 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +32 -1
package/out/mcp-server.js
CHANGED
|
@@ -19004,7 +19004,6 @@ var EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
|
19004
19004
|
]);
|
|
19005
19005
|
var SCAN_DEPTH_LIMIT = 3;
|
|
19006
19006
|
var PAYLOAD_MODULES = {
|
|
19007
|
-
"claude-runtime": [".claude/**"],
|
|
19008
19007
|
config: ["config/**"]
|
|
19009
19008
|
};
|
|
19010
19009
|
var FORBIDDEN_GLOBS = [
|
package/package.json
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
.DESCRIPTION
|
|
6
6
|
This script is invoked by the Claude Code PreToolUse hook before any Write or Edit
|
|
7
7
|
operation on a file path matching a Pester test file (*.Tests.ps1 or tests/**/*.ps1).
|
|
8
|
-
It
|
|
9
|
-
'file_path'
|
|
10
|
-
rejects the operation when the proposed content
|
|
11
|
-
dependencies or violates the mocking rules.
|
|
8
|
+
It acquires the hook payload through the shared reader and reads the envelope's
|
|
9
|
+
nested tool_input ('file_path' plus a content field: 'content' for Write,
|
|
10
|
+
'new_string' for Edit), then rejects the operation when the proposed content
|
|
11
|
+
introduces forbidden runtime dependencies or violates the mocking rules.
|
|
12
12
|
|
|
13
13
|
Forbidden patterns in Pester unit tests include:
|
|
14
14
|
- direct external-executable mocking (Mock git, Mock gh, Mock actionlint, etc.)
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
[CmdletBinding()]
|
|
33
33
|
param()
|
|
34
34
|
|
|
35
|
+
|
|
36
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
35
37
|
function Get-PowerShellTestPurityBlockDecision {
|
|
36
38
|
[CmdletBinding()]
|
|
37
39
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
@@ -68,18 +70,15 @@ function Invoke-PowerShellTestPurityDecision {
|
|
|
68
70
|
[string] $ToolInputRaw
|
|
69
71
|
)
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
return $null
|
|
73
|
+
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
74
|
+
if (-not $payload.IsValid) {
|
|
75
|
+
return Get-PowerShellTestPurityBlockDecision -Reason (
|
|
76
|
+
'PowerShell unit test purity hook received an unreadable PreToolUse envelope: ' +
|
|
77
|
+
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $payload.Anomaly) +
|
|
78
|
+
'. The gate fails closed on an envelope it cannot read.')
|
|
80
79
|
}
|
|
81
80
|
|
|
82
|
-
$filePath = $
|
|
81
|
+
$filePath = Get-ClaudeHookToolInputString -ToolInput $payload.Value -Name 'file_path'
|
|
83
82
|
if (-not $filePath) {
|
|
84
83
|
return $null
|
|
85
84
|
}
|
|
@@ -88,12 +87,9 @@ function Invoke-PowerShellTestPurityDecision {
|
|
|
88
87
|
return $null
|
|
89
88
|
}
|
|
90
89
|
|
|
91
|
-
$content = $
|
|
92
|
-
if (
|
|
93
|
-
$content =
|
|
94
|
-
}
|
|
95
|
-
elseif ($null -ne $toolInput.new_string) {
|
|
96
|
-
$content = [string]$toolInput.new_string
|
|
90
|
+
$content = Get-ClaudeHookToolInputString -ToolInput $payload.Value -Name 'content'
|
|
91
|
+
if (-not $content) {
|
|
92
|
+
$content = Get-ClaudeHookToolInputString -ToolInput $payload.Value -Name 'new_string'
|
|
97
93
|
}
|
|
98
94
|
|
|
99
95
|
if (-not $content) {
|
|
@@ -141,7 +137,7 @@ if ($MyInvocation.InvocationName -eq '.') {
|
|
|
141
137
|
return
|
|
142
138
|
}
|
|
143
139
|
|
|
144
|
-
$decision = Invoke-PowerShellTestPurityDecision -ToolInputRaw
|
|
140
|
+
$decision = Invoke-PowerShellTestPurityDecision -ToolInputRaw (Read-ClaudeHookRawPayload)
|
|
145
141
|
if ($null -ne $decision -and $decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
146
142
|
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
147
143
|
}
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
.DESCRIPTION
|
|
6
6
|
This script is invoked by the Claude Code PreToolUse hook before any Write or Edit
|
|
7
|
-
operation on a file path matching tests/**/*.py. It
|
|
8
|
-
|
|
9
|
-
'content' for Write, 'new_string' for Edit)
|
|
10
|
-
proposed content introduces forbidden runtime dependencies.
|
|
7
|
+
operation on a file path matching tests/**/*.py. It acquires the hook payload
|
|
8
|
+
through the shared reader and reads the envelope's nested tool_input ('file_path'
|
|
9
|
+
plus a content field: 'content' for Write, 'new_string' for Edit), then rejects the
|
|
10
|
+
operation when the proposed content introduces forbidden runtime dependencies.
|
|
11
11
|
|
|
12
12
|
Forbidden patterns in unit tests include:
|
|
13
13
|
- temporary filesystem usage (tempfile, NamedTemporaryFile, TemporaryDirectory,
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
[CmdletBinding()]
|
|
30
30
|
param()
|
|
31
31
|
|
|
32
|
+
|
|
33
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
32
34
|
function Get-PythonTestPurityBlockDecision {
|
|
33
35
|
[CmdletBinding()]
|
|
34
36
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
@@ -65,17 +67,15 @@ function Invoke-PythonTestPurityDecision {
|
|
|
65
67
|
[string] $ToolInputRaw
|
|
66
68
|
)
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
71
|
+
if (-not $payload.IsValid) {
|
|
72
|
+
return Get-PythonTestPurityBlockDecision -Reason (
|
|
73
|
+
'Python unit test purity hook received an unreadable PreToolUse envelope: ' +
|
|
74
|
+
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $payload.Anomaly) +
|
|
75
|
+
'. The gate fails closed on an envelope it cannot read.')
|
|
70
76
|
}
|
|
71
77
|
|
|
72
|
-
|
|
73
|
-
$toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
|
|
74
|
-
} catch {
|
|
75
|
-
return Get-PythonTestPurityBlockDecision -Reason 'Python unit test purity hook received malformed JSON in CLAUDE_TOOL_INPUT.'
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
$filePath = $toolInput.file_path
|
|
78
|
+
$filePath = Get-ClaudeHookToolInputString -ToolInput $payload.Value -Name 'file_path'
|
|
79
79
|
if (-not $filePath) {
|
|
80
80
|
return $null
|
|
81
81
|
}
|
|
@@ -84,11 +84,9 @@ function Invoke-PythonTestPurityDecision {
|
|
|
84
84
|
return $null
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
$content = $
|
|
88
|
-
if (
|
|
89
|
-
$content =
|
|
90
|
-
} elseif ($null -ne $toolInput.new_string) {
|
|
91
|
-
$content = [string]$toolInput.new_string
|
|
87
|
+
$content = Get-ClaudeHookToolInputString -ToolInput $payload.Value -Name 'content'
|
|
88
|
+
if (-not $content) {
|
|
89
|
+
$content = Get-ClaudeHookToolInputString -ToolInput $payload.Value -Name 'new_string'
|
|
92
90
|
}
|
|
93
91
|
|
|
94
92
|
if (-not $content) {
|
|
@@ -141,7 +139,7 @@ if ($MyInvocation.InvocationName -eq '.') {
|
|
|
141
139
|
return
|
|
142
140
|
}
|
|
143
141
|
|
|
144
|
-
$decision = Invoke-PythonTestPurityDecision -ToolInputRaw
|
|
142
|
+
$decision = Invoke-PythonTestPurityDecision -ToolInputRaw (Read-ClaudeHookRawPayload)
|
|
145
143
|
if ($null -ne $decision -and $decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
146
144
|
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
147
145
|
}
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
[CmdletBinding()]
|
|
44
44
|
param()
|
|
45
45
|
|
|
46
|
+
|
|
47
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
46
48
|
$script:CanonicalStepPrefixes = @(
|
|
47
49
|
'S0_startup_checks',
|
|
48
50
|
'S1_change_budget_estimation',
|
|
@@ -197,7 +199,7 @@ function Test-IsCheckpointPath {
|
|
|
197
199
|
function Invoke-CheckpointMonotonicDecision {
|
|
198
200
|
<#
|
|
199
201
|
.SYNOPSIS
|
|
200
|
-
Parses
|
|
202
|
+
Parses the PreToolUse envelope and returns an allow-or-block decision.
|
|
201
203
|
#>
|
|
202
204
|
[CmdletBinding()]
|
|
203
205
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
@@ -205,18 +207,20 @@ function Invoke-CheckpointMonotonicDecision {
|
|
|
205
207
|
[string] $ToolInputRaw
|
|
206
208
|
)
|
|
207
209
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
210
|
+
$envelope = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
211
|
+
if (-not $envelope.IsValid) {
|
|
212
|
+
return [ordered]@{
|
|
213
|
+
hookSpecificOutput = [ordered]@{
|
|
214
|
+
hookEventName = 'PreToolUse'
|
|
215
|
+
permissionDecision = 'deny'
|
|
216
|
+
permissionDecisionReason = 'CHECKPOINT_MONOTONIC_BLOCKED: payload anomaly - ' +
|
|
217
|
+
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $envelope.Anomaly) +
|
|
218
|
+
'. The gate fails closed on an envelope it cannot read.'
|
|
219
|
+
}
|
|
220
|
+
}
|
|
217
221
|
}
|
|
218
222
|
|
|
219
|
-
$filePath = $
|
|
223
|
+
$filePath = Get-ClaudeHookToolInputString -ToolInput $envelope.Value -Name 'file_path'
|
|
220
224
|
if (-not $filePath) {
|
|
221
225
|
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
222
226
|
}
|
|
@@ -228,7 +232,7 @@ function Invoke-CheckpointMonotonicDecision {
|
|
|
228
232
|
|
|
229
233
|
# Write tool: validate the content payload. Edit tool: partial new_string is
|
|
230
234
|
# not reliable without the full target file content, so allow.
|
|
231
|
-
$content = $
|
|
235
|
+
$content = Get-ClaudeHookToolInputString -ToolInput $envelope.Value -Name 'content'
|
|
232
236
|
if (-not $content) {
|
|
233
237
|
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
234
238
|
}
|
|
@@ -298,13 +302,7 @@ if ($MyInvocation.InvocationName -eq '.') {
|
|
|
298
302
|
return
|
|
299
303
|
}
|
|
300
304
|
|
|
301
|
-
|
|
302
|
-
$decision = Invoke-CheckpointMonotonicDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
|
|
303
|
-
}
|
|
304
|
-
catch {
|
|
305
|
-
Write-Error $_
|
|
306
|
-
exit 1
|
|
307
|
-
}
|
|
305
|
+
$decision = Invoke-CheckpointMonotonicDecision -ToolInputRaw (Read-ClaudeHookRawPayload)
|
|
308
306
|
|
|
309
307
|
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
310
308
|
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
[CmdletBinding()]
|
|
42
42
|
param()
|
|
43
43
|
|
|
44
|
+
|
|
45
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
44
46
|
# Dot-source the shared validation helpers. Guarded so a missing file produces a
|
|
45
47
|
# clear error and so dot-sourcing this hook in tests loads the helpers too.
|
|
46
48
|
$script:CompletionHelpersPath = Join-Path $PSScriptRoot 'enforce-completion-helpers.ps1'
|
|
@@ -314,7 +316,7 @@ function Resolve-EditedCheckpointContent {
|
|
|
314
316
|
function Invoke-CompletionConsistencyDecision {
|
|
315
317
|
<#
|
|
316
318
|
.SYNOPSIS
|
|
317
|
-
Parses
|
|
319
|
+
Parses the PreToolUse envelope and returns an allow-or-block decision based on
|
|
318
320
|
completion-evidence consistency.
|
|
319
321
|
#>
|
|
320
322
|
[CmdletBinding()]
|
|
@@ -332,18 +334,21 @@ function Invoke-CompletionConsistencyDecision {
|
|
|
332
334
|
[scriptblock] $RoutingMatrixReader
|
|
333
335
|
)
|
|
334
336
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
337
|
+
$envelope = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
338
|
+
if (-not $envelope.IsValid) {
|
|
339
|
+
return [ordered]@{
|
|
340
|
+
hookSpecificOutput = [ordered]@{
|
|
341
|
+
hookEventName = 'PreToolUse'
|
|
342
|
+
permissionDecision = 'deny'
|
|
343
|
+
permissionDecisionReason = 'COMPLETION_CONSISTENCY_BLOCKED: payload anomaly - ' +
|
|
344
|
+
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $envelope.Anomaly) +
|
|
345
|
+
'. The gate fails closed on an envelope it cannot read.'
|
|
346
|
+
}
|
|
347
|
+
}
|
|
344
348
|
}
|
|
345
349
|
|
|
346
|
-
$
|
|
350
|
+
$toolInput = $envelope.Value
|
|
351
|
+
$filePath = Get-ClaudeHookToolInputString -ToolInput $toolInput -Name 'file_path'
|
|
347
352
|
if (-not $filePath) {
|
|
348
353
|
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
349
354
|
}
|
|
@@ -356,7 +361,7 @@ function Invoke-CompletionConsistencyDecision {
|
|
|
356
361
|
# Write tool: validate the content payload directly. Edit tool: no content is
|
|
357
362
|
# supplied, so read the on-disk checkpoint through the injectable seam and
|
|
358
363
|
# apply the old_string -> new_string patch in memory (read-then-validate).
|
|
359
|
-
$content = $toolInput
|
|
364
|
+
$content = Get-ClaudeHookToolInputString -ToolInput $toolInput -Name 'content'
|
|
360
365
|
if (-not $content) {
|
|
361
366
|
$content = Resolve-EditedCheckpointContent -ToolInput $toolInput -CheckpointReader $CheckpointReader
|
|
362
367
|
if (-not $content) {
|
|
@@ -403,13 +408,7 @@ if ($MyInvocation.InvocationName -eq '.') {
|
|
|
403
408
|
return
|
|
404
409
|
}
|
|
405
410
|
|
|
406
|
-
|
|
407
|
-
$decision = Invoke-CompletionConsistencyDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
|
|
408
|
-
}
|
|
409
|
-
catch {
|
|
410
|
-
Write-Error $_
|
|
411
|
-
exit 1
|
|
412
|
-
}
|
|
411
|
+
$decision = Invoke-CompletionConsistencyDecision -ToolInputRaw (Read-ClaudeHookRawPayload)
|
|
413
412
|
|
|
414
413
|
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
415
414
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
.DESCRIPTION
|
|
7
7
|
Invoked by the Claude Code PreToolUse hook on Write or Edit operations
|
|
8
|
-
(matcher "Write|Edit"). The hook reads
|
|
8
|
+
(matcher "Write|Edit"). The hook reads the PreToolUse envelope JSON
|
|
9
9
|
containing file_path, and either content (Write) or old_string/new_string
|
|
10
10
|
(Edit).
|
|
11
11
|
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
[CmdletBinding()]
|
|
34
34
|
param()
|
|
35
35
|
|
|
36
|
+
|
|
37
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
36
38
|
function Invoke-DiscoveryValidatorExe {
|
|
37
39
|
<#
|
|
38
40
|
.SYNOPSIS
|
|
@@ -151,7 +153,7 @@ function Get-RequiredDiscoveryArtifactDeclaration {
|
|
|
151
153
|
function Invoke-DiscoveryArtifactGateDecision {
|
|
152
154
|
<#
|
|
153
155
|
.SYNOPSIS
|
|
154
|
-
Parses
|
|
156
|
+
Parses the PreToolUse envelope and returns an allow-or-deny decision for a
|
|
155
157
|
discovery-artifact completion gate.
|
|
156
158
|
#>
|
|
157
159
|
[CmdletBinding()]
|
|
@@ -163,18 +165,21 @@ function Invoke-DiscoveryArtifactGateDecision {
|
|
|
163
165
|
[scriptblock] $RequiredArtifactReader = { Get-RequiredDiscoveryArtifactDeclaration }
|
|
164
166
|
)
|
|
165
167
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
168
|
+
$envelope = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
169
|
+
if (-not $envelope.IsValid) {
|
|
170
|
+
return [ordered]@{
|
|
171
|
+
hookSpecificOutput = [ordered]@{
|
|
172
|
+
hookEventName = 'PreToolUse'
|
|
173
|
+
permissionDecision = 'deny'
|
|
174
|
+
permissionDecisionReason = 'DISCOVERY_ARTIFACT_GATE_BLOCKED: payload anomaly - ' +
|
|
175
|
+
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $envelope.Anomaly) +
|
|
176
|
+
'. The gate fails closed on an envelope it cannot read.'
|
|
177
|
+
}
|
|
178
|
+
}
|
|
175
179
|
}
|
|
176
180
|
|
|
177
|
-
$
|
|
181
|
+
$toolInput = $envelope.Value
|
|
182
|
+
$filePath = Get-ClaudeHookToolInputString -ToolInput $toolInput -Name 'file_path'
|
|
178
183
|
if (-not $filePath) {
|
|
179
184
|
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
180
185
|
}
|
|
@@ -220,13 +225,7 @@ if ($MyInvocation.InvocationName -eq '.') {
|
|
|
220
225
|
return
|
|
221
226
|
}
|
|
222
227
|
|
|
223
|
-
|
|
224
|
-
$decision = Invoke-DiscoveryArtifactGateDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
|
|
225
|
-
}
|
|
226
|
-
catch {
|
|
227
|
-
Write-Error $_
|
|
228
|
-
exit 1
|
|
229
|
-
}
|
|
228
|
+
$decision = Invoke-DiscoveryArtifactGateDecision -ToolInputRaw (Read-ClaudeHookRawPayload)
|
|
230
229
|
|
|
231
230
|
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
232
231
|
|
|
@@ -10,16 +10,22 @@
|
|
|
10
10
|
subagent_type is 'epic-planner', 'epic-orchestrator', 'parallel-planner',
|
|
11
11
|
or 'parallel-orchestrator'.
|
|
12
12
|
|
|
13
|
+
The payload is acquired through the shared reader
|
|
14
|
+
(.claude/lib/hook-payload/HookPayload.psm1: stdin first, then the two
|
|
15
|
+
environment-variable fallbacks).
|
|
16
|
+
|
|
13
17
|
Caller identity resolution:
|
|
14
|
-
- The
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- The
|
|
18
|
-
|
|
18
|
+
- The PreToolUse envelope carries a top-level 'agent_type' field only when
|
|
19
|
+
the tool call is made from inside a subagent context. A main-thread call
|
|
20
|
+
carries no 'agent_type'.
|
|
21
|
+
- The envelope's nested 'tool_input' object carries the delegation target
|
|
22
|
+
'subagent_type'.
|
|
19
23
|
|
|
20
24
|
Decision procedure:
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
0. An envelope this hook cannot read at all -- empty on every transport,
|
|
26
|
+
unparseable, or carrying no usable tool_input -- denies (fail closed).
|
|
27
|
+
1. Resolve the target subagent_type from the nested tool_input. A non-gated
|
|
28
|
+
target allows.
|
|
23
29
|
2. Resolve the calling agent_type from the payload. An absent or empty
|
|
24
30
|
agent_type indicates a main-thread invocation, which allows.
|
|
25
31
|
3. Deny when the calling agent_type is exactly 'orchestrator', with the
|
|
@@ -37,9 +43,12 @@
|
|
|
37
43
|
[CmdletBinding()]
|
|
38
44
|
param()
|
|
39
45
|
|
|
46
|
+
|
|
47
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
40
48
|
$script:GatedSubagentTypes = @('epic-planner', 'epic-orchestrator', 'parallel-planner', 'parallel-orchestrator')
|
|
41
49
|
$script:ParallelSubagentTypes = @('parallel-planner', 'parallel-orchestrator')
|
|
42
50
|
$script:ProhibitedCallerAgentType = 'orchestrator'
|
|
51
|
+
$script:EmptyPayloadAnomaly = 'EmptyPayload'
|
|
43
52
|
|
|
44
53
|
function Get-EpicInvocationOriginAllowDecision {
|
|
45
54
|
[CmdletBinding()]
|
|
@@ -110,9 +119,9 @@ function Get-EpicInvocationOriginTargetSubagent {
|
|
|
110
119
|
Resolves the delegation target subagent_type from the tool input,
|
|
111
120
|
falling back to the full payload's tool_input object.
|
|
112
121
|
.PARAMETER ToolInput
|
|
113
|
-
Parsed
|
|
122
|
+
Parsed legacy tool-input object, or $null when absent.
|
|
114
123
|
.PARAMETER HookInput
|
|
115
|
-
Parsed
|
|
124
|
+
Parsed PreToolUse envelope object, or $null when absent.
|
|
116
125
|
.OUTPUTS
|
|
117
126
|
System.String or $null
|
|
118
127
|
#>
|
|
@@ -151,7 +160,7 @@ function Get-EpicInvocationOriginCallerAgentType {
|
|
|
151
160
|
Resolves the calling agent_type from the full hook payload. Returns
|
|
152
161
|
$null for a main-thread invocation (no agent_type field).
|
|
153
162
|
.PARAMETER HookInput
|
|
154
|
-
Parsed
|
|
163
|
+
Parsed PreToolUse envelope object, or $null when absent.
|
|
155
164
|
.OUTPUTS
|
|
156
165
|
System.String or $null
|
|
157
166
|
#>
|
|
@@ -180,10 +189,17 @@ function Invoke-EpicInvocationOriginDecision {
|
|
|
180
189
|
<#
|
|
181
190
|
.SYNOPSIS
|
|
182
191
|
Parses the hook payloads and returns an allow-or-block decision.
|
|
192
|
+
.DESCRIPTION
|
|
193
|
+
The caller's agent_type is read off the envelope root and the delegation
|
|
194
|
+
target's subagent_type off the nested tool_input. An envelope this hook cannot
|
|
195
|
+
read at all -- empty on every transport, unparseable, or carrying no usable
|
|
196
|
+
tool_input -- is a fail-closed deny, because an unreadable envelope means the
|
|
197
|
+
PreToolUse contract drifted (issue #501).
|
|
183
198
|
.PARAMETER HookInputRaw
|
|
184
|
-
The raw
|
|
199
|
+
The raw PreToolUse envelope JSON acquired by Read-ClaudeHookRawPayload.
|
|
185
200
|
.PARAMETER ToolInputRaw
|
|
186
|
-
|
|
201
|
+
Optional legacy direct tool-input payload, retained so an undocumented wrapper
|
|
202
|
+
that supplies the bare tool input keeps working.
|
|
187
203
|
.OUTPUTS
|
|
188
204
|
System.Collections.Specialized.OrderedDictionary
|
|
189
205
|
#>
|
|
@@ -199,29 +215,40 @@ function Invoke-EpicInvocationOriginDecision {
|
|
|
199
215
|
[string] $ToolInputRaw
|
|
200
216
|
)
|
|
201
217
|
|
|
202
|
-
# The
|
|
203
|
-
#
|
|
204
|
-
$
|
|
205
|
-
$hookInputParsed = $false
|
|
218
|
+
# The envelope carries both halves: the caller's agent_type at the root and the
|
|
219
|
+
# delegation target's subagent_type inside tool_input.
|
|
220
|
+
$anomaly = $script:EmptyPayloadAnomaly
|
|
206
221
|
$hookInput = $null
|
|
222
|
+
if (-not [string]::IsNullOrWhiteSpace($HookInputRaw)) {
|
|
223
|
+
$resolved = Resolve-ClaudeHookToolInput -Raw $HookInputRaw
|
|
224
|
+
$hookInput = $resolved.Envelope
|
|
225
|
+
$anomaly = if ($resolved.IsValid) { $null } else { $resolved.Anomaly }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# Legacy direct tool-input payload, retained for an undocumented wrapper that
|
|
229
|
+
# supplies the bare tool input rather than the documented envelope.
|
|
230
|
+
$toolInput = $null
|
|
231
|
+
if (-not [string]::IsNullOrWhiteSpace($ToolInputRaw)) {
|
|
232
|
+
$legacy = ConvertFrom-ClaudeHookEnvelope -Raw $ToolInputRaw
|
|
233
|
+
if ($legacy.IsValid) {
|
|
234
|
+
$toolInput = $legacy.Value
|
|
235
|
+
$anomaly = $null
|
|
236
|
+
}
|
|
237
|
+
}
|
|
207
238
|
|
|
208
239
|
$target = Get-EpicInvocationOriginTargetSubagent -ToolInput $toolInput -HookInput $hookInput
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
240
|
+
|
|
241
|
+
if (-not $target -and $anomaly) {
|
|
242
|
+
return Get-EpicInvocationOriginBlockDecision -Reason (
|
|
243
|
+
'EPIC_INVOCATION_ORIGIN_BLOCKED: payload anomaly - ' +
|
|
244
|
+
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $anomaly) +
|
|
245
|
+
'. The gate fails closed on an envelope it cannot read.')
|
|
215
246
|
}
|
|
216
247
|
|
|
217
248
|
if (-not $target -or $script:GatedSubagentTypes -notcontains $target) {
|
|
218
249
|
return Get-EpicInvocationOriginAllowDecision
|
|
219
250
|
}
|
|
220
251
|
|
|
221
|
-
if (-not $hookInputParsed) {
|
|
222
|
-
$hookInput = ConvertFrom-EpicInvocationOriginPayload -RawPayload $HookInputRaw -PayloadName 'CLAUDE_HOOK_INPUT'
|
|
223
|
-
}
|
|
224
|
-
|
|
225
252
|
# An absent agent_type marks a main-thread invocation, which is the
|
|
226
253
|
# intended entry point for every gated agent; only an orchestrator-context
|
|
227
254
|
# invocation is prohibited.
|
|
@@ -246,12 +273,7 @@ if ($MyInvocation.InvocationName -eq '.') {
|
|
|
246
273
|
return
|
|
247
274
|
}
|
|
248
275
|
|
|
249
|
-
|
|
250
|
-
$decision = Invoke-EpicInvocationOriginDecision -HookInputRaw $env:CLAUDE_HOOK_INPUT -ToolInputRaw $env:CLAUDE_TOOL_INPUT
|
|
251
|
-
} catch {
|
|
252
|
-
Write-Error $_
|
|
253
|
-
exit 1
|
|
254
|
-
}
|
|
276
|
+
$decision = Invoke-EpicInvocationOriginDecision -HookInputRaw (Read-ClaudeHookRawPayload)
|
|
255
277
|
|
|
256
278
|
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
257
279
|
|