@danmoisan/drm-copilot-mcp 1.0.13 → 1.0.15

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.
Files changed (131) hide show
  1. package/out/mcp-server.js +3160 -458
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +24 -0
  4. package/resources/claude-customizations/.claude/agents/epic-planner.md +121 -0
  5. package/resources/claude-customizations/.claude/agents/orchestrator.md +2 -2
  6. package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +246 -0
  7. package/resources/claude-customizations/.claude/settings.json +9 -2
  8. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +7 -0
  9. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +189 -0
  10. package/resources/claude-customizations/.claude/skills/epic-run/SKILL.md +38 -0
  11. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +21 -2
  12. package/resources/claude-customizations/pack-manifests/core.json +4 -0
  13. package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +0 -1
  14. package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +100 -0
  15. package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/agents/openai.yaml +4 -0
  16. package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +170 -0
  17. package/resources/codex-and-agents-customizations/.agents/skills/epic-plan/SKILL.md +230 -0
  18. package/resources/codex-and-agents-customizations/.agents/skills/epic-run/SKILL.md +39 -0
  19. package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +0 -1
  20. package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +0 -1
  21. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +89 -9
  22. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +39 -4
  23. package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +0 -1
  24. package/resources/codex-and-agents-customizations/.agents/skills/tonality/SKILL.md +0 -1
  25. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +153 -0
  26. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +153 -0
  27. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +153 -0
  28. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +153 -0
  29. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +153 -0
  30. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +2 -0
  31. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +95 -0
  32. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +95 -0
  33. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +95 -0
  34. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +95 -0
  35. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +95 -0
  36. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +2 -0
  37. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c1.toml +99 -0
  38. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c2.toml +99 -0
  39. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3-elevated.toml +99 -0
  40. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3.toml +99 -0
  41. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c4.toml +99 -0
  42. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer.toml +2 -0
  43. package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +96 -0
  44. package/resources/codex-and-agents-customizations/.codex/agents/epic-planner.toml +97 -0
  45. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c1.toml +62 -0
  46. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c2.toml +62 -0
  47. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3-elevated.toml +62 -0
  48. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3.toml +62 -0
  49. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c4.toml +62 -0
  50. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer.toml +2 -0
  51. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +200 -0
  52. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +200 -0
  53. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +200 -0
  54. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +200 -0
  55. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +200 -0
  56. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +53 -3
  57. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +110 -0
  58. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +110 -0
  59. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +110 -0
  60. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +110 -0
  61. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +110 -0
  62. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +2 -0
  63. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c1.toml +28 -0
  64. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c2.toml +28 -0
  65. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c3-elevated.toml +28 -0
  66. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c3.toml +28 -0
  67. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c4.toml +28 -0
  68. package/resources/codex-and-agents-customizations/.codex/agents/pr-author.toml +2 -0
  69. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c1.toml +55 -0
  70. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c2.toml +55 -0
  71. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c3-elevated.toml +55 -0
  72. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c3.toml +55 -0
  73. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c4.toml +55 -0
  74. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature.toml +2 -0
  75. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +102 -0
  76. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +102 -0
  77. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +102 -0
  78. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +102 -0
  79. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +102 -0
  80. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +2 -0
  81. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c1.toml +105 -0
  82. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c2.toml +105 -0
  83. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3-elevated.toml +105 -0
  84. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3.toml +105 -0
  85. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c4.toml +105 -0
  86. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher.toml +2 -0
  87. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c1.toml +50 -0
  88. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c2.toml +50 -0
  89. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c3-elevated.toml +50 -0
  90. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c3.toml +50 -0
  91. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c4.toml +50 -0
  92. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer.toml +2 -0
  93. package/resources/codex-and-agents-customizations/.codex/config.toml +202 -37
  94. package/resources/codex-and-agents-customizations/.codex/hooks/authorize-root-epic-invocation.ps1 +236 -0
  95. package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +178 -69
  96. package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +92 -19
  97. package/resources/codex-and-agents-customizations/.codex/hooks/codex-agent-profile-attestation.ps1 +199 -0
  98. package/resources/codex-and-agents-customizations/.codex/hooks/codex-authority-store.ps1 +182 -0
  99. package/resources/codex-and-agents-customizations/.codex/hooks/codex-epic-child-launch-attestation.ps1 +129 -0
  100. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +148 -31
  101. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +198 -0
  102. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +28 -19
  103. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +325 -0
  104. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +140 -0
  105. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +292 -0
  106. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-root-invocation.ps1 +135 -0
  107. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-wave-barrier.ps1 +295 -0
  108. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +151 -0
  109. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +99 -34
  110. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +143 -27
  111. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +88 -43
  112. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +65 -20
  113. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +88 -42
  114. package/resources/codex-and-agents-customizations/.codex/hooks/record-subagent-routing-attestation.ps1 +414 -0
  115. package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +164 -51
  116. package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +154 -0
  117. package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +58 -23
  118. package/resources/codex-and-agents-customizations/AGENTS.md +27 -0
  119. package/resources/codex-and-agents-customizations/pack-manifests/core.json +66 -1
  120. package/resources/codex-and-agents-customizations/pack-manifests/csharp-legacy.json +6 -1
  121. package/resources/codex-and-agents-customizations/pack-manifests/csharp-modern.json +6 -1
  122. package/resources/codex-and-agents-customizations/pack-manifests/powershell.json +6 -1
  123. package/resources/codex-and-agents-customizations/pack-manifests/python.json +6 -1
  124. package/resources/codex-and-agents-customizations/pack-manifests/typescript.json +6 -1
  125. package/resources/config/orchestration-routing.json +167 -0
  126. package/resources/powershell/PoshQC/PoshQC.Analyzer.psm1 +1 -1
  127. package/resources/powershell/PoshQC/PoshQC.ScanConfig.psm1 +125 -0
  128. package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +14 -2
  129. package/resources/powershell/PoshQC/PoshQC.psd1 +0 -4
  130. package/resources/powershell/PoshQC/PoshQC.psm1 +26 -6
  131. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +34 -18
