@danmoisan/drm-copilot-mcp 1.1.15 → 1.1.16

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 (23) hide show
  1. package/package.json +1 -1
  2. package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +4 -5
  3. package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget-route.ps1 +137 -0
  4. package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +95 -66
  5. package/resources/claude-customizations/.claude/rules/powershell.md +3 -3
  6. package/resources/claude-customizations/.claude/skills/invoke-powershell-engineer/SKILL.md +3 -4
  7. package/resources/claude-customizations/.claude/skills/powershell-change-budget-router/SKILL.md +6 -6
  8. package/resources/claude-customizations/pack-manifests/powershell.json +1 -0
  9. package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +1 -1
  10. package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +1 -2
  11. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +2 -3
  12. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +2 -3
  13. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +2 -3
  14. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +2 -3
  15. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +2 -3
  16. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +2 -3
  17. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  18. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +285 -80
  19. package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +7 -7
  20. package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +8 -10
  21. package/resources/customizations/.github/prompts/orchestrate-powershell-work.prompt.md +2 -2
  22. package/resources/customizations/.github/skills/powershell-change-budget-router/SKILL.md +3 -3
  23. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danmoisan/drm-copilot-mcp",
3
- "version": "1.1.15",
3
+ "version": "1.1.16",
4
4
  "description": "Stdio MCP server exposing drm-copilot repo-automation tools.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: powershell-typed-engineer
3
3
  model: sonnet
4
- description: Project-scoped worker that implements and verifies PowerShell changes within typed repository boundaries. Applies PoshQC format -> PSScriptAnalyzer -> Pester toolchain, the 1-2 production-file direct-mode budget, the 3-production + 3-test per-batch cap, and zero-regression quality gates.
4
+ description: Project-scoped worker that implements and verifies PowerShell changes within typed repository boundaries. Applies PoshQC format -> PSScriptAnalyzer -> Pester toolchain, the 1-3 production-file direct-mode budget with routing to the orchestrated large path above it, and zero-regression quality gates.
5
5
  tools:
6
6
  - Read
7
7
  - Grep
@@ -36,7 +36,7 @@ Language standards and toolchain are defined in `.claude/rules/powershell.md` an
36
36
  Follow the phased workflow defined by the preloaded skills:
37
37
 
38
38
  1. **Policy compliance** — apply `policy-compliance-order` to load mandatory repo policies before any change.
39
- 2. **Routing and scope** — apply `powershell-change-budget-router` to estimate scope and select direct mode (1-2 production files) vs `powershell-orchestrator` escalation. Enforce the 3 production + 3 test per-batch cap in all modes.
39
+ 2. **Routing and scope** — apply `powershell-change-budget-router` to estimate scope and select direct mode (1-3 production files) vs large-path escalation through `/orchestrate`. The large path has no production-file cap.
40
40
  3. **Plan and baseline** — apply `atomic-plan-contract` for Phase 0 baseline capture and atomic plan structure. Delegate plan authoring to `atomic_planner` when no plan is supplied. Plans must include the proposed script or module structure, minimal DI seams (wrapper > delegate > adapter), Pester scenario-level test strategy, and the external executable wrapper mock strategy.
41
41
  4. **Implement in batches** — apply the approved plan. After each batch, run targeted PSScriptAnalyzer on touched files plus targeted Pester, and confirm per-file coverage.
42
42
  5. **Final QA gate** — apply `powershell-qa-gate` to run the full toolchain, enforce zero-regression deltas against the baseline, and produce the required reporting block before declaring completion.
