@danmoisan/drm-copilot-mcp 1.1.11 → 1.1.12

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 (52) hide show
  1. package/out/mcp-server.js +366 -159
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +1 -1
  4. package/resources/claude-customizations/.claude/agents/epic-planner.md +4 -1
  5. package/resources/claude-customizations/.claude/agents/pr-author.md +15 -0
  6. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-authorization.ps1 +443 -0
  7. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +33 -37
  8. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +120 -5
  9. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +127 -0
  10. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +163 -15
  11. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +14 -27
  12. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +66 -5
  13. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +134 -10
  14. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +37 -8
  15. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +3 -1
  16. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +332 -0
  17. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +211 -182
  18. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +1 -0
  19. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeReadiness.psm1 +176 -0
  20. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +369 -0
  21. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +392 -0
  22. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeResolution.psm1 +500 -0
  23. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeTargetResolution.psm1 +347 -0
  24. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +27 -0
  25. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +12 -0
  26. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +14 -1
  27. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +4 -0
  28. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +4 -1
  29. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +39 -6
  30. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +15 -0
  31. package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +221 -0
  32. package/resources/claude-customizations/pack-manifests/core.json +9 -0
  33. package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +13 -14
  34. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  35. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +193 -0
  36. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +15 -5
  37. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +163 -15
  38. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexDeployment.ps1 +87 -0
  39. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +96 -0
  40. package/resources/codex-and-agents-customizations/.codex/scripts/codex-routing-cli-common.ps1 +370 -0
  41. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-contract.ps1 +475 -0
  42. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-runtime.ps1 +445 -0
  43. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-persistence-runtime.ps1 +118 -0
  44. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-sandbox-preflight.ps1 +69 -0
  45. package/resources/codex-and-agents-customizations/.codex/scripts/launch-epic-child-wave.ps1 +471 -0
  46. package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +280 -0
  47. package/resources/codex-and-agents-customizations/.codex/scripts/resume-epic-child.ps1 +263 -0
  48. package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +2 -1
  49. package/resources/codex-and-agents-customizations/pack-manifests/core.json +18 -1
  50. package/resources/lib/codex-routing/CodexDeployment.psm1 +315 -0
  51. package/resources/lib/codex-routing/CodexTopology.psm1 +394 -0
  52. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +26 -0
@@ -27,15 +27,34 @@ $script:OrchestrationBookkeepingTrees = @(
27
27
  'artifacts/orchestration/'
28
28
  )
29
29
 
30
- # Characters that make a command line statically unresolvable: shell interpolation and
31
- # redirection (D4 row 12). Tested across the whole line rather than per operand, because a
32
- # redirection anywhere in the line moves content the operand list cannot describe.
33
- $script:UnresolvableCommandCharacters = [char[]]@('$', '`', '>', '<')
30
+ # Characters that make a command line statically unresolvable (D4 row 12, as narrowed by
31
+ # issue #663). Interpolation characters are unresolvable in every quote state, because a
32
+ # double-quoted span still expands them. Redirection characters are unresolvable only
33
+ # outside a quoted span: inside single or double quotes the shell treats `<` and `>` as
34
+ # literal text, so a quoted Co-Authored-By trailer is not a redirection.
35
+ $script:InterpolationCommandCharacters = [char[]]@('$', '`')
36
+ $script:RedirectionCommandCharacters = [char[]]@('>', '<')
34
37
 
35
38
  # Wildcards that make an operand a glob (D4 row 15). Only the literal prefix before the
36
39
  # first of these is prefix-tested.
37
40
  $script:PathspecWildcardCharacters = [char[]]@('*', '?', '[')
38
41
 
