@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.
Files changed (71) hide show
  1. package/out/mcp-server.js +639 -27
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +12 -6
  4. package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +4 -5
  5. package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +3 -3
  6. package/resources/claude-customizations/.claude/hooks/enforce-batch-budget-route.ps1 +139 -0
  7. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +221 -0
  8. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +42 -66
  9. package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +58 -10
  10. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +144 -0
  11. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +36 -47
  12. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +179 -27
  13. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +21 -38
  14. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +4 -4
  15. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +58 -10
  16. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +59 -9
  17. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +114 -37
  18. package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +95 -66
  19. package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +99 -64
  20. package/resources/claude-customizations/.claude/lib/bash/abandon-parallel-item.sh +176 -0
  21. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +193 -14
  22. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusGlob.psm1 +51 -28
  23. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +5 -8
  24. package/resources/claude-customizations/.claude/lib/parallel-drift/Invoke-ParallelDriftDetection.ps1 +336 -0
  25. package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDrift.psm1 +467 -0
  26. package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDriftHalt.psm1 +388 -0
  27. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +53 -32
  28. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +19 -4
  29. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +497 -0
  30. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +21 -0
  31. package/resources/claude-customizations/.claude/rules/powershell.md +3 -3
  32. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +2 -0
  33. package/resources/claude-customizations/.claude/skills/invoke-csharp-engineer/SKILL.md +4 -0
  34. package/resources/claude-customizations/.claude/skills/invoke-powershell-engineer/SKILL.md +7 -4
  35. package/resources/claude-customizations/.claude/skills/invoke-python-engineer/SKILL.md +6 -3
  36. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +3 -1
  37. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +32 -18
  38. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +10 -10
  39. package/resources/claude-customizations/.claude/skills/powershell-change-budget-router/SKILL.md +6 -6
  40. package/resources/claude-customizations/.claude/skills/python-change-budget-router/SKILL.md +11 -35
  41. package/resources/claude-customizations/pack-manifests/core.json +8 -0
  42. package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +1 -2
  43. package/resources/codex-and-agents-customizations/.agents/skills/invoke-python-engineer/SKILL.md +2 -3
  44. package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +1 -2
  45. package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/SKILL.md +10 -34
  46. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +2 -3
  47. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +2 -3
  48. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +2 -3
  49. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +2 -3
  50. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +2 -3
  51. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +2 -3
  52. package/resources/codex-and-agents-customizations/.codex/agents/python-orchestrator.toml +2 -2
  53. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +3 -3
  54. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +3 -3
  55. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +3 -3
  56. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +3 -3
  57. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +3 -3
  58. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +3 -3
  59. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  60. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-batch-budget-route.ps1 +139 -0
  61. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +165 -80
  62. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +169 -79
  63. package/resources/codex-and-agents-customizations/pack-manifests/core.json +1 -0
  64. package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +7 -7
  65. package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +8 -10
  66. package/resources/customizations/.github/agents/python-orchestrator.agent.md +4 -4
  67. package/resources/customizations/.github/agents/python-typed-engineer.agent.md +7 -8
  68. package/resources/customizations/.github/prompts/orchestrate-powershell-work.prompt.md +2 -2
  69. package/resources/customizations/.github/prompts/orchestrate-python-work.prompt.md +3 -3
  70. package/resources/customizations/.github/skills/powershell-change-budget-router/SKILL.md +3 -3
  71. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +12 -0
@@ -1,32 +1,44 @@
1
1
 
2
2
  <#
3
3
  .SYNOPSIS
4
- Pre-tool-use hook that enforces the Python per-batch change budget.
4
+ Pre-tool-use hook that routes Python 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
- operation. When the target file is a Python file, it classifies the file as either
9
- production or test and checks the running count against the per-batch cap:
10
- - 3 production .py files per batch
11
- - 3 test .py files per batch
12
-
13
- A "batch" is scoped to the current Codex session. The running count is
14
- persisted under .codex/state/python-batch-budget.<session_id>.json. Only distinct
15
- file paths are counted; repeated edits to the same file consume one slot.
16
-
17
- Test files are those matching:
8
+ operation. When the target file is a Python file (.py), it decides whether the
9
+ change may proceed in direct mode.
10
+
11
+ Direct mode counts distinct production Python paths per Codex session. The running
12
+ set is persisted under .codex/state/python-batch-budget.<session_id>.json, and
13
+ repeated edits to the same file are counted once. The 4th distinct production path
14
+ is denied with a PYTHON_LARGE_PATH_REQUIRED reason that instructs the caller to
15
+ 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 read or written. Every other checkpoint outcome, including
24
+ an absent, unreadable, or malformed checkpoint, enforces direct mode.
25
+
26
+ Test files are never counted. They are those matching:
18
27
  - tests/**/*.py
19
28
  - test_*.py
20
29
 