@@ -46,7 +46,7 @@ For long-running orchestrated runs, apply `powershell-orchestration-state-machin
46
46
 
47
47
  ## Invocation Modes
48
48
 
49
- - **Direct mode** (default, no directive present): strict 1-2 production PowerShell files cap. If the estimated scope exceeds 2 production files, stop and instruct the caller to invoke `powershell-orchestrator` per `powershell-change-budget-router`.
49
+ - **Direct mode** (default, no directive present): strict 1-3 production PowerShell files cap. If the estimated scope exceeds 3 production files, stop and instruct the caller to invoke `/orchestrate` per `powershell-change-budget-router`.
50
50
  - **Orchestrator handoff mode** (request contains the exact line `DIRECTIVE: ORCHESTRATOR HANDOFF MODE`): overall production-file cap is lifted, but execution requires a complete context package (`objective`, `promotion-type`, `issue-num`, `feature-folder`, `issue.md`, `spec.md`, `user-story.md` or `NONE`, research artifact paths, constraints). In this mode the agent is routing/planning-only until `atomic_planner` returns `PREFLIGHT: ALL CLEAR` from the `atomic-executor` validation loop; all implementation and QA execution must occur via delegated `atomic-executor` handoffs.
51
51
 
52
52
  ## Mode Marker Resolution
@@ -64,8 +64,7 @@ If the marker is missing or malformed, fail closed to `full-feature`.
64
64
 
65
65
  Stop implementation and return to the user when:
66
66
 
67
- - the scope estimate exceeds the 2-production-file cap in direct mode,
68
- - an in-flight batch would exceed the 3-production-file or 3-test-file per-batch cap,
67
+ - the scope estimate exceeds the 3-production-file cap in direct mode,
69
68
  - a file is near or would exceed the 500-line limit,
70
69
  - any QA gate delta is non-zero after self-correction,
71
70
  - the toolchain cannot be executed in the current environment (mark the change **unverified**),
@@ -0,0 +1,137 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Pure route helpers for the PowerShell batch-budget hook.
4
+
5
+ .DESCRIPTION
6
+ Defines the functions that read the selected route from orchestrator checkpoint
7
+ text and decide whether that checkpoint selects the uncapped orchestrated large
8
+ path. The file is dot-sourced by enforce-powershell-batch-budget.ps1 and has no
9
+ entry point. None of the functions reads a file or an environment variable; the
10
+ checkpoint text is supplied by the caller.
11
+
12
+ .NOTES
13
+ Compatible with PowerShell 7+.
14
+ #>
15
+
16
+ function ConvertFrom-PowerShellBatchBudgetCheckpoint {
17
+ <#
18
+ .SYNOPSIS
19
+ Parses orchestrator checkpoint text into a checkpoint object.
20
+ .DESCRIPTION
21
+ Returns the parsed object only when the text is a JSON object. Null or
22
+ whitespace text, text that is not parseable JSON, and JSON whose root is not
23
+ an object all return $null. Performs no file or environment access.
24
+ .PARAMETER CheckpointText
25
+ Raw text of the orchestrator checkpoint file.
26
+ #>
27
+ [CmdletBinding()]
28
+ [OutputType([psobject])]
29
+ param(
30
+ [AllowNull()]
31
+ [AllowEmptyString()]
32
+ [string] $CheckpointText
33
+ )
34
+
35
+ if ([string]::IsNullOrWhiteSpace($CheckpointText)) {
36
+ return $null
37
+ }
38
+
39
+ try {
40
+ $parsed = ConvertFrom-Json -InputObject $CheckpointText -ErrorAction Stop
41
+ } catch {
42
+ Write-Verbose "Ignoring unparseable orchestrator checkpoint text: $($_.Exception.Message)"
43
+ return $null
44
+ }
45
+
46
+ if ($parsed -is [System.Management.Automation.PSCustomObject]) {
47
+ return $parsed
48
+ }
49
+
50
+ return $null
51
+ }
52
+
53
+ function Get-PowerShellBatchBudgetSelectedRoute {
54
+ <#
55
+ .SYNOPSIS
56
+ Returns the route selected by an orchestrator checkpoint.
57
+ .DESCRIPTION
58
+ Uses route_id when the checkpoint has that key, otherwise path_selected. The
59
+ value is returned only when it is a non-blank string; every other case,
60
+ including unparseable text, returns an empty string.
61
+ .PARAMETER CheckpointText
62
+ Raw text of the orchestrator checkpoint file.
63
+ #>
64
+ [CmdletBinding()]
65
+ [OutputType([string])]
66
+ param(
67
+ [AllowNull()]
68
+ [AllowEmptyString()]
69
+ [string] $CheckpointText
70
+ )
71
+
72
+ $checkpoint = ConvertFrom-PowerShellBatchBudgetCheckpoint -CheckpointText $CheckpointText
73
+ if ($null -eq $checkpoint) {
74
+ return ''
75
+ }
76
+
77
+ $propertyNames = @($checkpoint.PSObject.Properties.Name)
78
+ $rawRoute = $null
79
+ if ($propertyNames -contains 'route_id') {
80
+ $rawRoute = $checkpoint.route_id
81
+ } elseif ($propertyNames -contains 'path_selected') {
82
+ $rawRoute = $checkpoint.path_selected
83
+ }
84
+
85
+ if ($rawRoute -is [string] -and -not [string]::IsNullOrWhiteSpace($rawRoute)) {
86
+ return $rawRoute
87
+ }
88
+
89
+ return ''
90
+ }
91
+
92
+ function Test-PowerShellBatchBudgetLargePathRoute {
93
+ <#
94
+ .SYNOPSIS
95
+ Reports whether an orchestrator checkpoint selects the uncapped large path.
96
+ .DESCRIPTION
97
+ Returns $true only when the checkpoint parses to an object, its selected route
98
+ is large, remediation, or preparation (case-sensitive), next_step is not
99
+ complete, and completed_steps does not contain S12_complete. No input causes
100
+ an exception; any failure returns $false.
101
+ .PARAMETER CheckpointText
102
+ Raw text of the orchestrator checkpoint file.
103
+ #>
104
+ [CmdletBinding()]
105
+ [OutputType([bool])]
106
+ param(
107
+ [AllowNull()]
108
+ [AllowEmptyString()]
109
+ [string] $CheckpointText
110
+ )
111
+
112
+ try {
113
+ $checkpoint = ConvertFrom-PowerShellBatchBudgetCheckpoint -CheckpointText $CheckpointText
114
+ if ($null -eq $checkpoint) {
115
+ return $false
116
+ }
117
+
118
+ $route = Get-PowerShellBatchBudgetSelectedRoute -CheckpointText $CheckpointText
119
+ if (-not (@('large', 'remediation', 'preparation') -ccontains $route)) {
120
+ return $false
121
+ }
122
+
123
+ $propertyNames = @($checkpoint.PSObject.Properties.Name)
124
+ if ($propertyNames -contains 'next_step' -and $checkpoint.next_step -is [string] -and $checkpoint.next_step -ceq 'complete') {
125
+ return $false
126
+ }
127
+
128
+ if ($propertyNames -contains 'completed_steps' -and (@($checkpoint.completed_steps) -ccontains 'S12_complete')) {
129
+ return $false
130
+ }
131
+
132
+ return $true
133
+ } catch {
134
+ Write-Verbose "Treating the orchestrator checkpoint as direct mode: $($_.Exception.Message)"
135
+ return $false
136
+ }
137
+ }
@@ -1,18 +1,35 @@
1
1
  <#
2
2
  .SYNOPSIS
3
- Pre-tool-use hook that enforces the PowerShell per-batch change budget.
3
+ Pre-tool-use hook that routes PowerShell changes of more than three production files to the orchestrated large path.
4
4
 
5
5
  .DESCRIPTION
6
6
  This script is invoked by the Claude Code PreToolUse hook before any Write or Edit
7
7
  operation. When the target file is a PowerShell source file (.ps1, .psm1, .psd1),
8
- it classifies the file as either production or test and checks the running count
9
- against the per-batch cap:
10
- - 3 production PowerShell files per batch
11
- - 3 test PowerShell files per batch
8
+ it decides whether the change may proceed in direct mode.
9
+
10
+ Direct mode counts distinct production PowerShell paths per Claude Code session.
11
+ The running set is persisted under .claude/state/powershell-batch-budget.<session_id>.json,
12
+ and repeated edits to the same file are counted once. The 4th distinct production
13
+ path is denied with a POWERSHELL_LARGE_PATH_REQUIRED reason that instructs the
14
+ caller to route the change through /orchestrate.
15
+
16
+ The orchestrated large path is detected from
17
+ <root>/artifacts/orchestration/orchestrator-state.json. The selected route is the
18
+ route_id value when that key is present, otherwise the path_selected value, and it
19
+ is usable only as a non-blank string. When the selected route is large,
20
+ remediation, or preparation and the checkpoint is not terminal (next_step is not
21
+ complete and completed_steps does not contain S12_complete), no path is denied for
22
+ count and no state is written. Every other checkpoint outcome, including an absent,
23
+ unreadable, or malformed checkpoint, enforces direct mode.
24
+
25
+ Test files are never counted. They are those matching:
26
+ - tests/**/*.ps1
27
+ - *.Tests.ps1
12
28
 
13
- A "batch" is scoped to the current Claude Code session. The running count is
14
- persisted under .claude/state/powershell-batch-budget.<session_id>.json. Only
15
- distinct file paths are counted; repeated edits to the same file consume one slot.
29
+ All other .ps1/.psm1/.psd1 files are production files. Non-PowerShell paths pass
30
+ through. The threshold of three production files is a routing constant and is not
31
+ configurable at runtime. Legacy prodCap, testCap, and testFiles keys in a persisted
32
+ state file are ignored when the state is loaded.
16
33
 
17
34
  The session id is resolved from the first non-empty of: the CLAUDE_SESSION_ID
18
35
  environment variable; the contents of <root>/.claude/state/current-session-id;
@@ -27,22 +44,13 @@
27
44
  dropped when state is rehydrated, so a state file carried between worktrees
28
45
  cannot spend this worktree's budget.
29
46
 
30
- Test files are those matching:
31
- - tests/**/*.ps1
32
- - *.Tests.ps1
33
-
34
- All other .ps1/.psm1/.psd1 files are treated as production files. Non-PowerShell
35
- paths pass through.
36
-
37
- The cap may be overridden per session by setting the environment variable
38
- CLAUDE_POWERSHELL_BUDGET_PROD or CLAUDE_POWERSHELL_BUDGET_TEST to a positive integer
39
- before the session starts, or by writing {"prodCap": N, "testCap": M} into the
40
- state file.
47
+ When a production path is denied, the script emits a PreToolUse JSON response with
48
+ hookSpecificOutput.permissionDecision = 'deny' and exits 0. Files already counted
49
+ are always allowed through.
41
50
 
42
- When the cap would be exceeded by a new file, the script emits a PreToolUse JSON
43
- response with hookSpecificOutput.permissionDecision = 'deny' and exits 0. The session
44
- must explicitly reset the counter by deleting the state file before starting a new
45
- batch. Files already counted are always allowed through.
51
+ Known limitation: a stale non-terminal large-path checkpoint left at the root
52
+ exempts a later direct-mode session at that root. Orchestrator checkpoint hygiene
53
+ (issue #673) moves foreign checkpoints aside before a new run.
46
54
 
47
55
  .NOTES
48
56
  Compatible with PowerShell 7+.
@@ -52,6 +60,7 @@ param()
52
60
 
53
61
 
54
62
  Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
63
+ . (Join-Path $PSScriptRoot 'enforce-powershell-batch-budget-route.ps1')
55
64
 
56
65
  function Test-PowerShellBatchBudgetPathInRoot {
57
66
  <#
@@ -174,25 +183,24 @@ function Get-PowerShellBatchBudgetSessionId {
174
183
  }
175
184
 
176
185
  function Get-PowerShellBatchBudgetState {
186
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'TestCap', Justification = 'Accepted and ignored for callers written against the removed test-file cap.')]
177
187
  [CmdletBinding()]
178
188
  [OutputType([System.Collections.Specialized.OrderedDictionary])]
179
189
  param(
180
190
  [Parameter(Mandatory)]
181
191
  [int] $ProdCap,
182
192
 
183
- [Parameter(Mandatory)]
184
- [int] $TestCap
193
+ [int] $TestCap = 0
185
194
  )
186
195
 
187
196
  [ordered]@{
188
197
  prodCap = $ProdCap
189
- testCap = $TestCap
190
198
  prodFiles = @()
191
- testFiles = @()
192
199
  }
193
200
  }
194
201
 
195
202
  function ConvertTo-PowerShellBatchBudgetState {
203
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'TestCap', Justification = 'Accepted and ignored for callers written against the removed test-file cap.')]
196
204
  [CmdletBinding()]
197
205
  [OutputType([System.Collections.Specialized.OrderedDictionary])]
198
206
  param(
@@ -202,17 +210,16 @@ function ConvertTo-PowerShellBatchBudgetState {
202
210
  [Parameter(Mandatory)]
203
211
  [int] $ProdCap,
204
212
 
205
- [Parameter(Mandatory)]
206
- [int] $TestCap,
213
+ [int] $TestCap = 0,
207
214
 
208
215
  [AllowNull()]
209
216
  [AllowEmptyString()]
210
217
  [string] $Root = (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent)
211
218
  )
212
219
 
213
- $state = Get-PowerShellBatchBudgetState -ProdCap $ProdCap -TestCap $TestCap
214
- if ($null -ne $InputObject.prodCap) { $state.prodCap = [int]$InputObject.prodCap }
215
- if ($null -ne $InputObject.testCap) { $state.testCap = [int]$InputObject.testCap }
220
+ # Only prodFiles is carried over. Persisted prodCap, testCap, and testFiles keys
221
+ # come from the removed test-file cap and runtime overrides and are ignored.
222
+ $state = Get-PowerShellBatchBudgetState -ProdCap $ProdCap
216
223
 
217
224
  # Persisted entries that resolve outside this root belong to another worktree
218
225
  # and are dropped, so a state file carried across worktrees cannot spend this
@@ -220,9 +227,6 @@ function ConvertTo-PowerShellBatchBudgetState {
220
227
  if ($null -ne $InputObject.prodFiles) {
221
228
  $state.prodFiles = @(@($InputObject.prodFiles) | Where-Object { Test-PowerShellBatchBudgetPathInRoot -Path $_ -Root $Root })
222
229
  }
223
- if ($null -ne $InputObject.testFiles) {
224
- $state.testFiles = @(@($InputObject.testFiles) | Where-Object { Test-PowerShellBatchBudgetPathInRoot -Path $_ -Root $Root })
225
- }
226
230
 
227
231
  return $state
228
232
  }
@@ -261,12 +265,17 @@ function Invoke-PowerShellBatchBudgetDecision {
261
265
  [Parameter(Mandatory)]
262
266
  [System.Collections.IDictionary] $State,
263
267
 
264
- [Parameter(Mandatory)]
265
- [string] $StateFile,
268
+ [AllowEmptyString()]
269
+ [string] $StateFile = '',
266
270
 
267
271
  [AllowNull()]
268
272
  [AllowEmptyString()]
269
- [string] $Root = (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent)
273
+ [string] $Root = (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent),
274
+
275
+ [switch] $LargePathRoute,
276
+
277
+ [AllowEmptyString()]
278
+ [string] $ObservedRoute = ''
270
279
  )
271
280
 
272
281
  $normalized = $FilePath -replace '\\', '/'
@@ -281,27 +290,32 @@ function Invoke-PowerShellBatchBudgetDecision {
281
290
  return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
282
291
  }
283
292
 
293
+ # The orchestrated large path has no production-file cap, so nothing is counted.
294
+ if ($LargePathRoute) {
295
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
296
+ }
297
+
298
+ # Test files never count toward the routing threshold.
284
299
  $isTestFile = ($normalized -match '(^|/)tests/.*\.ps1$') -or ($normalized -match '\.Tests\.ps1$')
285
- $targetList = if ($isTestFile) { @($State.testFiles) } else { @($State.prodFiles) }
286
- $cap = if ($isTestFile) { [int]$State.testCap } else { [int]$State.prodCap }
287
- $kind = if ($isTestFile) { 'test' } else { 'production' }
300
+ if ($isTestFile) {
301
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
302
+ }
288
303
 
289
- if ($targetList -contains $normalized) {
304
+ $countedFiles = @($State.prodFiles)
305
+ if ($countedFiles -contains $normalized) {
290
306
  return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $false }
291
307
  }
292
308
 
293
- if ($targetList.Count -ge $cap) {
294
- $currentFiles = ($targetList -join ', ')
295
- $kindUpper = $kind.ToUpperInvariant()
296
- $reason = "PowerShell per-batch budget exceeded: $kind file cap is $cap and is already full ($currentFiles). Requested new file: $normalized. Split the work into a new batch, raise the cap via CLAUDE_POWERSHELL_BUDGET_$kindUpper environment variable with approved scope, or reset the batch by deleting $StateFile."
309
+ $cap = [int]$State.prodCap
310
+ if ($countedFiles.Count -ge $cap) {
311
+ Write-Verbose "Denying production PowerShell path '$normalized' in direct mode; counted paths are recorded in '$StateFile'."
312
+ $counted = $countedFiles -join ', '
313
+ $route = if ([string]::IsNullOrWhiteSpace($ObservedRoute)) { 'none' } else { $ObservedRoute }
314
+ $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 /orchestrate. Checkpoint route observed: $route."
297
315
  return Get-PowerShellBatchBudgetBlockDecision -Reason $reason -State $State
298
316
  }
299
317
 
300
- if ($isTestFile) {
301
- $State.testFiles = @($State.testFiles) + @($normalized)
302
- } else {
303
- $State.prodFiles = @($State.prodFiles) + @($normalized)
304
- }
318
+ $State.prodFiles = $countedFiles + @($normalized)
305
319
 
306
320
  return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' }; state = $State; shouldWriteState = $true }
307
321
  }
@@ -314,7 +328,6 @@ function Invoke-PowerShellBatchBudgetHook {
314
328
  [string] $SessionId = '',
315
329
  [string] $Root = (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent),
316
330
  [int] $ProdCap = 3,
317
- [int] $TestCap = 3,
318
331
  [scriptblock] $ReadSessionIdFile = {
319
332
  param([string] $Path)
320
333
  if (Test-Path -LiteralPath $Path -PathType Leaf) {
@@ -322,6 +335,13 @@ function Invoke-PowerShellBatchBudgetHook {
322
335
  }
323
336
  return ''
324
337
  },
338
+ [scriptblock] $ReadCheckpoint = {
339
+ param([string] $Path)
340
+ if (Test-Path -LiteralPath $Path -PathType Leaf) {
341
+ return (Get-Content -LiteralPath $Path -Raw)
342
+ }
343
+ return ''
344
+ },
325
345
  [scriptblock] $TestPathExists = { param([string] $Path) Test-Path -Path $Path },
326
346
  [scriptblock] $EnsureDirectory = { param([string] $Path) New-Item -ItemType Directory -Path $Path -Force | Out-Null },
327
347
  [scriptblock] $ReadState = { param([string] $Path) Get-Content -Path $Path -Raw },
@@ -359,23 +379,41 @@ function Invoke-PowerShellBatchBudgetHook {
359
379
  -SessionIdFilePath (Join-Path -Path $stateDir -ChildPath 'current-session-id') `