42
+ # The single repository selector modelled between the command name and the subcommand: D4
43
+ # row 14 as narrowed by issue #671, the Lexical Absolute-Canonical Selector (LACS) rule in
44
+ # docs/features/active/2026-09-13-preimplementation-gate-worktree-selector-671/spec.md.
45
+ # Compared case-sensitively; no other relocating spelling is modelled.
46
+ $script:OrchestrationSelectorOptionName = '-C'
47
+
48
+ # Accepted widening (issue #671): a selector naming a nested subdirectory of a worktree
49
+ # passes L1 through L8 lexically yet relocates what a relative operand denotes, so
50
+ # `docs/features/active/X` under `-C <root>/tests/fixtures/resolve_execute_plan_prompt`
51
+ # stages `tests/fixtures/resolve_execute_plan_prompt/docs/features/active/X`. Measured
52
+ # exposure in this repository is seven Markdown test fixtures under the
53
+ # resolve_execute_plan_prompt fixture tree, all of which the gate's file_path leg already
54
+ # classifies as non-implementation. This module stays pure string logic; the epic's F1
55
+ # resolution module composes upstream to close the escape later without a schema change,
56
+ # the same posture D4 row 16 takes toward issue #516.
57
+
39
58
  function Split-OrchestrationCommandLine {
40
59
  <#
41
60
  .SYNOPSIS
@@ -88,6 +107,59 @@ function Split-OrchestrationCommandLine {
88
107
  }
89
108
  }
90
109
 