21
- All other .py files are treated as production files. Non-Python paths pass through.
30
+ All other .py files are production files. Non-Python paths pass through. The
31
+ threshold of three production files is a routing constant and is not configurable
32
+ at runtime. Legacy prodCap, testCap, and testFiles keys in a persisted state file
33
+ are ignored when the state is loaded.
22
34
 
23
- An approved per-session override is recorded by writing
24
- {"prodCap": N, "testCap": M} into the Codex state file.
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.
25
38
 
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.
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.
30
42
 
31
43
  .NOTES
32
44
  Compatible with PowerShell 7+.
@@ -38,26 +50,28 @@ param()
38
50
  # mapping for every tool name the ^(apply_patch|Edit|Write)$ matcher admits.
39
51
  . (Join-Path $PSScriptRoot 'codex-pretooluse-file-mapping.ps1')
40
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
+
41
56
  function Get-PythonBatchBudgetState {
57
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'TestCap', Justification = 'Accepted and ignored for callers written against the removed test-file cap.')]
42
58
  [CmdletBinding()]
43
59
  [OutputType([System.Collections.Specialized.OrderedDictionary])]
44
60
  param(
45
61
  [Parameter(Mandatory)]
46
62
  [int] $ProdCap,
47
63
 
48
- [Parameter(Mandatory)]
49
- [int] $TestCap
64
+ [int] $TestCap = 0
50
65
  )
51
66
 
52
67
  [ordered]@{
53
68
  prodCap = $ProdCap
54
- testCap = $TestCap
55
69
  prodFiles = @()
56
- testFiles = @()
57
70
  }
58
71
  }
59
72
 
60
73
  function ConvertTo-PythonBatchBudgetState {
74
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'TestCap', Justification = 'Accepted and ignored for callers written against the removed test-file cap.')]
61
75
  [CmdletBinding()]
62
76
  [OutputType([System.Collections.Specialized.OrderedDictionary])]
63
77
  param(
@@ -67,15 +81,13 @@ function ConvertTo-PythonBatchBudgetState {
67
81
  [Parameter(Mandatory)]
68
82
  [int] $ProdCap,
69
83
 
70
- [Parameter(Mandatory)]
71
- [int] $TestCap
84
+ [int] $TestCap = 0
72
85
  )
73
86
 
74
- $state = Get-PythonBatchBudgetState -ProdCap $ProdCap -TestCap $TestCap
75
- if ($null -ne $InputObject.prodCap) { $state.prodCap = [int]$InputObject.prodCap }
76
- if ($null -ne $InputObject.testCap) { $state.testCap = [int]$InputObject.testCap }
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-PythonBatchBudgetState -ProdCap $ProdCap
77
90
  if ($null -ne $InputObject.prodFiles) { $state.prodFiles = @($InputObject.prodFiles) }
78
- if ($null -ne $InputObject.testFiles) { $state.testFiles = @($InputObject.testFiles) }
79
91
 
80
92
  return $state
81
93
  }
@@ -105,6 +117,7 @@ function Get-PythonBatchBudgetBlockDecision {
105
117
  }
106
118
 
107
119
  function Invoke-PythonBatchBudgetDecision {
120
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'TestCap', Justification = 'Accepted and ignored for callers written against the removed test-file cap.')]
108
121
  [CmdletBinding()]
109
122
  [OutputType([System.Collections.Specialized.OrderedDictionary])]
