@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
package/out/mcp-server.js CHANGED
@@ -16208,6 +16208,10 @@ var REPO_AUTOMATION_TOOL_DEFINITIONS = [
16208
16208
  require_complete: {
16209
16209
  type: "boolean",
16210
16210
  description: "When true and artifact_type is 'orchestrator-state', require all phases to be complete."
16211
+ },
16212
+ require_model_routing: {
16213
+ type: "boolean",
16214
+ description: "When true and artifact_type is 'orchestrator-state', require a model_routing_receipts entry per delegated agent once a delegation is recorded. The TypeScript side performs the existence check only; the Python validator is authoritative for full per-receipt correctness."
16211
16215
  }
16212
16216
  },
16213
16217
  required: ["artifact_type", "artifact_path"],
@@ -16597,6 +16601,7 @@ function resolveValidateOrchestrationArtifactsToolInput(rawInput, fallbackWorksp
16597
16601
  );
16598
16602
  }
16599
16603
  const requireComplete = args["require_complete"];
16604
+ const requireModelRouting = args["require_model_routing"];
16600
16605
  return {
16601
16606
  workspaceRoot: normalizeWorkspaceRoot(
16602
16607
  args["workspace_root"],
@@ -16604,7 +16609,8 @@ function resolveValidateOrchestrationArtifactsToolInput(rawInput, fallbackWorksp
16604
16609
  ),
16605
16610
  artifactType,
16606
16611
  artifactPath: normalizeRequiredText(args["artifact_path"], "artifact_path"),
16607
- ...requireComplete === true ? { requireComplete: true } : {}
16612
+ ...requireComplete === true ? { requireComplete: true } : {},
16613
+ ...requireModelRouting === true ? { requireModelRouting: true } : {}
16608
16614
  };
16609
16615
  }
16610
16616
 
@@ -22519,6 +22525,14 @@ var STEP_STATUS_KEYS = [
22519
22525
  "step9_status",
22520
22526
  "step10_status"
22521
22527
  ];
22528
+ var DELEGATING_AGENTS = /* @__PURE__ */ new Set([
22529
+ "atomic-planner",
22530
+ "atomic-executor",
22531
+ "feature-review",
22532
+ "task-researcher",
22533
+ "prd-feature",
22534
+ "pr-author"
22535
+ ]);
22522
22536
  function isObject7(value) {
22523
22537
  return typeof value === "object" && value !== null && !Array.isArray(value);
22524
22538
  }
@@ -22582,6 +22596,53 @@ function resolveRoutingMatrix(options) {
22582
22596
  }
22583
22597
  return void 0;
22584
22598
  }
22599
+ function delegatedAgents(stateMap) {
22600
+ const agents = /* @__PURE__ */ new Set();
22601
+ const receipts = stateMap["delegation_receipts"];
22602
+ if (Array.isArray(receipts)) {
22603
+ receipts.forEach((receipt) => {
22604
+ if (!isObject7(receipt)) {
22605
+ return;
22606
+ }
22607
+ const agentName = receipt["agent_name"];
22608
+ if (typeof agentName === "string" && agentName.trim() !== "") {
22609
+ agents.add(agentName);
22610
+ }
22611
+ });
22612
+ }
22613
+ const nextStep = stateMap["next_step"];
22614
+ if (typeof nextStep === "string" && DELEGATING_AGENTS.has(nextStep)) {
22615
+ agents.add(nextStep);
22616
+ }
22617
+ return agents;
22618
+ }
22619
+ function validateModelRoutingExistence(stateMap) {
22620
+ const errors = [];
22621
+ const delegated = delegatedAgents(stateMap);
22622
+ if (delegated.size === 0) {
22623
+ return errors;
22624
+ }
22625
+ const receiptAgents2 = /* @__PURE__ */ new Set();
22626
+ const receipts = stateMap["model_routing_receipts"];
22627
+ if (Array.isArray(receipts)) {
22628
+ receipts.forEach((receipt) => {
22629
+ if (!isObject7(receipt)) {
22630
+ return;
22631
+ }
22632
+ const agent = receipt["agent"];
22633
+ if (typeof agent === "string" && agent.trim() !== "") {
22634
+ receiptAgents2.add(agent);
22635
+ }
22636
+ });
22637
+ }
22638
+ const missing = [...delegated].filter((a) => !receiptAgents2.has(a)).sort();
22639
+ for (const agent of missing) {
22640
+ errors.push(
22641
+ `Checkpoint model_routing_receipts is missing a receipt for delegated agent: ${agent}.`
22642
+ );
22643
+ }
22644
+ return errors;
22645
+ }
22585
22646
  function validateOrchestratorStateText(text, options = {}) {
22586
22647
  const errors = [];
22587
22648
  let state;
@@ -22655,6 +22716,9 @@ function validateOrchestratorStateText(text, options = {}) {
22655
22716
  })