360
380
  -ReadSessionIdFile $ReadSessionIdFile
361
381
 
382
+ $stateFile = Join-Path -Path $stateDir -ChildPath ("powershell-batch-budget.$resolvedSessionId.json")
383
+
384
+ # The checkpoint is read before any state operation, so the large path neither
385
+ # creates the state directory nor reads or writes the state file.
386
+ $checkpointPath = Join-Path -Path $Root -ChildPath 'artifacts/orchestration/orchestrator-state.json'
387
+ $checkpointText = ''
388
+ try {
389
+ $checkpointText = [string](& $ReadCheckpoint $checkpointPath)
390
+ } catch {
391
+ Write-Verbose "Treating unreadable orchestrator checkpoint '$checkpointPath' as direct mode: $($_.Exception.Message)"
392
+ $checkpointText = ''
393
+ }
394
+
395
+ $isLargePath = Test-PowerShellBatchBudgetLargePathRoute -CheckpointText $checkpointText
396
+ $observedRoute = Get-PowerShellBatchBudgetSelectedRoute -CheckpointText $checkpointText
397
+ if ($isLargePath) {
398
+ return Invoke-PowerShellBatchBudgetDecision -FilePath $filePath -State (Get-PowerShellBatchBudgetState -ProdCap $ProdCap) -StateFile $stateFile -Root $Root -LargePathRoute
399
+ }
400
+
362
401
  if (-not (& $TestPathExists $stateDir)) {
363
402
  & $EnsureDirectory $stateDir
364
403
  }