110
123
  param(
@@ -114,8 +127,15 @@ function Invoke-PythonBatchBudgetDecision {
114
127
  [Parameter(Mandatory)]
115
128
  [System.Collections.IDictionary] $State,
116
129
 
117
- [Parameter(Mandatory)]
118
- [string] $StateFile
130
+ [AllowEmptyString()]
131
+ [string] $StateFile = '',
132
+
133
+ [int] $TestCap = 0,
134
+
135
+ [switch] $LargePathRoute,
136
+
137
+ [AllowEmptyString()]
138
+ [string] $ObservedRoute = ''
119
139
  )
120
140
 
121
141
  $normalized = $FilePath -replace '\\', '/'
@@ -123,26 +143,32 @@ function Invoke-PythonBatchBudgetDecision {
123
143
  return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
124
144
  }
125
145
 
146
+ # The orchestrated large path has no production-file cap, so nothing is counted.
147
+ if ($LargePathRoute) {
148
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
149
+ }
150
+
151
+ # Test files never count toward the routing threshold.
126
152
  $isTestFile = ($normalized -match '(^|/)tests/.*\.py$') -or ($normalized -match '(^|/)test_[^/]+\.py$')
127
- $targetList = if ($isTestFile) { @($State.testFiles) } else { @($State.prodFiles) }
128
- $cap = if ($isTestFile) { [int]$State.testCap } else { [int]$State.prodCap }
129
- $kind = if ($isTestFile) { 'test' } else { 'production' }
153
+ if ($isTestFile) {
154
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
155
+ }
130
156
 
131
- if ($targetList -contains $normalized) {
157
+ $countedFiles = @($State.prodFiles)
158
+ if ($countedFiles -contains $normalized) {
132
159
  return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
133
160
  }
134
161
 
135
- if ($targetList.Count -ge $cap) {
136
- $currentFiles = ($targetList -join ', ')
137
- $reason = "Python 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."
162
+ $cap = [int]$State.prodCap
163
+ if ($countedFiles.Count -ge $cap) {
164
+ Write-Verbose "Denying production Python path '$normalized' in direct mode; counted paths are recorded in '$StateFile'."
165
+ $counted = $countedFiles -join ', '
166
+ $route = if ([string]::IsNullOrWhiteSpace($ObservedRoute)) { 'none' } else { $ObservedRoute }
167
+ $reason = "PYTHON_LARGE_PATH_REQUIRED: this change touches more than $cap production Python 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."
138
168
  return Get-PythonBatchBudgetBlockDecision -Reason $reason -State $State
139
169
  }
140
170
 
141
- if ($isTestFile) {
142
- $State.testFiles = @($State.testFiles) + @($normalized)
143
- } else {
144
- $State.prodFiles = @($State.prodFiles) + @($normalized)
145
- }
171
+ $State.prodFiles = $countedFiles + @($normalized)
146
172
 
147
173
  return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $true }
148
174
  }
@@ -155,7 +181,13 @@ function Invoke-PythonBatchBudgetHook {
155
181
  [string] $SessionId = 'default',
156
182
  [string] $Root = (Get-Location).Path,
157
183
  [int] $ProdCap = 3,
158
- [int] $TestCap = 3,
184
+ [scriptblock] $ReadCheckpoint = {
185
+ param([string] $Path)
186
+ if (Test-Path -LiteralPath $Path -PathType Leaf) {
187
+ return (Get-Content -LiteralPath $Path -Raw)
188
+ }
189
+ return ''
190
+ },
159
191
  [scriptblock] $TestPathExists = { param([string] $Path) Test-Path -Path $Path },
160
192
  [scriptblock] $EnsureDirectory = { param([string] $Path) New-Item -ItemType Directory -Path $Path -Force | Out-Null },
161
193
  [scriptblock] $ReadState = { param([string] $Path) Get-Content -Path $Path -Raw },
@@ -186,23 +218,41 @@ function Invoke-PythonBatchBudgetHook {
186
218
  }
187
219
 
188
220
  $stateDir = Join-Path -Path $Root -ChildPath '.codex/state'
221
+ $stateFile = Join-Path -Path $stateDir -ChildPath ("python-batch-budget.$SessionId.json")
222
+
223
+ # The checkpoint is read before any state operation, so the large path neither
224
+ # creates the state directory nor reads or writes the state file.
225
+ $checkpointPath = Join-Path -Path $Root -ChildPath 'artifacts/orchestration/orchestrator-state.json'
226
+ $checkpointText = ''
227
+ try {
228
+ $checkpointText = [string](& $ReadCheckpoint $checkpointPath)
229
+ } catch {
230
+ Write-Verbose "Treating unreadable orchestrator checkpoint '$checkpointPath' as direct mode: $($_.Exception.Message)"
231
+ $checkpointText = ''
232
+ }
233
+
234
+ $isLargePath = Test-BatchBudgetLargePathRoute -CheckpointText $checkpointText
235
+ $observedRoute = Get-BatchBudgetSelectedRoute -CheckpointText $checkpointText
236
+ if ($isLargePath) {
237
+ return Invoke-PythonBatchBudgetDecision -FilePath $filePath -State (Get-PythonBatchBudgetState -ProdCap $ProdCap) -StateFile $stateFile -LargePathRoute
238
+ }
239
+
189
240
  if (-not (& $TestPathExists $stateDir)) {
190
241
  & $EnsureDirectory $stateDir
191
242
  }
192
243
 
193
- $stateFile = Join-Path -Path $stateDir -ChildPath ("python-batch-budget.$SessionId.json")
194
- $state = Get-PythonBatchBudgetState -ProdCap $ProdCap -TestCap $TestCap
244
+ $state = Get-PythonBatchBudgetState -ProdCap $ProdCap
195
245
 
196
246
  if (& $TestPathExists $stateFile) {
197
247
  try {
198
248
  $loaded = & $ReadState $stateFile | ConvertFrom-Json -ErrorAction Stop
199
- $state = ConvertTo-PythonBatchBudgetState -InputObject $loaded -ProdCap $ProdCap -TestCap $TestCap
249
+ $state = ConvertTo-PythonBatchBudgetState -InputObject $loaded -ProdCap $ProdCap
200
250
  } catch {
201
251
  Write-Verbose "Ignoring unreadable Python batch-budget state file '$stateFile': $($_.Exception.Message)"
202
252
  }
203
253
  }
204
254
 
205
- $decision = Invoke-PythonBatchBudgetDecision -FilePath $filePath -State $state -StateFile $stateFile
255
+ $decision = Invoke-PythonBatchBudgetDecision -FilePath $filePath -State $state -StateFile $stateFile -ObservedRoute $observedRoute
206
256
  if ($decision.shouldWriteState) {
207
257
  try {
208
258
  & $WriteState $stateFile $decision.state
@@ -214,41 +264,81 @@ function Invoke-PythonBatchBudgetHook {
214
264
  return $decision
215
265
  }
216
266
 
217
- if ($MyInvocation.InvocationName -eq '.') {
218
- return
219
- }
267
+ function Invoke-PythonBatchBudgetCodexEntryPoint {
268
+ <#
269
+ .SYNOPSIS
270
+ Runs the Codex Python batch-budget decision and returns the process exit code.
271
+ .DESCRIPTION
272
+ Parses the Codex PreToolUse payload through the shared transport, collects both
273
+ sides of every mapped file edit, and evaluates each path with the Python hook.
274
+ The first deny is written to the output stream as compact JSON without its state
275
+ property, followed by exit code 0. When nothing is denied the function returns
276
+ 0 and writes nothing else. Any failure writes the error text to standard error
277
+ and returns 2. The function does not call exit.
278
+ .PARAMETER PayloadRaw
279
+ Raw PreToolUse payload text.
280
+ .PARAMETER RepositoryRoot
281
+ Root used to locate the Codex state directory and the orchestrator checkpoint.
282
+ .PARAMETER HookSeams
283
+ Optional seam overrides splatted into Invoke-PythonBatchBudgetHook.
284
+ #>
285
+ [CmdletBinding()]
286
+ [OutputType([int])]
287
+ param(
288
+ [Parameter(Mandatory)]
289
+ [AllowNull()]
290
+ [AllowEmptyString()]
291
+ [string] $PayloadRaw,
292
+
293
+ [Parameter(Mandatory)]
294
+ [string] $RepositoryRoot,
220
295
 
221
- try {
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
225
- $sessionId = ([string]$payload.session_id) -replace '[^A-Za-z0-9._-]', '_'
226
- $repositoryRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
227
- $prodCap = 3
228
- $testCap = 3
229
-
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
296
+ [hashtable] $HookSeams = @{}
239
297
  )
240
298
 
241
- foreach ($path in $budgetPaths) {
242
- $toolInputRaw = @{ file_path = $path } | ConvertTo-Json -Compress
243
- $decision = Invoke-PythonBatchBudgetHook -ToolInputRaw $toolInputRaw -SessionId $sessionId -Root $repositoryRoot -ProdCap $prodCap -TestCap $testCap
244
- if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
245
- $decision.Remove('state')
246
- $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
247
- exit 0
299
+ try {
300
+ # Transport and mapping come from the shared module. session_id is still
301
+ # required because the direct-mode counter is keyed by it.
302
+ $payload = ConvertFrom-CodexPreToolUsePayload -PayloadRaw $PayloadRaw -HookName 'enforce-python-batch-budget' -RequireSessionId
303
+ $sessionId = ([string]$payload.session_id) -replace '[^A-Za-z0-9._-]', '_'
304
+
305
+ # Both sides of a rename are evaluated, matching the path scan that collected
306
+ # Add/Update/Delete targets and Move destinations alike. Ordering and
307
+ # de-duplication are preserved. A well-formed payload that maps to no file
308
+ # yields no paths, so no state is written and the hook allows silently.
309
+ $budgetPaths = @(
310
+ @(ConvertTo-CodexFileEditInput -Payload $payload) |
311
+ ForEach-Object { $_.source_path; $_.file_path } |
312
+ Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
313
+ Select-Object -Unique
314
+ )
315
+
316
+ foreach ($path in $budgetPaths) {
317
+ $toolInputRaw = @{ file_path = $path } | ConvertTo-Json -Compress
318
+ $decision = Invoke-PythonBatchBudgetHook -ToolInputRaw $toolInputRaw -SessionId $sessionId -Root $RepositoryRoot @HookSeams
319
+ if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
320
+ $decision.Remove('state')
321
+ $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
322
+ return 0
323
+ }
248
324
  }
325
+
326
+ return 0
327
+ } catch {
328
+ [Console]::Error.WriteLine([string]$_)
329
+ return 2
249
330
  }
250
- exit 0
251
- } catch {
252
- [Console]::Error.WriteLine([string]$_)
253
- exit 2
254
331
  }
332
+
333
+ if ($MyInvocation.InvocationName -eq '.') {
334
+ return
335
+ }
336
+
337
+ # The entry point returns its [int] exit code as the last pipeline element and any
338
+ # deny JSON before it, so the JSON is written explicitly before the process exits.
339
+ $entryPointResult = @(Invoke-PythonBatchBudgetCodexEntryPoint -PayloadRaw ([Console]::In.ReadToEnd()) -RepositoryRoot (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent))
340
+ if ($entryPointResult.Count -gt 1) {
341
+ $entryPointResult[0..($entryPointResult.Count - 2)] | Write-Output
342
+ }
343
+
344
+ exit ([int]$entryPointResult[-1])
@@ -48,6 +48,7 @@
48
48
  ".codex/hooks/validate-codex-subagent-routing.ps1",
49
49
  ".codex/hooks/check-powershell-test-purity.ps1",
50
50
  ".codex/hooks/check-python-test-purity.ps1",
51
+ ".codex/hooks/enforce-batch-budget-route.ps1",
51
52
  ".codex/hooks/enforce-checkpoint-monotonic.ps1",
52
53
  ".codex/hooks/enforce-completion-consistency.ps1",
53
54
  ".codex/hooks/enforce-completion-helpers.ps1",
@@ -14,7 +14,7 @@ handoffs:
14
14
  send: true
15
15
  - label: Small-scope implementation path
16
16
  agent: powershell-typed-engineer
17
- prompt: "Estimate and confirm scope (1-2 production PowerShell files + corresponding tests). If confirmed, execute the constrained short-path implementation phase from the approved minimal-audit plan and return analyzer/test/coverage deltas."
17
+ prompt: "Estimate and confirm scope (1-3 production PowerShell files + corresponding tests). If confirmed, execute the constrained short-path implementation phase from the approved minimal-audit plan and return analyzer/test/coverage deltas."
18
18
  send: true
19
19
  - label: Validate small-path delivery and post-QC docs
20
20
  agent: atomic_executor
@@ -98,8 +98,8 @@ Use these reusable skills to avoid duplicating shared operations:
98
98
 
99
99
  3) **Single source of routing truth = change budget**
100
100
  - First action is always to estimate rough change budget by identifying likely affected production PowerShell files.
101
- - If estimate is `1-2` production PowerShell files (+ corresponding tests), use **small path**.
102
- - If estimate is `>2` production PowerShell files, use **large path**.
101
+ - If estimate is `1-3` production PowerShell files (+ corresponding tests), use **small path**.
102
+ - If estimate is more than 3 (`>3`) production PowerShell files, use **large path**. The large path has no production-file cap.
103
103
 
104
104
  4) **Deterministic variable handling**
105
105
  - Persist and reuse these variables exactly as names:
@@ -123,12 +123,12 @@ Use these reusable skills to avoid duplicating shared operations:
123
123
  2. Estimate rough change budget.
124
124
  3. Write/update orchestration checkpoint.
125
125
  4. Route to one of two paths:
126
- - **Small path**: budget `1-2`
127
- - **Large path**: budget `>2`
126
+ - **Small path**: budget `1-3`
127
+ - **Large path**: budget `>3`
128
128
 
129
129
  ---
130
130
 
131
- ## Small path (budget 1-2 production PowerShell files)
131
+ ## Small path (budget 1-3 production PowerShell files)
132
132
 
133
133
  Follow this exact sequence.
134
134
 
@@ -245,7 +245,7 @@ Hard enforcement for S8:
245
245
 
246
246
  ---
247
247
 
248
- ## Large path (budget >2 production PowerShell files)
248
+ ## Large path (budget >3 production PowerShell files)
249
249
 
250
250
  Follow this exact sequence.
251
251
 
@@ -49,8 +49,8 @@ Use these reusable skills to avoid duplicating shared operations:
49
49
 
50
50
  - **Direct mode (default)**
51
51
  - Trigger: no directive line present.
52
- - Intended scope: up to 2 production PowerShell files (+ corresponding tests).
53
- - If estimated scope is >2 production files: stop and instruct caller to use `powershell-orchestrator` (or `.github/prompts/orchestrate-powershell-work.prompt.md`).
52
+ - Intended scope: 1-3 production PowerShell files (+ corresponding tests).
53
+ - If estimated scope is more than 3 production files: stop and instruct caller to use `powershell-orchestrator` (or `.github/prompts/orchestrate-powershell-work.prompt.md`).
54
54
 
55
55
  - **Orchestrator handoff mode**
56
56
  - Trigger: request includes exact line `DIRECTIVE: ORCHESTRATOR HANDOFF MODE`.
@@ -115,9 +115,9 @@ Blocking rules in orchestrator mode:
115
115
 
116
116
  ## 1) Scope control (NO scope creep)
117
117
 
118
- - **Direct mode** default scope is one small feature/bug slice (typically **1–2 production PowerShell files**) plus corresponding test file(s).
119
- - In **Direct mode**, if estimated scope exceeds **2 production PowerShell files**, do not continue implementation; instruct the user to invoke `powershell-orchestrator` (or `.github/prompts/orchestrate-powershell-work.prompt.md`) and stop.
120
- - In **Orchestrator handoff mode**, overall scope may exceed 2 production files when supported by provided context package and approved plan artifacts.
118
+ - **Direct mode** default scope is one small feature/bug slice (typically **1-3 production PowerShell files**) plus corresponding test file(s).
119
+ - In **Direct mode**, if estimated scope exceeds **3 production PowerShell files**, do not continue implementation; instruct the user to invoke `powershell-orchestrator` (or `.github/prompts/orchestrate-powershell-work.prompt.md`) and stop.
120
+ - In **Orchestrator handoff mode**, overall scope may exceed 3 production files when supported by provided context package and approved plan artifacts.
121
121
  - In all modes, avoid unrelated files and preserve minimal, targeted changes.
122
122
  - If scope expansion is required, STOP and provide:
123
123
  - a one-paragraph justification,
@@ -127,11 +127,9 @@ Blocking rules in orchestrator mode:
127
127
 
128
128
  ## 2) Change budget (hard gate)
129
129
 
130
- - **Direct mode** overall budget: up to **2 production PowerShell files** (+ corresponding tests).
130
+ - **Direct mode** overall budget: **1-3 production PowerShell files** (+ corresponding tests).
131
131
  - **Orchestrator handoff mode**: no strict overall production-file budget, provided required documentation package is present.
132
- - In all modes, per-batch budget remains: at most **3 production files** and **3 test files** unless explicit override is approved.
133
- - If no override is provided, the 3/3 per-batch limit applies.
134
- - If a batch would exceed budget, split it into smaller batches.
132
+ - Test files are not counted toward the routing threshold in any mode, and the large path has no production-file cap.
135
133
 
136
134
  ## 3) Deterministic unit tests only (no external dependency coupling)
137
135
 
@@ -195,7 +193,7 @@ If tools cannot run in the environment, STOP implementation and provide plan + p
195
193
  - if `DIRECTIVE: ORCHESTRATOR HANDOFF MODE` is present, use orchestrator handoff mode and validate full context package.
196
194
  - otherwise use direct mode.
197
195
  4) Enforce budget routing:
198
- - in direct mode, if estimated scope is >2 production PowerShell files, STOP and instruct user to invoke `powershell-orchestrator` for orchestration.
196
+ - in direct mode, if estimated scope is more than 3 production PowerShell files, STOP and instruct user to invoke `powershell-orchestrator` for orchestration.
199
197
  5) Summarize root cause/design constraint in one paragraph.
200
198
 
201
199
  ## Phase B — Design + plan (no edits)
@@ -97,7 +97,7 @@ Use these reusable skills to avoid duplicating shared operations:
97
97
  3) **Single source of routing truth = change budget**
