@danmoisan/drm-copilot-mcp 1.0.8 → 1.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danmoisan/drm-copilot-mcp",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Stdio MCP server exposing drm-copilot repo-automation tools.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -0,0 +1,209 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Model-routing reference formulas for the orchestrator, ported from the Python references.
4
+
5
+ .DESCRIPTION
6
+ Provides the destination-runtime PowerShell ports of the two self-contained,
7
+ pure model-routing formulas that the `orchestrate` skill instructs the
8
+ orchestrator to run:
9
+
10
+ - Get-ComplexityFloor port of scripts/dev_tools/compute_complexity_floor.py
11
+ - Resolve-DelegationModel port of scripts/dev_tools/resolve_delegation_model.py
12
+
13
+ Both functions are pure and deterministic: they read no file at runtime and
14
+ encode only the fixed band ordering, the base complexity-to-model table, the
15
+ preferred overlay, and the disabled-mode clamp as module-scope constants.
16
+ Those literals are pinned to config/orchestration-routing.json (model_policy /
17
+ model_budget) by a static config-parity Pester test, and the Python modules
18
+ remain the validator's authoritative reference. This module is one half of a
19
+ two-language mirror; it never imports validator logic.
20
+ #>
21
+
22
+ Set-StrictMode -Version Latest
23
+
24
+ # The fixed complexity-band vocabulary, ordered from lowest to highest rigor.
25
+ # The array order defines "higher" and "lower" band comparisons used by the
26
+ # floor computation, mirroring BAND_ORDER in compute_complexity_floor.py.
27
+ $script:BAND_ORDER = @('C1', 'C2', 'C3', 'C4')
28
+
29
+ # The lowest band, returned when no floor signal is present (LOWEST_BAND).
30
+ $script:LOWEST_BAND = 'C1'
31
+
32
+ # Every present floor signal contributes this uniform candidate band, per the
33
+ # model_policy.complexity contract (each [floor] signal contributes C3).
34
+ $script:FLOOR_CANDIDATE_BAND = 'C3'
35
+
36
+ # Floors never exceed this ceiling; C4 is judgment-only and never floor-forced,
37
+ # so the computed floor is clamped to at most C3 (FLOOR_CEILING_BAND).
38
+ $script:FLOOR_CEILING_BAND = 'C3'
39
+
40
+ # The three session-level fable policies (model_budget.fable_policy).
41
+ $script:DISABLED_POLICY = 'disabled'
42
+ $script:PREFERRED_POLICY = 'preferred'
43
+
44
+ # The model tier removed from consideration under the disabled policy, the tier
45
+ # a disabled-mode fable cell clamps down to, and the recorded clamp reason.
46
+ $script:FABLE_MODEL = 'fable'
47
+ $script:DISABLED_CLAMP_MODEL = 'opus'
48
+ $script:DISABLED_CLAMP_REASON = 'fable_disabled'
49
+
50
+ # The base complexity-to-model table applied uniformly across delegated agents
51
+ # (BASE_COMPLEXITY_TO_MODEL). Pinned to model_policy.complexity_to_model.
52
+ $script:BASE_COMPLEXITY_TO_MODEL = @{
53
+ C1 = 'haiku'
54
+ C2 = 'sonnet'
55
+ C3 = 'opus'
56
+ C4 = 'fable'
57
+ }
58
+
59
+ # The agents whose C3 cell the preferred overlay redirects to fable. No other
60
+ # agent and no other band is affected (PREFERRED_OVERLAY_AGENTS).
61
+ $script:PREFERRED_OVERLAY_AGENTS = @(
62
+ 'atomic-planner',
63
+ 'prd-feature',
64
+ 'feature-review',
65
+ 'task-researcher'
66
+ )
67
+
68
+ # The single band and target model the preferred overlay applies.
69
+ $script:PREFERRED_OVERLAY_BAND = 'C3'
70
+ $script:PREFERRED_OVERLAY_MODEL = 'fable'
71
+
72
+
73
+ function Get-ComplexityFloor {
74
+ <#
75
+ .SYNOPSIS
76
+ Compute the deterministic complexity-band floor from present floor signals.
77
+
78
+ .DESCRIPTION
79
+ Faithful PowerShell port of compute_complexity_floor
80
+ (scripts/dev_tools/compute_complexity_floor.py). Returns the deterministic
81
+ lower-bound complexity band implied by the set of present floor signals:
82
+ each present floor signal contributes a candidate band of C3, the floor is
83
+ the maximum triggered candidate band, and the floor never exceeds C3
84
+ (C4 is never floor-forced). With no floor signal present the floor is the
85
+ lowest band C1. The function is pure: it reads no file and does not mutate
86
+ its input, and the result is independent of input ordering.
87
+
88
+ .PARAMETER SignalsPresent
89
+ The names of the present signals flagged [floor] in the
90
+ model_policy.complexity catalog. Every element is treated as a triggered
91
+ floor signal contributing the candidate band C3. An empty collection means
92
+ no floor signal is present.
93
+
94
+ .OUTPUTS
95
+ System.String. The floor band: C1 when no floor signal is present,
96
+ otherwise the maximum triggered candidate band clamped to at most C3.
97
+ C4 is never returned.
98
+ #>
99
+ [CmdletBinding()]
100
+ [OutputType([string])]
101
+ param(
102
+ [Parameter(Mandatory = $true)]
103
+ [AllowEmptyCollection()]
104
+ [string[]] $SignalsPresent
105
+ )
106
+
107
+ # With no present floor signal there is no candidate band to raise the floor
108
+ # above the lowest band, so the floor is C1 (mirrors the empty-input guard).
109
+ if (-not $SignalsPresent -or $SignalsPresent.Count -eq 0) {
110
+ return $script:LOWEST_BAND
111
+ }
112
+
113
+ # Each present floor signal contributes the uniform candidate band; the floor
114
+ # is the maximum triggered candidate rank across all of them. Because every
115
+ # signal contributes the same candidate band, the max equals that rank.
116
+ $candidateRank = $script:BAND_ORDER.IndexOf($script:FLOOR_CANDIDATE_BAND)
117
+ $highestRank = $candidateRank
118
+
119
+ # Clamp with the ceiling rank so the floor can never exceed C3; this is what
120
+ # keeps C4 from ever being floor-forced regardless of how many signals exist.
121
+ $ceilingRank = $script:BAND_ORDER.IndexOf($script:FLOOR_CEILING_BAND)
122
+ $floorRank = [Math]::Min($highestRank, $ceilingRank)
123
+ return $script:BAND_ORDER[$floorRank]
124
+ }
125
+
126
+ function Resolve-DelegationModel {
127
+ <#
128
+ .SYNOPSIS
129
+ Resolve the delegation model tier for an agent, band, and fable policy.
130
+
131
+ .DESCRIPTION
132
+ Faithful PowerShell port of resolve_delegation_model
133
+ (scripts/dev_tools/resolve_delegation_model.py). Applies the model_policy
134
+ selection formula to a single delegation: it computes the pre-clamp
135
+ table_model (the base complexity_to_model table plus any preferred overlay)
136
+ and the post-clamp model, recording the clamp provenance. The preferred
137
+ overlay redirects only the C3 cell to fable and only for the four overlay
138
+ agents; atomic-executor and pr-author C3 cells stay opus under every
139
+ policy. Under the disabled policy a fable table cell clamps to opus with
140
+ clamped_from = fable and clamp_reason = fable_disabled. The function is
141
+ pure: it reads no file and mutates no input.
142
+
143
+ .PARAMETER Agent
144
+ The target delegate agent name (for example atomic-planner). Only
145
+ participates in preferred-overlay eligibility.
146
+
147
+ .PARAMETER Band
148
+ The assessed complexity band, one of C1..C4. Used as the key into the
149
+ base complexity_to_model table. A band outside the table is the PowerShell
150
+ analog of the Python KeyError and causes a terminating error (throw).
151
+
152
+ .PARAMETER FablePolicy
153
+ The session fable policy, one of disabled, available, or preferred.
154
+
155
+ .OUTPUTS
156
+ System.Collections.Hashtable. A hashtable with keys table_model (the
157
+ pre-clamp table lookup, including any overlay), model (the post-clamp
158
+ result), clamped_from (fable when a clamp occurred, else $null), and
159
+ clamp_reason (fable_disabled when a clamp occurred, else $null).
160
+ #>
161
+ [CmdletBinding()]
162
+ [OutputType([hashtable])]
163
+ param(
164
+ [Parameter(Mandatory = $true)]
165
+ [string] $Agent,
166
+ [Parameter(Mandatory = $true)]
167
+ [string] $Band,
168
+ [Parameter(Mandatory = $true)]
169
+ [string] $FablePolicy
170
+ )
171
+
172
+ # The preferred overlay redirects only the C3 cell to fable, and only for the
173
+ # overlay agents; every other case reads the base table unchanged. The three
174
+ # conditions (policy, agent membership, band) must all hold for the overlay.
175
+ if ($FablePolicy -eq $script:PREFERRED_POLICY -and
176
+ $script:PREFERRED_OVERLAY_AGENTS -contains $Agent -and
177
+ $Band -eq $script:PREFERRED_OVERLAY_BAND) {
178
+ $tableModel = $script:PREFERRED_OVERLAY_MODEL
179
+ }
180
+ else {
181
+ # A band outside the base table is the PowerShell analog of the Python
182
+ # KeyError: fail fast rather than return a silently wrong value.
183
+ if (-not $script:BASE_COMPLEXITY_TO_MODEL.ContainsKey($Band)) {
184
+ throw "Unknown complexity band '$Band'; expected one of $($script:BAND_ORDER -join ', ')."
185
+ }
186
+ $tableModel = $script:BASE_COMPLEXITY_TO_MODEL[$Band]
187
+ }
188
+
189
+ # Under the disabled policy, fable is removed from consideration: a fable
190
+ # table cell clamps down to opus and records the clamp provenance.
191
+ if ($FablePolicy -eq $script:DISABLED_POLICY -and $tableModel -eq $script:FABLE_MODEL) {
192
+ return @{
193
+ table_model = $tableModel
194
+ model = $script:DISABLED_CLAMP_MODEL
195
+ clamped_from = $script:FABLE_MODEL
196
+ clamp_reason = $script:DISABLED_CLAMP_REASON
197
+ }
198
+ }
199
+
200
+ # No clamp applies: the resolved model is the table model verbatim.
201
+ return @{
202
+ table_model = $tableModel
203
+ model = $tableModel
204
+ clamped_from = $null
205
+ clamp_reason = $null
206
+ }
207
+ }
208
+
209
+ Export-ModuleMember -Function Get-ComplexityFloor, Resolve-DelegationModel
@@ -115,9 +115,9 @@ The child's own `orchestrator` reads this line and applies the two-axis model-se
115
115
  documented in `.claude/skills/orchestrate/SKILL.md` (`## Model Selection`): it assesses a