365
404
 
366
- $stateFile = Join-Path -Path $stateDir -ChildPath ("powershell-batch-budget.$resolvedSessionId.json")
367
- $state = Get-PowerShellBatchBudgetState -ProdCap $ProdCap -TestCap $TestCap
405
+ $state = Get-PowerShellBatchBudgetState -ProdCap $ProdCap
368
406
 
369
407
  if (& $TestPathExists $stateFile) {
370
408
  try {
371
409
  $loaded = & $ReadState $stateFile | ConvertFrom-Json -ErrorAction Stop
372
- $state = ConvertTo-PowerShellBatchBudgetState -InputObject $loaded -ProdCap $ProdCap -TestCap $TestCap -Root $Root
410
+ $state = ConvertTo-PowerShellBatchBudgetState -InputObject $loaded -ProdCap $ProdCap -Root $Root
373
411
  } catch {
374
412
  Write-Verbose "Ignoring unreadable PowerShell batch-budget state file '$stateFile': $($_.Exception.Message)"
375
413
  }
376
414
  }
377
415
 
378
- $decision = Invoke-PowerShellBatchBudgetDecision -FilePath $filePath -State $state -StateFile $stateFile -Root $Root
416
+ $decision = Invoke-PowerShellBatchBudgetDecision -FilePath $filePath -State $state -StateFile $stateFile -Root $Root -ObservedRoute $observedRoute
379
417
  if ($decision.shouldWriteState) {
380
418
  try {
381
419
  & $WriteState $stateFile $decision.state
@@ -423,16 +461,7 @@ function Invoke-PowerShellBatchBudgetEntryPoint {
423
461
  # worktree-derived identifier.
424
462
  $sessionId = [string]$env:CLAUDE_SESSION_ID
425
463
 
426
- $prodCap = 3
427
- $testCap = 3
428
- if ($env:CLAUDE_POWERSHELL_BUDGET_PROD -match '^\d+$') {
429
- $prodCap = [int]$env:CLAUDE_POWERSHELL_BUDGET_PROD
430
- }
431
- if ($env:CLAUDE_POWERSHELL_BUDGET_TEST -match '^\d+$') {
432
- $testCap = [int]$env:CLAUDE_POWERSHELL_BUDGET_TEST
433
- }
434
-
435
- $decision = Invoke-PowerShellBatchBudgetHook -ToolInputRaw $ToolInputRaw -SessionId $sessionId -ProdCap $prodCap -TestCap $testCap
464
+ $decision = Invoke-PowerShellBatchBudgetHook -ToolInputRaw $ToolInputRaw -SessionId $sessionId
436
465
  if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
437
466
  $decision.Remove('state')
438
467
  $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
@@ -36,9 +36,9 @@ Run the toolchain in order: format → analyze → test. Restart from step 1 if
36
36
 
37
37
  ## Change Budget
38
38
 
39
- - Direct-mode overall scope: up to 2 production PowerShell files (plus corresponding tests). Requests exceeding this must be routed to `powershell-orchestrator` per `powershell-change-budget-router`.
40
- - Per-batch cap in all modes: at most 3 production files and 3 test files unless an explicit override has been approved.
41
- - If a batch would exceed the cap, split the work into smaller batches.
39
+ - Direct-mode scope: 1-3 production PowerShell files (plus corresponding tests). A change that touches more than 3 production PowerShell files belongs on the orchestrated large path: route it through `/orchestrate` (the `orchestrator` agent) per `powershell-change-budget-router`.
40
+ - The orchestrated large path has no production-file cap. Test files are not counted toward the routing threshold.
41
+ - `.claude/hooks/enforce-powershell-batch-budget.ps1` enforces the threshold outside the large path: it denies the 4th distinct production PowerShell file with a routing instruction.
42
42
 
43
43
  ## Design Seams (Minimal DI)
44
44
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: invoke-powershell-engineer
3
- description: Invoke the powershell-typed-engineer worker to design, implement, and verify PowerShell changes within typed repository boundaries. Applies PoshQC format -> analyze -> test toolchain, the 1-2 production-file direct-mode budget, the 3-production + 3-test per-batch cap, and zero-regression quality gates.
3
+ description: Invoke the powershell-typed-engineer worker to design, implement, and verify PowerShell changes within typed repository boundaries. Applies PoshQC format -> analyze -> test toolchain, the 1-3 production-file direct-mode budget with routing to the orchestrated large path above it, and zero-regression quality gates.
4
4
  ---
5
5
 
6
6
  # Implement PowerShell Skill
@@ -12,10 +12,10 @@ This direct-use wrapper delegates PowerShell implementation work to the `powersh
12
12
  Use this skill when:
13
13
 
14
14
  - The user requests a PowerShell code change, bug fix, refactor, or test addition.
15
- - Estimated scope fits the direct-mode path (1-2 production PowerShell files plus corresponding tests).
15
+ - Estimated scope fits the direct-mode path (1-3 production PowerShell files plus corresponding tests).
16
16
  - The toolchain (PoshQC format, PSScriptAnalyzer, Pester with coverage where enforced) can be run in the current environment, or the user has explicitly authorized an unverified plan-only response.
17
17
 
18
- If the estimated scope exceeds the direct-mode budget, this skill defers to the orchestrated flow via `powershell-change-budget-router` instead of proceeding directly.
18
+ If the estimated scope exceeds the direct-mode budget, this skill defers to the orchestrated large path (`/orchestrate`) via `powershell-change-budget-router` instead of proceeding directly.
19
19
 
20
20
  ## Inputs
21
21
 
@@ -23,7 +23,6 @@ If the estimated scope exceeds the direct-mode budget, this skill defers to the
23
23
  - Files or entrypoints in scope (exact script or module paths and corresponding `*.Tests.ps1` paths).
24
24
  - Constraints, including public function or module contracts that must be preserved.
25
25
  - Optional approved plan. If none is supplied, the worker delegates plan authoring to `atomic_planner` before any edits.
26
- - Optional budget override in the form `budget: prod=<N>, test=<M>` subject to repo policy compliance.
27
26
 
28
27
  ## Output Paths
29
28
 
@@ -5,7 +5,7 @@ description: Budget-first routing contract for PowerShell work: estimate product
5
5
 
6
6
  # PowerShell Change Budget Router
7
7
 
8
- Canonical guidance for deciding whether work should execute directly in `powershell-typed-engineer` or be escalated to `powershell-orchestrator`.
8
+ Canonical guidance for deciding whether work should execute directly in `powershell-typed-engineer` or be escalated to the orchestrated large path through `/orchestrate` (the `orchestrator` agent).
9
9
 
10
10
  ## When to Use This Skill
11
11
 
@@ -18,12 +18,12 @@ 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** (`/orchestrate`, the `orchestrator` agent). The large path has no production-file cap.
23
23
 
24
24
  ## Orchestrated Small-Path Requirements
25
25
 
26
- When routed through `powershell-orchestrator`, small path still requires lifecycle scaffolding before implementation:
26
+ When routed through `/orchestrate`, small path still requires lifecycle scaffolding before implementation:
27
27
  - invoke promotion/folder lifecycle steps through `vscode/runCommand` + extension access per `feature-promotion-lifecycle` when available; use script/CLI fallback only when direct extension command execution is unavailable,
28
28
  - promote potential item to GitHub issue with `--work-mode minor-audit`,
29
29
  - create active feature folder with `--work-mode minor-audit`,
@@ -36,9 +36,9 @@ 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
- - Return explicit routing instruction to invoke `powershell-orchestrator`.
41
+ - Return explicit routing instruction to invoke `/orchestrate`.
42
42
 
43
43
  ## Documentation Expectations
44
44
 
@@ -6,6 +6,7 @@
6
6
  ".claude/agents/powershell-typed-engineer.md",
7
7
  ".claude/hooks/check-powershell-test-purity.ps1",
8
8
  ".claude/hooks/enforce-powershell-batch-budget.ps1",
9
+ ".claude/hooks/enforce-powershell-batch-budget-route.ps1",
9
10
  ".claude/skills/invoke-powershell-engineer/SKILL.md",
10
11
  ".claude/skills/powershell-change-budget-router/SKILL.md",
11
12
  ".claude/skills/powershell-orchestration-state-machine/SKILL.md",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: invoke-powershell-engineer
3
- description: Invoke the powershell-typed-engineer worker to design, implement, and verify PowerShell changes within typed repository boundaries. Applies PoshQC formatting, analysis, and testing with a one-to-two production-file direct-mode budget, a three-production plus three-test batch cap, and zero-regression quality gates.
3
+ description: Invoke the powershell-typed-engineer worker to design, implement, and verify PowerShell changes within typed repository boundaries. Applies PoshQC formatting, analysis, and testing with a one-to-two production-file direct-mode budget and zero-regression quality gates.
4
4
  ---
5
5
 
6
6
  # Implement PowerShell Skill
@@ -34,8 +34,7 @@ Run the toolchain in order: format → analyze → test. Restart from step 1 if
34
34
  ## Change Budget
35
35
 
36
36
  - Direct-mode overall scope: up to 2 production PowerShell files (plus corresponding tests). Requests exceeding this must be routed to `powershell-orchestrator` per `powershell-change-budget-router`.
37
- - Per-batch cap in all modes: at most 3 production files and 3 test files unless an explicit override has been approved.
38
- - If a batch would exceed the cap, split the work into smaller batches.
37
+ - The orchestrated large path has no production-file cap, and test files are not counted toward the routing threshold.
39
38
 
40
39
  ## Design Seams (Minimal DI)
41
40
 
@@ -29,7 +29,7 @@ Migration contract:
29
29
 
30
30
  ---
31
31
  name: powershell-typed-engineer
32
- description: Project-scoped worker that implements and verifies PowerShell changes within typed repository boundaries. Applies PoshQC format -> PSScriptAnalyzer -> Pester toolchain, the 1-2 production-file direct-mode budget, the 3-production + 3-test per-batch cap, and zero-regression quality gates.
32
+ description: Project-scoped worker that implements and verifies PowerShell changes within typed repository boundaries. Applies PoshQC format -> PSScriptAnalyzer -> Pester toolchain, the 1-2 production-file direct-mode budget, and zero-regression quality gates.
33
33
  tools:
34
34
  - Read
35
35
  - Grep
@@ -64,7 +64,7 @@ Language standards and toolchain are defined in `.agents/skills/powershell/SKILL
64
64
  Follow the phased workflow defined by the preloaded skills:
65
65
 
66
66
  1. **Policy compliance** — apply `policy-compliance-order` to load mandatory repo policies before any change.
67
- 2. **Routing and scope** — apply `powershell-change-budget-router` to estimate scope and select direct mode (1-2 production files) vs `powershell-orchestrator` escalation. Enforce the 3 production + 3 test per-batch cap in all modes.
67
+ 2. **Routing and scope** — apply `powershell-change-budget-router` to estimate scope and select direct mode (1-2 production files) vs `powershell-orchestrator` escalation. The large path has no production-file cap.
68
68
  3. **Plan and baseline** — apply `atomic-plan-contract` for Phase 0 baseline capture and atomic plan structure. Delegate plan authoring to `atomic_planner` when no plan is supplied. Plans must include the proposed script or module structure, minimal DI seams (wrapper > delegate > adapter), Pester scenario-level test strategy, and the external executable wrapper mock strategy.
69
69
  4. **Implement in batches** — apply the approved plan. After each batch, run targeted PSScriptAnalyzer on touched files plus targeted Pester, and confirm per-file coverage.
70
70
  5. **Final QA gate** — apply `powershell-qa-gate` to run the full toolchain, enforce zero-regression deltas against the baseline, and produce the required reporting block before declaring completion.
@@ -93,7 +93,6 @@ If the marker is missing or malformed, fail closed to `full-feature`.
93
93
  Stop implementation and return to the user when:
94
94
 
95
95
  - the scope estimate exceeds the 2-production-file cap in direct mode,
96
- - an in-flight batch would exceed the 3-production-file or 3-test-file per-batch cap,
97
96
  - a file is near or would exceed the 500-line limit,
98
97
  - any QA gate delta is non-zero after self-correction,
99
98
  - the toolchain cannot be executed in the current environment (mark the change **unverified**),