110
+ function Test-OrchestrationCommandTextUnresolvable {
111
+ <#
112
+ .SYNOPSIS
113
+ Reports whether a command line carries a statically unresolvable character.
114
+ .DESCRIPTION
115
+ Realizes D4 row 12 as narrowed by issue #663. Quote state is tracked with the same
116
+ state machine as Split-OrchestrationCommandLine. An interpolation character (`$` or
117
+ backtick) answers true in any quote state; a redirection character (`<` or `>`)
118
+ answers true only outside a quoted span. Backslash escapes are not modelled, so a
119
+ backslash before any quote character, or anywhere inside a double-quoted span,
120
+ answers true: the shell may end the span where this scan does not (fail closed).
121
+ .PARAMETER CommandText
122
+ The full command line as the shell would receive it.
123
+ .OUTPUTS
124
+ System.Boolean
125
+ #>
126
+ [CmdletBinding()]
127
+ [OutputType([bool])]
128
+ param([Parameter(Mandatory)][AllowEmptyString()][string] $CommandText)
129
+
130
+ # An escaped quote moves a span boundary the scan cannot model (issue #663 remediation CR-1).
131
+ if ($CommandText.Contains('\"') -or $CommandText.Contains("\'")) {
132
+ return $true
133
+ }
134
+
135
+ $openQuote = [char]0
136
+
137
+ # Scan every character once, tracking whether it sits inside a quoted span so that
138
+ # redirection characters are judged only where the shell would honour them.
139
+ foreach ($character in $CommandText.ToCharArray()) {
140
+ if ($script:InterpolationCommandCharacters -contains $character) {
141
+ return $true
142
+ }
143
+
144
+ # Decide by quote state: inside a span only the closing quote matters, and any
145
+ # backslash inside a double-quoted span is an unmodelled escape; outside a span a
146
+ # quote opens one and a redirection character is unresolvable.
147
+ if ($openQuote -ne [char]0) {
148
+ if ($openQuote -eq '"' -and $character -eq '\') {
149
+ return $true
150
+ }
151
+ if ($character -eq $openQuote) {
152
+ $openQuote = [char]0
153
+ }
154
+ } elseif ($character -eq '"' -or $character -eq "'") {
155
+ $openQuote = $character
156
+ } elseif ($script:RedirectionCommandCharacters -contains $character) {
157
+ return $true
158
+ }
159
+ }
160
+ return $false
161
+ }
162
+
91
163
  function ConvertTo-OrchestrationCommandToken {
92
164
  <#
93
165
  .SYNOPSIS
@@ -203,6 +275,67 @@ function Test-ExemptOrchestrationOperand {
203
275
  return $false
204
276
  }
205
277
 
278
+ function Test-ExemptOrchestrationSelector {
279
+ <#
280
+ .SYNOPSIS
281
+ Tests the repository selector of one tokenized segment against LACS L1 through L8.
282
+ .DESCRIPTION
283
+ Realizes D4 row 14 as narrowed by issue #671 (the Lexical Absolute-Canonical Selector
284
+ rule in docs/features/active/2026-09-13-preimplementation-gate-worktree-selector-671/spec.md).
285
+ Returns true only when the segment reads `git -C <value> <subcommand> ...` and the
286
+ value is decidable from the command text alone: exactly one case-sensitive `-C`, at
287
+ index 1 (L1, L2); a value at index 2 and the subcommand at index 3 (L3); a
288
+ drive-lettered or rooted, non-UNC value after separator normalization (L4); no `.` or
289
+ `..` segment (L5); no wildcard (L6); no colon other than the drive colon (L7); and a
290
+ non-empty value (L8). Each rejection writes a diagnostic token through Write-Debug.
291
+ The tokens are not contractual and change no decision.
292
+ L3 checks only that a non-option token follows the value; the caller
293
+ Test-ExemptOrchestrationSegmentToken rejects any subcommand other than add or commit.
294
+ .OUTPUTS
295
+ System.Boolean
296
+ #>
297
+ [CmdletBinding()]
298
+ [OutputType([bool])]
299
+ param([Parameter(Mandatory)][AllowEmptyCollection()][AllowEmptyString()][string[]] $Token)
300
+
301
+ $selector = $script:OrchestrationSelectorOptionName
302
+ if ($Token.Count -lt 2 -or $Token[1] -cne $selector) {
303
+ Write-Debug 'PREIMPL_SELECTOR_UNMODELLED_OPTION: the token after the command name is not the -C selector.'
304
+ return $false
305
+ }
306
+ if (@($Token | Where-Object { $_ -ceq $selector }).Count -gt 1) {
307
+ Write-Debug 'PREIMPL_SELECTOR_REPEATED: the segment carries more than one -C selector.'
308
+ return $false
309
+ }
310
+ if ($Token.Count -lt 4 -or $Token[3].StartsWith('-')) {
311
+ Write-Debug 'PREIMPL_SELECTOR_MALFORMED: no subcommand immediately follows the selector value.'
312
+ return $false
313
+ }
314
+ $value = $Token[2]
315
+ if (-not $value) {
316
+ Write-Debug 'PREIMPL_SELECTOR_MALFORMED: the selector value is empty.'
317
+ return $false
318
+ }
319
+
320
+ $normalized = $value -replace '\\', '/'
321
+ $isDriveRooted = $normalized -match '^[A-Za-z]:/'
322
+ if ($normalized.StartsWith('//') -or -not ($isDriveRooted -or $normalized.StartsWith('/'))) {
323
+ Write-Debug 'PREIMPL_SELECTOR_NOT_ROOTED: the selector value is relative or UNC.'
324
+ return $false
325
+ }
326
+ $segments = $normalized -split '/'
327
+ if ($segments -contains '..' -or $segments -contains '.') {
328
+ Write-Debug 'PREIMPL_SELECTOR_TRAVERSAL: the selector value carries a dot segment.'
329
+ return $false
330
+ }
331
+ $afterDrive = if ($isDriveRooted) { $normalized.Substring(2) } else { $normalized }
332
+ if ($normalized.IndexOfAny($script:PathspecWildcardCharacters) -ge 0 -or $afterDrive.Contains(':')) {
333
+ Write-Debug 'PREIMPL_SELECTOR_NOT_LITERAL: the selector value carries a wildcard or a stray colon.'
334
+ return $false
335
+ }
336
+ return $true
337
+ }
338
+
206
339
  function Test-ExemptOrchestrationSegmentToken {
207
340
  <#
208
341
  .SYNOPSIS
@@ -218,18 +351,27 @@ function Test-ExemptOrchestrationSegmentToken {
218
351
  #>
219
352
  [CmdletBinding()]
220
353
  [OutputType([bool])]
221
- param([Parameter(Mandatory)][AllowEmptyCollection()][string[]] $Token)
354
+ param([Parameter(Mandatory)][AllowEmptyCollection()][AllowEmptyString()][string[]] $Token)
222
355
 
223
356
  if ($Token.Count -lt 2) {
224
357
  return $false
225
358
  }
226
359
 
227
- # Row 14: the command name leads and the subcommand follows immediately. Anything in
228
- # between - a relocating option or an env-style prefix - moves the pathspec base and is
229
- # rejected here rather than modelled.
360
+ # Row 14, narrowed by issue #671: the command name leads and the subcommand follows it,
361
+ # either immediately or after one lexically absolute `-C <value>` selector (LACS L1-L8).
362
+ # Anything else in between - an unmodelled or unresolvable relocating option, or an
363
+ # env-style prefix - moves the pathspec base undecidably and is rejected here.
230
364
  if ($Token[0] -cne 'git') {
231
365
  return $false
232
366
  }
367
+ if ($Token[1] -cne 'add' -and $Token[1] -cne 'commit') {
368
+ if (-not (Test-ExemptOrchestrationSelector -Token $Token)) {
369
+ return $false
370
+ }
371
+ # Drop the accepted selector and its value so the subcommand is again at index 1
372
+ # and operand collection below starts at index 2 exactly as before.
373
+ $Token = @($Token[0]) + @($Token[3..($Token.Count - 1)])
374
+ }
233
375
  $subcommand = $Token[1]
234
376
  if ($subcommand -cne 'add' -and $subcommand -cne 'commit') {
235
377
  return $false
@@ -322,9 +464,9 @@ function Test-ExemptOrchestrationStagingCommand {
322
464
  return $false
323
465
  }
324
466
 
325
- # Row 12: interpolation and redirection are not statically resolvable, so the operand
326
- # list cannot be trusted to describe what the line actually touches.
327
- if ($CommandText.IndexOfAny($script:UnresolvableCommandCharacters) -ge 0) {
467
+ # Row 12: interpolation anywhere, redirection outside quotes, and unmodelled backslash
468
+ # escapes are not statically resolvable, so the operand list cannot be trusted.
469
+ if (Test-OrchestrationCommandTextUnresolvable -CommandText $CommandText) {
328
470
  return $false
329
471
  }
330
472
 
@@ -339,11 +481,17 @@ function Test-ExemptOrchestrationStagingCommand {
339
481
  return $false
340
482
  }
341
483
 
342
- foreach ($segment in $segments) {
343
- $tokens = @(ConvertTo-OrchestrationCommandToken -Segment $segment)
344
- if (-not (Test-ExemptOrchestrationSegmentToken -Token $tokens)) {
345
- return $false
484
+ # Fail closed (issue #671): an error raised while classifying any segment is a parse
485
+ # ambiguity, so it answers false instead of letting the caller continue past it.
486
+ try {
487
+ foreach ($segment in $segments) {
488
+ $tokens = @(ConvertTo-OrchestrationCommandToken -Segment $segment)
489
+ if (-not (Test-ExemptOrchestrationSegmentToken -Token $tokens)) {
490
+ return $false
491
+ }
346
492
  }
493
+ } catch {
494
+ return $false
347
495
  }
348
496
  return $true
349
497
  }
@@ -0,0 +1,87 @@
1
+ #Requires -Version 7.0
2
+ <#
3
+ .SYNOPSIS
4
+ Resolve the Codex deployment agent, model, and reasoning effort (issue #697).
5
+
6
+ .DESCRIPTION
7
+ PowerShell CLI equivalent of `python -m scripts.dev_tools.resolve_codex_deployment`
8
+ for destinations without a Python toolchain. Accepts the same GNU-style flags,
9
+ imports CodexDeployment.psm1 from .codex/lib/codex-routing (published) or
10
+ .claude/lib/codex-routing (self-hosting), and prints the same JSON receipt.
11
+ Exit codes: 0 success, 1 resolver error or missing module, 2 usage error.
12
+
13
+ .EXAMPLE
14
+ pwsh -NoProfile -File .codex/scripts/Resolve-CodexDeployment.ps1 --logical-agent orchestrator --complexity-band C2 --execution-context standalone --orchestration-complexity-ceiling C3
15
+ #>
16
+
17
+ . (Join-Path $PSScriptRoot 'codex-routing-cli-common.ps1')
18
+
19
+ function Invoke-CodexDeploymentCli {
20
+ <#
21
+ .SYNOPSIS
22
+ Run the deployment CLI over a token list and return its streams and exit code.
23
+ .PARAMETER Arguments
24
+ CLI tokens, for example @('--logical-agent', 'orchestrator', '--complexity-band', 'C2', ...).
25
+ .PARAMETER ModuleCandidates
26
+ Ordered CodexDeployment.psm1 candidate paths; the first existing one is used.
27
+ .OUTPUTS
28
+ PSCustomObject with ExitCode, Stdout, and Stderr.
29
+ #>
30
+ [CmdletBinding()]
31
+ [OutputType([pscustomobject])]
32
+ param(
33
+ [Parameter(Mandatory = $true)][AllowEmptyCollection()][AllowEmptyString()][string[]] $Arguments,
34
+ [string[]] $ModuleCandidates = (Get-CodexRoutingModuleCandidate -ScriptRoot $PSScriptRoot -ModuleName 'CodexDeployment.psm1')
35
+ )
36
+
37
+ $program = 'Resolve-CodexDeployment.ps1'
38
+ $bands = @('C1', 'C2', 'C3', 'C4')
39
+ # Flags, requirement, and choices match build_parser() in
40
+ # scripts/dev_tools/resolve_codex_deployment.py.
41
+ $specification = [ordered]@{
42
+ '--logical-agent' = @{ Key = 'logical_agent'; Kind = 'value'; Required = $true; Default = $null }
43
+ '--complexity-band' = @{ Key = 'complexity_band'; Kind = 'value'; Required = $true; Choices = $bands; Default = $null }
44
+ '--execution-context' = @{
45
+ Key = 'execution_context'
46
+ Kind = 'value'
47
+ Required = $true
48
+ Choices = @('epic_execution_child', 'epic_preparation_child', 'standalone')
49
+ Default = $null
50
+ }
51
+ '--orchestration-complexity-ceiling' = @{ Key = 'orchestration_complexity_ceiling'; Kind = 'value'; Required = $true; Choices = $bands; Default = $null }
52
+ }
53
+
54
+ $parsed = ConvertFrom-CodexRoutingArgument -Arguments $Arguments -Specification $specification
55
+ if ($null -ne $parsed.Error) {
56
+ return New-CodexRoutingCliResult -ExitCode 2 -Stderr "${program}: $($parsed.Error)`n"
57
+ }
58
+ $modulePath = Resolve-CodexRoutingModulePath -Candidate $ModuleCandidates
59
+ if ($null -eq $modulePath) {
60
+ return New-CodexRoutingCliResult -ExitCode 1 -Stderr "CODEX_ROUTING_MODULE_NOT_FOUND: none of the candidate module paths exist: $($ModuleCandidates -join '; ')`n"
61
+ }
62
+
63
+ $module = Import-Module -Name $modulePath -Force -PassThru -ErrorAction Stop
64
+ $resolver = $module.ExportedCommands['Resolve-CodexDeployment']
65
+ $values = $parsed.Values
66
+ # The module throws ArgumentException for every invalid combination; that is
67
+ # the equivalent of the Python ValueError and maps to exit 1.
68
+ try {
69
+ $receipt = & $resolver `
70
+ -LogicalAgent $values['logical_agent'] `
71
+ -ComplexityBand $values['complexity_band'] `
72
+ -ExecutionContext $values['execution_context'] `
73
+ -OrchestrationComplexityCeiling $values['orchestration_complexity_ceiling']
74
+ } catch [System.ArgumentException] {
75
+ return New-CodexRoutingCliResult -ExitCode 1 -Stderr "$($_.Exception.Message)`n"
76
+ }
77
+ return New-CodexRoutingCliResult -ExitCode 0 -Stdout ((ConvertTo-CodexRoutingJson -InputObject $receipt) + "`n")
78
+ }
79
+
80
+ if ($MyInvocation.InvocationName -eq '.') {
81
+ return
82
+ }
83
+
84
+ $cliResult = Invoke-CodexDeploymentCli -Arguments ([string[]]$args)
85
+ [Console]::Out.Write($cliResult.Stdout)
86
+ [Console]::Error.Write($cliResult.Stderr)
87
+ exit $cliResult.ExitCode
@@ -0,0 +1,96 @@
1
+ #Requires -Version 7.0
2
+ <#
3
+ .SYNOPSIS
4
+ Resolve the initial Codex implementation agent from scope data (issue #697).
5
+
6
+ .DESCRIPTION
7
+ PowerShell CLI equivalent of `python -m scripts.dev_tools.resolve_codex_topology`
8
+ for destinations without a Python toolchain. Accepts the same GNU-style flags,
9
+ imports CodexTopology.psm1 from .codex/lib/codex-routing (published) or
10
+ .claude/lib/codex-routing (self-hosting), and prints the same JSON receipt.
11
+ Exit codes: 0 success, 1 resolver error or missing module, 2 usage error.
12
+
13
+ .EXAMPLE
14
+ pwsh -NoProfile -File .codex/scripts/Resolve-CodexTopology.ps1 --language python --production-file-count 2 --test-file-count 3 --execution-context standalone
15
+ #>
16
+
17
+ . (Join-Path $PSScriptRoot 'codex-routing-cli-common.ps1')
18
+
19
+ function Invoke-CodexTopologyCli {
20
+ <#
21
+ .SYNOPSIS
22
+ Run the topology CLI over a token list and return its streams and exit code.
23
+ .PARAMETER Arguments
24
+ CLI tokens, for example @('--language', 'python', '--execution-context', 'standalone').
25
+ .PARAMETER ModuleCandidates
26
+ Ordered CodexTopology.psm1 candidate paths; the first existing one is used.
27
+ .OUTPUTS
28
+ PSCustomObject with ExitCode, Stdout, and Stderr.
29
+ #>
30
+ [CmdletBinding()]
31
+ [OutputType([pscustomobject])]
32
+ param(
33
+ [Parameter(Mandatory = $true)][AllowEmptyCollection()][AllowEmptyString()][string[]] $Arguments,
34
+ [string[]] $ModuleCandidates = (Get-CodexRoutingModuleCandidate -ScriptRoot $PSScriptRoot -ModuleName 'CodexTopology.psm1')
35
+ )
36
+
37
+ $program = 'Resolve-CodexTopology.ps1'
38
+ # Flags, kinds, defaults, and choices match build_parser() in
39
+ # scripts/dev_tools/resolve_codex_topology.py.
40
+ $specification = [ordered]@{
41
+ '--language' = @{ Key = 'language'; Kind = 'append'; Required = $false; Default = $null }
42
+ '--production-file-count' = @{ Key = 'production_file_count'; Kind = 'int'; Required = $false; Default = 0 }
43
+ '--test-file-count' = @{ Key = 'test_file_count'; Kind = 'int'; Required = $false; Default = 0 }
44
+ '--execution-context' = @{
45
+ Key = 'execution_context'
46
+ Kind = 'value'
47
+ Required = $true
48
+ Choices = @('epic_execution_child', 'epic_preparation_child', 'standalone')
49
+ Default = $null
50
+ }
51
+ '--cross-cutting' = @{ Key = 'cross_cutting'; Kind = 'switch'; Required = $false; Default = $false }
52
+ '--root-persona' = @{
53
+ Key = 'root_persona'
54
+ Kind = 'value'
55
+ Required = $false
56
+ Choices = @('epic-orchestrator', 'epic-planner')
57
+ Default = $null
58
+ }
59
+ }
60
+
61
+ $parsed = ConvertFrom-CodexRoutingArgument -Arguments $Arguments -Specification $specification
62
+ if ($null -ne $parsed.Error) {
63
+ return New-CodexRoutingCliResult -ExitCode 2 -Stderr "${program}: $($parsed.Error)`n"
64
+ }
65
+ $modulePath = Resolve-CodexRoutingModulePath -Candidate $ModuleCandidates
66
+ if ($null -eq $modulePath) {
67
+ return New-CodexRoutingCliResult -ExitCode 1 -Stderr "CODEX_ROUTING_MODULE_NOT_FOUND: none of the candidate module paths exist: $($ModuleCandidates -join '; ')`n"
68
+ }
69
+
70
+ $module = Import-Module -Name $modulePath -Force -PassThru -ErrorAction Stop
71
+ $resolver = $module.ExportedCommands['Resolve-CodexTopology']
72
+ $values = $parsed.Values
73
+ # The module throws ArgumentException for every invalid combination; that is
74
+ # the equivalent of the Python ValueError and maps to exit 1.
75
+ try {
76
+ $receipt = & $resolver `
77
+ -Language ([string[]]$values['language']) `
78
+ -ProductionFileCount $values['production_file_count'] `
79
+ -TestFileCount $values['test_file_count'] `
80
+ -ExecutionContext $values['execution_context'] `
81
+ -CrossCutting ([bool]$values['cross_cutting']) `
82
+ -RootPersona $values['root_persona']
83
+ } catch [System.ArgumentException] {
84
+ return New-CodexRoutingCliResult -ExitCode 1 -Stderr "$($_.Exception.Message)`n"
85
+ }
86
+ return New-CodexRoutingCliResult -ExitCode 0 -Stdout ((ConvertTo-CodexRoutingJson -InputObject $receipt) + "`n")
87
+ }
88
+
89
+ if ($MyInvocation.InvocationName -eq '.') {
90
+ return
91
+ }
92
+
93
+ $cliResult = Invoke-CodexTopologyCli -Arguments ([string[]]$args)
94
+ [Console]::Out.Write($cliResult.Stdout)
95
+ [Console]::Error.Write($cliResult.Stderr)
96
+ exit $cliResult.ExitCode