@danmoisan/drm-copilot-mcp 1.0.7 → 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.
Files changed (29) hide show
  1. package/out/mcp-server.js +82 -8
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/atomic-executor.md +1 -0
  4. package/resources/claude-customizations/.claude/agents/atomic-planner.md +1 -0
  5. package/resources/claude-customizations/.claude/agents/csharp-typed-engineer.md +1 -0
  6. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +1 -0
  7. package/resources/claude-customizations/.claude/agents/epic-review.md +1 -0
  8. package/resources/claude-customizations/.claude/agents/feature-review.md +1 -0
  9. package/resources/claude-customizations/.claude/agents/orchestrator.md +13 -0
  10. package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +1 -0
  11. package/resources/claude-customizations/.claude/agents/prd-feature.md +1 -0
  12. package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +1 -0
  13. package/resources/claude-customizations/.claude/agents/staged-review.md +1 -0
  14. package/resources/claude-customizations/.claude/agents/status-updater.md +1 -0
  15. package/resources/claude-customizations/.claude/agents/typescript-engineer.md +1 -0
  16. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +182 -0
  17. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +9 -1
  18. package/resources/claude-customizations/.claude/lib/model-routing/ModelRouting.psm1 +209 -0
  19. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +17 -0
  20. package/resources/claude-customizations/.claude/settings.json +4 -0
  21. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +3 -3
  22. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +22 -3
  23. package/resources/claude-customizations/pack-manifests/core.json +3 -1
  24. package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +8 -2
  25. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +18 -0
  26. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +13 -0
  27. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +15 -14
  28. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +144 -28
  29. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-helpers.ps1 +163 -0
@@ -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
@@ -66,10 +66,27 @@ Each entry records one delegation with the shape `{ agent, phase, complexity_ban
66
66
 
67
67
  The session `model_budget.fable_policy` switch is a three-way enum `disabled | available | preferred` defined in `config/orchestration-routing.json`, defaulting to `disabled`. It governs only the delegation model tier and is not a route input. `disabled` removes `fable` from the consideration set and clamps `fable` cells to `opus`; `available` applies the base `complexity_to_model` table as-is; `preferred` applies the `preferred_overlay` (which redirects only the C3 cell to `fable` for the overlay agents `atomic-planner`, `prd-feature`, `feature-review`, `task-researcher`) and leaves `atomic-executor` and `pr-author` C3 cells at `opus`. `route` is never an input to model selection.
68
68
 
69
+ ## Require-Model-Routing Mode Scope and Backward Compatibility
70
+
71
+ The complexity-assessment and model-routing-receipt invariants above are key-gated: they run only when their key is present, so a checkpoint that omits both arrays passes at every stage. The `require_model_routing` mode adds an existence gate that closes that gap without changing the default behavior. It is an opt-in keyword on `validate_orchestrator_state_text(..., require_model_routing=False)` (CLI flag `--require-model-routing`; MCP parameter `require_model_routing`), defaulting off. Plain, `require_complete`, and `require_pr_creation_ready` calls are unaffected and produce byte-identical results.
72
+
73
+ ## Invariants (require_model_routing mode)
74
+
75
+ These invariants apply only when a caller passes `require_model_routing=True` and the checkpoint records at least one delegation. A checkpoint with zero delegations (no well-formed `delegation_receipts[]` entry and a `next_step` that names no delegating agent) imposes no requirement, so genuinely old, delegation-free checkpoints stay valid.
76
+
77
+ 1. **Required routing receipt once delegated.** Once the checkpoint records a delegation, the set of `model_routing_receipts[].agent` must be a superset of the delegated-agent set (each well-formed `delegation_receipts[].agent_name` plus a `next_step` that names a delegating agent). A delegated agent with no matching receipt is a violation. The delegating agent set excludes `orchestrator` (the caller, not a delegated subagent).
78
+
79
+ 2. **Required complexity assessment per matched phase.** Each phase named by a routing receipt whose agent is in the delegated-agent set must have a `complexity_assessments[]` entry for that phase.
80
+
81
+ 3. **Per-entry consistency reused, not reimplemented.** Present receipts and assessments must satisfy the model-routing-receipt and complexity-assessment invariants above; the gate reuses `_validate_model_routing_receipts` and `_validate_complexity_assessments` and never reimplements `compute_complexity_floor` or `resolve_delegation_model`. The gate logic lives in `scripts/dev_tools/_orchestrator_state_model_routing_gate.py`; enforcement is the Python validator, not an imported schema.
82
+
83
+ The completion hook (`.claude/hooks/validate-orchestrator-output.ps1`) passes `--require-model-routing` alongside `--require-complete` and surfaces a gate failure as the `MODEL_ROUTING_BLOCKED:` block reason. The PreToolUse deterrent (`.claude/hooks/enforce-model-routing-receipt.ps1`) performs presence-only gating before a delegation. The MCP TypeScript surface performs the existence check only (delegated-agent set ⊆ routing-receipt-agent set); the Python validator remains authoritative for per-receipt correctness.
84
+
69
85
  ## Enforcement
70
86
 
71
87
  - `scripts/dev_tools/validate_orchestrator_state.py` appends one error per violated invariant when a `remediation_loop` is present, using the existing validator message style (literal, checkpoint-context prefixed). The validator returns a list of error strings and does not mutate its input.
72
88
  - `scripts/dev_tools/validate_orchestrator_state.py` likewise appends one error per violated `human_interaction` invariant when a `human_interaction` key is present, using the same literal, checkpoint-context-prefixed message style. The check does not import or read any schema file.
73
89
  - `scripts/dev_tools/validate_orchestrator_state.py` appends one error per violated `complexity_assessments` invariant when a `complexity_assessments` key is present, delegating to `scripts/dev_tools/_orchestrator_state_complexity.py`, which recomputes the floor via `compute_complexity_floor`. The check does not import or read any schema file.
74
90
  - `scripts/dev_tools/validate_orchestrator_state.py` appends one error per violated `model_routing_receipts` invariant when a `model_routing_receipts` key is present, delegating to `scripts/dev_tools/_orchestrator_state_model_routing.py`, which recomputes the resolved model via `resolve_delegation_model`. The check does not import or read any schema file.
91
+ - `scripts/dev_tools/validate_orchestrator_state.py` appends one error per violated `require_model_routing` invariant only when the caller passes `require_model_routing=True`, delegating to `scripts/dev_tools/_orchestrator_state_model_routing_gate.py`, which reuses the complexity and model-routing per-entry validators. When the flag is not passed the gate does not run, so existing calls are byte-identical.
75
92
  - The validator is consumed by the MCP tool `validate_orchestration_artifacts`; backward compatibility for existing step-based checkpoints is preserved.
@@ -156,6 +156,10 @@
156
156
  {
157
157
  "type": "command",
158
158
  "command": "pwsh -NoProfile -File .claude/hooks/enforce-epic-wave-barrier.ps1"
159
+ },
160
+ {
161
+ "type": "command",
162
+ "command": "pwsh -NoProfile -File .claude/hooks/enforce-model-routing-receipt.ps1"
159
163
  }
160
164
  ]
161
165
  }
@@ -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`
@@ -24,6 +24,18 @@ On every invocation, the main session must:
24
24
  2. If a valid checkpoint exists with a matching objective, resume from the recorded `next_step`.
