@danmoisan/drm-copilot-mcp 1.0.9 → 1.0.11
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 +230 -225
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +2 -41
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +26 -5
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +485 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletion.psm1 +243 -0
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +7 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +2 -0
- package/resources/claude-customizations/pack-manifests/core.json +3 -1
- package/resources/config/orchestration-routing.json +1 -1
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Portable completion-gate presence checks for the orchestrator-state checkpoint.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Provides the destination-runtime PowerShell mirror of the completion-gate
|
|
7
|
+
presence checks the pushed-down validate-orchestrator-output hook needs when the
|
|
8
|
+
authoritative Python validator (`scripts/dev_tools`) is not importable. It
|
|
9
|
+
reuses the shared load, field-accessor, and base-presence primitives from the
|
|
10
|
+
sibling `OrchestratorState.psm1` and imports `.claude/lib/model-routing/ModelRouting.psm1`
|
|
11
|
+
so per-receipt model formulas are available where practical.
|
|
12
|
+
|
|
13
|
+
The single public function `Test-OrchestratorStateCompletionReadiness` fails
|
|
14
|
+
closed on a missing checkpoint file, invalid JSON, or an invalid base shape, then
|
|
15
|
+
applies the model-routing "required once delegated" existence gate - the
|
|
16
|
+
delegated-agent set (derived from `delegation_receipts[].agent_name` plus a
|
|
17
|
+
delegating `next_step`) must be a subset of `model_routing_receipts[].agent` -
|
|
18
|
+
mirroring `scripts/dev_tools/_orchestrator_state_model_routing_gate.py`. Deep
|
|
19
|
+
per-receipt routing-contract correctness that requires full Python authority is a
|
|
20
|
+
documented Non-Goal for the portable path; the gate performs the presence-level
|
|
21
|
+
existence check and reports missing receipts with error text containing the
|
|
22
|
+
literal token `model_routing_receipts`, so the completion hook maps a failure to
|
|
23
|
+
its `MODEL_ROUTING_BLOCKED:` block reason. The Python validator remains
|
|
24
|
+
authoritative; this module is the fallback mirror only.
|
|
25
|
+
#>
|
|
26
|
+
|
|
27
|
+
Set-StrictMode -Version Latest
|
|
28
|
+
|
|
29
|
+
# Import the sibling shared module and the portable model-routing module, resolved
|
|
30
|
+
# relative to this module's directory so the imports travel with the pushed-down
|
|
31
|
+
# pack regardless of the consumer repository's working directory.
|
|
32
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'OrchestratorState.psm1') -Force
|
|
33
|
+
$script:ModelRoutingModulePath = Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '..') -ChildPath (Join-Path -Path 'model-routing' -ChildPath 'ModelRouting.psm1')
|
|
34
|
+
Import-Module $script:ModelRoutingModulePath -Force
|
|
35
|
+
|
|
36
|
+
# The subagent types delegated via the Agent tool that can be named by a delegating
|
|
37
|
+
# next_step. Pinned to _DELEGATING_AGENTS in
|
|
38
|
+
# scripts/dev_tools/_orchestrator_state_model_routing_gate.py. The `orchestrator`
|
|
39
|
+
# type is deliberately excluded: it is the caller, never a routing-receipt target.
|
|
40
|
+
$script:DELEGATING_AGENTS = @(
|
|
41
|
+
'atomic-planner',
|
|
42
|
+
'atomic-executor',
|
|
43
|
+
'feature-review',
|
|
44
|
+
'task-researcher',
|
|
45
|
+
'prd-feature',
|
|
46
|
+
'pr-author'
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Checkpoint keys the gate reads to derive the delegated-agent and receipt-agent sets.
|
|
50
|
+
$script:DELEGATION_RECEIPTS_KEY = 'delegation_receipts'
|
|
51
|
+
$script:MODEL_ROUTING_RECEIPTS_KEY = 'model_routing_receipts'
|
|
52
|
+
$script:NEXT_STEP_KEY = 'next_step'
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
function Get-OrchestratorStateDelegatedAgent {
|
|
56
|
+
<#
|
|
57
|
+
.SYNOPSIS
|
|
58
|
+
Derive the set of agents a checkpoint has delegated (or is about to).
|
|
59
|
+
.DESCRIPTION
|
|
60
|
+
Private helper mirroring ``_delegated_agents`` in the Python gate. Collects
|
|
61
|
+
each well-formed ``delegation_receipts[]`` entry's non-empty ``agent_name``
|
|
62
|
+
plus the agent implied by a ``next_step`` that names a recognized delegating
|
|
63
|
+
agent. The list form of ``delegation_receipts`` is the authoritative "a
|
|
64
|
+
delegation happened" record; the namespaced (promotion) object form carries
|
|
65
|
+
no agent_name list and contributes no delegated agents.
|
|
66
|
+
.PARAMETER State
|
|
67
|
+
The parsed checkpoint PSCustomObject.
|
|
68
|
+
.OUTPUTS
|
|
69
|
+
System.String[] - the delegated-agent names (may be empty).
|
|
70
|
+
#>
|
|
71
|
+
[CmdletBinding()]
|
|
72
|
+
[OutputType([string[]])]
|
|
73
|
+
param(
|
|
74
|
+
[Parameter(Mandatory = $true)]
|
|
75
|
+
[psobject] $State
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
$agents = [System.Collections.Generic.HashSet[string]]::new()
|
|
79
|
+
|
|
80
|
+
# Collect each list-form delegation receipt's non-empty agent_name. A non-list
|
|
81
|
+
# (namespaced) delegation_receipts value contributes nothing here.
|
|
82
|
+
$receiptsField = Get-OrchestratorStateField -State $State -Name $script:DELEGATION_RECEIPTS_KEY
|
|
83
|
+
if ($receiptsField.Present -and ($receiptsField.Value -is [System.Array])) {
|
|
84
|
+
foreach ($receipt in $receiptsField.Value) {
|
|
85
|
+
if ($receipt -is [System.Management.Automation.PSCustomObject]) {
|
|
86
|
+
$nameField = Get-OrchestratorStateField -State $receipt -Name 'agent_name'
|
|
87
|
+
if ($nameField.Present -and $null -ne $nameField.Value -and
|
|
88
|
+
-not [string]::IsNullOrWhiteSpace([string]$nameField.Value)) {
|
|
89
|
+
[void]$agents.Add([string]$nameField.Value)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# A delegating next_step names the upcoming delegation that may not yet have a
|
|
96
|
+
# receipt; include it only when it matches a recognized delegating agent so a
|
|
97
|
+
# non-delegating label (for example "complete") never triggers the gate.
|
|
98
|
+
$nextStepField = Get-OrchestratorStateField -State $State -Name $script:NEXT_STEP_KEY
|
|
99
|
+
if ($nextStepField.Present -and $null -ne $nextStepField.Value -and
|
|
100
|
+
($script:DELEGATING_AGENTS -contains [string]$nextStepField.Value)) {
|
|
101
|
+
[void]$agents.Add([string]$nextStepField.Value)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return [string[]]@($agents)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function Get-OrchestratorStateRoutingReceiptAgent {
|
|
108
|
+
<#
|
|
109
|
+
.SYNOPSIS
|
|
110
|
+
Collect the set of agents that carry a model-routing receipt.
|
|
111
|
+
.DESCRIPTION
|
|
112
|
+
Private helper mirroring the receipt-agent harvest in the Python gate. Reads
|
|
113
|
+
the checkpoint's ``model_routing_receipts[]`` array and returns the set of
|
|
114
|
+
non-empty ``agent`` values present on well-formed receipt objects. A non-list
|
|
115
|
+
value contributes no agents (the existence gate then reports every delegated
|
|
116
|
+
agent as unreceipted, preserving fail-closed semantics).
|
|
117
|
+
.PARAMETER State
|
|
118
|
+
The parsed checkpoint PSCustomObject.
|
|
119
|
+
.OUTPUTS
|
|
120
|
+
System.String[] - the receipt-agent names (may be empty).
|
|
121
|
+
#>
|
|
122
|
+
[CmdletBinding()]
|
|
123
|
+
[OutputType([string[]])]
|
|
124
|
+
param(
|
|
125
|
+
[Parameter(Mandatory = $true)]
|
|
126
|
+
[psobject] $State
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
$agents = [System.Collections.Generic.HashSet[string]]::new()
|
|
130
|
+
|
|
131
|
+
$receiptsField = Get-OrchestratorStateField -State $State -Name $script:MODEL_ROUTING_RECEIPTS_KEY
|
|
132
|
+
if ($receiptsField.Present -and ($receiptsField.Value -is [System.Array])) {
|
|
133
|
+
# Record each well-formed receipt's non-empty agent so the existence gate can
|
|
134
|
+
# test the delegated-agent set against it.
|
|
135
|
+
foreach ($receipt in $receiptsField.Value) {
|
|
136
|
+
if ($receipt -is [System.Management.Automation.PSCustomObject]) {
|
|
137
|
+
$agentField = Get-OrchestratorStateField -State $receipt -Name 'agent'
|
|
138
|
+
if ($agentField.Present -and $null -ne $agentField.Value -and
|
|
139
|
+
-not [string]::IsNullOrWhiteSpace([string]$agentField.Value)) {
|
|
140
|
+
[void]$agents.Add([string]$agentField.Value)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return [string[]]@($agents)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function Get-OrchestratorStateModelRoutingGateError {
|
|
150
|
+
<#
|
|
151
|
+
.SYNOPSIS
|
|
152
|
+
Return the required-once-delegated existence-gate errors.
|
|
153
|
+
.DESCRIPTION
|
|
154
|
+
Private gate mirroring ``validate_model_routing_gate`` at the presence level:
|
|
155
|
+
it fires only when the checkpoint has delegated (or is about to delegate to)
|
|
156
|
+
at least one agent, then reports one error per delegated agent that lacks a
|
|
157
|
+
matching ``model_routing_receipts[]`` entry. A delegation-free checkpoint
|
|
158
|
+
contributes zero errors, preserving backward compatibility. Each error names
|
|
159
|
+
the literal token ``model_routing_receipts`` so the completion hook routes a
|
|
160
|
+
failure to ``MODEL_ROUTING_BLOCKED:``.
|
|
161
|
+
.PARAMETER State
|
|
162
|
+
The parsed checkpoint PSCustomObject.
|
|
163
|
+
.OUTPUTS
|
|
164
|
+
System.String[] - zero or more error strings; empty when the gate is satisfied
|
|
165
|
+
or does not fire.
|
|
166
|
+
#>
|
|
167
|
+
[CmdletBinding()]
|
|
168
|
+
[OutputType([string[]])]
|
|
169
|
+
param(
|
|
170
|
+
[Parameter(Mandatory = $true)]
|
|
171
|
+
[psobject] $State
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
$errors = [System.Collections.Generic.List[string]]::new()
|
|
175
|
+
|
|
176
|
+
# Backward-compat gate: a delegation-free checkpoint imposes no routing-receipt
|
|
177
|
+
# requirement, so return early with no errors.
|
|
178
|
+
$delegated = @(Get-OrchestratorStateDelegatedAgent -State $State)
|
|
179
|
+
if ($delegated.Count -eq 0) {
|
|
180
|
+
return $errors.ToArray()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
$receiptAgents = @(Get-OrchestratorStateRoutingReceiptAgent -State $State)
|
|
184
|
+
|
|
185
|
+
# Existence invariant: the routing-receipt agent set must be a superset of the
|
|
186
|
+
# delegated-agent set. Report each delegated agent with no receipt, sorted for
|
|
187
|
+
# deterministic error ordering.
|
|
188
|
+
$missing = $delegated | Where-Object { $receiptAgents -notcontains $_ } | Sort-Object
|
|
189
|
+
foreach ($agent in $missing) {
|
|
190
|
+
$errors.Add("Checkpoint model_routing_receipts is missing a receipt for delegated agent: $agent.")
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return $errors.ToArray()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function Test-OrchestratorStateCompletionReadiness {
|
|
197
|
+
<#
|
|
198
|
+
.SYNOPSIS
|
|
199
|
+
Validate a checkpoint satisfies the portable completion-gate presence checks.
|
|
200
|
+
.DESCRIPTION
|
|
201
|
+
Public entry point used by the pushed-down validate-orchestrator-output hook
|
|
202
|
+
when the authoritative Python validator is not importable. Loads the
|
|
203
|
+
checkpoint (fail-closed on missing file / invalid JSON / invalid base shape),
|
|
204
|
+
runs the base-presence check (required keys, step-status validity,
|
|
205
|
+
blocked_reason validity), then applies the model-routing required-once-
|
|
206
|
+
delegated existence gate. Returns a hashtable compatible with the hook's
|
|
207
|
+
invoker contract: ExitCode is 1 whenever any error is present, and Output
|
|
208
|
+
carries the newline-joined error text (empty on success). A missing routing
|
|
209
|
+
receipt yields error text containing ``model_routing_receipts`` so the hook
|
|
210
|
+
surfaces it under ``MODEL_ROUTING_BLOCKED:``.
|
|
211
|
+
.PARAMETER CheckpointPath
|
|
212
|
+
The path to the orchestrator-state checkpoint JSON file.
|
|
213
|
+
.OUTPUTS
|
|
214
|
+
System.Collections.Hashtable with keys ExitCode (int, 0 or 1) and Output (string).
|
|
215
|
+
#>
|
|
216
|
+
[CmdletBinding()]
|
|
217
|
+
[OutputType([hashtable])]
|
|
218
|
+
param(
|
|
219
|
+
[Parameter(Mandatory = $true)]
|
|
220
|
+
[string] $CheckpointPath
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Fail closed when the checkpoint cannot be loaded: the load error is the whole
|
|
224
|
+
# output and ExitCode is 1.
|
|
225
|
+
$loaded = Get-OrchestratorStateCheckpoint -CheckpointPath $CheckpointPath
|
|
226
|
+
if (-not $loaded.Ok) {
|
|
227
|
+
return @{ ExitCode = 1; Output = $loaded.Error }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# Accumulate base-presence errors and existence-gate errors; any error yields a
|
|
231
|
+
# non-zero ExitCode so the completion hook blocks DONE.
|
|
232
|
+
$errors = [System.Collections.Generic.List[string]]::new()
|
|
233
|
+
$errors.AddRange([string[]]@(Get-OrchestratorStateBasePresenceError -State $loaded.State))
|
|
234
|
+
$errors.AddRange([string[]]@(Get-OrchestratorStateModelRoutingGateError -State $loaded.State))
|
|
235
|
+
|
|
236
|
+
if ($errors.Count -gt 0) {
|
|
237
|
+
return @{ ExitCode = 1; Output = ($errors -join [System.Environment]::NewLine) }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return @{ ExitCode = 0; Output = '' }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
Export-ModuleMember -Function Test-OrchestratorStateCompletionReadiness
|
|
@@ -119,6 +119,13 @@ reference implementations are `.claude/lib/model-routing/ModelRouting.psm1`
|
|
|
119
119
|
(`Get-ComplexityFloor`) and `.claude/lib/model-routing/ModelRouting.psm1`
|
|
120
120
|
(`Resolve-DelegationModel`). Default `fable_policy` is `disabled` when the marker is absent.
|
|
121
121
|
|
|
122
|
+
When `epic-orchestrator` itself spawns `Agent(orchestrator)` or `Agent(pr-author)`, it applies
|
|
123
|
+
the same per-delegation resolution and passes `model` equal to the routing receipt's `model` on
|
|
124
|
+
the spawn call. It MUST NOT omit `model` (an omitted `model` falls back to the delegate's
|
|
125
|
+
frontmatter default — `opus` for these workers — which suppresses a `fable` resolution) and
|
|
126
|
+
MUST NOT hard-code `model=opus` in a way that overrides the resolved routing model, mirroring
|
|
127
|
+
step 5 of `## Model Selection` in `.claude/skills/orchestrate/SKILL.md`.
|
|
128
|
+
|
|
122
129
|
`route` is never an input to model selection; `route` remains file-count driven and governs only
|
|
123
130
|
agents, skills, and MCP tools. A skill whose frontmatter `context` field holds the value `fork`
|
|
124
131
|
inherits the parent model and ignores a model override, so model selection applies to agent
|
|
@@ -95,6 +95,8 @@ End-to-end procedure:
|
|
|
95
95
|
3. **Run the per-delegation selection order.** For each delegation, resolve the model as `resolve_delegation_model(agent, complexity_band, fable_policy)`: the `table_model` is the `preferred` overlay value when (`fable_policy == "preferred"` and the agent is in the overlay set `{atomic-planner, prd-feature, feature-review, task-researcher}` and `band == "C3"`), otherwise the base `complexity_to_model[band]`. Under `fable_policy == "disabled"`, a `fable` `table_model` clamps to `model = "opus"` with `clamped_from = "fable"`. `atomic-executor` and `pr-author` C3 cells stay `opus` under every policy.
|
|
96
96
|
4. **Emit a routing receipt.** Record a `model_routing_receipts[]` entry `{ agent, phase, complexity_band, fable_policy, table_model, clamped_from | null, model }`. `table_model` is the pre-clamp lookup; `model` is the post-clamp result.
|
|
97
97
|
|
|
98
|
+
5. **Delegate with the resolved model.** Pass `model` equal to the receipt's `model` on the `Agent(...)` spawn call for that delegation. The orchestrator MUST NOT omit `model` on the spawn (an omitted `model` falls back to the delegate's frontmatter default — `opus` for most workers — which suppresses a `fable` or `sonnet` resolution), and MUST NOT hard-code `model=opus` in a way that overrides the resolved routing model. This applies to every fresh delegation, mirroring the resume-path rule ("delegate with `model` equal to the receipt's `model`") so both paths bind the spawn model to the routing receipt.
|
|
99
|
+
|
|
98
100
|
The `complexity_assessments[]` and `model_routing_receipts[]` invariants are enforced by `scripts/dev_tools/validate_orchestrator_state.py` per `.claude/rules/orchestrator-state.md`; both arrays remain additive (a checkpoint that predates model routing stays valid).
|
|
99
101
|
|
|
100
102
|
### Required-once-delegated invariant (`require_model_routing` mode)
|
|
@@ -70,6 +70,8 @@
|
|
|
70
70
|
".claude/skills/skill-canonical-location-audit/SKILL.md",
|
|
71
71
|
".claude/skills/translate-copilot-to-claude/SKILL.md",
|
|
72
72
|
".claude/skills/update-status/SKILL.md",
|
|
73
|
-
".claude/lib/model-routing/ModelRouting.psm1"
|
|
73
|
+
".claude/lib/model-routing/ModelRouting.psm1",
|
|
74
|
+
".claude/lib/orchestrator-state/OrchestratorState.psm1",
|
|
75
|
+
".claude/lib/orchestrator-state/OrchestratorStateCompletion.psm1"
|
|
74
76
|
]
|
|
75
77
|
}
|
|
@@ -163,6 +163,6 @@
|
|
|
163
163
|
},
|
|
164
164
|
"model_budget": {
|
|
165
165
|
"description": "Session-level model budget. fable_policy is a three-way switch controlling whether the fable tier is disabled (removed and clamped to opus), available (used as-is), or preferred (applies the preferred_overlay).",
|
|
166
|
-
"fable_policy": "
|
|
166
|
+
"fable_policy": "preferred"
|
|
167
167
|
}
|
|
168
168
|
}
|