116
116
  judgment-based `complexity_band`, records `complexity_assessments[]` and `model_routing_receipts[]`,
117
117
  and resolves each delegation's model tier under the given `fable_policy`. The two canonical, tested
118
- reference implementations are `scripts/dev_tools/compute_complexity_floor.py`
119
- (`compute_complexity_floor`) and `scripts/dev_tools/resolve_delegation_model.py`
120
- (`resolve_delegation_model`). Default `fable_policy` is `disabled` when the marker is absent.
118
+ reference implementations are `.claude/lib/model-routing/ModelRouting.psm1`
119
+ (`Get-ComplexityFloor`) and `.claude/lib/model-routing/ModelRouting.psm1`
120
+ (`Resolve-DelegationModel`). Default `fable_policy` is `disabled` when the marker is absent.
121
121
 
122
122
  `route` is never an input to model selection; `route` remains file-count driven and governs only
123
123
  agents, skills, and MCP tools. A skill whose frontmatter `context` field holds the value `fork`
@@ -29,9 +29,9 @@ On every invocation, the main session must:
29
29
  Because model selection is required once delegation occurs (see `## Model Selection`), a resuming orchestrator must repair a missing model choice deterministically before delegating at a delegating `next_step`. When the resumed `next_step` is a delegating step:
30
30
 