25
25
  3. If no checkpoint exists or the objective is new, begin the orchestration lifecycle from the start.
26
26
 
27
+ ### Model-choice reconciliation on resume
28
+
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
+
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 `Get-ComplexityFloor -SignalsPresent <names>` (`.claude/lib/model-routing/ModelRouting.psm1`); do not reimplement the formula.
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-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
+ e. **Persist and delegate.** Persist the checkpoint, then delegate with `model` equal to the receipt's `model`.
36
+
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`.
38
+
27
39
  ## Autonomous-Execution Mandate
28
40
 
29
41
  The orchestrator must achieve all actions agentically with no human interaction; full autonomy is a hard requirement. A silent manual blocker discovered at the end of a workflow is a defect, not an acceptable outcome. Every unautomatable (human-interaction) requirement must be detected early, resolved by exactly one of three permitted responses, and recorded in orchestrator state.
@@ -71,8 +83,10 @@ Model selection is a second axis, strictly separate from `route`. `route` (`smal
71
83
 
72
84
  The two canonical, tested reference implementations express the formulas the orchestrator applies by judgment:
73
85
 
74
- - `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.
75
- - `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.
76
90
 
77
91
  End-to-end procedure:
78
92
 
@@ -81,7 +95,11 @@ End-to-end procedure:
81
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.
82
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.
83
97
 
84
- 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 are additive and optional.
98
+ 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
+
100
+ ### Required-once-delegated invariant (`require_model_routing` mode)
101
+
102
+ The `validate_orchestrator_state_text(...)` validator accepts a `require_model_routing` mode (CLI flag `--require-model-routing`; MCP parameter `require_model_routing`). Under this mode the arrays stop being merely optional: once the checkpoint records at least one delegation (a well-formed `delegation_receipts[]` entry, or a `next_step` that names a delegating agent), every delegated agent must have a matching `model_routing_receipts[]` entry, each matched receipt's phase must have a `complexity_assessments[]` entry, and every present receipt/assessment must be consistent with the reference formulas. A delegation-free checkpoint imposes no requirement, so old checkpoints stay valid. The gate is implemented in `scripts/dev_tools/_orchestrator_state_model_routing_gate.py`; it reuses the per-entry validators and never reimplements `compute_complexity_floor` or `resolve_delegation_model`. Two enforcement layers consume it: the completion gate (`.claude/hooks/validate-orchestrator-output.ps1` passes `--require-model-routing` and surfaces failures as `MODEL_ROUTING_BLOCKED:`), and the pre-delegation deterrent (`.claude/hooks/enforce-model-routing-receipt.ps1`, presence-only). The MCP TypeScript surface performs the existence check only (delegated-agent set ⊆ routing-receipt-agent set); the Python validator is authoritative for per-receipt correctness.
85
103
 
