@danmoisan/drm-copilot-mcp 1.1.15 → 1.1.17
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 +639 -27
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +12 -6
- package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +4 -5
- package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +3 -3
- package/resources/claude-customizations/.claude/hooks/enforce-batch-budget-route.ps1 +139 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +221 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +42 -66
- package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +58 -10
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +144 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +36 -47
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +179 -27
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +21 -38
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +4 -4
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +58 -10
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +59 -9
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +114 -37
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +95 -66
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +99 -64
- package/resources/claude-customizations/.claude/lib/bash/abandon-parallel-item.sh +176 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +193 -14
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusGlob.psm1 +51 -28
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +5 -8
- package/resources/claude-customizations/.claude/lib/parallel-drift/Invoke-ParallelDriftDetection.ps1 +336 -0
- package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDrift.psm1 +467 -0
- package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDriftHalt.psm1 +388 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +53 -32
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +19 -4
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +497 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +21 -0
- package/resources/claude-customizations/.claude/rules/powershell.md +3 -3
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +2 -0
- package/resources/claude-customizations/.claude/skills/invoke-csharp-engineer/SKILL.md +4 -0
- package/resources/claude-customizations/.claude/skills/invoke-powershell-engineer/SKILL.md +7 -4
- package/resources/claude-customizations/.claude/skills/invoke-python-engineer/SKILL.md +6 -3
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +3 -1
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +32 -18
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +10 -10
- package/resources/claude-customizations/.claude/skills/powershell-change-budget-router/SKILL.md +6 -6
- package/resources/claude-customizations/.claude/skills/python-change-budget-router/SKILL.md +11 -35
- package/resources/claude-customizations/pack-manifests/core.json +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +1 -2
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-python-engineer/SKILL.md +2 -3
- package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +1 -2
- package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/SKILL.md +10 -34
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-orchestrator.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-batch-budget-route.ps1 +139 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +165 -80
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +169 -79
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +1 -0
- package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +7 -7
- package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +8 -10
- package/resources/customizations/.github/agents/python-orchestrator.agent.md +4 -4
- package/resources/customizations/.github/agents/python-typed-engineer.agent.md +7 -8
- package/resources/customizations/.github/prompts/orchestrate-powershell-work.prompt.md +2 -2
- package/resources/customizations/.github/prompts/orchestrate-python-work.prompt.md +3 -3
- package/resources/customizations/.github/skills/powershell-change-budget-router/SKILL.md +3 -3
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +12 -0
package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1
CHANGED
|
@@ -1,34 +1,44 @@
|
|
|
1
1
|
|
|
2
2
|
<#
|
|
3
3
|
.SYNOPSIS
|
|
4
|
-
Pre-tool-use hook that
|
|
4
|
+
Pre-tool-use hook that routes PowerShell changes of more than three production files to the orchestrated large path.
|
|
5
5
|
|
|
6
6
|
.DESCRIPTION
|
|
7
7
|
This script is invoked by the Codex PreToolUse hook before any Write or Edit
|
|
8
8
|
operation. When the target file is a PowerShell source file (.ps1, .psm1, .psd1),
|
|
9
|
-
it
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
it decides whether the change may proceed in direct mode.
|
|
10
|
+
|
|
11
|
+
Direct mode counts distinct production PowerShell paths per Codex session. The
|
|
12
|
+
running set is persisted under .codex/state/powershell-batch-budget.<session_id>.json,
|
|
13
|
+
and repeated edits to the same file are counted once. The 4th distinct production
|
|
14
|
+
path is denied with a POWERSHELL_LARGE_PATH_REQUIRED reason that instructs the
|
|
15
|
+
caller to route the change through .codex/prompts/orchestrate-work.md.
|
|
16
|
+
|
|
17
|
+
The orchestrated large path is detected from
|
|
18
|
+
<root>/artifacts/orchestration/orchestrator-state.json. The selected route is the
|
|
19
|
+
route_id value when that key is present, otherwise the path_selected value, and it
|
|
20
|
+
is usable only as a non-blank string. When the selected route is large,
|
|
21
|
+
remediation, or preparation and the checkpoint is not terminal (next_step is not
|
|
22
|
+
complete and completed_steps does not contain S12_complete), no path is denied for
|
|
23
|
+
count and no state is written. Every other checkpoint outcome, including an absent,
|
|
24
|
+
unreadable, or malformed checkpoint, enforces direct mode.
|
|
25
|
+
|
|
26
|
+
Test files are never counted. They are those matching:
|
|
19
27
|
- tests/**/*.ps1
|
|
20
28
|
- *.Tests.ps1
|
|
21
29
|
|
|
22
|
-
All other .ps1/.psm1/.psd1 files are
|
|
23
|
-
|
|
30
|
+
All other .ps1/.psm1/.psd1 files are production files. Non-PowerShell paths pass
|
|
31
|
+
through. The threshold of three production files is a routing constant and is not
|
|
32
|
+
configurable at runtime. Legacy prodCap, testCap, and testFiles keys in a persisted
|
|
33
|
+
state file are ignored when the state is loaded.
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
When a production path is denied, the script emits a PreToolUse JSON response with
|
|
36
|
+
hookSpecificOutput.permissionDecision = 'deny' and exits 0. Files already counted
|
|
37
|
+
are always allowed through.
|
|
27
38
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
batch. Files already counted are always allowed through.
|
|
39
|
+
Known limitation: a stale non-terminal large-path checkpoint left at the root
|
|
40
|
+
exempts a later direct-mode session at that root. Orchestrator checkpoint hygiene
|
|
41
|
+
(issue #673) moves foreign checkpoints aside before a new run.
|
|
32
42
|
|
|
33
43
|
.NOTES
|
|
34
44
|
Compatible with PowerShell 7+.
|
|
@@ -40,26 +50,28 @@ param()
|
|
|
40
50
|
# mapping for every tool name the ^(apply_patch|Edit|Write)$ matcher admits.
|
|
41
51
|
. (Join-Path $PSScriptRoot 'codex-pretooluse-file-mapping.ps1')
|
|
42
52
|
|
|
53
|
+
# Shared route helpers; this file is byte-identical to the Claude runtime copy.
|
|
54
|
+
. (Join-Path $PSScriptRoot 'enforce-batch-budget-route.ps1')
|
|
55
|
+
|
|
43
56
|
function Get-PowerShellBatchBudgetState {
|
|
57
|
+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'TestCap', Justification = 'Accepted and ignored for callers written against the removed test-file cap.')]
|
|
44
58
|
[CmdletBinding()]
|
|
45
59
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
46
60
|
param(
|
|
47
61
|
[Parameter(Mandatory)]
|
|
48
62
|
[int] $ProdCap,
|
|
49
63
|
|
|
50
|
-
[
|
|
51
|
-
[int] $TestCap
|
|
64
|
+
[int] $TestCap = 0
|
|
52
65
|
)
|
|
53
66
|
|
|
54
67
|
[ordered]@{
|
|
55
68
|
prodCap = $ProdCap
|
|
56
|
-
testCap = $TestCap
|
|
57
69
|
prodFiles = @()
|
|
58
|
-
testFiles = @()
|
|
59
70
|
}
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
function ConvertTo-PowerShellBatchBudgetState {
|
|
74
|
+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'TestCap', Justification = 'Accepted and ignored for callers written against the removed test-file cap.')]
|
|
63
75
|
[CmdletBinding()]
|
|
64
76
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
65
77
|
param(
|
|
@@ -69,15 +81,13 @@ function ConvertTo-PowerShellBatchBudgetState {
|
|
|
69
81
|
[Parameter(Mandatory)]
|
|
70
82
|
[int] $ProdCap,
|
|
71
83
|
|
|
72
|
-
[
|
|
73
|
-
[int] $TestCap
|
|
84
|
+
[int] $TestCap = 0
|
|
74
85
|
)
|
|
75
86
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
87
|
+
# Only prodFiles is carried over. Persisted prodCap, testCap, and testFiles keys
|
|
88
|
+
# come from the removed test-file cap and recorded overrides and are ignored.
|
|
89
|
+
$state = Get-PowerShellBatchBudgetState -ProdCap $ProdCap
|
|
79
90
|
if ($null -ne $InputObject.prodFiles) { $state.prodFiles = @($InputObject.prodFiles) }
|
|
80
|
-
if ($null -ne $InputObject.testFiles) { $state.testFiles = @($InputObject.testFiles) }
|
|
81
91
|
|
|
82
92
|
return $state
|
|
83
93
|
}
|
|
@@ -116,8 +126,13 @@ function Invoke-PowerShellBatchBudgetDecision {
|
|
|
116
126
|
[Parameter(Mandatory)]
|
|
117
127
|
[System.Collections.IDictionary] $State,
|
|
118
128
|
|
|
119
|
-
[
|
|
120
|
-
[string] $StateFile
|
|
129
|
+
[AllowEmptyString()]
|
|
130
|
+
[string] $StateFile = '',
|
|
131
|
+
|
|
132
|
+
[switch] $LargePathRoute,
|
|
133
|
+
|
|
134
|
+
[AllowEmptyString()]
|
|
135
|
+
[string] $ObservedRoute = ''
|
|
121
136
|
)
|
|
122
137
|
|
|
123
138
|
$normalized = $FilePath -replace '\\', '/'
|
|
@@ -125,26 +140,32 @@ function Invoke-PowerShellBatchBudgetDecision {
|
|
|
125
140
|
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
|
|
126
141
|
}
|
|
127
142
|
|
|
143
|
+
# The orchestrated large path has no production-file cap, so nothing is counted.
|
|
144
|
+
if ($LargePathRoute) {
|
|
145
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Test files never count toward the routing threshold.
|
|
128
149
|
$isTestFile = ($normalized -match '(^|/)tests/.*\.ps1$') -or ($normalized -match '\.Tests\.ps1$')
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
150
|
+
if ($isTestFile) {
|
|
151
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
|
|
152
|
+
}
|
|
132
153
|
|
|
133
|
-
|
|
154
|
+
$countedFiles = @($State.prodFiles)
|
|
155
|
+
if ($countedFiles -contains $normalized) {
|
|
134
156
|
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
|
|
135
157
|
}
|
|
136
158
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
159
|
+
$cap = [int]$State.prodCap
|
|
160
|
+
if ($countedFiles.Count -ge $cap) {
|
|
161
|
+
Write-Verbose "Denying production PowerShell path '$normalized' in direct mode; counted paths are recorded in '$StateFile'."
|
|
162
|
+
$counted = $countedFiles -join ', '
|
|
163
|
+
$route = if ([string]::IsNullOrWhiteSpace($ObservedRoute)) { 'none' } else { $ObservedRoute }
|
|
164
|
+
$reason = "POWERSHELL_LARGE_PATH_REQUIRED: this change touches more than $cap production PowerShell files (already counted: $counted; requested: $normalized). A change of this size belongs on the orchestrated large path, which has no production-file cap. Route the change through .codex/prompts/orchestrate-work.md. Checkpoint route observed: $route."
|
|
140
165
|
return Get-PowerShellBatchBudgetBlockDecision -Reason $reason -State $State
|
|
141
166
|
}
|
|
142
167
|
|
|
143
|
-
|
|
144
|
-
$State.testFiles = @($State.testFiles) + @($normalized)
|
|
145
|
-
} else {
|
|
146
|
-
$State.prodFiles = @($State.prodFiles) + @($normalized)
|
|
147
|
-
}
|
|
168
|
+
$State.prodFiles = $countedFiles + @($normalized)
|
|
148
169
|
|
|
149
170
|
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $true }
|
|
150
171
|
}
|
|
@@ -157,7 +178,13 @@ function Invoke-PowerShellBatchBudgetHook {
|
|
|
157
178
|
[string] $SessionId = 'default',
|
|
158
179
|
[string] $Root = (Get-Location).Path,
|
|
159
180
|
[int] $ProdCap = 3,
|
|
160
|
-
[
|
|
181
|
+
[scriptblock] $ReadCheckpoint = {
|
|
182
|
+
param([string] $Path)
|
|
183
|
+
if (Test-Path -LiteralPath $Path -PathType Leaf) {
|
|
184
|
+
return (Get-Content -LiteralPath $Path -Raw)
|
|
185
|
+
}
|
|
186
|
+
return ''
|
|
187
|
+
},
|
|
161
188
|
[scriptblock] $TestPathExists = { param([string] $Path) Test-Path -Path $Path },
|
|
162
189
|
[scriptblock] $EnsureDirectory = { param([string] $Path) New-Item -ItemType Directory -Path $Path -Force | Out-Null },
|
|
163
190
|
[scriptblock] $ReadState = { param([string] $Path) Get-Content -Path $Path -Raw },
|
|
@@ -188,23 +215,41 @@ function Invoke-PowerShellBatchBudgetHook {
|
|
|
188
215
|
}
|
|
189
216
|
|
|
190
217
|
$stateDir = Join-Path -Path $Root -ChildPath '.codex/state'
|
|
218
|
+
$stateFile = Join-Path -Path $stateDir -ChildPath ("powershell-batch-budget.$SessionId.json")
|
|
219
|
+
|
|
220
|
+
# The checkpoint is read before any state operation, so the large path neither
|
|
221
|
+
# creates the state directory nor reads or writes the state file.
|
|
222
|
+
$checkpointPath = Join-Path -Path $Root -ChildPath 'artifacts/orchestration/orchestrator-state.json'
|
|
223
|
+
$checkpointText = ''
|
|
224
|
+
try {
|
|
225
|
+
$checkpointText = [string](& $ReadCheckpoint $checkpointPath)
|
|
226
|
+
} catch {
|
|
227
|
+
Write-Verbose "Treating unreadable orchestrator checkpoint '$checkpointPath' as direct mode: $($_.Exception.Message)"
|
|
228
|
+
$checkpointText = ''
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
$isLargePath = Test-BatchBudgetLargePathRoute -CheckpointText $checkpointText
|
|
232
|
+
$observedRoute = Get-BatchBudgetSelectedRoute -CheckpointText $checkpointText
|
|
233
|
+
if ($isLargePath) {
|
|
234
|
+
return Invoke-PowerShellBatchBudgetDecision -FilePath $filePath -State (Get-PowerShellBatchBudgetState -ProdCap $ProdCap) -StateFile $stateFile -LargePathRoute
|
|
235
|
+
}
|
|
236
|
+
|
|
191
237
|
if (-not (& $TestPathExists $stateDir)) {
|
|
192
238
|
& $EnsureDirectory $stateDir
|
|
193
239
|
}
|
|
194
240
|
|
|
195
|
-
$
|
|
196
|
-
$state = Get-PowerShellBatchBudgetState -ProdCap $ProdCap -TestCap $TestCap
|
|
241
|
+
$state = Get-PowerShellBatchBudgetState -ProdCap $ProdCap
|
|
197
242
|
|
|
198
243
|
if (& $TestPathExists $stateFile) {
|
|
199
244
|
try {
|
|
200
245
|
$loaded = & $ReadState $stateFile | ConvertFrom-Json -ErrorAction Stop
|
|
201
|
-
$state = ConvertTo-PowerShellBatchBudgetState -InputObject $loaded -ProdCap $ProdCap
|
|
246
|
+
$state = ConvertTo-PowerShellBatchBudgetState -InputObject $loaded -ProdCap $ProdCap
|
|
202
247
|
} catch {
|
|
203
248
|
Write-Verbose "Ignoring unreadable PowerShell batch-budget state file '$stateFile': $($_.Exception.Message)"
|
|
204
249
|
}
|
|
205
250
|
}
|
|
206
251
|
|
|
207
|
-
$decision = Invoke-PowerShellBatchBudgetDecision -FilePath $filePath -State $state -StateFile $stateFile
|
|
252
|
+
$decision = Invoke-PowerShellBatchBudgetDecision -FilePath $filePath -State $state -StateFile $stateFile -ObservedRoute $observedRoute
|
|
208
253
|
if ($decision.shouldWriteState) {
|
|
209
254
|
try {
|
|
210
255
|
& $WriteState $stateFile $decision.state
|
|
@@ -216,41 +261,81 @@ function Invoke-PowerShellBatchBudgetHook {
|
|
|
216
261
|
return $decision
|
|
217
262
|
}
|
|
218
263
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
264
|
+
function Invoke-PowerShellBatchBudgetCodexEntryPoint {
|
|
265
|
+
<#
|
|
266
|
+
.SYNOPSIS
|
|
267
|
+
Runs the Codex PowerShell batch-budget decision and returns the process exit code.
|
|
268
|
+
.DESCRIPTION
|
|
269
|
+
Parses the Codex PreToolUse payload through the shared transport, collects both
|
|
270
|
+
sides of every mapped file edit, and evaluates each path with the hook. The
|
|
271
|
+
first deny is written to the output stream as compact JSON without its state
|
|
272
|
+
property, followed by exit code 0. When nothing is denied the function returns
|
|
273
|
+
0 and writes nothing else. Any failure writes the error text to standard error
|
|
274
|
+
and returns 2. The function does not call exit.
|
|
275
|
+
.PARAMETER PayloadRaw
|
|
276
|
+
Raw PreToolUse payload text.
|
|
277
|
+
.PARAMETER RepositoryRoot
|
|
278
|
+
Root used to locate the Codex state directory and the orchestrator checkpoint.
|
|
279
|
+
.PARAMETER HookSeams
|
|
280
|
+
Optional seam overrides splatted into Invoke-PowerShellBatchBudgetHook.
|
|
281
|
+
#>
|
|
282
|
+
[CmdletBinding()]
|
|
283
|
+
[OutputType([int])]
|
|
284
|
+
param(
|
|
285
|
+
[Parameter(Mandatory)]
|
|
286
|
+
[AllowNull()]
|
|
287
|
+
[AllowEmptyString()]
|
|
288
|
+
[string] $PayloadRaw,
|
|
289
|
+
|
|
290
|
+
[Parameter(Mandatory)]
|
|
291
|
+
[string] $RepositoryRoot,
|
|
222
292
|
|
|
223
|
-
|
|
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
|
|
227
|
-
$sessionId = ([string]$payload.session_id) -replace '[^A-Za-z0-9._-]', '_'
|
|
228
|
-
$repositoryRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
|
229
|
-
$prodCap = 3
|
|
230
|
-
$testCap = 3
|
|
231
|
-
|
|
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
|
|
293
|
+
[hashtable] $HookSeams = @{}
|
|
241
294
|
)
|
|
242
295
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
296
|
+
try {
|
|
297
|
+
# Transport and mapping come from the shared module. session_id is still
|
|
298
|
+
# required because the direct-mode counter is keyed by it.
|
|
299
|
+
$payload = ConvertFrom-CodexPreToolUsePayload -PayloadRaw $PayloadRaw -HookName 'enforce-powershell-batch-budget' -RequireSessionId
|
|
300
|
+
$sessionId = ([string]$payload.session_id) -replace '[^A-Za-z0-9._-]', '_'
|
|
301
|
+
|
|
302
|
+
# Both sides of a rename are evaluated, matching the path scan that collected
|
|
303
|
+
# Add/Update/Delete targets and Move destinations alike. Ordering and
|
|
304
|
+
# de-duplication are preserved. A well-formed payload that maps to no file
|
|
305
|
+
# yields no paths, so no state is written and the hook allows silently.
|
|
306
|
+
$budgetPaths = @(
|
|
307
|
+
@(ConvertTo-CodexFileEditInput -Payload $payload) |
|
|
308
|
+
ForEach-Object { $_.source_path; $_.file_path } |
|
|
309
|
+
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
|
|
310
|
+
Select-Object -Unique
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
foreach ($path in $budgetPaths) {
|
|
314
|
+
$toolInputRaw = @{ file_path = $path } | ConvertTo-Json -Compress
|
|
315
|
+
$decision = Invoke-PowerShellBatchBudgetHook -ToolInputRaw $toolInputRaw -SessionId $sessionId -Root $RepositoryRoot @HookSeams
|
|
316
|
+
if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
317
|
+
$decision.Remove('state')
|
|
318
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
319
|
+
return 0
|
|
320
|
+
}
|
|
250
321
|
}
|
|
322
|
+
|
|
323
|
+
return 0
|
|
324
|
+
} catch {
|
|
325
|
+
[Console]::Error.WriteLine([string]$_)
|
|
326
|
+
return 2
|
|
251
327
|
}
|
|
252
|
-
exit 0
|
|
253
|
-
} catch {
|
|
254
|
-
[Console]::Error.WriteLine([string]$_)
|
|
255
|
-
exit 2
|
|
256
328
|
}
|
|
329
|
+
|
|
330
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
331
|
+
return
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
# The entry point returns its [int] exit code as the last pipeline element and any
|
|
335
|
+
# deny JSON before it, so the JSON is written explicitly before the process exits.
|
|
336
|
+
$entryPointResult = @(Invoke-PowerShellBatchBudgetCodexEntryPoint -PayloadRaw ([Console]::In.ReadToEnd()) -RepositoryRoot (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent))
|
|
337
|
+
if ($entryPointResult.Count -gt 1) {
|
|
338
|
+
$entryPointResult[0..($entryPointResult.Count - 2)] | Write-Output
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
exit ([int]$entryPointResult[-1])
|