31
31
  a. **Preflight the checkpoint.** Run the orchestrator-state validator with `--require-model-routing` (via `mcp__drm-copilot__validate_orchestration_artifacts` or the local CLI) against `artifacts/orchestration/orchestrator-state.json` before the first delegation. Record the result in a `model_routing_preflight` block `{ status ("pass"|"fail"), checked_at (ISO-8601 UTC), validator_command, output_summary }`.
32
- b. **Recompute the floor.** For the upcoming phase, recompute the complexity floor with `compute_complexity_floor(signals_present)` (`scripts/dev_tools/compute_complexity_floor.py`); do not reimplement the formula.
32
+ b. **Recompute the floor.** For the upcoming phase, recompute the complexity floor with `Get-ComplexityFloor -SignalsPresent <names>` (`.claude/lib/model-routing/ModelRouting.psm1`); do not reimplement the formula.
33
33
  c. **Record the assessment.** Write a `complexity_assessments[]` entry `{ phase, band, floor, signals_present[], rationale, assessed_at }` with `floor` equal to the recomputed value and `band >= floor`.
34
- d. **Resolve and record the receipt.** Resolve the model with `resolve_delegation_model(agent, complexity_band, fable_policy)` (`scripts/dev_tools/resolve_delegation_model.py`) and write a `model_routing_receipts[]` entry `{ agent, phase, complexity_band, fable_policy, table_model, clamped_from | null, model }`.
34
+ d. **Resolve and record the receipt.** Resolve the model with `Resolve-DelegationModel -Agent <agent> -Band <complexity_band> -FablePolicy <fable_policy>` (`.claude/lib/model-routing/ModelRouting.psm1`) and write a `model_routing_receipts[]` entry `{ agent, phase, complexity_band, fable_policy, table_model, clamped_from | null, model }`.
35
35
  e. **Persist and delegate.** Persist the checkpoint, then delegate with `model` equal to the receipt's `model`.
