@danmoisan/drm-copilot-mcp 1.0.23 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/mcp-server.js +504 -174
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/feature-review.md +5 -3
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +11 -4
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +5 -2
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +28 -8
- package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +28 -8
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +117 -46
- package/resources/claude-customizations/.claude/lib/bash/parallel-manifest-validate.sh +115 -3
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +312 -0
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexTopology.psm1 +392 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryValidation.psm1 +500 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +58 -67
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCheckpointValue.psm1 +383 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCodexModelReceipts.psm1 +297 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCodexTopologyReceipts.psm1 +298 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletion.psm1 +232 -43
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletionChecks.psm1 +416 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateModelReceipts.psm1 +366 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +408 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingContract.psm1 +428 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingMatrix.psm1 +377 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateUnconditional.psm1 +166 -0
- package/resources/claude-customizations/.claude/rules/general-unit-test.md +1 -1
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +28 -3
- package/resources/claude-customizations/.claude/rules/powershell.md +1 -1
- package/resources/claude-customizations/.claude/rules/quality-tiers.md +3 -3
- package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +4 -4
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +10 -5
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +108 -34
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +71 -9
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +7 -3
- package/resources/claude-customizations/.claude/skills/powershell-qa-gate/SKILL.md +1 -1
- package/resources/claude-customizations/.claude-variants/csharp-legacy/rules/csharp.md +4 -4
- package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +5 -3
- package/resources/claude-customizations/config/blast-radius.json +1 -3
- package/resources/claude-customizations/pack-manifests/core.json +12 -0
- package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +3 -3
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp/SKILL.md +3 -3
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +5 -3
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +21 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Codex implementation-topology resolver, ported from the Python reference.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Destination-runtime PowerShell port of
|
|
7
|
+
`scripts/dev_tools/resolve_codex_topology.py`. It resolves the initial
|
|
8
|
+
implementation agent for one change from deterministic scope data: the
|
|
9
|
+
languages touched, the production and test file counts, the execution
|
|
10
|
+
context, the cross-cutting indicator, and an optional forced root persona.
|
|
11
|
+
|
|
12
|
+
Routing summary. A standalone, single-language change inside that language's
|
|
13
|
+
canonical direct-mode budget selects the language's typed engineer on the
|
|
14
|
+
`small` route. Every escalation condition selects the orchestrator on the
|
|
15
|
+
`large` route. An explicit root epic persona selects itself on the `epic`
|
|
16
|
+
route. The escalation conditions are evaluated in a fixed precedence order,
|
|
17
|
+
and the first match wins:
|
|
18
|
+
|
|
19
|
+
epic_child_context -> invalid_estimate -> cross_language ->
|
|
20
|
+
unsupported_language -> cross_cutting -> direct_mode_disabled ->
|
|
21
|
+
production_budget_exceeded
|
|
22
|
+
|
|
23
|
+
SINGLE-IMPLEMENTATION RULE. The orchestrator-state U6.T checks
|
|
24
|
+
(`OrchestratorStateCodexTopologyReceipts.psm1`) MUST call
|
|
25
|
+
`Resolve-CodexTopology` and read `Get-CodexForcedRootPersona` from this
|
|
26
|
+
module. They must never re-implement the language-budget table or the
|
|
27
|
+
escalation precedence. This module is the one PowerShell implementation of
|
|
28
|
+
the Codex topology axis.
|
|
29
|
+
|
|
30
|
+
Every constant below is pinned to `config/orchestration-routing.json`
|
|
31
|
+
(`codex_topology_policy`). Following the `ModelRouting.psm1` pattern, the
|
|
32
|
+
values are hard-coded here and never read from disk, because this module is
|
|
33
|
+
pushed down to consumer repositories that do not receive that config.
|
|
34
|
+
|
|
35
|
+
Error surface, relied on by the U6.T receipt checks:
|
|
36
|
+
- [System.ArgumentException] is the ValueError-equivalent surface. Its
|
|
37
|
+
Message text reproduces the Python message verbatim, including Python
|
|
38
|
+
tuple and repr() rendering, because inventory row U6.T10 interpolates
|
|
39
|
+
the exception text into its error string.
|
|
40
|
+
|
|
41
|
+
Documented divergence: Python raises TypeError (not ValueError) when
|
|
42
|
+
`languages` is not iterable, and the receipt validator does not catch
|
|
43
|
+
TypeError. This port treats a null Language argument as an empty collection,
|
|
44
|
+
which routes to the `unsupported_language` escalation. The difference is
|
|
45
|
+
unreachable from the U6.T checks, which reject a non-list `languages` before
|
|
46
|
+
the resolver is ever called.
|
|
47
|
+
|
|
48
|
+
The function is pure: it reads no file, starts no process, and never mutates
|
|
49
|
+
its input.
|
|
50
|
+
#>
|
|
51
|
+
|
|
52
|
+
Set-StrictMode -Version Latest
|
|
53
|
+
|
|
54
|
+
# The three permitted execution contexts, the sorted tuple rendering the Python
|
|
55
|
+
# message interpolates, and the two contexts that mark epic child work.
|
|
56
|
+
$script:VALID_EXECUTION_CONTEXTS = @('standalone', 'epic_preparation_child', 'epic_execution_child')
|
|
57
|
+
$script:VALID_EXECUTION_CONTEXTS_PYTHON_TUPLE = "('epic_execution_child', 'epic_preparation_child', 'standalone')"
|
|
58
|
+
$script:EPIC_CHILD_CONTEXTS = @('epic_preparation_child', 'epic_execution_child')
|
|
59
|
+
$script:STANDALONE_CONTEXT = 'standalone'
|
|
60
|
+
|
|
61
|
+
# The two personas that may be forced as the root of a run, and the logical agent
|
|
62
|
+
# every escalation routes to.
|
|
63
|
+
$script:FORCED_ROOT_PERSONAS = @('epic-planner', 'epic-orchestrator')
|
|
64
|
+
$script:ORCHESTRATOR_LOGICAL_AGENT = 'orchestrator'
|
|
65
|
+
|
|
66
|
+
# The per-language direct-mode budgets. A language absent from this table is an
|
|
67
|
+
# unsupported language and escalates.
|
|
68
|
+
$script:LANGUAGE_BUDGETS = @{
|
|
69
|
+
python = @{ direct_mode_enabled = $true; max_production_files = 3; max_test_files = 3; logical_agent = 'python-typed-engineer' }
|
|
70
|
+
powershell = @{ direct_mode_enabled = $true; max_production_files = 2; max_test_files = 3; logical_agent = 'powershell-typed-engineer' }
|
|
71
|
+
csharp = @{ direct_mode_enabled = $true; max_production_files = 3; max_test_files = 3; logical_agent = 'csharp-typed-engineer' }
|
|
72
|
+
typescript = @{ direct_mode_enabled = $false; max_production_files = 0; max_test_files = 0; logical_agent = 'typescript-engineer' }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# The integral CLR types a JSON integer can deserialize to. A CLR boolean is
|
|
76
|
+
# deliberately absent: Python rejects bool where an int is required.
|
|
77
|
+
$script:INTEGRAL_TYPES = @([int], [long], [short], [byte])
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
function Get-CodexForcedRootPersona {
|
|
81
|
+
<#
|
|
82
|
+
.SYNOPSIS
|
|
83
|
+
Return the two personas that may be forced as the root of a run.
|
|
84
|
+
.DESCRIPTION
|
|
85
|
+
Read-only accessor for FORCED_ROOT_PERSONAS. Exported so the U6.T
|
|
86
|
+
root_persona enum check reads the same set the resolver enforces instead
|
|
87
|
+
of restating it.
|
|
88
|
+
.OUTPUTS
|
|
89
|
+
System.String[] - the forced root persona names, in declaration order.
|
|
90
|
+
The names are emitted to the pipeline individually, so a caller collects
|
|
91
|
+
them with @(...) in the usual way.
|
|
92
|
+
#>
|
|
93
|
+
[CmdletBinding()]
|
|
94
|
+
[OutputType([string[]])]
|
|
95
|
+
param()
|
|
96
|
+
|
|
97
|
+
return [string[]]@($script:FORCED_ROOT_PERSONAS)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function Test-CodexIntegralValue {
|
|
101
|
+
<#
|
|
102
|
+
.SYNOPSIS
|
|
103
|
+
Report whether a value is an integer, rejecting booleans.
|
|
104
|
+
.DESCRIPTION
|
|
105
|
+
Private predicate mirroring the Python guard
|
|
106
|
+
`isinstance(value, bool) or not isinstance(value, int)`. Python's bool is
|
|
107
|
+
a subclass of int, so the reference rejects it explicitly; this port
|
|
108
|
+
compares the exact CLR type so no boolean satisfies the test.
|
|
109
|
+
.PARAMETER Value
|
|
110
|
+
The candidate value. May be $null.
|
|
111
|
+
.OUTPUTS
|
|
112
|
+
System.Boolean - $true only for a non-boolean integral value.
|
|
113
|
+
#>
|
|
114
|
+
[CmdletBinding()]
|
|
115
|
+
[OutputType([bool])]
|
|
116
|
+
param(
|
|
117
|
+
[Parameter(Mandatory = $true)]
|
|
118
|
+
[AllowNull()]
|
|
119
|
+
[object] $Value
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if ($null -eq $Value -or $Value -is [bool]) { return $false }
|
|
123
|
+
foreach ($integralType in $script:INTEGRAL_TYPES) {
|
|
124
|
+
if ($Value.GetType() -eq $integralType) { return $true }
|
|
125
|
+
}
|
|
126
|
+
return $false
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function Get-CodexNormalizedLanguage {
|
|
130
|
+
<#
|
|
131
|
+
.SYNOPSIS
|
|
132
|
+
Return unique, lowercased language names in stable ordinal order.
|
|
133
|
+
.DESCRIPTION
|
|
134
|
+
Private helper mirroring _normalize_languages. Every element must be a
|
|
135
|
+
non-blank string; anything else throws the ValueError-equivalent. The
|
|
136
|
+
result is deduplicated and ordinally sorted so the resolved receipt is
|
|
137
|
+
order-independent, matching Python's sorted(set(...)).
|
|
138
|
+
.PARAMETER Language
|
|
139
|
+
The raw language collection. A null value is treated as empty.
|
|
140
|
+
.OUTPUTS
|
|
141
|
+
System.String[] - the normalized language names. Declared additionally as
|
|
142
|
+
System.Object[] because the array is emitted through the unary-comma form
|
|
143
|
+
that stops PowerShell unrolling it.
|
|
144
|
+
#>
|
|
145
|
+
[CmdletBinding()]
|
|
146
|
+
[OutputType([string[]], [object[]])]
|
|
147
|
+
param(
|
|
148
|
+
[Parameter(Mandatory = $true)]
|
|
149
|
+
[AllowNull()]
|
|
150
|
+
[object] $Language
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
$normalized = [System.Collections.Generic.HashSet[string]]::new()
|
|
154
|
+
|
|
155
|
+
# Reject the whole collection on the first malformed member so a partially
|
|
156
|
+
# valid language list can never route a delegation.
|
|
157
|
+
foreach ($item in @($Language)) {
|
|
158
|
+
if (-not ($item -is [string]) -or [string]::IsNullOrWhiteSpace([string]$item)) {
|
|
159
|
+
throw [System.ArgumentException]::new('languages must contain non-empty strings.')
|
|
160
|
+
}
|
|
161
|
+
[void]$normalized.Add(([string]$item).Trim().ToLowerInvariant())
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Ordinal ordering matches Python's sorted() over the same names; PowerShell's
|
|
165
|
+
# culture-aware Sort-Object would not be guaranteed to. The unary comma stops
|
|
166
|
+
# PowerShell from unrolling the array on return, which would otherwise turn a
|
|
167
|
+
# single-language result into a bare string and a zero-language result into
|
|
168
|
+
# nothing at all.
|
|
169
|
+
$sorted = [string[]]@($normalized)
|
|
170
|
+
[Array]::Sort($sorted, [System.StringComparer]::Ordinal)
|
|
171
|
+
return , $sorted
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function Get-CodexEscalationReceipt {
|
|
175
|
+
<#
|
|
176
|
+
.SYNOPSIS
|
|
177
|
+
Build a large-route orchestrator receipt for one escalation reason.
|
|
178
|
+
.DESCRIPTION
|
|
179
|
+
Private pure builder mirroring _orchestrator_receipt. Every escalation returns
|
|
180
|
+
the same shape and differs only in routing_reason and in whether a
|
|
181
|
+
language budget was known at the point of escalation.
|
|
182
|
+
.PARAMETER ExecutionContext
|
|
183
|
+
The validated execution context.
|
|
184
|
+
.PARAMETER Language
|
|
185
|
+
The normalized language names.
|
|
186
|
+
.PARAMETER ProductionFileCount
|
|
187
|
+
The validated production file count.
|
|
188
|
+
.PARAMETER TestFileCount
|
|
189
|
+
The validated test file count.
|
|
190
|
+
.PARAMETER CrossCutting
|
|
191
|
+
The validated cross-cutting indicator.
|
|
192
|
+
.PARAMETER Reason
|
|
193
|
+
The escalation reason recorded in routing_reason.
|
|
194
|
+
.PARAMETER Budget
|
|
195
|
+
The language budget when one was resolved, otherwise $null. When absent,
|
|
196
|
+
both max file counts are reported as null.
|
|
197
|
+
.OUTPUTS
|
|
198
|
+
System.Collections.Hashtable - the twelve-key topology receipt.
|
|
199
|
+
#>
|
|
200
|
+
[CmdletBinding()]
|
|
201
|
+
[OutputType([hashtable])]
|
|
202
|
+
param(
|
|
203
|
+
[Parameter(Mandatory = $true)][string] $ExecutionContext,
|
|
204
|
+
[Parameter(Mandatory = $true)][AllowEmptyCollection()][string[]] $Language,
|
|
205
|
+
[Parameter(Mandatory = $true)][int] $ProductionFileCount,
|
|
206
|
+
[Parameter(Mandatory = $true)][int] $TestFileCount,
|
|
207
|
+
[Parameter(Mandatory = $true)][bool] $CrossCutting,
|
|
208
|
+
[Parameter(Mandatory = $true)][string] $Reason,
|
|
209
|
+
[Parameter(Mandatory = $false)][AllowNull()][hashtable] $Budget = $null
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
return @{
|
|
213
|
+
execution_context = $ExecutionContext
|
|
214
|
+
languages = $Language
|
|
215
|
+
production_file_count = $ProductionFileCount
|
|
216
|
+
test_file_count = $TestFileCount
|
|
217
|
+
cross_cutting = $CrossCutting
|
|
218
|
+
root_persona = $null
|
|
219
|
+
route = 'large'
|
|
220
|
+
topology = 'orchestrator'
|
|
221
|
+
logical_agent = $script:ORCHESTRATOR_LOGICAL_AGENT
|
|
222
|
+
routing_reason = $Reason
|
|
223
|
+
max_production_files = $(if ($null -ne $Budget) { $Budget['max_production_files'] } else { $null })
|
|
224
|
+
max_test_files = $(if ($null -ne $Budget) { $Budget['max_test_files'] } else { $null })
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function Resolve-CodexTopology {
|
|
229
|
+
<#
|
|
230
|
+
.SYNOPSIS
|
|
231
|
+
Resolve the initial Codex implementation agent from scope data.
|
|
232
|
+
.DESCRIPTION
|
|
233
|
+
Faithful PowerShell port of resolve_codex_topology
|
|
234
|
+
(scripts/dev_tools/resolve_codex_topology.py). Validates the execution
|
|
235
|
+
context, the language collection, the two file counts, and the
|
|
236
|
+
cross-cutting indicator, then applies the forced-root-persona branch or
|
|
237
|
+
the fixed escalation precedence documented in the module header.
|
|
238
|
+
.PARAMETER Language
|
|
239
|
+
The languages the change touches. Normalized to unique lowercase names.
|
|
240
|
+
.PARAMETER ProductionFileCount
|
|
241
|
+
The production file count. Must be a non-boolean integer.
|
|
242
|
+
.PARAMETER TestFileCount
|
|
243
|
+
The test file count. Must be a non-boolean integer.
|
|
244
|
+
.PARAMETER ExecutionContext
|
|
245
|
+
One of standalone, epic_preparation_child, epic_execution_child.
|
|
246
|
+
.PARAMETER CrossCutting
|
|
247
|
+
Whether the change is cross-cutting. Must be a boolean.
|
|
248
|
+
.PARAMETER RootPersona
|
|
249
|
+
An optional forced root persona, which requires a standalone context.
|
|
250
|
+
.OUTPUTS
|
|
251
|
+
System.Collections.Hashtable with the twelve resolved keys:
|
|
252
|
+
execution_context, languages, production_file_count, test_file_count,
|
|
253
|
+
cross_cutting, root_persona, route, topology, logical_agent,
|
|
254
|
+
routing_reason, max_production_files, max_test_files.
|
|
255
|
+
#>
|
|
256
|
+
[CmdletBinding()]
|
|
257
|
+
[OutputType([hashtable])]
|
|
258
|
+
param(
|
|
259
|
+
[Parameter(Mandatory = $true)]
|
|
260
|
+
[AllowNull()]
|
|
261
|
+
[object] $Language,
|
|
262
|
+
|
|
263
|
+
[Parameter(Mandatory = $true)]
|
|
264
|
+
[AllowNull()]
|
|
265
|
+
[object] $ProductionFileCount,
|
|
266
|
+
|
|
267
|
+
[Parameter(Mandatory = $true)]
|
|
268
|
+
[AllowNull()]
|
|
269
|
+
[object] $TestFileCount,
|
|
270
|
+
|
|
271
|
+
[Parameter(Mandatory = $true)]
|
|
272
|
+
[AllowNull()]
|
|
273
|
+
[AllowEmptyString()]
|
|
274
|
+
[string] $ExecutionContext,
|
|
275
|
+
|
|
276
|
+
[Parameter(Mandatory = $false)]
|
|
277
|
+
[AllowNull()]
|
|
278
|
+
[object] $CrossCutting = $false,
|
|
279
|
+
|
|
280
|
+
[Parameter(Mandatory = $false)]
|
|
281
|
+
[AllowNull()]
|
|
282
|
+
[object] $RootPersona = $null
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
if ($script:VALID_EXECUTION_CONTEXTS -cnotcontains $ExecutionContext) {
|
|
286
|
+
throw [System.ArgumentException]::new(
|
|
287
|
+
"execution_context must be one of $($script:VALID_EXECUTION_CONTEXTS_PYTHON_TUPLE), found '$ExecutionContext'."
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
$context = $ExecutionContext
|
|
291
|
+
$languages = Get-CodexNormalizedLanguage -Language $Language
|
|
292
|
+
|
|
293
|
+
# Counts are validated before any routing so a malformed estimate can never
|
|
294
|
+
# be silently coerced into a route decision.
|
|
295
|
+
if (-not (Test-CodexIntegralValue -Value $ProductionFileCount)) {
|
|
296
|
+
throw [System.ArgumentException]::new('production_file_count must be an integer.')
|
|
297
|
+
}
|
|
298
|
+
if (-not (Test-CodexIntegralValue -Value $TestFileCount)) {
|
|
299
|
+
throw [System.ArgumentException]::new('test_file_count must be an integer.')
|
|
300
|
+
}
|
|
301
|
+
if (-not ($CrossCutting -is [bool])) {
|
|
302
|
+
throw [System.ArgumentException]::new('cross_cutting must be a boolean.')
|
|
303
|
+
}
|
|
304
|
+
$productionCount = [int]$ProductionFileCount
|
|
305
|
+
$testCount = [int]$TestFileCount
|
|
306
|
+
$isCrossCutting = [bool]$CrossCutting
|
|
307
|
+
|
|
308
|
+
# A forced root persona short-circuits all escalation logic and selects
|
|
309
|
+
# itself, but only from a standalone context.
|
|
310
|
+
if ($null -ne $RootPersona) {
|
|
311
|
+
if (-not ($RootPersona -is [string]) -or ($script:FORCED_ROOT_PERSONAS -cnotcontains [string]$RootPersona)) {
|
|
312
|
+
throw [System.ArgumentException]::new("Unsupported Codex root persona: '$RootPersona'.")
|
|
313
|
+
}
|
|
314
|
+
if ($context -cne $script:STANDALONE_CONTEXT) {
|
|
315
|
+
throw [System.ArgumentException]::new('A forced root persona requires standalone context.')
|
|
316
|
+
}
|
|
317
|
+
return @{
|
|
318
|
+
execution_context = $context
|
|
319
|
+
languages = $languages
|
|
320
|
+
production_file_count = $productionCount
|
|
321
|
+
test_file_count = $testCount
|
|
322
|
+
cross_cutting = $isCrossCutting
|
|
323
|
+
root_persona = [string]$RootPersona
|
|
324
|
+
route = 'epic'
|
|
325
|
+
topology = 'epic_persona'
|
|
326
|
+
logical_agent = [string]$RootPersona
|
|
327
|
+
routing_reason = 'forced_root_persona'
|
|
328
|
+
max_production_files = $null
|
|
329
|
+
max_test_files = $null
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# Escalation precedence. The first matching condition wins, so the order of
|
|
334
|
+
# these guards is part of the contract, not an implementation detail: epic
|
|
335
|
+
# child work escalates before any estimate is trusted, an invalid estimate
|
|
336
|
+
# escalates before language analysis, and the budget checks run last because
|
|
337
|
+
# they require a resolved single-language budget.
|
|
338
|
+
$escalationArgument = @{
|
|
339
|
+
ExecutionContext = $context
|
|
340
|
+
Language = $languages
|
|
341
|
+
ProductionFileCount = $productionCount
|
|
342
|
+
TestFileCount = $testCount
|
|
343
|
+
CrossCutting = $isCrossCutting
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if ($script:EPIC_CHILD_CONTEXTS -ccontains $context) {
|
|
347
|
+
return Get-CodexEscalationReceipt @escalationArgument -Reason 'epic_child_context'
|
|
348
|
+
}
|
|
349
|
+
if ($productionCount -le 0 -or $testCount -lt 0) {
|
|
350
|
+
return Get-CodexEscalationReceipt @escalationArgument -Reason 'invalid_estimate'
|
|
351
|
+
}
|
|
352
|
+
if ($languages.Count -gt 1) {
|
|
353
|
+
return Get-CodexEscalationReceipt @escalationArgument -Reason 'cross_language'
|
|
354
|
+
}
|
|
355
|
+
if ($languages.Count -ne 1) {
|
|
356
|
+
return Get-CodexEscalationReceipt @escalationArgument -Reason 'unsupported_language'
|
|
357
|
+
}
|
|
358
|
+
if (-not $script:LANGUAGE_BUDGETS.ContainsKey($languages[0])) {
|
|
359
|
+
return Get-CodexEscalationReceipt @escalationArgument -Reason 'unsupported_language'
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
$budget = $script:LANGUAGE_BUDGETS[$languages[0]]
|
|
363
|
+
if ($isCrossCutting) {
|
|
364
|
+
return Get-CodexEscalationReceipt @escalationArgument -Reason 'cross_cutting' -Budget $budget
|
|
365
|
+
}
|
|
366
|
+
if (-not $budget['direct_mode_enabled']) {
|
|
367
|
+
return Get-CodexEscalationReceipt @escalationArgument -Reason 'direct_mode_disabled' -Budget $budget
|
|
368
|
+
}
|
|
369
|
+
if ($productionCount -gt $budget['max_production_files']) {
|
|
370
|
+
return Get-CodexEscalationReceipt @escalationArgument -Reason 'production_budget_exceeded' -Budget $budget
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return @{
|
|
374
|
+
execution_context = $context
|
|
375
|
+
languages = $languages
|
|
376
|
+
production_file_count = $productionCount
|
|
377
|
+
test_file_count = $testCount
|
|
378
|
+
cross_cutting = $isCrossCutting
|
|
379
|
+
root_persona = $null
|
|
380
|
+
route = 'small'
|
|
381
|
+
topology = 'typed_engineer'
|
|
382
|
+
logical_agent = $budget['logical_agent']
|
|
383
|
+
routing_reason = 'within_language_budget'
|
|
384
|
+
max_production_files = $budget['max_production_files']
|
|
385
|
+
max_test_files = $budget['max_test_files']
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
# The resolver and the forced-root-persona accessor are exported; the validation,
|
|
390
|
+
# normalization, and receipt-construction helpers stay private so no consumer can
|
|
391
|
+
# bypass the single entry point.
|
|
392
|
+
Export-ModuleMember -Function Resolve-CodexTopology, Get-CodexForcedRootPersona
|