98
98
  - First action is always to estimate rough change budget by identifying likely affected production Python files.
99
99
  - If estimate is `1-3` production Python files (+ corresponding tests), use **small path**.
100
- - If estimate is `>3` production Python files or `>3` test Python files, use **large path**.
100
+ - If estimate is more than 3 (`>3`) production Python files, use **large path**. The large path has no production-file cap. Test files are not counted toward the routing threshold.
101
101
 
102
102
  4) **Deterministic variable handling**
103
103
  - Persist and reuse these variables exactly as names:
@@ -117,7 +117,7 @@ Use these reusable skills to avoid duplicating shared operations:
117
117
 
118
118
  ## Phase 0 — Intake and budget estimate (mandatory)
119
119
 
120
- 1. Read user request and infer likely touched production Python files and/or test Python files.
120
+ 1. Read user request and infer likely touched production Python files.
121
121
  2. Estimate rough change budget.
122
122
  3. Write/update orchestration checkpoint.
123
123
  4. Route to one of two paths:
@@ -126,7 +126,7 @@ Use these reusable skills to avoid duplicating shared operations:
126
126
 
127
127
  ---
128
128
 
129
- ## Small path (budget 1-3 production Python files and 1-3 test Python files)
129
+ ## Small path (budget 1-3 production Python files)
130
130
 
