@danmoisan/drm-copilot-mcp 1.0.0 → 1.0.7
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 +814 -194
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/commit-message.md +42 -0
- package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +127 -0
- package/resources/claude-customizations/.claude/agents/human-exception-runbook.md +48 -0
- package/resources/claude-customizations/.claude/agents/orchestrator.md +67 -2
- package/resources/claude-customizations/.claude/agents/pr-author.md +37 -21
- 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-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 +19 -7
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +36 -0
- package/resources/claude-customizations/.claude/settings.json +34 -1
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +252 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +42 -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 +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +6 -3
- 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/config.toml +1 -25
- 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
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
CLAUDE_PYTHON_BUDGET_PROD or CLAUDE_PYTHON_BUDGET_TEST to a positive integer before
|
|
24
24
|
the session starts, or by writing {"prodCap": N, "testCap": M} into the state file.
|
|
25
25
|
|
|
26
|
-
When the cap would be exceeded by a new file, the script emits a JSON
|
|
27
|
-
|
|
28
|
-
deleting the state file before starting a new
|
|
29
|
-
always allowed through.
|
|
26
|
+
When the cap would be exceeded by a new file, the script emits a PreToolUse JSON
|
|
27
|
+
response with hookSpecificOutput.permissionDecision = 'deny' and exits 0. The session
|
|
28
|
+
must explicitly reset the counter by deleting the state file before starting a new
|
|
29
|
+
batch. Files already counted are always allowed through.
|
|
30
30
|
|
|
31
31
|
.NOTES
|
|
32
32
|
Compatible with PowerShell 7+.
|
|
@@ -87,8 +87,11 @@ function Get-PythonBatchBudgetBlockDecision {
|
|
|
87
87
|
)
|
|
88
88
|
|
|
89
89
|
$decision = [ordered]@{
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
hookSpecificOutput = [ordered]@{
|
|
91
|
+
hookEventName = 'PreToolUse'
|
|
92
|
+
permissionDecision = 'deny'
|
|
93
|
+
permissionDecisionReason = $Reason
|
|
94
|
+
}
|
|
92
95
|
}
|
|
93
96
|
if ($State) {
|
|
94
97
|
$decision.state = $State
|
|
@@ -113,7 +116,7 @@ function Invoke-PythonBatchBudgetDecision {
|
|
|
113
116
|
|
|
114
117
|
$normalized = $FilePath -replace '\\', '/'
|
|
115
118
|
if ($normalized -notmatch '\.py$') {
|
|
116
|
-
return [ordered]@{
|
|
119
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
$isTestFile = ($normalized -match '(^|/)tests/.*\.py$') -or ($normalized -match '(^|/)test_[^/]+\.py$')
|
|
@@ -122,7 +125,7 @@ function Invoke-PythonBatchBudgetDecision {
|
|
|
122
125
|
$kind = if ($isTestFile) { 'test' } else { 'production' }
|
|
123
126
|
|
|
124
127
|
if ($targetList -contains $normalized) {
|
|
125
|
-
return [ordered]@{
|
|
128
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
if ($targetList.Count -ge $cap) {
|
|
@@ -138,7 +141,7 @@ function Invoke-PythonBatchBudgetDecision {
|
|
|
138
141
|
$State.prodFiles = @($State.prodFiles) + @($normalized)
|
|
139
142
|
}
|
|
140
143
|
|
|
141
|
-
return [ordered]@{
|
|
144
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $true }
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
function Invoke-PythonBatchBudgetHook {
|
|
@@ -160,7 +163,7 @@ function Invoke-PythonBatchBudgetHook {
|
|
|
160
163
|
)
|
|
161
164
|
|
|
162
165
|
if (-not $ToolInputRaw) {
|
|
163
|
-
return [ordered]@{
|
|
166
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
164
167
|
}
|
|
165
168
|
|
|
166
169
|
try {
|
|
@@ -171,12 +174,12 @@ function Invoke-PythonBatchBudgetHook {
|
|
|
171
174
|
|
|
172
175
|
$filePath = $toolInput.file_path
|
|
173
176
|
if (-not $filePath) {
|
|
174
|
-
return [ordered]@{
|
|
177
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
175
178
|
}
|
|
176
179
|
|
|
177
180
|
$normalized = $filePath -replace '\\', '/'
|
|
178
181
|
if ($normalized -notmatch '\.py$') {
|
|
179
|
-
return [ordered]@{
|
|
182
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
180
183
|
}
|
|
181
184
|
|
|
182
185
|
$stateDir = Join-Path -Path $Root -ChildPath '.claude/state'
|
|
@@ -227,9 +230,9 @@ if ($env:CLAUDE_PYTHON_BUDGET_TEST -match '^\d+$') {
|
|
|
227
230
|
}
|
|
228
231
|
|
|
229
232
|
$decision = Invoke-PythonBatchBudgetHook -ToolInputRaw $env:CLAUDE_TOOL_INPUT -SessionId $sessionId -ProdCap $prodCap -TestCap $testCap
|
|
230
|
-
if ($decision.
|
|
233
|
+
if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
231
234
|
$decision.Remove('state')
|
|
232
|
-
$decision | ConvertTo-Json -Compress | Write-Output
|
|
235
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
233
236
|
}
|
|
234
237
|
|
|
235
238
|
exit 0
|
|
@@ -5,11 +5,20 @@
|
|
|
5
5
|
.DESCRIPTION
|
|
6
6
|
This script is invoked by the Claude Code PreToolUse hook before any Bash
|
|
7
7
|
command is executed. It reads the proposed command string from the
|
|
8
|
-
CLAUDE_TOOL_INPUT environment variable (JSON with a
|
|
9
|
-
falls back to the first positional argument. If the
|
|
10
|
-
blocked pattern (destructive operations such as forced
|
|
11
|
-
pushes, or hard resets), the script
|
|
12
|
-
|
|
8
|
+
CLAUDE_TOOL_INPUT (or CLAUDE_HOOK_INPUT) environment variable (JSON with a
|
|
9
|
+
'command' field) or falls back to the first positional argument. If the
|
|
10
|
+
command matches any blocked pattern (destructive operations such as forced
|
|
11
|
+
deletions, forced pushes, or hard resets), the script writes a PreToolUse
|
|
12
|
+
deny decision to stdout and exits with code 0. The deny decision uses the
|
|
13
|
+
Claude Code PreToolUse schema:
|
|
14
|
+
|
|
15
|
+
{"hookSpecificOutput":{"hookEventName":"PreToolUse",
|
|
16
|
+
"permissionDecision":"deny","permissionDecisionReason":"<reason>"}}
|
|
17
|
+
|
|
18
|
+
Safe commands emit no decision and exit with code 0 (an absent decision is a
|
|
19
|
+
valid allow at PreToolUse). The legacy top-level decision/block form and the
|
|
20
|
+
deny-path 'exit 1' are intentionally NOT used: PreToolUse fail-opens on both,
|
|
21
|
+
so they would silently fail to block.
|
|
13
22
|
|
|
14
23
|
.NOTES
|
|
15
24
|
Compatible with PowerShell 7+.
|
|
@@ -21,49 +30,150 @@ param(
|
|
|
21
30
|
[string]$CommandInput
|
|
22
31
|
)
|
|
23
32
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
function Get-BlockedBashPattern {
|
|
34
|
+
[CmdletBinding()]
|
|
35
|
+
[OutputType([string[]])]
|
|
36
|
+
param()
|
|
37
|
+
|
|
38
|
+
return [string[]]@(
|
|
39
|
+
'rm -rf',
|
|
40
|
+
'git push --force',
|
|
41
|
+
'git push origin --force',
|
|
42
|
+
'Remove-Item -Recurse -Force',
|
|
43
|
+
'git reset --hard',
|
|
44
|
+
'git push -f'
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function Get-BlockedPatternMatch {
|
|
49
|
+
[CmdletBinding()]
|
|
50
|
+
[OutputType([string])]
|
|
51
|
+
param(
|
|
52
|
+
[Parameter(Mandatory = $false)]
|
|
53
|
+
[AllowEmptyString()]
|
|
54
|
+
[AllowNull()]
|
|
55
|
+
[string] $Command
|
|
56
|
+
)
|
|
33
57
|
|
|
34
|
-
|
|
35
|
-
$
|
|
58
|
+
if (-not $Command) {
|
|
59
|
+
return $null
|
|
60
|
+
}
|
|
36
61
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if ($parsed.command) {
|
|
41
|
-
$commandToCheck = $parsed.command
|
|
62
|
+
foreach ($pattern in (Get-BlockedBashPattern)) {
|
|
63
|
+
if ($Command.Contains($pattern)) {
|
|
64
|
+
return $pattern
|
|
42
65
|
}
|
|
43
66
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
67
|
+
|
|
68
|
+
return $null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function Get-BashBlockReason {
|
|
72
|
+
[CmdletBinding()]
|
|
73
|
+
[OutputType([string])]
|
|
74
|
+
param(
|
|
75
|
+
[Parameter(Mandatory = $false)]
|
|
76
|
+
[AllowEmptyString()]
|
|
77
|
+
[AllowNull()]
|
|
78
|
+
[string] $Command
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
$pattern = Get-BlockedPatternMatch -Command $Command
|
|
82
|
+
if (-not $pattern) {
|
|
83
|
+
return $null
|
|
47
84
|
}
|
|
85
|
+
|
|
86
|
+
return "Blocked dangerous command pattern detected: '$pattern'"
|
|
48
87
|
}
|
|
49
88
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
89
|
+
function Get-BashDenyDecision {
|
|
90
|
+
[CmdletBinding()]
|
|
91
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
92
|
+
param(
|
|
93
|
+
[Parameter(Mandatory)]
|
|
94
|
+
[string] $Reason
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
[ordered]@{
|
|
98
|
+
hookSpecificOutput = [ordered]@{
|
|
99
|
+
hookEventName = 'PreToolUse'
|
|
100
|
+
permissionDecision = 'deny'
|
|
101
|
+
permissionDecisionReason = $Reason
|
|
102
|
+
}
|
|
103
|
+
}
|
|
53
104
|
}
|
|
54
105
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
106
|
+
function Get-BashCommandToCheck {
|
|
107
|
+
[CmdletBinding()]
|
|
108
|
+
[OutputType([string])]
|
|
109
|
+
param(
|
|
110
|
+
[Parameter(Mandatory = $false)]
|
|
111
|
+
[AllowEmptyString()]
|
|
112
|
+
[AllowNull()]
|
|
113
|
+
[string] $ToolInputRaw,
|
|
114
|
+
|
|
115
|
+
[Parameter(Mandatory = $false)]
|
|
116
|
+
[AllowEmptyString()]
|
|
117
|
+
[AllowNull()]
|
|
118
|
+
[string] $PositionalInput
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
if ($ToolInputRaw) {
|
|
122
|
+
try {
|
|
123
|
+
$parsed = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
|
|
124
|
+
if ($parsed.command) {
|
|
125
|
+
return [string]$parsed.command
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
# If JSON parsing fails, treat the raw input as the command.
|
|
129
|
+
return $ToolInputRaw
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if ($PositionalInput) {
|
|
134
|
+
return $PositionalInput
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return ''
|
|
58
138
|
}
|
|
59
139
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
140
|
+
function Invoke-ValidateBashDecision {
|
|
141
|
+
[CmdletBinding()]
|
|
142
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
143
|
+
param(
|
|
144
|
+
[Parameter(Mandatory = $false)]
|
|
145
|
+
[AllowEmptyString()]
|
|
146
|
+
[AllowNull()]
|
|
147
|
+
[string] $ToolInputRaw,
|
|
148
|
+
|
|
149
|
+
[Parameter(Mandatory = $false)]
|
|
150
|
+
[AllowEmptyString()]
|
|
151
|
+
[AllowNull()]
|
|
152
|
+
[string] $PositionalInput
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
$commandToCheck = Get-BashCommandToCheck -ToolInputRaw $ToolInputRaw -PositionalInput $PositionalInput
|
|
156
|
+
|
|
157
|
+
$reason = Get-BashBlockReason -Command $commandToCheck
|
|
158
|
+
if ($reason) {
|
|
159
|
+
return Get-BashDenyDecision -Reason $reason
|
|
65
160
|
}
|
|
161
|
+
|
|
162
|
+
return $null
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
$toolInputRaw = $env:CLAUDE_TOOL_INPUT
|
|
170
|
+
if (-not $toolInputRaw) {
|
|
171
|
+
$toolInputRaw = $env:CLAUDE_HOOK_INPUT
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
$decision = Invoke-ValidateBashDecision -ToolInputRaw $toolInputRaw -PositionalInput $CommandInput
|
|
175
|
+
if ($null -ne $decision -and $decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
176
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
66
177
|
}
|
|
67
178
|
|
|
68
|
-
# Command passed all checks.
|
|
69
179
|
exit 0
|
|
@@ -27,7 +27,13 @@
|
|
|
27
27
|
#>
|
|
28
28
|
|
|
29
29
|
[CmdletBinding()]
|
|
30
|
-
param(
|
|
30
|
+
param(
|
|
31
|
+
[Parameter(Mandatory = $false)]
|
|
32
|
+
[string] $CheckpointPath = 'artifacts/orchestration/orchestrator-state.json',
|
|
33
|
+
|
|
34
|
+
[Parameter(Mandatory = $false)]
|
|
35
|
+
[string] $ArtifactType = 'orchestrator-state'
|
|
36
|
+
)
|
|
31
37
|
|
|
32
38
|
Set-StrictMode -Version Latest
|
|
33
39
|
$ErrorActionPreference = 'Stop'
|
|
@@ -150,9 +156,11 @@ function Invoke-RoutingContractValidation {
|
|
|
150
156
|
Invokes the validator through an injectable subprocess scriptblock seam.
|
|
151
157
|
The default Invoker runs the authoritative Python CLI:
|
|
152
158
|
python -m scripts.dev_tools.validate_orchestration_artifacts \
|
|
153
|
-
|
|
159
|
+
<ArtifactType> <CheckpointPath> --require-complete
|
|
154
160
|
Tests inject a mock scriptblock so no Python process runs. The function
|
|
155
161
|
does not reimplement routing logic; it delegates to the Python validator.
|
|
162
|
+
ArtifactType defaults to 'orchestrator-state' so the default invocation
|
|
163
|
+
string is unchanged for every existing caller of this hook.
|
|
156
164
|
|
|
157
165
|
Returns a hashtable with keys:
|
|
158
166
|
- HasErrors: $true when the validator reported a non-zero exit or
|
|
@@ -165,11 +173,14 @@ function Invoke-RoutingContractValidation {
|
|
|
165
173
|
[Parameter(Mandatory = $true)]
|
|
166
174
|
[string] $CheckpointPath,
|
|
167
175
|
|
|
176
|
+
[Parameter(Mandatory = $false)]
|
|
177
|
+
[string] $ArtifactType = 'orchestrator-state',
|
|
178
|
+
|
|
168
179
|
[Parameter(Mandatory = $false)]
|
|
169
180
|
[scriptblock] $Invoker = {
|
|
170
|
-
param($Path)
|
|
181
|
+
param($Path, $Type)
|
|
171
182
|
$output = & python -m scripts.dev_tools.validate_orchestration_artifacts `
|
|
172
|
-
|
|
183
|
+
$Type $Path --require-complete 2>&1
|
|
173
184
|
[pscustomobject]@{
|
|
174
185
|
ExitCode = $LASTEXITCODE
|
|
175
186
|
Output = ($output | Out-String)
|
|
@@ -177,7 +188,7 @@ function Invoke-RoutingContractValidation {
|
|
|
177
188
|
}
|
|
178
189
|
)
|
|
179
190
|
|
|
180
|
-
$result = & $Invoker $CheckpointPath
|
|
191
|
+
$result = & $Invoker $CheckpointPath $ArtifactType
|
|
181
192
|
$exitCode = 0
|
|
182
193
|
if ($null -ne $result -and ($result.PSObject.Properties.Name -contains 'ExitCode')) {
|
|
183
194
|
$exitCode = [int]$result.ExitCode
|
|
@@ -207,6 +218,7 @@ function Invoke-OrchestratorOutputValidation {
|
|
|
207
218
|
param(
|
|
208
219
|
[string] $RawPayload,
|
|
209
220
|
[string] $CheckpointPath = 'artifacts/orchestration/orchestrator-state.json',
|
|
221
|
+
[string] $ArtifactType = 'orchestrator-state',
|
|
210
222
|
|
|
211
223
|
[Parameter(Mandatory = $false)]
|
|
212
224
|
[scriptblock] $RoutingInvoker
|
|
@@ -275,7 +287,7 @@ function Invoke-OrchestratorOutputValidation {
|
|
|
275
287
|
# Delegate to the authoritative Python routing-contract validator. The
|
|
276
288
|
# optional RoutingInvoker seam lets tests inject a mock; the default seam
|
|
277
289
|
# produces the real subprocess call.
|
|
278
|
-
$routingArgs = @{ CheckpointPath = $CheckpointPath }
|
|
290
|
+
$routingArgs = @{ CheckpointPath = $CheckpointPath; ArtifactType = $ArtifactType }
|
|
279
291
|
if ($PSBoundParameters.ContainsKey('RoutingInvoker') -and $null -ne $RoutingInvoker) {
|
|
280
292
|
$routingArgs['Invoker'] = $RoutingInvoker
|
|
281
293
|
}
|
|
@@ -292,7 +304,7 @@ if ($MyInvocation.InvocationName -eq '.') {
|
|
|
292
304
|
return
|
|
293
305
|
}
|
|
294
306
|
|
|
295
|
-
$result = Invoke-OrchestratorOutputValidation -RawPayload $env:CLAUDE_HOOK_INPUT
|
|
307
|
+
$result = Invoke-OrchestratorOutputValidation -RawPayload $env:CLAUDE_HOOK_INPUT -CheckpointPath $CheckpointPath -ArtifactType $ArtifactType
|
|
296
308
|
if (-not $result.Ok) {
|
|
297
309
|
Write-Error $result.Message
|
|
298
310
|
exit 1
|
|
@@ -32,8 +32,44 @@ These invariants apply only when the checkpoint contains a top-level `human_inte
|
|
|
32
32
|
|
|
33
33
|
3. **Exception requires `runbook_path`.** A requirement whose `response == "exception"` must carry a non-empty `runbook_path` string. A missing, non-string, or empty/whitespace-only `runbook_path` on an `exception` requirement is a malformed requirement.
|
|
34
34
|
|
|
35
|
+
## Complexity-Assessment Scope and Backward Compatibility
|
|
36
|
+
|
|
37
|
+
These invariants apply only when the checkpoint contains a top-level `complexity_assessments` array. A checkpoint with no `complexity_assessments` key (the existing checkpoint shape) is unaffected: it validates exactly as before and produces no new errors. The invariants are additive and support the two-axis model-selection mechanism documented in `.claude/skills/orchestrate/SKILL.md` (`## Model Selection`). Enforcement is the Python validator, not an imported schema.
|
|
38
|
+
|
|
39
|
+
## Invariants (complexity_assessments array)
|
|
40
|
+
|
|
41
|
+
Each entry records one assessed phase with the shape `{ phase, band, floor, signals_present[], rationale, assessed_at }`.
|
|
42
|
+
|
|
43
|
+
1. **Band enum membership.** Each entry's `band` must be one of `C1`, `C2`, `C3`, `C4`. A missing or out-of-enum `band` is a malformed entry.
|
|
44
|
+
|
|
45
|
+
2. **Band at or above floor.** Each entry must satisfy `band >= floor` using the band ordering `C1 < C2 < C3 < C4`. The floor constrains the lower bound only; a `band` below its `floor` is a malformed entry.
|
|
46
|
+
|
|
47
|
+
3. **Floor equals the computed floor.** Each entry's `floor` must equal `compute_complexity_floor(signals_present)` (the reference implementation in `scripts/dev_tools/compute_complexity_floor.py`). A `floor` that does not equal the recomputed value is a malformed entry. C4 is never floor-forced; the floor never exceeds `C3`.
|
|
48
|
+
|
|
49
|
+
4. **Non-empty `rationale`.** Each entry's `rationale` must be a non-empty string. A missing, non-string, or empty/whitespace-only `rationale` is a malformed entry.
|
|
50
|
+
|
|
51
|
+
The validator never judges the merit of the assessed band; it checks shape, floor equality, and lower-bound ordering only.
|
|
52
|
+
|
|
53
|
+
## Model-Routing-Receipt Scope and Backward Compatibility
|
|
54
|
+
|
|
55
|
+
These invariants apply only when the checkpoint contains a top-level `model_routing_receipts` array. A checkpoint with no `model_routing_receipts` key (the existing checkpoint shape) is unaffected: it validates exactly as before and produces no new errors. The invariants are additive. Enforcement is the Python validator, not an imported schema.
|
|
56
|
+
|
|
57
|
+
## Invariants (model_routing_receipts array)
|
|
58
|
+
|
|
59
|
+
Each entry records one delegation with the shape `{ agent, phase, complexity_band, fable_policy, table_model, clamped_from | null, model }`.
|
|
60
|
+
|
|
61
|
+
1. **Model equals the resolved model.** Each entry's `model` must equal `resolve_delegation_model(agent, complexity_band, fable_policy)["model"]` (the reference implementation in `scripts/dev_tools/resolve_delegation_model.py`). A `complexity_band` outside `C1`..`C4` is a malformed entry. A `model` that does not equal the resolved model is a malformed entry.
|
|
62
|
+
|
|
63
|
+
2. **Disabled-mode clamp.** Under `fable_policy == "disabled"` no entry's `model` may equal `fable`; any entry whose `table_model == "fable"` must record `clamped_from == "fable"` and `model == "opus"`. A disabled-mode entry that resolves to `fable`, or a disabled-mode `fable` table cell that does not record the clamp, is a malformed entry.
|
|
64
|
+
|
|
65
|
+
## Model-Budget Contract
|
|
66
|
+
|
|
67
|
+
The session `model_budget.fable_policy` switch is a three-way enum `disabled | available | preferred` defined in `config/orchestration-routing.json`, defaulting to `disabled`. It governs only the delegation model tier and is not a route input. `disabled` removes `fable` from the consideration set and clamps `fable` cells to `opus`; `available` applies the base `complexity_to_model` table as-is; `preferred` applies the `preferred_overlay` (which redirects only the C3 cell to `fable` for the overlay agents `atomic-planner`, `prd-feature`, `feature-review`, `task-researcher`) and leaves `atomic-executor` and `pr-author` C3 cells at `opus`. `route` is never an input to model selection.
|
|
68
|
+
|
|
35
69
|
## Enforcement
|
|
36
70
|
|
|
37
71
|
- `scripts/dev_tools/validate_orchestrator_state.py` appends one error per violated invariant when a `remediation_loop` is present, using the existing validator message style (literal, checkpoint-context prefixed). The validator returns a list of error strings and does not mutate its input.
|
|
38
72
|
- `scripts/dev_tools/validate_orchestrator_state.py` likewise appends one error per violated `human_interaction` invariant when a `human_interaction` key is present, using the same literal, checkpoint-context-prefixed message style. The check does not import or read any schema file.
|
|
73
|
+
- `scripts/dev_tools/validate_orchestrator_state.py` appends one error per violated `complexity_assessments` invariant when a `complexity_assessments` key is present, delegating to `scripts/dev_tools/_orchestrator_state_complexity.py`, which recomputes the floor via `compute_complexity_floor`. The check does not import or read any schema file.
|
|
74
|
+
- `scripts/dev_tools/validate_orchestrator_state.py` appends one error per violated `model_routing_receipts` invariant when a `model_routing_receipts` key is present, delegating to `scripts/dev_tools/_orchestrator_state_model_routing.py`, which recomputes the resolved model via `resolve_delegation_model`. The check does not import or read any schema file.
|
|
39
75
|
- The validator is consumed by the MCP tool `validate_orchestration_artifacts`; backward compatibility for existing step-based checkpoints is preserved.
|
|
@@ -31,10 +31,13 @@
|
|
|
31
31
|
"Agent(epic-review)",
|
|
32
32
|
"Agent(status-updater)",
|
|
33
33
|
"Agent(pr-author)",
|
|
34
|
+
"Agent(commit-message)",
|
|
35
|
+
"Agent(human-exception-runbook)",
|
|
34
36
|
"Agent(python-typed-engineer)",
|
|
35
37
|
"Agent(powershell-typed-engineer)",
|
|
36
38
|
"Agent(csharp-typed-engineer)",
|
|
37
39
|
"Agent(typescript-engineer)",
|
|
40
|
+
"Agent(epic-orchestrator)",
|
|
38
41
|
"Skill(orchestrate *)",
|
|
39
42
|
"Skill(commit-message *)",
|
|
40
43
|
"Skill(pr-author *)",
|
|
@@ -87,6 +90,14 @@
|
|
|
87
90
|
{
|
|
88
91
|
"type": "command",
|
|
89
92
|
"command": "pwsh -NoProfile -File .claude/hooks/enforce-orchestration-preimplementation-gate.ps1"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"type": "command",
|
|
96
|
+
"command": "pwsh -NoProfile -File .claude/hooks/enforce-epic-merge-gate.ps1"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"type": "command",
|
|
100
|
+
"command": "pwsh -NoProfile -File .claude/hooks/enforce-epic-worktree-removal-gate.ps1"
|
|
90
101
|
}
|
|
91
102
|
]
|
|
92
103
|
},
|
|
@@ -141,13 +152,17 @@
|
|
|
141
152
|
{
|
|
142
153
|
"type": "command",
|
|
143
154
|
"command": "pwsh -NoProfile -File .claude/hooks/enforce-orchestration-preimplementation-gate.ps1"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"type": "command",
|
|
158
|
+
"command": "pwsh -NoProfile -File .claude/hooks/enforce-epic-wave-barrier.ps1"
|
|
144
159
|
}
|
|
145
160
|
]
|
|
146
161
|
}
|
|
147
162
|
],
|
|
148
163
|
"SubagentStop": [
|
|
149
164
|
{
|
|
150
|
-
"matcher": "atomic-planner|atomic-executor|feature-review|task-researcher|prd-feature|staged-review|epic-review|status-updater|python-typed-engineer|powershell-typed-engineer|csharp-typed-engineer|typescript-engineer",
|
|
165
|
+
"matcher": "atomic-planner|atomic-executor|feature-review|task-researcher|prd-feature|staged-review|epic-review|status-updater|python-typed-engineer|powershell-typed-engineer|csharp-typed-engineer|typescript-engineer|orchestrator|epic-orchestrator",
|
|
151
166
|
"hooks": [
|
|
152
167
|
{
|
|
153
168
|
"type": "command",
|
|
@@ -181,6 +196,24 @@
|
|
|
181
196
|
"command": "pwsh -NoProfile -File .claude/hooks/validate-pr-author-output.ps1"
|
|
182
197
|
}
|
|
183
198
|
]
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"matcher": "orchestrator",
|
|
202
|
+
"hooks": [
|
|
203
|
+
{
|
|
204
|
+
"type": "command",
|
|
205
|
+
"command": "pwsh -NoProfile -File .claude/hooks/validate-orchestrator-output.ps1"
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"matcher": "epic-orchestrator",
|
|
211
|
+
"hooks": [
|
|
212
|
+
{
|
|
213
|
+
"type": "command",
|
|
214
|
+
"command": "pwsh -NoProfile -File .claude/hooks/validate-orchestrator-output.ps1 -CheckpointPath artifacts/orchestration/epic-orchestrator-state.json -ArtifactType epic-orchestrator-state"
|
|
215
|
+
}
|
|
216
|
+
]
|
|
184
217
|
}
|
|
185
218
|
]
|
|
186
219
|
},
|