@@ -1,12 +1,12 @@
1
+
1
2
  <#
2
3
  .SYNOPSIS
3
- Blocks implementation writes before Issue #232 orchestration readiness exists.
4
+ Blocks implementation operations before orchestration readiness exists.
4
5
  #>
5
6
  [CmdletBinding()]
6
7
  param()
7
8
 
8
9
  $script:CheckpointPath = 'artifacts/orchestration/orchestrator-state.json'
9
- $script:Issue232FeatureFolder = 'docs/features/active/2026-06-24-harden-orchestrate-skill-232'
10
10
 
11
11
  function ConvertFrom-CheckpointJson {
12
12
  [CmdletBinding()]
@@ -29,12 +29,12 @@ function Get-StringProperty {
29
29
  return ([string]$Value.$Name).Trim()
30
30
  }
31
31
 
32
- function Test-DocumentationOrEvidencePath {
32
+ function Test-FeatureDocumentationOrEvidencePath {
33
33
  [CmdletBinding()]
34
34
  [OutputType([bool])]
35
35
  param([Parameter(Mandatory)][string] $NormalizedPath)
36
36
 
37
- return $NormalizedPath.StartsWith($script:Issue232FeatureFolder + '/')
37
+ return $NormalizedPath.StartsWith('docs/features/active/')
38
38
  }
39
39
 
40
40
  function Test-ImplementationPath {
@@ -42,7 +42,7 @@ function Test-ImplementationPath {
42
42
  [OutputType([bool])]
43
43
  param([Parameter(Mandatory)][string] $NormalizedPath)
44
44
 
45
- if (Test-DocumentationOrEvidencePath -NormalizedPath $NormalizedPath) {
45
+ if (Test-FeatureDocumentationOrEvidencePath -NormalizedPath $NormalizedPath) {
46
46
  return $false
47
47
  }
48
48
  if ($NormalizedPath -eq $script:CheckpointPath) {
@@ -51,7 +51,59 @@ function Test-ImplementationPath {
51
51
  return $NormalizedPath -match '\.(py|ps1|psm1|ts|tsx|js|jsx|cs|json|yml|yaml)$'
52
52
  }
53
53
 
54
- function Test-Issue232OrchestrationReady {
54
+ function Test-ImplementationCommand {
55
+ [CmdletBinding()]
56
+ [OutputType([bool])]
57
+ param([Parameter(Mandatory)][string] $Command)
58
+
59
+ $normalizedCommand = $Command.Trim()
60
+ if (-not $normalizedCommand) {
61
+ return $false
62
+ }
63
+
64
+ foreach ($match in [regex]::Matches($normalizedCommand, '(?m)^\*\*\* (?:Add|Update|Delete) File:\s*(?<path>.+?)\s*$')) {
65
+ $path = (([string]$match.Groups['path'].Value).Trim()) -replace '\\', '/'
66
+ if (Test-ImplementationPath -NormalizedPath $path) {
67
+ return $true
68
+ }
69
+ }
70
+ foreach ($match in [regex]::Matches($normalizedCommand, '(?m)^\*\*\* Move to:\s*(?<path>.+?)\s*$')) {
71
+ $path = (([string]$match.Groups['path'].Value).Trim()) -replace '\\', '/'
72
+ if (Test-ImplementationPath -NormalizedPath $path) {
73
+ return $true
74
+ }
75
+ }
76
+
77
+ $implementationCommandPatterns = @(
78
+ '(^|\s)git\s+(add|commit)\b',
79
+ '(^|\s)(poetry\s+run\s+)?(black|ruff|pyright|pytest)\b',
80
+ '(^|\s)npm\s+.*\s+(prettier|lint|typecheck|test:unit)\b',
81
+ '(^|\s)npx\s+(prettier|eslint|tsc|jest)\b',
82
+ '(^|\s)pwsh\s+.*(Invoke-Pester|tests/scripts/)'
83
+ )
84
+
85
+ foreach ($pattern in $implementationCommandPatterns) {
86
+ if ($normalizedCommand -match $pattern) {
87
+ return $true
88
+ }
89
+ }
90
+ return $false
91
+ }
92
+
93
+ function Test-ImplementationDelegation {
94
+ [CmdletBinding()]
95
+ [OutputType([bool])]
96
+ param([Parameter(Mandatory)][AllowNull()] $ToolInput)
97
+
98
+ if ($null -eq $ToolInput) {
99
+ return $false
100
+ }
101
+
102
+ $payloadText = ($ToolInput | ConvertTo-Json -Depth 20 -Compress)
103
+ return $payloadText -match '(python-typed-engineer|powershell-typed-engineer|typescript-engineer|csharp-typed-engineer|atomic-executor|implementation|execute)'
104
+ }
105
+
106
+ function Test-OrchestrationReady {
55
107
  [CmdletBinding()]
56
108
  [OutputType([bool])]
57
109
  param([Parameter(Mandatory)][AllowNull()] $Payload)
@@ -70,9 +122,12 @@ function Test-Issue232OrchestrationReady {
70
122
  $lifecycleReady = [bool]$Payload.lifecycle_ready
71
123
  }
72
124
 
125
+ if (-not $issueNum -or -not $featureFolder -or -not $routeId -or -not $lifecycleReady) {
126
+ return $false
127
+ }
128
+
73
129
  return (
74
- $issueNum -eq '232' -and
75
- $featureFolder -eq $script:Issue232FeatureFolder -and
130
+ $featureFolder.StartsWith('docs/features/active/') -and
76
131
  $routeId -and
77
132
  $lifecycleReady
78
133
  )
@@ -89,6 +144,36 @@ function Get-CheckpointContent {
89
144
  return Get-Content -Raw -LiteralPath $script:CheckpointPath
90
145
  }
91
146
 
147
+ function Get-OrchestrationPreimplementationGateAllowDecision {
148
+ [CmdletBinding()]
149
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
150
+ param()
151
+
152
+ return [ordered]@{
153
+ hookSpecificOutput = [ordered]@{
154
+ hookEventName = 'PreToolUse'
155
+ permissionDecision = 'allow'
156
+ }
157
+ }
158
+ }
159
+
160
+ function Get-OrchestrationPreimplementationGateBlockDecision {
161
+ [CmdletBinding()]
162
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
163
+ param(
164
+ [Parameter(Mandatory)]
165
+ [string] $Reason
166
+ )
167
+
168
+ return [ordered]@{
169
+ hookSpecificOutput = [ordered]@{
170
+ hookEventName = 'PreToolUse'
171
+ permissionDecision = 'deny'
172
+ permissionDecisionReason = $Reason
173
+ }
174
+ }
175
+ }
176
+
92
177
  function Invoke-OrchestrationPreimplementationGateDecision {
93
178
  [CmdletBinding()]
94
179
  [OutputType([System.Collections.Specialized.OrderedDictionary])]
@@ -98,21 +183,30 @@ function Invoke-OrchestrationPreimplementationGateDecision {
98
183
  )
99
184
 
100
185
  if (-not $ToolInputRaw) {
101
- return [ordered]@{ decision = 'allow' }
186
+ return Get-OrchestrationPreimplementationGateAllowDecision
102
187
  }
103
188
  try {
104
189
  $toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
105
190
  } catch {
106
- throw "enforce-orchestration-preimplementation-gate hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
191
+ throw "enforce-orchestration-preimplementation-gate received malformed mapped tool_input JSON: $_"
107
192
  }
108
193
 
109
- $filePath = $toolInput.file_path
110
- if (-not $filePath) {
111
- return [ordered]@{ decision = 'allow' }
194
+ $requiresReadyCheckpoint = $false
195
+ $filePath = Get-StringProperty -Value $toolInput -Name 'file_path'
196
+ if ($filePath) {
197
+ $normalized = ([string]$filePath) -replace '\\', '/'
198
+ $requiresReadyCheckpoint = Test-ImplementationPath -NormalizedPath $normalized
199
+ } else {
200
+ $command = Get-StringProperty -Value $toolInput -Name 'command'
201
+ if ($command) {
202
+ $requiresReadyCheckpoint = Test-ImplementationCommand -Command $command
203
+ } else {
204
+ $requiresReadyCheckpoint = Test-ImplementationDelegation -ToolInput $toolInput
205
+ }
112
206
  }
113
- $normalized = ([string]$filePath) -replace '\\', '/'
114
- if (-not (Test-ImplementationPath -NormalizedPath $normalized)) {
115
- return [ordered]@{ decision = 'allow' }
207
+
208
+ if (-not $requiresReadyCheckpoint) {
209
+ return Get-OrchestrationPreimplementationGateAllowDecision
116
210
  }
117
211
 
118
212
  if (-not $CheckpointRaw) {
@@ -124,13 +218,32 @@ function Invoke-OrchestrationPreimplementationGateDecision {
124
218
  $checkpoint = $null
125
219
  }
126
220
 
127
- if (Test-Issue232OrchestrationReady -Payload $checkpoint) {
128
- return [ordered]@{ decision = 'allow' }
221
+ if (Test-OrchestrationReady -Payload $checkpoint) {
222
+ return Get-OrchestrationPreimplementationGateAllowDecision
129
223
  }
130
- return [ordered]@{
131
- decision = 'block'
132
- reason = 'PREIMPLEMENTATION_GATE_BLOCKED: Issue #232 implementation writes require artifacts/orchestration/orchestrator-state.json to contain route metadata, lifecycle readiness, and checkpoint state before implementation begins.'
224
+ return Get-OrchestrationPreimplementationGateBlockDecision -Reason 'PREIMPLEMENTATION_GATE_BLOCKED: Implementation operations require artifacts/orchestration/orchestrator-state.json to contain issue number, feature folder, route metadata, lifecycle readiness, and checkpoint state before implementation begins.'
225
+ }
226
+
227
+ function ConvertFrom-CodexPreimplementationHookPayload {
228
+ [CmdletBinding()]
229
+ param([Parameter(Mandatory)][string] $PayloadRaw)
230
+
231
+ if ([string]::IsNullOrWhiteSpace($PayloadRaw)) {
232
+ throw 'enforce-orchestration-preimplementation-gate hook input is empty.'
133
233
  }
234
+ try {
235
+ $payload = $PayloadRaw | ConvertFrom-Json -ErrorAction Stop
236
+ } catch {
237
+ throw "enforce-orchestration-preimplementation-gate hook input is malformed JSON: $_"
238
+ }
239
+ if ($payload.PSObject.Properties.Name -notcontains 'tool_input' -or $null -eq $payload.tool_input) {
240
+ throw 'enforce-orchestration-preimplementation-gate hook input is missing tool_input.'
241
+ }
242
+ if ([string]$payload.hook_event_name -ne 'PreToolUse' -or
243
+ @('Bash', 'apply_patch') -notcontains [string]$payload.tool_name) {
244
+ throw 'enforce-orchestration-preimplementation-gate requires a supported PreToolUse payload.'
245
+ }
246
+ return $payload
134
247
  }
135
248
 
136
249
  if ($MyInvocation.InvocationName -eq '.') {
@@ -138,11 +251,14 @@ if ($MyInvocation.InvocationName -eq '.') {
138
251
  }
139
252
 
140
253
  try {
141
- $decision = Invoke-OrchestrationPreimplementationGateDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
254
+ $payload = ConvertFrom-CodexPreimplementationHookPayload -PayloadRaw ([Console]::In.ReadToEnd())
255
+ $toolInputRaw = $payload.tool_input | ConvertTo-Json -Compress -Depth 20
256
+ $decision = Invoke-OrchestrationPreimplementationGateDecision -ToolInputRaw $toolInputRaw
257
+ if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
258
+ $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
259
+ }
260
+ exit 0
142
261
  } catch {
143
- Write-Error $_
144
- exit 1
262
+ [Console]::Error.WriteLine([string]$_)
263
+ exit 2
145
264
  }
146
-
147
- $decision | ConvertTo-Json -Compress | Write-Output
148
- exit 0
@@ -1,20 +1,18 @@
1
- # Converted hook
2
- # Review the generated hook behavior before enabling it.
3
1
 
4
2
  <#
5
3
  .SYNOPSIS
6
4
  Pre-tool-use hook that enforces the PowerShell per-batch change budget.
7
5
 
8
6
  .DESCRIPTION
9
- This script is invoked by the Claude Code PreToolUse hook before any Write or Edit
7
+ This script is invoked by the Codex PreToolUse hook before any Write or Edit
10
8
  operation. When the target file is a PowerShell source file (.ps1, .psm1, .psd1),
11
9
  it classifies the file as either production or test and checks the running count
12
10
  against the per-batch cap:
13
11
  - 3 production PowerShell files per batch
14
12
  - 3 test PowerShell files per batch
15
13
 
16
- A "batch" is scoped to the current Claude Code session. The running count is
17
- persisted under .claude/state/powershell-batch-budget.<session_id>.json. Only
14
+ A "batch" is scoped to the current Codex session. The running count is
15
+ persisted under .codex/state/powershell-batch-budget.<session_id>.json. Only
18
16
  distinct file paths are counted; repeated edits to the same file consume one slot.
19
17
 
20
18
  Test files are those matching:
@@ -24,15 +22,13 @@
24
22
  All other .ps1/.psm1/.psd1 files are treated as production files. Non-PowerShell
25
23
  paths pass through.
26
24
 
27
- The cap may be overridden per session by setting the environment variable
28
- CLAUDE_POWERSHELL_BUDGET_PROD or CLAUDE_POWERSHELL_BUDGET_TEST to a positive integer
29
- before the session starts, or by writing {"prodCap": N, "testCap": M} into the
30
- state file.
25
+ An approved per-session override is recorded by writing
26
+ {"prodCap": N, "testCap": M} into the Codex state file.
31
27
 
32
- When the cap would be exceeded by a new file, the script emits a JSON response with
33
- 'decision': 'block' and exits 0. The session must explicitly reset the counter by
34
- deleting the state file before starting a new batch. Files already counted are
35
- always allowed through.
28
+ When the cap would be exceeded by a new file, the script emits a PreToolUse JSON
29
+ response with hookSpecificOutput.permissionDecision = 'deny' and exits 0. The session
30
+ must explicitly reset the counter by deleting the state file before starting a new
31
+ batch. Files already counted are always allowed through.
36
32
 
37
33
  .NOTES
38
34
  Compatible with PowerShell 7+.
@@ -93,8 +89,11 @@ function Get-PowerShellBatchBudgetBlockDecision {
93
89
  )
94
90
 
95
91
  $decision = [ordered]@{
96
- decision = 'block'
97
- reason = $Reason
92
+ hookSpecificOutput = [ordered]@{
93
+ hookEventName = 'PreToolUse'
94
+ permissionDecision = 'deny'
95
+ permissionDecisionReason = $Reason
96
+ }
98
97
  }
99
98
  if ($State) {
100
99
  $decision.state = $State
@@ -119,7 +118,7 @@ function Invoke-PowerShellBatchBudgetDecision {
119
118
 
120
119
  $normalized = $FilePath -replace '\\', '/'
121
120
  if ($normalized -notmatch '\.(ps1|psm1|psd1)$') {
122
- return [ordered]@{ decision = 'allow'; state = $State; shouldWriteState = $false }
121
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
123
122
  }
124
123
 
125
124
  $isTestFile = ($normalized -match '(^|/)tests/.*\.ps1$') -or ($normalized -match '\.Tests\.ps1$')
@@ -128,13 +127,12 @@ function Invoke-PowerShellBatchBudgetDecision {
128
127
  $kind = if ($isTestFile) { 'test' } else { 'production' }
129
128
 
130
129
  if ($targetList -contains $normalized) {
131
- return [ordered]@{ decision = 'allow'; state = $State; shouldWriteState = $false }
130
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
132
131
  }
133
132
 
134
133
  if ($targetList.Count -ge $cap) {
135
134
  $currentFiles = ($targetList -join ', ')
136
- $kindUpper = $kind.ToUpperInvariant()
137
- $reason = "PowerShell per-batch budget exceeded: $kind file cap is $cap and is already full ($currentFiles). Requested new file: $normalized. Split the work into a new batch, raise the cap via CLAUDE_POWERSHELL_BUDGET_$kindUpper environment variable with approved scope, or reset the batch by deleting $StateFile."
135
+ $reason = "PowerShell per-batch budget exceeded: $kind file cap is $cap and is already full ($currentFiles). Requested new file: $normalized. Split the work into a new batch, record an approved cap in $StateFile, or reset the batch by deleting that state file."
138
136
  return Get-PowerShellBatchBudgetBlockDecision -Reason $reason -State $State
139
137
  }
140
138
 
@@ -144,7 +142,7 @@ function Invoke-PowerShellBatchBudgetDecision {
144
142
  $State.prodFiles = @($State.prodFiles) + @($normalized)
145
143
  }
146
144
 
147
- return [ordered]@{ decision = 'allow'; state = $State; shouldWriteState = $true }
145
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $true }
148
146
  }
149
147
 
150
148
  function Invoke-PowerShellBatchBudgetHook {
@@ -166,26 +164,26 @@ function Invoke-PowerShellBatchBudgetHook {
166
164
  )
167
165
 
168
166
  if (-not $ToolInputRaw) {
169
- return [ordered]@{ decision = 'allow' }
167
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
170
168
  }
171
169
 
172
170
  try {
173
171
  $toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
174
172
  } catch {
175
- return Get-PowerShellBatchBudgetBlockDecision -Reason 'PowerShell batch-budget hook received malformed JSON in CLAUDE_TOOL_INPUT.'
173
+ return Get-PowerShellBatchBudgetBlockDecision -Reason 'PowerShell batch-budget hook received malformed JSON in Codex tool_input.'
176
174
  }
177
175
 
178
176
  $filePath = $toolInput.file_path
179
177
  if (-not $filePath) {
180
- return [ordered]@{ decision = 'allow' }
178
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
181
179
  }
182
180
 
183
181
  $normalized = $filePath -replace '\\', '/'
184
182
  if ($normalized -notmatch '\.(ps1|psm1|psd1)$') {
185
- return [ordered]@{ decision = 'allow' }
183
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
186
184
  }
187
185
 
188
- $stateDir = Join-Path -Path $Root -ChildPath '.claude/state'
186
+ $stateDir = Join-Path -Path $Root -ChildPath '.codex/state'
189
187
  if (-not (& $TestPathExists $stateDir)) {
190
188
  & $EnsureDirectory $stateDir
191
189
  }
@@ -214,28 +212,75 @@ function Invoke-PowerShellBatchBudgetHook {
214
212
  return $decision
215
213
  }
216
214
 
217
- if ($MyInvocation.InvocationName -eq '.') {
218
- return
219
- }
215
+ function ConvertFrom-CodexPowerShellBudgetPayload {
216
+ [CmdletBinding()]
217
+ param([Parameter(Mandatory)][string] $PayloadRaw)
220
218
 
221
- $sessionId = $env:CLAUDE_SESSION_ID
222
- if (-not $sessionId) {
223
- $sessionId = 'default'
219
+ if ([string]::IsNullOrWhiteSpace($PayloadRaw)) {
220
+ throw 'enforce-powershell-batch-budget hook input is empty.'
221
+ }
222
+ try {
223
+ $payload = $PayloadRaw | ConvertFrom-Json -ErrorAction Stop
224
+ } catch {
225
+ throw "enforce-powershell-batch-budget hook input is malformed JSON: $_"
226
+ }
227
+ if ($payload.PSObject.Properties.Name -notcontains 'tool_input' -or $null -eq $payload.tool_input) {
228
+ throw 'enforce-powershell-batch-budget hook input is missing tool_input.'
229
+ }
230
+ if ([string]$payload.hook_event_name -ne 'PreToolUse' -or [string]$payload.tool_name -ne 'apply_patch') {
231
+ throw 'enforce-powershell-batch-budget requires a PreToolUse apply_patch payload.'
232
+ }
233
+ if ([string]::IsNullOrWhiteSpace([string]$payload.session_id)) {
234
+ throw 'enforce-powershell-batch-budget hook input is missing session_id.'
235
+ }
236
+ return $payload
224
237
  }
225
238
 
226
- $prodCap = 3
227
- $testCap = 3
228
- if ($env:CLAUDE_POWERSHELL_BUDGET_PROD -match '^\d+$') {
229
- $prodCap = [int]$env:CLAUDE_POWERSHELL_BUDGET_PROD
230
- }
231
- if ($env:CLAUDE_POWERSHELL_BUDGET_TEST -match '^\d+$') {
232
- $testCap = [int]$env:CLAUDE_POWERSHELL_BUDGET_TEST
239
+ function Get-CodexPowerShellBudgetPath {
240
+ [CmdletBinding()]
241
+ [OutputType([string])]
242
+ param([Parameter(Mandatory)] $Payload)
243
+
244
+ if ($Payload.tool_input.PSObject.Properties.Name -contains 'file_path') {
245
+ return [string]$Payload.tool_input.file_path
246
+ }
247
+ $command = [string]$Payload.tool_input.command
248
+ if ([string]::IsNullOrWhiteSpace($command)) {
249
+ throw 'enforce-powershell-batch-budget cannot map tool_input to a file edit.'
250
+ }
251
+ [string[]] $paths = @(
252
+ [regex]::Matches($command, '(?m)^\*\*\* (?:(?:Add|Update|Delete) File|Move to):\s*(?<path>.+?)\s*$') |
253
+ ForEach-Object { ([string]$_.Groups['path'].Value).Trim() }
254
+ )
255
+ $paths = @($paths | Select-Object -Unique)
256
+ if ($paths.Count -eq 0) {
257
+ throw 'enforce-powershell-batch-budget received an unrecognized apply_patch command.'
258
+ }
259
+ return $paths
233
260
  }
234
261
 
235
- $decision = Invoke-PowerShellBatchBudgetHook -ToolInputRaw $env:CLAUDE_TOOL_INPUT -SessionId $sessionId -ProdCap $prodCap -TestCap $testCap
236
- if ($decision.decision -eq 'block') {
237
- $decision.Remove('state')
238
- $decision | ConvertTo-Json -Compress | Write-Output
262
+ if ($MyInvocation.InvocationName -eq '.') {
263
+ return
239
264
  }
240
265
 
241
- exit 0
266
+ try {
267
+ $payload = ConvertFrom-CodexPowerShellBudgetPayload -PayloadRaw ([Console]::In.ReadToEnd())
268
+ $sessionId = ([string]$payload.session_id) -replace '[^A-Za-z0-9._-]', '_'
269
+ $repositoryRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
270
+ $prodCap = 3
271
+ $testCap = 3
272
+
273
+ foreach ($path in @(Get-CodexPowerShellBudgetPath -Payload $payload)) {
274
+ $toolInputRaw = @{ file_path = $path } | ConvertTo-Json -Compress
275
+ $decision = Invoke-PowerShellBatchBudgetHook -ToolInputRaw $toolInputRaw -SessionId $sessionId -Root $repositoryRoot -ProdCap $prodCap -TestCap $testCap
276
+ if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
277
+ $decision.Remove('state')
278
+ $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
279
+ exit 0
280
+ }
281
+ }
282
+ exit 0
283
+ } catch {
284
+ [Console]::Error.WriteLine([string]$_)
285
+ exit 2
286
+ }
@@ -1,11 +1,11 @@
1
+
1
2
  <#
2
3
  .SYNOPSIS
3
4
  Pre-tool-use hook that blocks Bash promotion-script bypass attempts.
4
5
 
5
6
  .DESCRIPTION
6
- This script is invoked by the Claude Code PreToolUse hook before any Bash
7
- command runs. It reads the tool input from the CLAUDE_TOOL_INPUT environment
8
- variable, inspects the attempted command text, and blocks direct promotion
7
+ This script is invoked by the Codex PreToolUse hook before a shell command runs.
8
+ It reads one Codex JSON object from stdin, inspects tool_input.command, and blocks direct promotion
9
9
  script execution that would bypass the repository's MCP-only promotion path.
10
10
 
11
11
  Forbidden command tokens (legacy promotion-script bypass):
@@ -70,7 +70,7 @@ function Get-PromotionBypassReason {
70
70
  Returns the gh-CLI issue creation reason when a forbidden gh pattern is matched.
71
71
  Returns $null when the command is allowed.
72
72
  .PARAMETER CommandText
73
- The Bash command text extracted from CLAUDE_TOOL_INPUT.
73
+ The shell command text extracted from the Codex tool_input object.
74
74
  .OUTPUTS
75
75
  System.String or $null.
76
76
  #>
@@ -118,7 +118,7 @@ function Test-PromotionBypassToken {
118
118
  .SYNOPSIS
119
119
  Return $true when a Bash command contains a forbidden promotion bypass pattern.
120
120
  .PARAMETER CommandText
121
- The Bash command text extracted from CLAUDE_TOOL_INPUT.
121
+ The shell command text extracted from the Codex tool_input object.
122
122
  .OUTPUTS
123
123
  System.Boolean
124
124
  #>
@@ -153,17 +153,39 @@ function Get-PromotionMcpOnlyBlockDecision {
153
153
  }
154
154
 
155
155
  return [ordered]@{
156
- decision = 'block'
157
- reason = $Reason
156
+ hookSpecificOutput = [ordered]@{
157
+ hookEventName = 'PreToolUse'
158
+ permissionDecision = 'deny'
159
+ permissionDecisionReason = $Reason
160
+ }
161
+ }
162
+ }
163
+
164
+ function Get-PromotionMcpOnlyAllowDecision {
165
+ <#
166
+ .SYNOPSIS
167
+ Construct the structured allow decision for a permitted Bash command.
168
+ .OUTPUTS
169
+ System.Collections.Specialized.OrderedDictionary
170
+ #>
171
+ [CmdletBinding()]
172
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
173
+ param()
174
+
175
+ return [ordered]@{
176
+ hookSpecificOutput = [ordered]@{
177
+ hookEventName = 'PreToolUse'
178
+ permissionDecision = 'allow'
179
+ }
158
180
  }
159
181
  }
160
182
 
161
183
  function Invoke-PromotionMcpOnlyDecision {
162
184
  <#
163
185
  .SYNOPSIS
164
- Parse CLAUDE_TOOL_INPUT and return an allow-or-block decision.
186
+ Parse the mapped Codex tool_input JSON and return an allow-or-deny decision.
165
187
  .PARAMETER ToolInputRaw
166
- The raw JSON tool payload supplied by Claude Code.
188
+ The mapped tool_input JSON supplied by Codex.
167
189
  .OUTPUTS
168
190
  System.Collections.Specialized.OrderedDictionary
169
191
  .NOTES
@@ -177,18 +199,18 @@ function Invoke-PromotionMcpOnlyDecision {
177
199
  )
178
200
 
179
201
  if (-not $ToolInputRaw) {
180
- return [ordered]@{ decision = 'allow' }
202
+ return Get-PromotionMcpOnlyAllowDecision
181
203
  }
182
204
 
183
205
  try {
184
206
  $toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
185
207
  } catch {
186
- throw "enforce-promotion-mcp-only hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
208
+ throw "enforce-promotion-mcp-only received malformed mapped tool_input JSON: $_"
187
209
  }
188
210
 
189
211
  $commandText = $toolInput.command
190
212
  if (-not $commandText) {
191
- return [ordered]@{ decision = 'allow' }
213
+ return Get-PromotionMcpOnlyAllowDecision
192
214
  }
193
215
 
194
216
  $reason = Get-PromotionBypassReason -CommandText $commandText
@@ -196,7 +218,28 @@ function Invoke-PromotionMcpOnlyDecision {
196
218
  return Get-PromotionMcpOnlyBlockDecision -Reason $reason
197
219
  }
198
220
 
199
- return [ordered]@{ decision = 'allow' }
221
+ return Get-PromotionMcpOnlyAllowDecision
222
+ }
223
+
224
+ function ConvertFrom-CodexPromotionHookPayload {
225
+ [CmdletBinding()]
226
+ param([Parameter(Mandatory)][string] $PayloadRaw)
227
+
228
+ if ([string]::IsNullOrWhiteSpace($PayloadRaw)) {
229
+ throw 'enforce-promotion-mcp-only hook input is empty.'
230
+ }
231
+ try {
232
+ $payload = $PayloadRaw | ConvertFrom-Json -ErrorAction Stop
233
+ } catch {
234
+ throw "enforce-promotion-mcp-only hook input is malformed JSON: $_"
235
+ }
236
+ if ($payload.PSObject.Properties.Name -notcontains 'tool_input' -or $null -eq $payload.tool_input) {
237
+ throw 'enforce-promotion-mcp-only hook input is missing tool_input.'
238
+ }
239
+ if ([string]$payload.hook_event_name -ne 'PreToolUse' -or [string]$payload.tool_name -ne 'Bash') {
240
+ throw 'enforce-promotion-mcp-only requires a PreToolUse Bash payload.'
241
+ }
242
+ return $payload
200
243
  }
201
244
 
202
245
  # Allow dot-sourcing in tests without executing the entrypoint.
@@ -205,12 +248,14 @@ if ($MyInvocation.InvocationName -eq '.') {
205
248
  }
206
249
 
207
250
  try {
208
- $decision = Invoke-PromotionMcpOnlyDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
251
+ $payload = ConvertFrom-CodexPromotionHookPayload -PayloadRaw ([Console]::In.ReadToEnd())
252
+ $toolInputRaw = $payload.tool_input | ConvertTo-Json -Compress -Depth 20
253
+ $decision = Invoke-PromotionMcpOnlyDecision -ToolInputRaw $toolInputRaw
254
+ if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
255
+ $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
256
+ }
257
+ exit 0
209
258
  } catch {
210
- Write-Error $_
211
- exit 1
259
+ [Console]::Error.WriteLine([string]$_)
260
+ exit 2
212
261
  }
213
-
214
- $decision | ConvertTo-Json -Compress | Write-Output
215
-
216
- exit 0