131
131
  Follow this exact sequence.
132
132
 
@@ -243,7 +243,7 @@ Hard enforcement for S8:
243
243
 
244
244
  ---
245
245
 
246
- ## Large path (budget >3 production Python files or >3 test Python files)
246
+ ## Large path (budget >3 production Python files)
247
247
 
248
248
  Follow this exact sequence.
249
249
 
@@ -58,12 +58,12 @@ If any instructions conflict, **halt and notify the user**.
58
58
 
59
59
  ## 1) Scope control (NO scope creep)
60
60
 
61
- - Default scope is **one feature slice** (typically **1–3 production files** within the same package) plus its corresponding test file(s).
62
- - You may touch up to **3 production files** without additional approval **only** when it is required to:
61
+ - Default scope is **one feature slice** (typically **1-3 production files** within the same package) plus its corresponding test file(s).
62
+ - Within the direct-mode budget, a slice may include production files beyond the primary module only when it is required to:
63
63
  - introduce a minimal seam for testability (I/O boundary isolation, dependency injection),
64
64
  - make typing changes required for Pyright cleanliness in the slice, or
65
65
  - update the smallest set of call sites needed to preserve a stable public API.
66
- - Any change beyond **3 production files** requires explicit user approval.
66
+ - If the change requires more than 3 production files, stop and instruct the caller to use `python-orchestrator` (or `.github/prompts/orchestrate-python-work.prompt.md`). The orchestrated large path has no production-file cap.
67
67
  - You may not modify additional production files unless:
68
68
  - the user explicitly expands scope, OR
69
69
  - a shared helper is objectively broken and the minimal fix is required for the in-scope change.
@@ -73,11 +73,10 @@ If any instructions conflict, **halt and notify the user**.
73
73
  - the smallest alternative that avoids expanding scope
74
74
  Proceed only after user approval.
75
75
 
76
- ## 2) Change budget (hard gate)
76
+ ## 2) Change budget (routing threshold)
77
77
 
78
- - Per batch you may change at most **3 production files** and **3 test files**. This is the default and the hard gate. A user-supplied override may be honored only if it complies with repo policy and approved scope; if the requested scope exceeds 3 production files overall, stop before execution and seek explicit approval.
79
- - Override by specifying ‘budget: prod=<N>, test=<M>’ in the user prompt before Phase C begins.
80
- - If no override is provided, the 3/3 limit applies; if an override is requested, comfirm compliance before Phase C.
78
+ - Direct mode budget: 1-3 production Python files (+ corresponding tests). A change of more than 3 production Python files belongs on the orchestrated large path (`python-orchestrator`).
79
+ - Test files are not counted toward the routing threshold, and the orchestrated large path has no production-file cap.
81
80
 
82
81
  ## 3) Deterministic unit tests only (no temp files, no external systems)