36
36
 
37
37
  The orchestrator MUST NOT delegate at a delegating `next_step` while `model_routing_preflight` status is `fail`; it repairs the missing choice (steps b-e) and re-preflights until the status is `pass`.
@@ -83,8 +83,10 @@ Model selection is a second axis, strictly separate from `route`. `route` (`smal
83
83
 
84
84
  The two canonical, tested reference implementations express the formulas the orchestrator applies by judgment:
85
85
 
86
- - `scripts/dev_tools/compute_complexity_floor.py` (`compute_complexity_floor`) — the deterministic complexity-floor formula. Each present `[floor]` signal contributes a candidate band of `C3`; the floor is the maximum triggered candidate band; the floor never exceeds `C3`. C4 is never floor-forced; it is reached only by judgment.
87
- - `scripts/dev_tools/resolve_delegation_model.py` (`resolve_delegation_model`) — the delegation-model selection formula (base `complexity_to_model` table, the `preferred` overlay, and the `disabled` clamp).
86
+ - `.claude/lib/model-routing/ModelRouting.psm1` (`Get-ComplexityFloor`) — the deterministic complexity-floor formula. Each present `[floor]` signal contributes a candidate band of `C3`; the floor is the maximum triggered candidate band; the floor never exceeds `C3`. C4 is never floor-forced; it is reached only by judgment.
87
+ - `.claude/lib/model-routing/ModelRouting.psm1` (`Resolve-DelegationModel`) — the delegation-model selection formula (base `complexity_to_model` table, the `preferred` overlay, and the `disabled` clamp).
88
+
89
+ The runnable reference the destination runtime applies is the `.claude`-resident PowerShell module above; the repository validator remains the Python authority (`scripts/dev_tools/compute_complexity_floor.py` and `scripts/dev_tools/resolve_delegation_model.py`), pinned to the same `config/orchestration-routing.json` truth table by a static config-parity test.
88
90
 
89
91
  End-to-end procedure:
90
92
 
@@ -69,6 +69,7 @@
69
69
  ".claude/skills/review-staged/SKILL.md",
70
70
  ".claude/skills/skill-canonical-location-audit/SKILL.md",
71
71
  ".claude/skills/translate-copilot-to-claude/SKILL.md",
72
- ".claude/skills/update-status/SKILL.md"
72
+ ".claude/skills/update-status/SKILL.md",
73
+ ".claude/lib/model-routing/ModelRouting.psm1"
73
74
  ]
74
75
  }