86
104
  **`fork` caveat.** A skill whose frontmatter `context` field holds the value `fork` inherits the parent model and ignores a model override. Model selection therefore applies to agent delegations, not to fork-routed skill invocations.
87
105
 
@@ -126,6 +144,7 @@ The orchestrator must not report completion until:
126
144
  1. All required artifacts for the selected workflow path are present on disk.
127
145
  2. All validation gates (toolchain, acceptance criteria, audit artifacts) have passed.
128
146
  3. The checkpoint file at `artifacts/orchestration/orchestrator-state.json` reflects the completed state.
147
+ 4. The model-routing gate passes: `.claude/hooks/validate-orchestrator-output.ps1` runs the validator with `--require-model-routing` alongside `--require-complete` and refuses DONE with `MODEL_ROUTING_BLOCKED:` when a recorded delegation lacks a matching `model_routing_receipts[]` / `complexity_assessments[]` entry (see the required-once-delegated invariant under `## Model Selection`).
129
148
 
130
149
  ## Pre-Feature-Review Commit
131
150
 
@@ -22,6 +22,7 @@
22
22
  ".claude/hooks/enforce-epic-worktree-removal-gate.ps1",
23
23
  ".claude/hooks/enforce-evidence-locations.ps1",
24
24
  ".claude/hooks/enforce-feature-folder-order.ps1",
25
+ ".claude/hooks/enforce-model-routing-receipt.ps1",
25
26
  ".claude/hooks/enforce-orchestration-preimplementation-gate.ps1",
26
27
  ".claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1",
27
28
  ".claude/hooks/enforce-pr-author-skill.ps1",
@@ -68,6 +69,7 @@
68
69
  ".claude/skills/review-staged/SKILL.md",
69
70
  ".claude/skills/skill-canonical-location-audit/SKILL.md",
70
71
  ".claude/skills/translate-copilot-to-claude/SKILL.md",
71
- ".claude/skills/update-status/SKILL.md"
72
+ ".claude/skills/update-status/SKILL.md",
73
+ ".claude/lib/model-routing/ModelRouting.psm1"
72
74
  ]
73
75
  }
@@ -101,8 +101,14 @@ Lifecycle guardrails:
101
101
  - `DIRECTIVE: MINIMAL-AUDIT PLAN REQUIRED`
102
102
 
103
103
  8a) Resolve and persist `${plan-path}` before delegation:
104
- - reuse the earliest existing `plan*.md` in `${feature-folder}` when present
105
- - otherwise create exactly one canonical plan file path and reuse it for all revisions
104
+ - enumerate `${feature-folder}/plan*.md` files in deterministic filename order
105
+ - reuse the first existing `plan*.md` in `${feature-folder}` when present
106
+ - otherwise create exactly one canonical plan file path using the repository's
107
+ feature-folder plan naming convention and reuse it for all revisions
108
+ - never default to `${feature-folder}/plan.md` when a timestamped scaffolded
109
+ plan already exists
110
+ - if checkpoint state already contains a different `${plan-path}`, correct the
111
+ checkpoint before planner delegation instead of creating another plan file
106
112
 
107
113
  9) Require preflight validation via `atomic_executor` until:
108
114
  - `PREFLIGHT: ALL CLEAR`
@@ -109,6 +109,24 @@ staging, commits, or implementation delegation. This gate covers edits, formatte
109
109
  If any required item is missing, implementation is blocked until the checkpoint
110
110
  and lifecycle state are corrected.
111
111
 
112
+ ## Plan-Path Resolution Gate
113
+
114
+ After active feature folder creation and before any planning delegation, the
115
+ main session must resolve `${plan-path}` from the active feature folder:
116
+
117
+ 1. Enumerate existing `${feature-folder}/plan*.md` files in deterministic
118
+ filename order.
119
+ 2. If one or more files exist, persist `${plan-path}` as the first existing
120
+ file and require every planner and executor handoff to use that exact path.
121
+ 3. If no `plan*.md` file exists, create exactly one canonical target path using
122
+ the repository's feature-folder plan naming convention, persist that path,
123
+ and reuse it for all revisions.
124
+ 4. Do not default to `${feature-folder}/plan.md` when a timestamped scaffolded
125
+ plan already exists.
126
+ 5. If checkpoint state names a different plan path than the resolved existing
127
+ plan file, correct the checkpoint before planner delegation. Do not create a
128
+ second plan artifact to satisfy an incorrect checkpoint value.
129
+
112
130
  ## Pre-Implementation Violation Handling