83
82
 
@@ -135,7 +134,7 @@ If no plan is provided, delegate the creation of a plan to the `atomic_planner`.
135
134
  - Mocking plan (what is mocked, where patched, and why)
136
135
  - Exact files to change (must match scope guardrails)
137
136
 
138
- Before exiting Phase B, perform a quick line-count check on all in-scope files. If any file is near the 500-line limit or planned additions would push it over 500, decide upfront to split now (counting new files against the budget) or seek an override before Phase C. If uncertain, treat it as at-risk and plan for a split rather than discovering it mid-execution. If an approved plan would create a 500-line violation, halt and seek clarification before proceeding.
137
+ Before exiting Phase B, perform a quick line-count check on all in-scope files. If any file is near the 500-line limit or planned additions would push it over 500, decide upfront to split now (counting new files against the budget) before Phase C. If uncertain, treat it as at-risk and plan for a split rather than discovering it mid-execution. If an approved plan would create a 500-line violation, halt and seek clarification before proceeding.
139
138
 
140
139
  Do not proceed to edits until the user explicitly approves (e.g., “Proceed.”).
141
140
  However, if a plan is provided in the initial prompt, it is already implicitly approved.
@@ -21,9 +21,9 @@ Coordinate the user request from intake to completion using the correct path bas
21
21
  ## Required orchestration behavior
22
22
 
23
23
  1. Estimate rough change budget first (production PowerShell files).
24
- 2. If budget is **1–2 production PowerShell files** (+ corresponding tests):
24
+ 2. If budget is **1-3 production PowerShell files** (+ corresponding tests):
25
25
  - Delegate directly to `powershell-typed-engineer` for plan + implementation + QA closure.
26
- 3. If budget is **>2 production PowerShell files**:
26
+ 3. If budget is **more than 3 production PowerShell files**:
27
27
  - Execute full large-path lifecycle:
28
28
  1) Scope and create potential entry (`feature` vs `bug`, short-name creation)
29
29
  2) Promote to GitHub issue and capture issue metadata
@@ -20,10 +20,10 @@ Coordinate the user request from intake to completion using the correct path bas
20
20
 
21
21
  ## Required orchestration behavior
22
22
 
23
- 1. Estimate rough change budget first (production Python files and test Python files).
24
- 2. If budget is **1–3 production Python files** (+ corresponding tests):
23
+ 1. Estimate rough change budget first (production Python files; test files are not counted toward the routing threshold).
24
+ 2. If budget is **1-3 production Python files** (+ corresponding tests):
25
25
  - Delegate directly to `python-typed-engineer` for plan + implementation + QA closure.
26
- 3. If budget is **>3 production Python files** or **>3 test Python files**:
26
+ 3. If budget is **more than 3 production Python files**:
27
27
  - Execute full large-path lifecycle:
28
28
  1) Scope and create potential entry (`feature` vs `bug`, short-name creation)
29
29
  2) Promote to GitHub issue and capture issue metadata
