ruvnet-brain 2.0.0 → 2.4.1

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.
@@ -0,0 +1,220 @@
1
+ {
2
+ "_comment": "Model candidate catalog for model-router-engine.mjs. EDIT ME freely. Pricing is $/Mtok. 'verified' names the source+date a price was confirmed live; null pricing means UNKNOWN \u2014 the engine will NOT invent a cost for it (same honesty rule as route-cheap.mjs). 'harness' = which agent(s) can launch each model \u2014 EMPTY [] means landscape-only: known to exist, but no wired execution path yet, so the engine will never select it (see model-router-engine.mjs pool filter). Do not add a real execution path (an entry to route-cheap.mjs PRICING or a harness value) until dispatch is actually implemented and tested \u2014 an engine that 'chooses' a model it can't run is worse than one that doesn't know the model exists. 'subscription' = harness(es) under which this model is covered by a flat subscription (Claude Max, Codex/ChatGPT), so its marginal cost to the user is ~$0 \u2014 the default policy treats those as free and will NOT prefer a billed model over them (never spend where the subscription is free).",
3
+ "updated": "2026-07-12 (template)",
4
+ "candidates": [
5
+ {
6
+ "id": "claude-haiku-4-5-20251001",
7
+ "provider": "anthropic",
8
+ "harness": [
9
+ "claude-code"
10
+ ],
11
+ "subscription": [
12
+ "claude-code"
13
+ ],
14
+ "tier": "cheap",
15
+ "costPerMTok": null,
16
+ "verified": null,
17
+ "note": "covered by Claude Max under Claude Code (marginal $0); API price unverified"
18
+ },
19
+ {
20
+ "id": "claude-sonnet-5",
21
+ "provider": "anthropic",
22
+ "harness": [
23
+ "claude-code"
24
+ ],
25
+ "subscription": [
26
+ "claude-code"
27
+ ],
28
+ "tier": "mid",
29
+ "costPerMTok": null,
30
+ "verified": null
31
+ },
32
+ {
33
+ "id": "claude-opus-4-8",
34
+ "provider": "anthropic",
35
+ "harness": [
36
+ "claude-code"
37
+ ],
38
+ "subscription": [
39
+ "claude-code"
40
+ ],
41
+ "tier": "frontier",
42
+ "costPerMTok": {
43
+ "in": 5,
44
+ "out": 25
45
+ },
46
+ "verified": "2026-07-07 route-cheap.mjs FRONTIER (API price; $0 under Max)"
47
+ },
48
+ {
49
+ "id": "claude-fable-5",
50
+ "provider": "anthropic",
51
+ "harness": [
52
+ "claude-code"
53
+ ],
54
+ "subscription": [
55
+ "claude-code"
56
+ ],
57
+ "tier": "frontier",
58
+ "costPerMTok": null,
59
+ "verified": "2026-07-12 Artificial Analysis leaderboard (WebSearch) \u2014 listed as top-intelligence tier alongside GPT-5.6 Sol",
60
+ "note": "covered by Claude Max under Claude Code (marginal $0); API price unverified. Use sparingly per Stuart's directive \u2014 Opus 4.8 measures ~96% of comparable quality at far lower cost; Fable 5 is for tasks that specifically need its top-tier profile, not a default."
61
+ },
62
+ {
63
+ "id": "deepseek/deepseek-chat",
64
+ "provider": "openrouter",
65
+ "harness": [
66
+ "claude-code",
67
+ "codex"
68
+ ],
69
+ "subscription": [],
70
+ "tier": "cheap",
71
+ "costPerMTok": {
72
+ "in": 0.2002,
73
+ "out": 0.8001
74
+ },
75
+ "verified": "2026-07-12 OpenRouter API (goldie)",
76
+ "note": "Resolves to LEGACY DeepSeek V3 per OpenRouter model card (Goldie 2026-07-12) \u2014 superseded by deepseek/deepseek-v4-flash (landscape entry, cheaper+newer). Keep until v4-flash passes a measured quality check, then switch."
77
+ },
78
+ {
79
+ "id": "z-ai/glm-4.6",
80
+ "provider": "openrouter",
81
+ "harness": [
82
+ "claude-code",
83
+ "codex"
84
+ ],
85
+ "subscription": [],
86
+ "tier": "mid",
87
+ "costPerMTok": {
88
+ "in": 0.43,
89
+ "out": 1.75
90
+ },
91
+ "verified": "2026-07-12 OpenRouter API (goldie)"
92
+ },
93
+ {
94
+ "id": "z-ai/glm-5",
95
+ "provider": "openrouter",
96
+ "harness": [
97
+ "claude-code",
98
+ "codex"
99
+ ],
100
+ "subscription": [],
101
+ "tier": "mid",
102
+ "costPerMTok": {
103
+ "in": 0.6,
104
+ "out": 1.92
105
+ },
106
+ "verified": "2026-07-12 OpenRouter API (goldie)"
107
+ },
108
+ {
109
+ "id": "gpt-5.5",
110
+ "provider": "openai",
111
+ "harness": [
112
+ "codex"
113
+ ],
114
+ "subscription": [
115
+ "codex"
116
+ ],
117
+ "tier": "frontier",
118
+ "costPerMTok": null,
119
+ "verified": null,
120
+ "note": "Codex-harness model; whether YOUR install can launch it and whether a subscription covers it comes from profile.json (model-router-setup.mjs) and your live ~/.codex/models_cache.json"
121
+ },
122
+ {
123
+ "id": "gpt-5.6-sol",
124
+ "provider": "openai",
125
+ "harness": [],
126
+ "subscription": [],
127
+ "tier": "frontier",
128
+ "costPerMTok": null,
129
+ "verified": null,
130
+ "note": "Codex-harness model; whether YOUR install can launch it and whether a subscription covers it comes from profile.json (model-router-setup.mjs) and your live ~/.codex/models_cache.json"
131
+ },
132
+ {
133
+ "id": "gpt-5.6-terra",
134
+ "provider": "openai",
135
+ "harness": [],
136
+ "subscription": [],
137
+ "tier": "mid",
138
+ "costPerMTok": null,
139
+ "verified": null,
140
+ "note": "Codex-harness model; whether YOUR install can launch it and whether a subscription covers it comes from profile.json (model-router-setup.mjs) and your live ~/.codex/models_cache.json"
141
+ },
142
+ {
143
+ "id": "gpt-5.6-luna",
144
+ "provider": "openai",
145
+ "harness": [],
146
+ "subscription": [],
147
+ "tier": "cheap",
148
+ "costPerMTok": null,
149
+ "verified": null,
150
+ "note": "Codex-harness model; whether YOUR install can launch it and whether a subscription covers it comes from profile.json (model-router-setup.mjs) and your live ~/.codex/models_cache.json"
151
+ },
152
+ {
153
+ "id": "gpt-5-mini",
154
+ "provider": "openai",
155
+ "harness": [
156
+ "codex",
157
+ "claude-code"
158
+ ],
159
+ "subscription": [],
160
+ "tier": "mid",
161
+ "costPerMTok": null,
162
+ "verified": null
163
+ },
164
+ {
165
+ "id": "x-ai/grok-4.5",
166
+ "provider": "openrouter",
167
+ "harness": [
168
+ "claude-code",
169
+ "codex"
170
+ ],
171
+ "subscription": [],
172
+ "tier": "frontier",
173
+ "costPerMTok": {
174
+ "in": 2.0,
175
+ "out": 6.0
176
+ },
177
+ "verified": "2026-07-12 OpenRouter /models live (goldie judgment + direct API check)",
178
+ "note": "LANDSCAPE-ONLY, one step from wireable: live on OpenRouter (tools=true, ctx 500K) \u2014 the old 'needs a bespoke xAI adapter' note is obsolete. To wire: add to route-cheap.mjs PRICING + one tested dispatch + a measured quality check. Secondary-source numbers promising (SWE-bench Pro 64.7% between GLM-5.2 and Opus 4.8; ~4.2x fewer output tokens than Opus at max reasoning) \u2014 'mid priced, frontier-adjacent'. See goldie/2026-07-12.md Q3."
179
+ },
180
+ {
181
+ "id": "muse-spark-1.1",
182
+ "provider": "meta",
183
+ "harness": [],
184
+ "subscription": [],
185
+ "tier": null,
186
+ "costPerMTok": null,
187
+ "verified": "2026-07-12 Meta/Fortune announcement 2026-07-09 (WebSearch)",
188
+ "note": "LANDSCAPE-ONLY, not selectable: general-capability model (coding/captioning/reasoning per Meta's announcement) but developer/API accessibility, pricing, and tier are UNCONFIRMED \u2014 the announcement centers on Meta AI app / WhatsApp / Instagram integration, not a clear developer API. Do not assume this is reachable until that's verified."
189
+ },
190
+ {
191
+ "id": "deepseek/deepseek-v4-flash",
192
+ "provider": "openrouter",
193
+ "harness": [
194
+ "claude-code",
195
+ "codex"
196
+ ],
197
+ "subscription": [],
198
+ "tier": "cheap",
199
+ "costPerMTok": {
200
+ "in": 0.077,
201
+ "out": 0.154
202
+ },
203
+ "verified": "2026-07-12 OpenRouter /models live (direct API check)",
204
+ "note": "LANDSCAPE-ONLY: proposed successor to deepseek/deepseek-chat (which OpenRouter resolves to LEGACY DeepSeek V3 per its own model card \u2014 Goldie 2026-07-12). Full generation newer, ~5x cheaper output, tools=true, ctx 1M. Wire via route-cheap PRICING + tested dispatch + measured quality check before giving it a harness path."
205
+ },
206
+ {
207
+ "id": "tencent/hy3",
208
+ "provider": "openrouter",
209
+ "harness": [],
210
+ "subscription": [],
211
+ "tier": "cheap",
212
+ "costPerMTok": {
213
+ "in": 0.14,
214
+ "out": 0.58
215
+ },
216
+ "verified": "2026-07-12 OpenRouter /models live (direct API check)",
217
+ "note": "LANDSCAPE-ONLY: 295B MoE Apache-2.0, strong generalist, second-tier coder (SWE-bench V 78.0 vs GLM-5.2 84.2, vendor numbers). IMPORTANT: this is the PAID slug on purpose \u2014 the tencent/hy3:free promo window closes ~2026-07-20 and would silently die/bill; never wire the :free slug. See goldie/2026-07-12.md Q3."
218
+ }
219
+ ]
220
+ }
@@ -0,0 +1,83 @@
1
+ // ~/.claude/model-router/policy.default.mjs — the DEFAULT (placeholder) routing policy.
2
+ //
3
+ // HOW TO SWAP IN YOUR HEURISTICS: create ~/.claude/model-router/policy.mjs with the same
4
+ // `choose` signature. The engine prefers policy.mjs when it exists and only falls back to this
5
+ // file otherwise. A learned router (e.g. a @metaharness/router / tiny-dancer JSON) can also be a
6
+ // policy — load it inside choose() and map its output to {model, provider, tier}.
7
+ //
8
+ // SIGNATURE:
9
+ // choose({ features, candidates, harness }) -> { model, provider, tier, reason, confidence }
10
+ // features = output of the engine's extractFeatures() (chars, estTokens, hasCode,
11
+ // codeFences, fileTypes, questionCount, taskHints, harness)
12
+ // candidates = the catalog entries (already filterable by .harness)
13
+ // harness = 'claude-code' | 'codex'
14
+ //
15
+ // WHY THIS DEFAULT IS DELIBERATELY WEAK (and says so): ADR-040 (DRACO) MEASURED that a
16
+ // hand-built self-signal threshold routed WORSE than always-cheapest, while a learned map from a
17
+ // real feature beat the best fixed model. So this placeholder makes NO claim of optimality — it is
18
+ // a transparent complexity proxy so the engine is usable TODAY, to be replaced by your researched
19
+ // (ideally learned) policy. confidence is pinned low to signal "not tuned."
20
+
21
+ export function choose({ features, candidates, harness }) {
22
+ const pool = candidates.filter((m) => Array.isArray(m.harness) && m.harness.includes(harness));
23
+ if (pool.length === 0) {
24
+ return { model: null, provider: null, tier: null, reason: `no candidate supports harness=${harness}`, confidence: 0 };
25
+ }
26
+ const c = complexity(features);
27
+ const tier = c < 0.33 ? 'cheap' : c < 0.66 ? 'mid' : 'frontier';
28
+ let pick = cheapestInTier(pool, tier, harness) || cheapestInTier(pool, 'mid', harness) || pool[0];
29
+ let floorNote = '';
30
+ // $1,600 floor, CROSS-TIER (Goldie 2026-07-12): when the in-tier winner is a BILLED model but a
31
+ // subscription-covered model exists at-or-above the needed tier, the subscription model wins —
32
+ // more capability for $0 beats less capability for money, always. (Found live: demoting the
33
+ // unreachable gpt-5.6 tiers left codex cheap/mid pointing at billed DeepSeek while gpt-5.5,
34
+ // subscription-covered and MORE capable, sat unused one tier up.)
35
+ if (effCost(pick, harness) > 0) {
36
+ const order = ['cheap', 'mid', 'frontier'];
37
+ const atOrAbove = order.slice(order.indexOf(tier));
38
+ const subs = pool
39
+ .filter((m) => Array.isArray(m.subscription) && m.subscription.includes(harness) && atOrAbove.includes(m.tier))
40
+ .sort((a, b) => order.indexOf(a.tier) - order.indexOf(b.tier)); // least-capable sufficient one
41
+ if (subs.length) { pick = subs[0]; floorNote = ` [cross-tier $0 floor: subscription ${pick.tier} model beats billed ${tier} candidate]`; }
42
+ }
43
+ return {
44
+ model: pick.id,
45
+ provider: pick.provider,
46
+ tier,
47
+ reason: `default(placeholder) policy: complexity≈${c.toFixed(2)} → ${tier} tier; subscription-covered model preferred ($0), else cheapest ${harness} candidate.${floorNote} NOT a tuned heuristic — replace via policy.mjs.`,
48
+ confidence: 0.4,
49
+ };
50
+ }
51
+
52
+ // Transparent, crude complexity proxy in [0,1]. Documented as a starting point, not a claim.
53
+ function complexity(f) {
54
+ let s = 0;
55
+ s += Math.min(0.3, (f.estTokens || 0) / 4000); // longer prompt → likely harder
56
+ if (f.hasCode) s += 0.25;
57
+ s += Math.min(0.15, (f.codeFences || 0) * 0.05);
58
+ const hints = f.taskHints || '';
59
+ // Accumulate hard/easy signals rather than one flat bump, so genuinely complex prompts
60
+ // (architect + prove + optimize + security + debug …) actually climb past the tier thresholds
61
+ // instead of maxing out at a single +0.25. Still crude on purpose — a learned policy replaces this.
62
+ const hard = (hints.match(/\b(refactor|architect|design|debug|migrat\w*|optimi[sz]\w*|security|secure|proof|prove|correctness|distributed|consensus|concurren\w*|race\s+condition|algorithm|cryptograph\w*)\b/gi) || []).length;
63
+ const easy = (hints.match(/\b(summari[sz]e|translate|classify|extract|rephrase|list|format|rename|typo|lookup)\b/gi) || []).length;
64
+ s += Math.min(0.5, hard * 0.12);
65
+ s -= Math.min(0.3, easy * 0.12);
66
+ return Math.max(0, Math.min(1, s));
67
+ }
68
+
69
+ function cheapestInTier(pool, tier, harness) {
70
+ const inTier = pool.filter((m) => m.tier === tier);
71
+ // cheapest by EFFECTIVE cost for this harness; unknown-price candidates sort last (Infinity) so a
72
+ // priced option is preferred over an unpriced one, but an unpriced one is still returned last
73
+ // rather than nothing.
74
+ return inTier.sort((a, b) => effCost(a, harness) - effCost(b, harness))[0];
75
+ }
76
+ // SAFETY FLOOR — the cardinal $1,600 lesson (cross-ref AgentDB intel-1600-postmortem): a model
77
+ // covered by THIS harness's subscription is marginal-$0 to the user, so it must NEVER lose to a
78
+ // billed OpenRouter/API model that only looks cheaper by sticker price. Spending money where the
79
+ // subscription is free is exactly the "clever but reckless" mistake this system exists to prevent.
80
+ function effCost(m, harness) {
81
+ if (m && Array.isArray(m.subscription) && m.subscription.includes(harness)) return 0;
82
+ return m && m.costPerMTok && typeof m.costPerMTok.out === 'number' ? m.costPerMTok.out : Infinity;
83
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ruvnet-brain",
3
- "version": "2.0.0",
4
- "description": "One-command installer for RuvNet Brain — a portable, source-grounded brain over rUv's RuvNet building blocks, delivered as a Claude Code plugin so Claude uses the stack instead of fighting it.",
3
+ "version": "2.4.1",
4
+ "description": "One-command installer for RuvNet Brain \u2014 a portable, source-grounded brain over rUv's RuvNet building blocks, delivered as a Claude Code plugin so Claude uses the stack instead of fighting it.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "ruvnet-brain": "bin/install.mjs"
@@ -14,6 +14,8 @@
14
14
  "test:unit": "vitest run tests/unit",
15
15
  "test:cov": "vitest run tests/unit --coverage",
16
16
  "test:all": "npm run test:unit && npm run test:integration && npm test",
17
+ "metaharness:receipts": "node scripts/metaharness-receipts.mjs",
18
+ "route:cheap": "node scripts/route-cheap.mjs",
17
19
  "metaharness:fix": "node scripts/fix-metaharness-memretrieve.mjs --apply",
18
20
  "metaharness:check": "node scripts/fix-metaharness-memretrieve.mjs --check",
19
21
  "eval": "node scripts/eval-brain.mjs",
@@ -28,7 +30,14 @@
28
30
  "files": [
29
31
  "bin/install.mjs",
30
32
  "README.md",
31
- "LICENSE"
33
+ "LICENSE",
34
+ "config/",
35
+ "scripts/model-router-engine.mjs",
36
+ "scripts/model-router-setup.mjs",
37
+ "scripts/model-router-status.mjs",
38
+ "scripts/model-router-outcome.mjs",
39
+ "scripts/route-cheap.mjs",
40
+ "scripts/codex-routed.sh"
32
41
  ],
33
42
  "engines": {
34
43
  "node": ">=18"
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/codex-routed.sh — built 2026-07-12. Codex has no native routing surface
3
+ # (~/.codex/config.toml launches ONE model per run), so a consulted wrapper is the only
4
+ # per-prompt model-selection path available to it. Part of the MetaHarness router (see
5
+ # scripts/model-router-engine.mjs, the harness-neutral prompt -> {model, reason} decision
6
+ # engine). This script only CONSULTS the engine and LAUNCHES codex — all selection logic
7
+ # (features, policy, pricing) lives in the engine, not here.
8
+ #
9
+ # Contract: never block Stuart's launch. If the engine errors, times out, or returns a
10
+ # null model, fall straight through to plain `codex` with no --model flag — a missing
11
+ # routing decision must never be worse than no routing at all.
12
+ set -uo pipefail
13
+ DIR="$(cd "$(dirname "$0")" && pwd)"
14
+
15
+ DECISION=$(node "$DIR/model-router-engine.mjs" --harness codex --prompt "$*" --json 2>/dev/null)
16
+
17
+ # Extract .model + a short .reason without a jq dependency (Stuart directive). python3 ships
18
+ # with macOS by default and its json module handles arbitrary reason text (quotes, unicode)
19
+ # more safely than hand-rolled JS string escaping in a node -e one-liner would. Single call,
20
+ # tab-delimited output, so we only shell out once per launch.
21
+ IFS=$'\t' read -r M REASON <<< "$(printf '%s' "$DECISION" | python3 -c '
22
+ import json, sys
23
+ try:
24
+ d = json.load(sys.stdin)
25
+ m = d.get("model") or ""
26
+ r = (d.get("reason") or "").replace("\n", " ").replace("\t", " ")[:80]
27
+ print(f"{m}\t{r}")
28
+ except Exception:
29
+ print("\t")
30
+ ' 2>/dev/null)"
31
+
32
+ if [ -z "${M:-}" ] || [ "$M" = "None" ]; then
33
+ # Engine failed / no policy resolved a model — never block, just launch codex unrouted.
34
+ exec codex "$@"
35
+ fi
36
+
37
+ printf '\x1b[2m🧭 codex-routed → %s (%s)\x1b[0m\n' "$M" "$REASON"
38
+ exec codex --model "$M" "$@"
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env node
2
+ // scripts/model-router-engine.mjs — the harness-neutral MODEL SELECTION engine.
3
+ //
4
+ // WHAT THIS IS (and is NOT):
5
+ // • IS: a pure `prompt -> {model, provider, reason, cost}` DECISION engine. It extracts features
6
+ // from the prompt and hands them to a PLUGGABLE POLICY that decides which model to use. The
7
+ // policy is the swappable part — drop your researched heuristics (or a learned router) into
8
+ // ~/.claude/model-router/policy.mjs and the engine picks them up. NO heuristics are baked in
9
+ // here. (ADR-040 / DRACO, verified via search_ruvnet: a hand-built self-signal threshold routed
10
+ // WORSE than always-cheapest; a learned map from a real feature beat the best fixed model. So
11
+ // the SIGNAL/policy is everything and must never be hard-coded into the engine.)
12
+ // • IS harness-neutral: the SAME CLI is consulted by Claude Code AND Codex. It only DECIDES; it
13
+ // does not launch a model. The caller acts on the JSON. (Codex has no native routing surface —
14
+ // ~/.codex/config.toml launches one model per run — so a consulted CLI is the only way to make
15
+ // selection work for Codex too. That is the fix for "only partially OK for Codex.")
16
+ // • Is NOT an executor. Running a task on a cheap model is route-cheap.mjs's job (OpenRouter).
17
+ // This answers only "which model should handle this prompt?"
18
+ //
19
+ // INTEGRATION:
20
+ // Claude Code : call from a hook/skill, parse the JSON, use .model.
21
+ // node model-router-engine.mjs --harness claude-code --prompt "$PROMPT" --json
22
+ // Codex : wrap the codex launch —
23
+ // M=$(node model-router-engine.mjs --harness codex --prompt "$TASK" --json | jq -r .model)
24
+ // codex --model "$M" ...
25
+ //
26
+ // Config (edit freely): ~/.claude/model-router/catalog.json (candidates + verified pricing)
27
+ // ~/.claude/model-router/policy.mjs (YOUR policy; falls back to policy.default.mjs)
28
+ // Decision log: ~/.claude/metaharness/routing-decisions.jsonl (sibling to route-cheap's execution receipts)
29
+ //
30
+ // Usage:
31
+ // node model-router-engine.mjs --prompt "..." [--harness claude-code|codex] [--policy <path>] [--json|--line]
32
+ // echo "the prompt text" | node model-router-engine.mjs --harness codex
33
+
34
+ import fs from 'node:fs';
35
+ import path from 'node:path';
36
+ import os from 'node:os';
37
+ import { pathToFileURL, fileURLToPath } from 'node:url';
38
+ import { estTokens } from './route-cheap.mjs'; // reuse the verified char/4 estimator (DRY)
39
+
40
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
41
+ export const CONFIG_DIR = path.join(os.homedir(), '.claude', 'model-router');
42
+ const CATALOG_PATH = path.join(CONFIG_DIR, 'catalog.json');
43
+ const POLICY_USER = path.join(CONFIG_DIR, 'policy.mjs');
44
+ const POLICY_DEFAULT = path.join(CONFIG_DIR, 'policy.default.mjs');
45
+ const DECISIONS_LOG =
46
+ process.env.MODEL_ROUTER_DECISIONS ||
47
+ path.join(os.homedir(), '.claude', 'metaharness', 'routing-decisions.jsonl');
48
+
49
+ // ─── feature extraction: this is "based on what the prompt is" ────────────────────────────────
50
+ // Pure and deterministic. Emits SIGNALS only — it never decides. Policies consume these; extend
51
+ // this object as your research identifies new predictive features (it is the documented surface).
52
+ export function extractFeatures(prompt, harness) {
53
+ const text = prompt || '';
54
+ const codeFences = Math.floor((text.match(/```/g) || []).length / 2);
55
+ const fileTypes = [...new Set((text.match(/\.[a-z0-9]{1,5}\b/gi) || []).map((s) => s.toLowerCase()))].slice(0, 12);
56
+ const hasCode =
57
+ codeFences > 0 || /\b(function|const|let|def|class|import|=>|SELECT|async)\b/.test(text) || /[{};]\s*$/m.test(text);
58
+ return {
59
+ chars: text.length,
60
+ estTokens: estTokens(text),
61
+ codeFences,
62
+ hasCode,
63
+ fileTypes,
64
+ questionCount: (text.match(/\?/g) || []).length,
65
+ taskHints: text.slice(0, 4000), // policies may regex over the actual prompt head
66
+ harness,
67
+ };
68
+ }
69
+
70
+ // ── PER-USER SUBSCRIPTION PROFILE (2026-07-12) ─────────────────────────────────────────────────
71
+ // The catalog states facts about MODELS; the profile states facts about THIS USER (which harnesses
72
+ // they have, which are subscription-covered — detected/asked/verified by model-router-setup.mjs).
73
+ // The overlay strips any subscription or harness claim the profile doesn't back, so the $0 floor
74
+ // can never assume a plan the user doesn't have (silently billing them) or miss one they do
75
+ // (silently wasting it). No profile file = catalog taken as-is (pre-profile installs keep working).
76
+ export const PROFILE_PATH =
77
+ process.env.MODEL_ROUTER_PROFILE || path.join(CONFIG_DIR, 'profile.json');
78
+
79
+ export function loadProfile() {
80
+ try { return JSON.parse(fs.readFileSync(PROFILE_PATH, 'utf8')); } catch { return null; }
81
+ }
82
+
83
+ export function applyProfile(candidates, profile) {
84
+ const h = profile?.harnesses;
85
+ if (!h) return candidates;
86
+ return candidates.map((c) => ({
87
+ ...c,
88
+ // A harness the user doesn't have can never launch anything — remove it from the pool filter.
89
+ harness: (c.harness || []).filter((x) => h[x] === undefined || h[x].available !== false),
90
+ // A subscription claim only survives if THIS user's profile confirms that harness is covered.
91
+ subscription: (c.subscription || []).filter((x) => h[x]?.subscription === true),
92
+ }));
93
+ }
94
+
95
+ export function loadCatalog() {
96
+ try {
97
+ const j = JSON.parse(fs.readFileSync(CATALOG_PATH, 'utf8'));
98
+ if (Array.isArray(j.candidates) && j.candidates.length) return j.candidates;
99
+ } catch {
100
+ /* fall through to a minimal built-in so the engine still answers */
101
+ }
102
+ // Built-in fallback (verified OpenRouter prices from route-cheap; Anthropic frontier from same).
103
+ return [
104
+ { id: 'deepseek/deepseek-chat', provider: 'openrouter', harness: ['claude-code', 'codex'], tier: 'cheap', costPerMTok: { in: 0.2, out: 0.8 }, verified: '2026-07-07' },
105
+ { id: 'claude-opus-4-8', provider: 'anthropic', harness: ['claude-code'], tier: 'frontier', costPerMTok: { in: 5.0, out: 25.0 }, verified: '2026-07-07' },
106
+ { id: 'gpt-5.5', provider: 'openai', harness: ['codex'], tier: 'frontier', costPerMTok: null, verified: null },
107
+ ];
108
+ }
109
+
110
+ export async function loadPolicy(explicit) {
111
+ const candidatePaths = [explicit, POLICY_USER, POLICY_DEFAULT].filter(Boolean);
112
+ for (const p of candidatePaths) {
113
+ if (!fs.existsSync(p)) continue;
114
+ try {
115
+ const mod = await import(pathToFileURL(p).href);
116
+ if (typeof mod.choose === 'function') return { choose: mod.choose, source: p };
117
+ } catch (e) {
118
+ process.stderr.write(`[model-router] policy at ${p} failed to load: ${e.message}\n`);
119
+ }
120
+ }
121
+ return null;
122
+ }
123
+
124
+ function parseArgs(argv) {
125
+ const a = { harness: null, prompt: null, policy: null, mode: 'json' };
126
+ for (let i = 0; i < argv.length; i++) {
127
+ const k = argv[i];
128
+ if (k === '--prompt') a.prompt = argv[++i];
129
+ else if (k === '--harness') a.harness = argv[++i];
130
+ else if (k === '--policy') a.policy = argv[++i];
131
+ else if (k === '--line') a.mode = 'line';
132
+ else if (k === '--json') a.mode = 'json';
133
+ else if (k === '--help' || k === '-h') a.help = true;
134
+ }
135
+ return a;
136
+ }
137
+
138
+ function readStdin() {
139
+ try { return fs.readFileSync(0, 'utf8'); } catch { return ''; }
140
+ }
141
+
142
+ // Selection-time cost is INPUT-only and clearly labeled: at selection we don't know output length,
143
+ // so we never fabricate one. Returns null when the chosen model has no verified price.
144
+ function estInputCost(candidate, inTokens) {
145
+ const p = candidate && candidate.costPerMTok;
146
+ if (!p || typeof p.in !== 'number') return null;
147
+ return +((inTokens * p.in) / 1e6).toFixed(6);
148
+ }
149
+
150
+ async function main() {
151
+ const args = parseArgs(process.argv.slice(2));
152
+ if (args.help) {
153
+ process.stdout.write(fs.readFileSync(fileURLToPath(import.meta.url), 'utf8').split('\n').slice(1, 33).join('\n') + '\n');
154
+ return;
155
+ }
156
+ // Harness: explicit flag wins; else detect Codex by its env/dir; else default claude-code.
157
+ const harness =
158
+ args.harness ||
159
+ (process.env.CODEX_SANDBOX || fs.existsSync(path.join(os.homedir(), '.codex', 'config.toml')) && process.env.CODEX ? 'codex' : null) ||
160
+ 'claude-code';
161
+ const prompt = args.prompt || readStdin();
162
+ if (!prompt || !prompt.trim()) {
163
+ process.stderr.write('model-router-engine: no prompt (use --prompt "..." or pipe text on stdin)\n');
164
+ process.exit(2);
165
+ }
166
+
167
+ const profile = loadProfile();
168
+ const candidates = applyProfile(loadCatalog(), profile);
169
+ const policy = await loadPolicy(args.policy);
170
+ const features = extractFeatures(prompt, harness);
171
+
172
+ let decision;
173
+ if (!policy) {
174
+ // No policy at all: pick cheapest priced candidate for the harness as a safe floor, and SAY SO.
175
+ const pool = candidates.filter((m) => (m.harness || []).includes(harness));
176
+ const pick = pool.slice().sort((x, y) => (x.costPerMTok?.out ?? Infinity) - (y.costPerMTok?.out ?? Infinity))[0] || candidates[0];
177
+ decision = { model: pick?.id ?? null, provider: pick?.provider ?? null, tier: pick?.tier ?? null, reason: 'NO POLICY FOUND — fell back to cheapest priced candidate for the harness', confidence: 0 };
178
+ } else {
179
+ decision = policy.choose({ features, candidates, harness });
180
+ }
181
+
182
+ const chosen = candidates.find((m) => m.id === decision.model) || null;
183
+ const out = {
184
+ ts: new Date().toISOString(),
185
+ harness,
186
+ model: decision.model,
187
+ provider: decision.provider,
188
+ tier: decision.tier,
189
+ reason: decision.reason,
190
+ confidence: decision.confidence,
191
+ policy_source: policy ? policy.source.replace(os.homedir(), '~') : 'none',
192
+ profile: profile ? PROFILE_PATH.replace(os.homedir(), '~') : 'none (catalog taken as-is — run model-router-setup.mjs)',
193
+ price_verified: chosen ? chosen.verified : null,
194
+ est_input_cost_usd: estInputCost(chosen, features.estTokens), // null if price unknown — never invented
195
+ features: { estTokens: features.estTokens, hasCode: features.hasCode, codeFences: features.codeFences, fileTypes: features.fileTypes, questionCount: features.questionCount },
196
+ };
197
+
198
+ // Durable decision log (append-only; separate from route-cheap's execution/savings ledger).
199
+ try {
200
+ fs.mkdirSync(path.dirname(DECISIONS_LOG), { recursive: true });
201
+ fs.appendFileSync(DECISIONS_LOG, JSON.stringify({ ...out, features: undefined, prompt_head: prompt.slice(0, 120) }) + '\n');
202
+ } catch { /* logging must never break selection */ }
203
+
204
+ if (args.mode === 'line') {
205
+ const cost = out.est_input_cost_usd == null ? 'cost:unpriced' : `est-in:$${out.est_input_cost_usd}`;
206
+ process.stdout.write(`\x1b[2m🧭 model-router → ${out.model} (${out.harness}, ${out.tier}, ${cost}) — ${out.reason}\x1b[0m\n`);
207
+ } else {
208
+ process.stdout.write(JSON.stringify(out, null, 2) + '\n');
209
+ }
210
+ }
211
+
212
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
213
+ main().catch((e) => { process.stderr.write(`model-router-engine: ${e.stack || e.message}\n`); process.exit(1); });
214
+ }