22656
22717
  );
22657
22718
  }
22719
+ if (options.requireModelRouting === true) {
22720
+ errors.push(...validateModelRoutingExistence(stateMap));
22721
+ }
22658
22722
  return errors;
22659
22723
  }
22660
22724
 
@@ -23029,6 +23093,7 @@ function validateArtifact(input) {
23029
23093
  case "orchestrator-state": {
23030
23094
  const options = {
23031
23095
  ...input.requireComplete === void 0 ? {} : { requireComplete: input.requireComplete },
23096
+ ...input.requireModelRouting === void 0 ? {} : { requireModelRouting: input.requireModelRouting },
23032
23097
  ...input.fs === void 0 ? {} : { fs: input.fs },
23033
23098
  ...input.root === void 0 ? {} : { root: input.root },
23034
23099
  ...input.routingMatrix === void 0 ? {} : { routingMatrix: input.routingMatrix }
@@ -23056,6 +23121,7 @@ function validateOrchestrationServiceCall(input) {
23056
23121
  artifactType: input.artifactType,
23057
23122
  text,
23058
23123
  ...input.requireComplete === void 0 ? {} : { requireComplete: input.requireComplete },
23124
+ ...input.requireModelRouting === void 0 ? {} : { requireModelRouting: input.requireModelRouting },
23059
23125
  fs: input.fileSystem,
23060
23126
  root: input.workspaceRoot
23061
23127
  });
@@ -23072,6 +23138,18 @@ ${errors.join("\n")}`
23072
23138
  };
23073
23139
  }
23074
23140
 
23141
+ // ../../extensions/drm-copilot/src/lib/validate/build-validate-orchestration-service-call-input.ts
23142
+ function buildValidateOrchestrationServiceCallInput(fileSystem, input) {
23143
+ return {
23144
+ fileSystem,
23145
+ workspaceRoot: input.workspaceRoot,
23146
+ artifactType: input.artifactType,
23147
+ artifactPath: input.artifactPath,
23148
+ ...input.requireComplete === void 0 ? {} : { requireComplete: input.requireComplete },
23149
+ ...input.requireModelRouting === void 0 ? {} : { requireModelRouting: input.requireModelRouting }
23150
+ };
23151
+ }
23152
+
23075
23153
  // ../../extensions/drm-copilot/src/lib/new-potential-bug-entry.ts
23076
23154
  var fs5 = __toESM(require("node:fs"));
23077
23155
  var nodePath4 = __toESM(require("node:path"));
@@ -27713,13 +27791,9 @@ var DefaultRepoAutomationService = class {
27713
27791
  });
27714
27792
  }
27715
27793
  async validateOrchestrationArtifacts(input) {
27716
- return validateOrchestrationServiceCall({
27717
- fileSystem: this.fileSystem,
27718
- workspaceRoot: input.workspaceRoot,
27719
- artifactType: input.artifactType,
27720
- artifactPath: input.artifactPath,
27721
- ...input.requireComplete === void 0 ? {} : { requireComplete: input.requireComplete }
27722
- });
27794
+ return validateOrchestrationServiceCall(
27795
+ buildValidateOrchestrationServiceCallInput(this.fileSystem, input)
27796
+ );
27723
27797
  }
27724
27798
  async executeScript(options) {
27725
27799
  const execution = await executeBundledScriptFromExtensionRoot(this.output, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danmoisan/drm-copilot-mcp",
3
- "version": "1.0.7",
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",
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: atomic-executor
3
+ model: opus
3
4
  description: Plan execution agent that runs approved atomic plans task-by-task with explicit toolchain commands for Python, TypeScript, PowerShell, and C# quality gates.
4
5
  tools:
5
6
  - Read
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: atomic-planner
3
+ model: opus
3
4
  description: Planning-only agent that generates deterministic phased implementation plans with atomic P#-T# checkbox tasks, writing output to docs/ and artifacts/ paths only.
4
5
  tools:
5
6
  - Read
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: csharp-typed-engineer
3
+ model: sonnet
3
4
  description: Project-scoped worker that implements and verifies C# changes within typed repository boundaries. Applies the CSharpier -> .NET Analyzers -> Nullable Analysis -> xUnit toolchain, the 1-3 production-file small-path budget, and zero-regression quality gates.
4
5
  tools:
5
6
  - Read
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: epic-orchestrator
3
+ model: opus
3
4
  description: Deterministic epic-scale orchestrator that schedules a dependency graph of child features across parallel, isolated git worktrees, fans results back into a shared integration branch, and drives the final integration-to-main PR. Distinct from orchestrator; only this agent is authorized to delegate to Agent(orchestrator).
4
5
  tools:
5
6
  - "Agent(orchestrator)"
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: epic-review
3
+ model: opus
3
4
  description: Project-scoped worker that reviews epic folders and writes epic-audit artifacts.
4
5
  tools:
5
6
  - Read
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: feature-review
3
+ model: opus
3
4
  description: Feature branch review specialist that produces policy-audit, code-review, and feature-audit artifacts restricted to docs/features/active/ write path.
4
5
  tools:
5
6
  - Read
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: orchestrator
3
+ model: opus
3
4
  description: Deterministic repository orchestrator that estimates change budget, selects small or large workflow path, delegates to specialist subagents, persists checkpoint state, and enforces completion gates proactively.
4
5
  tools:
5
6
  - "Agent(atomic-planner,atomic-executor,feature-review,task-researcher,prd-feature,staged-review,epic-review,status-updater,pr-author,commit-message,human-exception-runbook,python-typed-engineer,powershell-typed-engineer,csharp-typed-engineer,typescript-engineer)"
@@ -56,6 +57,18 @@ On every invocation:
56
57
  4. If a valid checkpoint exists with a matching objective, resume from the recorded `next_step`.
57
58
  5. If no checkpoint exists or the objective is new, begin from change-budget estimation.
58
59
 
60
+ ### Model-choice reconciliation on resume
61
+
62
+ When the resumed `next_step` is a delegating step, repair a missing model choice deterministically before delegating (this mirrors `## Checkpoint Handling` in `.claude/skills/orchestrate/SKILL.md`):
63
+
64
+ a. Run the orchestrator-state validator with `--require-model-routing` before the first delegation and record a `model_routing_preflight` block `{ status ("pass"|"fail"), checked_at, validator_command, output_summary }`.
65
+ b. Recompute the upcoming phase's floor with `compute_complexity_floor(signals_present)` (no reimplementation).
66
+ c. Record a `complexity_assessments[]` entry `{ phase, band, floor, signals_present[], rationale, assessed_at }` with `floor` equal to the recomputed value and `band >= floor`.
67
+ d. Resolve the model with `resolve_delegation_model(agent, complexity_band, fable_policy)` and record a `model_routing_receipts[]` entry `{ agent, phase, complexity_band, fable_policy, table_model, clamped_from | null, model }`.
68
+ e. Persist the checkpoint, then delegate with `model` equal to the receipt's `model`.
69
+
70
+ 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`.
71
+
59
72
  ## Change Budget Routing
60
73
 
61
74
  The first action is always to estimate the change budget by identifying likely affected production files and tests:
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: powershell-typed-engineer
3
+ model: sonnet
3
4
  description: Project-scoped worker that implements and verifies PowerShell changes within typed repository boundaries. Applies PoshQC format -> PSScriptAnalyzer -> Pester toolchain, the 1-2 production-file direct-mode budget, the 3-production + 3-test per-batch cap, and zero-regression quality gates.
4
5
  tools:
5
6
  - Read
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: prd-feature
3
+ model: opus
3
4
  description: Project-scoped worker that produces feature-document outputs from issue and research context.
4
5
  tools:
5
6
  - Read
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: python-typed-engineer
3
+ model: sonnet
3
4
  description: Project-scoped worker that implements and verifies Python changes within typed repository boundaries. Applies the Black -> Ruff -> Pyright -> Pytest toolchain, the 3-production + 3-test per-batch budget, and zero-regression quality gates.
4
5
  tools:
5
6
  - Read
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: staged-review
3
+ model: opus
3
4
  description: Project-scoped worker that reviews staged diffs and writes staged-review artifacts.
4
5
  tools:
5
6
  - Read
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: status-updater
3
+ model: haiku
3
4
  description: Project-scoped worker that reconciles plan and issue status and writes status-sync artifacts.
4
5
  tools:
5
6
  - Read
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: typescript-engineer
3
+ model: sonnet
3
4
  description: Project-scoped worker that implements and verifies TypeScript changes within typed repository boundaries.
4
5
  tools:
5
6
  - Read
@@ -0,0 +1,182 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Pre-tool-use hook that blocks a delegation to a gated subagent when the
4
+ orchestrator checkpoint records no model-routing receipt for that agent.
5
+
6
+ .DESCRIPTION
7
+ Invoked by the Claude Code PreToolUse hook on the Agent (Task) tool. Reads
8
+ tool input JSON from the CLAUDE_TOOL_INPUT environment variable and the
9
+ orchestrator checkpoint from artifacts/orchestration/orchestrator-state.json.
10
+
11
+ The hook enforces presence only: it cannot read the delegate's chosen
12
+ `model` (no `model` field is exposed in the tool input), so it verifies that
13
+ a `model_routing_receipts[]` entry already exists for the target
14
+ `subagent_type`. Correctness of the recorded model stays with the
15
+ authoritative Python validator.
16
+
17
+ Gated subagent types are the Agent-tool delegates that participate in model
18
+ selection: atomic-planner, atomic-executor, feature-review, task-researcher,
19
+ prd-feature, pr-author. The `orchestrator` type is deliberately excluded: it
20
+ is the calling agent, not a subagent delegated via the Agent tool, so it is
21
+ never a receipt-gated `subagent_type`.
22
+
23
+ Allow-through (graceful allow) applies to a non-delegating `subagent_type`,
24
+ empty or absent tool input, and malformed tool-input JSON.
25
+
26
+ The checkpoint read goes through Get-ModelRoutingCheckpoint so tests can
27
+ inject a synthetic checkpoint without touching disk.
28
+
29
+ .NOTES
30
+ Compatible with PowerShell 7+. Read-only presence-gating deterrent.
31
+ #>
32
+ [CmdletBinding()]
33
+ param()
34
+
35
+ function Get-ModelRoutingCheckpoint {
36
+ <#
37
+ .SYNOPSIS
38
+ Returns the parsed orchestrator checkpoint object, or $null when the
39
+ file is missing or not valid JSON. Tests mock this seam.
40
+ #>
41
+ [CmdletBinding()]
42
+ [OutputType([object])]
43
+ param(
44
+ [string] $CheckpointPath = 'artifacts/orchestration/orchestrator-state.json'
45
+ )
46
+
47
+ if (-not (Test-Path -LiteralPath $CheckpointPath -PathType Leaf)) {
48
+ return $null
49
+ }
50
+
51
+ try {
52
+ $raw = Get-Content -LiteralPath $CheckpointPath -Raw -ErrorAction Stop
53
+ return $raw | ConvertFrom-Json -ErrorAction Stop
54
+ }
55
+ catch {
56
+ return $null
57
+ }
58
+ }
59
+
60
+ function Get-ModelRoutingGatedAgent {
61
+ <#
62
+ .SYNOPSIS
63
+ Returns the set of subagent types that are receipt-gated: the Agent-tool
64
+ delegates that participate in model selection. `orchestrator` is
65
+ excluded because it is the caller, not a delegated subagent.
66
+ #>
67
+ [CmdletBinding()]
68
+ [OutputType([string[]])]
69
+ param()
70
+
71
+ return [string[]] @(
72
+ 'atomic-planner',
73
+ 'atomic-executor',
74
+ 'feature-review',
75
+ 'task-researcher',
76
+ 'prd-feature',
77
+ 'pr-author'
78
+ )
79
+ }
80
+
81
+ function Test-ModelRoutingReceiptPresent {
82
+ <#
83
+ .SYNOPSIS
84
+ Returns $true when the checkpoint carries a model_routing_receipts entry
85
+ whose agent equals the target subagent type.
86
+ #>
87
+ [CmdletBinding()]
88
+ [OutputType([bool])]
89
+ param(
90
+ [Parameter(Mandatory)]
91
+ [AllowNull()]
92
+ $Checkpoint,
93
+
94
+ [Parameter(Mandatory)]
95
+ [string] $Subagent
96
+ )
97
+
98
+ if ($null -eq $Checkpoint) {
99
+ return $false
100
+ }
101
+ if ($Checkpoint.PSObject.Properties.Name -notcontains 'model_routing_receipts') {
102
+ return $false
103
+ }
104
+
105
+ # Scan every receipt for one whose agent matches the delegated subagent type.
106
+ foreach ($receipt in @($Checkpoint.model_routing_receipts)) {
107
+ if ($null -eq $receipt) {
108
+ continue
109
+ }
110
+ if ($receipt.PSObject.Properties.Name -contains 'agent' -and
111
+ [string]$receipt.agent -eq $Subagent) {
112
+ return $true
113
+ }
114
+ }
115
+ return $false
116
+ }
117
+
118
+ function Invoke-ModelRoutingReceiptDecision {
119
+ <#
120
+ .SYNOPSIS
121
+ Parses CLAUDE_TOOL_INPUT and returns an allow-or-block decision object.
122
+ #>
123
+ [CmdletBinding()]
124
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
125
+ param(
126
+ [string] $ToolInputRaw
127
+ )
128
+
129
+ $allow = [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
130
+
131
+ # Empty or absent tool input is not a delegation this hook can gate.
132
+ if (-not $ToolInputRaw) {
133
+ return $allow
134
+ }
135
+
136
+ # Malformed tool-input JSON is allowed through gracefully; this hook is a
137
+ # deterrent, not the authoritative validator.
138
+ try {
139
+ $toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
140
+ }
141
+ catch {
142
+ return $allow
143
+ }
144
+
145
+ $subagent = [string]$toolInput.subagent_type
146
+
147
+ # Only the gated Agent-tool delegates are receipt-checked; any other
148
+ # subagent_type (including orchestrator) passes through.
149
+ if (-not $subagent -or ((Get-ModelRoutingGatedAgent) -notcontains $subagent)) {
150
+ return $allow
151
+ }
152
+
153
+ $checkpoint = Get-ModelRoutingCheckpoint
154
+ if (Test-ModelRoutingReceiptPresent -Checkpoint $checkpoint -Subagent $subagent) {
155
+ return $allow
156
+ }
157
+
158
+ return [ordered]@{
159
+ hookSpecificOutput = [ordered]@{
160
+ hookEventName = 'PreToolUse'
161
+ permissionDecision = 'deny'
162
+ permissionDecisionReason = "MODEL_ROUTING_RECEIPT_BLOCKED: cannot delegate to '$subagent' before a model_routing_receipts entry for it is recorded in the orchestrator checkpoint. Perform Model Selection (record the complexity assessment and routing receipt) before delegating."
163
+ }
164
+ }
165
+ }
166
+
167
+ # Guard allows dot-sourcing in tests without executing the entrypoint.
168
+ if ($MyInvocation.InvocationName -eq '.') {
169
+ return
170
+ }
171
+
172
+ try {
173
+ $decision = Invoke-ModelRoutingReceiptDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
174
+ }
175
+ catch {
176
+ Write-Error $_
177
+ exit 1
178
+ }
179
+
180
+ $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
181
+
182
+ exit 0
@@ -180,7 +180,7 @@ function Invoke-RoutingContractValidation {
180
180
  [scriptblock] $Invoker = {
181
181
  param($Path, $Type)
182
182
  $output = & python -m scripts.dev_tools.validate_orchestration_artifacts `
183
- $Type $Path --require-complete 2>&1
183
+ $Type $Path --require-complete --require-model-routing 2>&1
184
184
  [pscustomobject]@{
185
185
  ExitCode = $LASTEXITCODE
186
186
  Output = ($output | Out-String)
@@ -293,6 +293,14 @@ function Invoke-OrchestratorOutputValidation {
293
293
  }
294
294
  $routingResult = Invoke-RoutingContractValidation @routingArgs
295
295
  if ($routingResult.HasErrors) {
296
+ # One subprocess call now covers both --require-complete and
297
+ # --require-model-routing. Surface a model-routing gate failure under its
298
+ # own block reason (its errors name model_routing_receipts or
299
+ # complexity_assessments); otherwise fall back to the routing-contract
300
+ # block reason for a generic completion/routing failure.
301
+ if ($routingResult.ErrorText -match 'model_routing_receipts|complexity_assessments') {
302
+ return @{ Ok = $false; Message = "MODEL_ROUTING_BLOCKED: $($routingResult.ErrorText)" }
303
+ }
296
304
  return @{ Ok = $false; Message = "ROUTING_CONTRACT_BLOCKED: $($routingResult.ErrorText)" }
297
305
  }
298
306