@danmoisan/drm-copilot-mcp 1.0.18 → 1.0.20
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 +22 -2
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/atomic-executor.md +2 -2
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +14 -6
- package/resources/claude-customizations/.claude/lib/model-routing/ModelRouting.psm1 +34 -14
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +30 -18
- package/resources/claude-customizations/.claude/rules/general-code-change.md +1 -1
- package/resources/claude-customizations/.claude/rules/general-unit-test.md +2 -2
- package/resources/claude-customizations/.claude/rules/typescript.md +5 -5
- package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +9 -65
- package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +9 -65
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-pretooluse-file-mapping.ps1 +474 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +20 -101
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +16 -3
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +14 -5
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +23 -5
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +30 -52
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +28 -27
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +19 -49
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +19 -49
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +1 -0
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +21 -1
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +19 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-pr-author-skill.ps1 +0 -500
package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1
CHANGED
|
@@ -245,6 +245,28 @@ function Invoke-EpicPlanningOnlyDecision {
|
|
|
245
245
|
return Get-EpicPlanningDenyDecision -Reason "tool '$toolName' is not classified for preparation mode."
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
+
function Invoke-EpicPlanningGit {
|
|
249
|
+
[CmdletBinding()]
|
|
250
|
+
param([Parameter(Mandatory)][string[]] $GitArgs)
|
|
251
|
+
|
|
252
|
+
return & git @GitArgs 2>$null
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function Get-EpicPlanningCurrentBranch {
|
|
256
|
+
[CmdletBinding()]
|
|
257
|
+
[OutputType([string])]
|
|
258
|
+
param([Parameter(Mandatory)][string] $RepositoryRoot)
|
|
259
|
+
|
|
260
|
+
$currentBranch = [string](Invoke-EpicPlanningGit -GitArgs @('-C', $RepositoryRoot, 'branch', '--show-current'))
|
|
261
|
+
if ($LASTEXITCODE -ne 0) {
|
|
262
|
+
throw 'EPIC_PLANNING_ONLY_BLOCKED: current branch could not be resolved before push.'
|
|
263
|
+
}
|
|
264
|
+
if ([string]::IsNullOrWhiteSpace($currentBranch)) {
|
|
265
|
+
return ''
|
|
266
|
+
}
|
|
267
|
+
return $currentBranch.Trim()
|
|
268
|
+
}
|
|
269
|
+
|
|
248
270
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
249
271
|
return
|
|
250
272
|
}
|
|
@@ -270,11 +292,7 @@ try {
|
|
|
270
292
|
}
|
|
271
293
|
if ([string]$payload.tool_name -eq 'Bash' -and
|
|
272
294
|
[string]$payload.tool_input.command -match '^\s*git\s+push\b') {
|
|
273
|
-
$currentBranch =
|
|
274
|
-
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($currentBranch)) {
|
|
275
|
-
throw 'EPIC_PLANNING_ONLY_BLOCKED: current branch could not be resolved before push.'
|
|
276
|
-
}
|
|
277
|
-
$currentBranch = $currentBranch.Trim()
|
|
295
|
+
$currentBranch = Get-EpicPlanningCurrentBranch -RepositoryRoot $repositoryRoot
|
|
278
296
|
}
|
|
279
297
|
$decision = Invoke-EpicPlanningOnlyDecision `
|
|
280
298
|
-PayloadRaw $payloadRaw `
|
package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1
CHANGED
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
|
|
31
31
|
If the file_path resolves to a forbidden prefix, the script writes a PreToolUse JSON
|
|
32
32
|
response to stdout with hookSpecificOutput.permissionDecision = 'deny' and exits with
|
|
33
|
-
code 0 so Codex surfaces the reason.
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
code 0 so Codex surfaces the reason. Allowed paths produce NO stdout at all: the
|
|
34
|
+
script allows silently and exits 0. On hard failure (empty, malformed, or
|
|
35
|
+
tool_input-less stdin) the script writes the reason to stderr and exits 2.
|
|
36
36
|
|
|
37
37
|
.NOTES
|
|
38
38
|
Compatible with PowerShell 7+.
|
|
@@ -41,6 +41,10 @@
|
|
|
41
41
|
[CmdletBinding()]
|
|
42
42
|
param()
|
|
43
43
|
|
|
44
|
+
# Shared Codex PreToolUse transport: stdin payload parsing and tool_input-to-file
|
|
45
|
+
# mapping for every tool name the ^(apply_patch|Edit|Write)$ matcher admits.
|
|
46
|
+
. (Join-Path $PSScriptRoot 'codex-pretooluse-file-mapping.ps1')
|
|
47
|
+
|
|
44
48
|
function Test-EvidenceLocationForbidden {
|
|
45
49
|
<#
|
|
46
50
|
.SYNOPSIS
|
|
@@ -142,60 +146,34 @@ function Invoke-EvidenceLocationDecision {
|
|
|
142
146
|
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
143
147
|
}
|
|
144
148
|
|
|
145
|
-
function ConvertFrom-CodexEvidenceLocationPayload {
|
|
146
|
-
[CmdletBinding()]
|
|
147
|
-
param([Parameter(Mandatory)][string] $PayloadRaw)
|
|
148
|
-
|
|
149
|
-
if ([string]::IsNullOrWhiteSpace($PayloadRaw)) {
|
|
150
|
-
throw 'enforce-evidence-locations hook input is empty.'
|
|
151
|
-
}
|
|
152
|
-
try {
|
|
153
|
-
$payload = $PayloadRaw | ConvertFrom-Json -ErrorAction Stop
|
|
154
|
-
} catch {
|
|
155
|
-
throw "enforce-evidence-locations hook input is malformed JSON: $_"
|
|
156
|
-
}
|
|
157
|
-
if ($payload.PSObject.Properties.Name -notcontains 'tool_input' -or $null -eq $payload.tool_input) {
|
|
158
|
-
throw 'enforce-evidence-locations hook input is missing tool_input.'
|
|
159
|
-
}
|
|
160
|
-
if ([string]$payload.hook_event_name -ne 'PreToolUse' -or [string]$payload.tool_name -ne 'apply_patch') {
|
|
161
|
-
throw 'enforce-evidence-locations requires a PreToolUse apply_patch payload.'
|
|
162
|
-
}
|
|
163
|
-
return $payload
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function Get-CodexEvidenceLocationPath {
|
|
167
|
-
[CmdletBinding()]
|
|
168
|
-
[OutputType([string])]
|
|
169
|
-
param([Parameter(Mandatory)] $Payload)
|
|
170
|
-
|
|
171
|
-
if ($Payload.tool_input.PSObject.Properties.Name -contains 'file_path') {
|
|
172
|
-
return [string]$Payload.tool_input.file_path
|
|
173
|
-
}
|
|
174
|
-
$command = [string]$Payload.tool_input.command
|
|
175
|
-
if ([string]::IsNullOrWhiteSpace($command)) {
|
|
176
|
-
throw 'enforce-evidence-locations cannot map tool_input to a file edit.'
|
|
177
|
-
}
|
|
178
|
-
[string[]] $paths = @(
|
|
179
|
-
[regex]::Matches($command, '(?m)^\*\*\* (?:(?:Add|Update|Delete) File|Move to):\s*(?<path>.+?)\s*$') |
|
|
180
|
-
ForEach-Object { ([string]$_.Groups['path'].Value).Trim() }
|
|
181
|
-
)
|
|
182
|
-
$paths = @($paths | Select-Object -Unique)
|
|
183
|
-
if ($paths.Count -eq 0) {
|
|
184
|
-
throw 'enforce-evidence-locations received an unrecognized apply_patch command.'
|
|
185
|
-
}
|
|
186
|
-
foreach ($path in $paths) {
|
|
187
|
-
Write-Output ([string]$path)
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
149
|
function Invoke-EvidenceLocationEntryPoint {
|
|
150
|
+
<#
|
|
151
|
+
.SYNOPSIS
|
|
152
|
+
Reads one Codex PreToolUse payload and returns the process exit code.
|
|
153
|
+
.PARAMETER PayloadRaw
|
|
154
|
+
The raw stdin text. AllowEmptyString is required so empty input reaches
|
|
155
|
+
the shared parser and fails closed with exit 2, rather than raising a
|
|
156
|
+
parameter-binding error that would skip the exit and allow silently.
|
|
157
|
+
#>
|
|
192
158
|
[CmdletBinding()]
|
|
193
159
|
[OutputType([int])]
|
|
194
|
-
param([Parameter(Mandatory)][string] $PayloadRaw)
|
|
160
|
+
param([Parameter(Mandatory)][AllowEmptyString()][string] $PayloadRaw)
|
|
195
161
|
|
|
196
162
|
try {
|
|
197
|
-
$payload = ConvertFrom-
|
|
198
|
-
|
|
163
|
+
$payload = ConvertFrom-CodexPreToolUsePayload -PayloadRaw $PayloadRaw -HookName 'enforce-evidence-locations'
|
|
164
|
+
|
|
165
|
+
# Both sides of a rename are evaluated, matching the pre-fix path scan
|
|
166
|
+
# that collected Add/Update/Delete targets and Move destinations alike.
|
|
167
|
+
# A well-formed payload that maps to no file yields no paths, so the loop
|
|
168
|
+
# body never runs and the hook allows silently.
|
|
169
|
+
$paths = @(
|
|
170
|
+
@(ConvertTo-CodexFileEditInput -Payload $payload) |
|
|
171
|
+
ForEach-Object { $_.source_path; $_.file_path } |
|
|
172
|
+
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
|
|
173
|
+
Select-Object -Unique
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
foreach ($path in $paths) {
|
|
199
177
|
$toolInputRaw = @{ file_path = $path } | ConvertTo-Json -Compress
|
|
200
178
|
$decision = Invoke-EvidenceLocationDecision -ToolInputRaw $toolInputRaw
|
|
201
179
|
if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
[CmdletBinding()]
|
|
7
7
|
param()
|
|
8
8
|
|
|
9
|
+
# Shared Codex PreToolUse transport: stdin payload parsing and tool_input-to-file
|
|
10
|
+
# mapping for every tool name the ^(apply_patch|Edit|Write)$ matcher admits.
|
|
11
|
+
. (Join-Path $PSScriptRoot 'codex-pretooluse-file-mapping.ps1')
|
|
12
|
+
|
|
9
13
|
$script:CheckpointPath = 'artifacts/orchestration/orchestrator-state.json'
|
|
10
14
|
|
|
11
15
|
function ConvertFrom-CheckpointJson {
|
|
@@ -224,38 +228,35 @@ function Invoke-OrchestrationPreimplementationGateDecision {
|
|
|
224
228
|
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
229
|
}
|
|
226
230
|
|
|
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.'
|
|
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
|
|
247
|
-
}
|
|
248
|
-
|
|
249
231
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
250
232
|
return
|
|
251
233
|
}
|
|
252
234
|
|
|
253
235
|
try {
|
|
254
|
-
$payload = ConvertFrom-
|
|
255
|
-
$
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
236
|
+
$payload = ConvertFrom-CodexPreToolUsePayload -PayloadRaw ([Console]::In.ReadToEnd()) -HookName 'enforce-orchestration-preimplementation-gate'
|
|
237
|
+
$toolName = [string]$payload.tool_name
|
|
238
|
+
|
|
239
|
+
# Bash and apply_patch take the pre-fix path unchanged: the raw tool_input is
|
|
240
|
+
# serialized and evaluated by the untouched decision function, so every
|
|
241
|
+
# allow/deny outcome those two tool names produce today is preserved exactly.
|
|
242
|
+
if (@('Bash', 'apply_patch') -contains $toolName) {
|
|
243
|
+
$decision = Invoke-OrchestrationPreimplementationGateDecision -ToolInputRaw ($payload.tool_input | ConvertTo-Json -Compress -Depth 20)
|
|
244
|
+
if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
245
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
246
|
+
}
|
|
247
|
+
exit 0
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
# Edit and Write map to file paths through the shared module; each mapped
|
|
251
|
+
# path enters the same untouched Test-ImplementationPath decision flow. Any
|
|
252
|
+
# other well-formed tool name maps to no records, so the hook allows.
|
|
253
|
+
foreach ($toolInput in @(ConvertTo-CodexFileEditInput -Payload $payload)) {
|
|
254
|
+
$mappedRaw = @{ file_path = [string]$toolInput.file_path } | ConvertTo-Json -Compress
|
|
255
|
+
$decision = Invoke-OrchestrationPreimplementationGateDecision -ToolInputRaw $mappedRaw
|
|
256
|
+
if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
257
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
258
|
+
exit 0
|
|
259
|
+
}
|
|
259
260
|
}
|
|
260
261
|
exit 0
|
|
261
262
|
} catch {
|
package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1
CHANGED
|
@@ -36,6 +36,10 @@
|
|
|
36
36
|
[CmdletBinding()]
|
|
37
37
|
param()
|
|
38
38
|
|
|
39
|
+
# Shared Codex PreToolUse transport: stdin payload parsing and tool_input-to-file
|
|
40
|
+
# mapping for every tool name the ^(apply_patch|Edit|Write)$ matcher admits.
|
|
41
|
+
. (Join-Path $PSScriptRoot 'codex-pretooluse-file-mapping.ps1')
|
|
42
|
+
|
|
39
43
|
function Get-PowerShellBatchBudgetState {
|
|
40
44
|
[CmdletBinding()]
|
|
41
45
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
@@ -212,65 +216,31 @@ function Invoke-PowerShellBatchBudgetHook {
|
|
|
212
216
|
return $decision
|
|
213
217
|
}
|
|
214
218
|
|
|
215
|
-
function ConvertFrom-CodexPowerShellBudgetPayload {
|
|
216
|
-
[CmdletBinding()]
|
|
217
|
-
param([Parameter(Mandatory)][string] $PayloadRaw)
|
|
218
|
-
|
|
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
|
|
237
|
-
}
|
|
238
|
-
|
|
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
|
|
260
|
-
}
|
|
261
|
-
|
|
262
219
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
263
220
|
return
|
|
264
221
|
}
|
|
265
222
|
|
|
266
223
|
try {
|
|
267
|
-
|
|
224
|
+
# Transport and mapping come from the shared module. session_id is still
|
|
225
|
+
# required because the batch counter is keyed by it.
|
|
226
|
+
$payload = ConvertFrom-CodexPreToolUsePayload -PayloadRaw ([Console]::In.ReadToEnd()) -HookName 'enforce-powershell-batch-budget' -RequireSessionId
|
|
268
227
|
$sessionId = ([string]$payload.session_id) -replace '[^A-Za-z0-9._-]', '_'
|
|
269
228
|
$repositoryRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
|
270
229
|
$prodCap = 3
|
|
271
230
|
$testCap = 3
|
|
272
231
|
|
|
273
|
-
|
|
232
|
+
# Both sides of a rename consume budget, matching the pre-fix path scan that
|
|
233
|
+
# collected Add/Update/Delete targets and Move destinations alike. Ordering
|
|
234
|
+
# and de-duplication are preserved. A well-formed payload that maps to no
|
|
235
|
+
# file yields no paths, so no state is written and the hook allows silently.
|
|
236
|
+
$budgetPaths = @(
|
|
237
|
+
@(ConvertTo-CodexFileEditInput -Payload $payload) |
|
|
238
|
+
ForEach-Object { $_.source_path; $_.file_path } |
|
|
239
|
+
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
|
|
240
|
+
Select-Object -Unique
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
foreach ($path in $budgetPaths) {
|
|
274
244
|
$toolInputRaw = @{ file_path = $path } | ConvertTo-Json -Compress
|
|
275
245
|
$decision = Invoke-PowerShellBatchBudgetHook -ToolInputRaw $toolInputRaw -SessionId $sessionId -Root $repositoryRoot -ProdCap $prodCap -TestCap $testCap
|
|
276
246
|
if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1
CHANGED
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
[CmdletBinding()]
|
|
35
35
|
param()
|
|
36
36
|
|
|
37
|
+
# Shared Codex PreToolUse transport: stdin payload parsing and tool_input-to-file
|
|
38
|
+
# mapping for every tool name the ^(apply_patch|Edit|Write)$ matcher admits.
|
|
39
|
+
. (Join-Path $PSScriptRoot 'codex-pretooluse-file-mapping.ps1')
|
|
40
|
+
|
|
37
41
|
function Get-PythonBatchBudgetState {
|
|
38
42
|
[CmdletBinding()]
|
|
39
43
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
@@ -210,65 +214,31 @@ function Invoke-PythonBatchBudgetHook {
|
|
|
210
214
|
return $decision
|
|
211
215
|
}
|
|
212
216
|
|
|
213
|
-
function ConvertFrom-CodexPythonBudgetPayload {
|
|
214
|
-
[CmdletBinding()]
|
|
215
|
-
param([Parameter(Mandatory)][string] $PayloadRaw)
|
|
216
|
-
|
|
217
|
-
if ([string]::IsNullOrWhiteSpace($PayloadRaw)) {
|
|
218
|
-
throw 'enforce-python-batch-budget hook input is empty.'
|
|
219
|
-
}
|
|
220
|
-
try {
|
|
221
|
-
$payload = $PayloadRaw | ConvertFrom-Json -ErrorAction Stop
|
|
222
|
-
} catch {
|
|
223
|
-
throw "enforce-python-batch-budget hook input is malformed JSON: $_"
|
|
224
|
-
}
|
|
225
|
-
if ($payload.PSObject.Properties.Name -notcontains 'tool_input' -or $null -eq $payload.tool_input) {
|
|
226
|
-
throw 'enforce-python-batch-budget hook input is missing tool_input.'
|
|
227
|
-
}
|
|
228
|
-
if ([string]$payload.hook_event_name -ne 'PreToolUse' -or [string]$payload.tool_name -ne 'apply_patch') {
|
|
229
|
-
throw 'enforce-python-batch-budget requires a PreToolUse apply_patch payload.'
|
|
230
|
-
}
|
|
231
|
-
if ([string]::IsNullOrWhiteSpace([string]$payload.session_id)) {
|
|
232
|
-
throw 'enforce-python-batch-budget hook input is missing session_id.'
|
|
233
|
-
}
|
|
234
|
-
return $payload
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function Get-CodexPythonBudgetPath {
|
|
238
|
-
[CmdletBinding()]
|
|
239
|
-
[OutputType([string])]
|
|
240
|
-
param([Parameter(Mandatory)] $Payload)
|
|
241
|
-
|
|
242
|
-
if ($Payload.tool_input.PSObject.Properties.Name -contains 'file_path') {
|
|
243
|
-
return [string]$Payload.tool_input.file_path
|
|
244
|
-
}
|
|
245
|
-
$command = [string]$Payload.tool_input.command
|
|
246
|
-
if ([string]::IsNullOrWhiteSpace($command)) {
|
|
247
|
-
throw 'enforce-python-batch-budget cannot map tool_input to a file edit.'
|
|
248
|
-
}
|
|
249
|
-
[string[]] $paths = @(
|
|
250
|
-
[regex]::Matches($command, '(?m)^\*\*\* (?:(?:Add|Update|Delete) File|Move to):\s*(?<path>.+?)\s*$') |
|
|
251
|
-
ForEach-Object { ([string]$_.Groups['path'].Value).Trim() }
|
|
252
|
-
)
|
|
253
|
-
$paths = @($paths | Select-Object -Unique)
|
|
254
|
-
if ($paths.Count -eq 0) {
|
|
255
|
-
throw 'enforce-python-batch-budget received an unrecognized apply_patch command.'
|
|
256
|
-
}
|
|
257
|
-
return $paths
|
|
258
|
-
}
|
|
259
|
-
|
|
260
217
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
261
218
|
return
|
|
262
219
|
}
|
|
263
220
|
|
|
264
221
|
try {
|
|
265
|
-
|
|
222
|
+
# Transport and mapping come from the shared module. session_id is still
|
|
223
|
+
# required because the batch counter is keyed by it.
|
|
224
|
+
$payload = ConvertFrom-CodexPreToolUsePayload -PayloadRaw ([Console]::In.ReadToEnd()) -HookName 'enforce-python-batch-budget' -RequireSessionId
|
|
266
225
|
$sessionId = ([string]$payload.session_id) -replace '[^A-Za-z0-9._-]', '_'
|
|
267
226
|
$repositoryRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
|
268
227
|
$prodCap = 3
|
|
269
228
|
$testCap = 3
|
|
270
229
|
|
|
271
|
-
|
|
230
|
+
# Both sides of a rename consume budget, matching the pre-fix path scan that
|
|
231
|
+
# collected Add/Update/Delete targets and Move destinations alike. Ordering
|
|
232
|
+
# and de-duplication are preserved. A well-formed payload that maps to no
|
|
233
|
+
# file yields no paths, so no state is written and the hook allows silently.
|
|
234
|
+
$budgetPaths = @(
|
|
235
|
+
@(ConvertTo-CodexFileEditInput -Payload $payload) |
|
|
236
|
+
ForEach-Object { $_.source_path; $_.file_path } |
|
|
237
|
+
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
|
|
238
|
+
Select-Object -Unique
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
foreach ($path in $budgetPaths) {
|
|
272
242
|
$toolInputRaw = @{ file_path = $path } | ConvertTo-Json -Compress
|
|
273
243
|
$decision = Invoke-PythonBatchBudgetHook -ToolInputRaw $toolInputRaw -SessionId $sessionId -Root $repositoryRoot -ProdCap $prodCap -TestCap $testCap
|
|
274
244
|
if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
".codex/hooks/codex-agent-profile-attestation.ps1",
|
|
30
30
|
".codex/hooks/codex-authority-store.ps1",
|
|
31
31
|
".codex/hooks/codex-epic-child-launch-attestation.ps1",
|
|
32
|
+
".codex/hooks/codex-pretooluse-file-mapping.ps1",
|
|
32
33
|
".codex/hooks/enforce-codex-model-routing.ps1",
|
|
33
34
|
".codex/hooks/enforce-epic-child-worktree-binding.ps1",
|
|
34
35
|
".codex/hooks/enforce-epic-merge-gate.ps1",
|
|
@@ -343,7 +343,27 @@ function Invoke-PoshQCTest {
|
|
|
343
343
|
if ([IO.Path]::IsPathRooted($_)) { $_ } else { Join-Path $Root $_ }
|
|
344
344
|
}
|
|
345
345
|
)
|
|
346
|
-
|
|
346
|
+
|
|
347
|
+
# Prune coverage paths that do not exist under the resolved root before Pester sees
|
|
348
|
+
# them. Pester's Resolve-CoverageInfo discards the whole set and raises a terminating
|
|
349
|
+
# error on the first unresolvable entry, which aborts the run at RunStart in any
|
|
350
|
+
# workspace that does not contain this repository's coverage layout (issue #409).
|
|
351
|
+
# Rooted entries are tested as-is; they are never re-joined to $Root.
|
|
352
|
+
$survivingCoveragePaths = @($resolvedCoveragePaths | Where-Object { & $TestPathExists $_ })
|
|
353
|
+
foreach ($prunedPath in @($resolvedCoveragePaths | Where-Object { $survivingCoveragePaths -notcontains $_ })) {
|
|
354
|
+
# Log every prune individually so coverage removal is never silent.
|
|
355
|
+
& $Logger "Pruned nonexistent code coverage path: $prunedPath"
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if ($survivingCoveragePaths.Count -gt 0) {
|
|
359
|
+
$config.CodeCoverage.Path = $survivingCoveragePaths
|
|
360
|
+
} else {
|
|
361
|
+
# An enabled-but-empty path set makes Pester instrument every Run.Path directory,
|
|
362
|
+
# so disable coverage for this invocation instead and continue with the test run.
|
|
363
|
+
$config.CodeCoverage.Enabled = $false
|
|
364
|
+
$coverageEnabled = $false
|
|
365
|
+
& $Logger "Code coverage disabled for this invocation: no configured coverage path exists under root '$Root'."
|
|
366
|
+
}
|
|
347
367
|
}
|
|
348
368
|
|
|
349
369
|
if ($config.CodeCoverage.OutputPath.Value) {
|
|
@@ -95,6 +95,25 @@
|
|
|
95
95
|
# entry path hosts the Pester run in the global session state; measured here so
|
|
96
96
|
# the fix produces real per-file changed-line coverage evidence.
|
|
97
97
|
'scripts/powershell/PoshQC/PoshQC.Testing.psm1'
|
|
98
|
+
# Issue #415 remediation cycle 1 (R1): the PreToolUse hook transport fix added the
|
|
99
|
+
# shared payload-parsing module and rewired seven Codex hooks to consume it. Those
|
|
100
|
+
# production files were absent from this list, so the changed surface was outside
|
|
101
|
+
# the coverage denominator. Measured here so the changed production surface is not
|
|
102
|
+
# excluded from coverage.
|
|
103
|
+
'.codex/hooks/codex-pretooluse-file-mapping.ps1'
|
|
104
|
+
'.codex/hooks/check-python-test-purity.ps1'
|
|
105
|
+
'.codex/hooks/check-powershell-test-purity.ps1'
|
|
106
|
+
'.codex/hooks/enforce-python-batch-budget.ps1'
|
|
107
|
+
'.codex/hooks/enforce-powershell-batch-budget.ps1'
|
|
108
|
+
'.codex/hooks/enforce-evidence-locations.ps1'
|
|
109
|
+
'.codex/hooks/enforce-checkpoint-monotonic.ps1'
|
|
110
|
+
'.codex/hooks/enforce-orchestration-preimplementation-gate.ps1'
|
|
111
|
+
# Issue #415 remediation cycle 2 (R-COV): the detached-HEAD null-guard fix changed
|
|
112
|
+
# these two Codex PreToolUse hooks. Both were absent from this list, so the changed
|
|
113
|
+
# production surface was outside the coverage denominator. Measured here so the
|
|
114
|
+
# changed production surface is not excluded from coverage.
|
|
115
|
+
'.codex/hooks/enforce-epic-child-worktree-binding.ps1'
|
|
116
|
+
'.codex/hooks/enforce-epic-planning-only.ps1'
|
|
98
117
|
)
|
|
99
118
|
# Optional: don't fail the run on coverage percentage
|
|
100
119
|
CoveragePercentTarget = 0
|