113
131
 
114
132
  If an implementation action is attempted before a required orchestration gate
@@ -101,6 +101,16 @@ Persist and reuse these fields exactly:
101
101
  - `lifecycle_operations`
102
102
  - `pre-implementation-violation`
103
103
 
104
+ Plan-path invariant:
105
+ - `${plan-path}` is resolved only after `${feature-folder}` exists.
106
+ - The orchestrator MUST enumerate existing `${feature-folder}/plan*.md` files
107
+ in deterministic filename order before planner delegation.
108
+ - If any existing plan file is present, `${plan-path}` MUST be that first
109
+ existing file. Do not persist or delegate against `${feature-folder}/plan.md`
110
+ when a timestamped scaffolded plan already exists.
111
+ - If checkpoint state conflicts with the resolved existing plan file, correct
112
+ checkpoint state before delegation and do not create a duplicate plan.
113
+
104
114
  For small-path runs, also persist:
105
115
  - `bootstrap_mode`
106
116
  - `phase0_execution_summary`
@@ -310,6 +320,9 @@ Required behavior:
310
320
  7. When those specialists are not yet migrated, perform the authoring steps directly without changing template headings.
311
321
  8. Spawn `atomic-planner` to finalize `${plan-path}` and require `PREFLIGHT: ALL CLEAR`.
312
322
  Hard enforcement for Step 7:
323
+ - Before spawning `atomic-planner`, resolve `${plan-path}` by enumerating
324
+ existing `${feature-folder}/plan*.md` files. Reuse the first existing file
325
+ in deterministic filename order, including timestamped scaffolded plans.
313
326
  - The planning route MUST be `atomic-planner -> atomic-executor` for preflight validation.
314
327
  - The planner MUST update `${plan-path}` in place and MUST NOT create additional `plan.*.md` files for revisions.
315
328
  - The approved plan MUST include explicit Phase 0 baseline evidence tasks and explicit final-QA evidence or coverage tasks for each language in scope where policy requires them.
@@ -76,6 +76,8 @@ Every agent named above must exist as a native Codex agent under `.codex/agents/
76
76
  - Do not rename, back up, or create sidecar checkpoint files.
77
77
  - Do not create or edit `${feature-folder}/issue.md`, `${feature-folder}/spec.md`, `${feature-folder}/user-story.md`, or `plan*.md` until lifecycle setup succeeds.
78
78
  - Do not persist placeholder lifecycle values such as `NONE`, `TBD`, or empty strings once lifecycle setup begins.
79
+ - After active folder creation and before planner delegation, resolve `${plan-path}` by enumerating existing `${feature-folder}/plan*.md` files in deterministic filename order. If a scaffolded timestamped plan exists, persist and delegate against that exact file. Do not default to `${feature-folder}/plan.md` and do not create a second plan artifact when a plan already exists.
80
+ - For issue #306, the canonical existing plan path is `docs/features/active/2026-07-04-codex-agent-role-config-306/plan.2026-07-04T13-47.md`; reuse that exact file and do not create `docs/features/active/2026-07-04-codex-agent-role-config-306/plan.md`.
79
81
 
80
82
  ## Checkpoint Persistence
81
83
 
@@ -133,17 +135,16 @@ The MCP validator and required CI checks are the hard completion boundary. There
133
135
  Do not claim mission completion unless all required delegations completed with receipts and the required orchestration artifacts exist on disk.
134
136
  '''
135
137
 
136
- [mcp_servers.drm-copilot]
137
- enabled = true
138
-
139
- [skills.config]
140
- policy-compliance-order = true
141
- orchestrate = true
142
- orchestrator-workflow = true
143
- feature-promotion-lifecycle = true
144
- repo-automation-adapter = true
145
- atomic-plan-contract = true
146
- acceptance-criteria-tracking = true
147
- evidence-and-timestamp-conventions = true
148
- pr-context-artifacts = true
149
- pr-base-branch-merge-base = true
138
+ [skills]
139
+ config = [
140
+ { name = "policy-compliance-order", enabled = true },
141
+ { name = "orchestrate", enabled = true },
142
+ { name = "orchestrator-workflow", enabled = true },
143
+ { name = "feature-promotion-lifecycle", enabled = true },
144
+ { name = "repo-automation-adapter", enabled = true },
145
+ { name = "atomic-plan-contract", enabled = true },
146
+ { name = "acceptance-criteria-tracking", enabled = true },
147
+ { name = "evidence-and-timestamp-conventions", enabled = true },
148
+ { name = "pr-context-artifacts", enabled = true },
149
+ { name = "pr-base-branch-merge-base", enabled = true },
150
+ ]