@@ -18,8 +18,8 @@ Use this skill when:
18
18
 
19
19
  1) Estimate rough change budget first based on likely **production PowerShell files** touched.
20
20
  2) Route:
21
- - `1-2` production files (+ corresponding tests) → **small path** (`powershell-typed-engineer` direct mode).
22
- - `>2` production files → **large path** (`powershell-orchestrator`).
21
+ - `1-3` production files (+ corresponding tests) → **small path** (`powershell-typed-engineer` direct mode).
22
+ - More than 3 production files → **large path** (`powershell-orchestrator`). The large path has no production-file cap.
23
23
 
24
24
  ## Orchestrated Small-Path Requirements
25
25
 
@@ -36,7 +36,7 @@ Direct invocation of `powershell-typed-engineer` remains implementation-focused
36
36
 
37
37
  ## Direct-Mode Rejection Rule
38
38
 
39
- If `powershell-typed-engineer` is invoked directly and estimated scope is `>2` production files:
39
+ If `powershell-typed-engineer` is invoked directly and estimated scope is more than 3 production files:
40
40
  - Stop before implementation.
41
41
  - Return explicit routing instruction to invoke `powershell-orchestrator` (or `.github/prompts/orchestrate-powershell-work.prompt.md`).
42
42
 
@@ -29,6 +29,7 @@
29
29
  '.claude/hooks/check-powershell-test-purity.ps1'
30
30
  '.claude/hooks/enforce-python-batch-budget.ps1'
31
31
  '.claude/hooks/enforce-powershell-batch-budget.ps1'
32
+ '.claude/hooks/enforce-batch-budget-route.ps1'
32
33
  # Issue #272 hardened this PreToolUse hook with a new orchestrator-state preflight
33
34
  # check; measured here so the change produces real per-file coverage evidence.
34
35
  '.claude/hooks/enforce-pr-author-skill.ps1'
@@ -44,8 +45,10 @@
44
45
  '.claude/hooks/enforce-epic-merge-gate.ps1'
45
46
  # Issue #670: CodeCoverage.Path is an explicit per-file allow-list, so the dot-sourced helpers file is listed here.
46
47
  '.claude/hooks/enforce-epic-merge-gate-authorization.ps1'
48
+ '.claude/hooks/enforce-epic-merge-gate-resolution.ps1'
47
49
  '.claude/hooks/enforce-epic-wave-barrier.ps1'
48
50
  '.claude/hooks/enforce-epic-worktree-removal-gate.ps1'
51
+ '.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1'
49
52
  '.claude/hooks/enforce-pr-author-skill.ps1'
50
53
  '.claude/hooks/validate-orchestrator-output.ps1'
51
54
  '.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1'
@@ -134,6 +137,7 @@
134
137
  '.codex/hooks/check-powershell-test-purity.ps1'
135
138
  '.codex/hooks/enforce-python-batch-budget.ps1'
136
139
  '.codex/hooks/enforce-powershell-batch-budget.ps1'
140
+ '.codex/hooks/enforce-batch-budget-route.ps1'
137
141
  '.codex/hooks/enforce-evidence-locations.ps1'
138
142
  '.codex/hooks/enforce-checkpoint-monotonic.ps1'
139
143
  '.codex/hooks/enforce-orchestration-preimplementation-gate.ps1'
@@ -317,11 +321,19 @@
317
321
  # Issue #663 added the epic-scope resolver and its pure readiness predicates.
318
322
  '.claude/lib/worktree-resolution/EpicScopeResolution.psm1'
319
323
  '.claude/lib/worktree-resolution/EpicScopeReadiness.psm1'
324
+ # Issue #690 added the run-target resolver; registered so it stays in the coverage denominator.
325
+ '.claude/lib/worktree-resolution/WorktreeRunResolution.psm1'
320
326
  # Issue #762 relocated the CI gate parser into the bundled .claude/lib tree so
321
327
  # push-down carries it with the orchestrate and epic-orchestrate skills.
322
328
  # CodeCoverage.Path is an explicit per-file allow-list, so the relocated production
323
329
  # file is registered here to stay in the coverage denominator.
324
330
  '.claude/lib/ci-gate/Invoke-CiGateParser.ps1'
331
+ # Issue #763 ported radius drift detection to the destination runtime as two pure
332
+ # modules and an entry script under .claude/lib/parallel-drift. CodeCoverage.Path is an
333
+ # explicit per-file allow-list, so each new production file is registered here.
334
+ '.claude/lib/parallel-drift/ParallelDriftHalt.psm1'
335
+ '.claude/lib/parallel-drift/ParallelDrift.psm1'
336
+ '.claude/lib/parallel-drift/Invoke-ParallelDriftDetection.ps1'
325
337
  )
326
338
  # Optional: don't fail the run on coverage percentage
327
339
  CoveragePercentTarget = 0