ruvnet-brain 2.9.1 β 3.4.6-dev
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/README.md +75 -16
- package/bin/install.mjs +210 -11
- package/config/scheduled-jobs.json +20 -0
- package/package.json +6 -2
- package/scripts/metaharness-router.mjs +12 -3
- package/scripts/route-cheap.mjs +4 -1
package/README.md
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
# π§ RuvNet Brain
|
|
6
6
|
|
|
7
|
-
### π§ RuvNet Brain β [](https://github.com/stuinfla/ruvnet-brain/blob/main/plugin/.claude-plugin/plugin.json)
|
|
8
8
|
|
|
9
9
|
**A portable, source-grounded brain over Reuven Cohen's (rUv's) RuvNet stack β delivered as a Claude Code plugin that makes Claude _use_ the stack instead of fighting it.**
|
|
10
10
|
|
|
11
11
|
[](plugin/.claude-plugin/plugin.json)
|
|
12
12
|
[](https://www.npmjs.com/package/ruvnet-brain)
|
|
13
|
-
[](https://github.com/stuinfla/ruvnet-brain/releases/latest)
|
|
14
14
|
[](https://isovision.ai/ruvnet-brain/)
|
|
15
15
|
[](LICENSE)
|
|
16
16
|
[](#testing--proof)
|
|
@@ -19,10 +19,14 @@
|
|
|
19
19
|
> **Three independent things version separately here β by design, not drift. Every number below is live (read straight from its real source, never hand-typed), so none of them can go stale:**
|
|
20
20
|
> - **`plugin`** (badge above) β the Claude Code plugin itself: SKILL.md, the grounding hooks, the MCP server. Read live from [`plugin/.claude-plugin/plugin.json`](plugin/.claude-plugin/plugin.json). Updates often β this is where behavior fixes land.
|
|
21
21
|
> - **`installer (npm)`** (badge above) β the `npx ruvnet-brain` setup script. Read live from the [npm registry](https://www.npmjs.com/package/ruvnet-brain). Only moves when the installer script itself changes β rare.
|
|
22
|
-
> - **Brain Release** (the downloadable
|
|
23
|
-
> - **
|
|
24
|
-
>
|
|
25
|
-
> -
|
|
22
|
+
> - **Brain Release** (the downloadable knowledge bundle, linked from the "download" badge above) β always resolves to [`releases/latest`](https://github.com/stuinfla/ruvnet-brain/releases/latest) (the nightly publishes fresh bundles as the corpus grows). Only moves when the underlying knowledge base is rebuilt β separate again from the two above.
|
|
23
|
+
> - **On an old version? One line makes you current β and, with `--auto`, keeps you current forever:**
|
|
24
|
+
> ```
|
|
25
|
+
> npx ruvnet-brain@latest --update --auto
|
|
26
|
+
> ```
|
|
27
|
+
> `--update` pulls the latest plugin + knowledge (backs up first, re-verifies, fails loud instead of half-applying). Adding `--auto` enrolls you in **Evergreen** β the brain keeps itself up to date from then on, so you never run this again. Drop `--auto` for a one-time update: `npx ruvnet-brain@latest --update`.
|
|
28
|
+
> - **Turn Evergreen off any time:** `npx ruvnet-brain --disable-nightly` (Linux/Windows get the cron line documented in the bundle's `forge-update.mjs`).
|
|
29
|
+
> - **Your copy only advances when a new Release is published** β the updater pulls `releases/latest`, so running it between releases is a safe no-op.
|
|
26
30
|
|
|
27
31
|
<sub>Built by **[Stuart Kerr](https://isovision.ai)** at [Isovision.ai](https://isovision.ai) Β· free & fair use, to help everyone leverage the high end of agentic coding.</sub>
|
|
28
32
|
|
|
@@ -36,7 +40,57 @@
|
|
|
36
40
|
|
|
37
41
|
---
|
|
38
42
|
|
|
39
|
-
## What's new in
|
|
43
|
+
## What's new in 3.4 β the invisible work, made visible (and readable)
|
|
44
|
+
|
|
45
|
+
**Shipped 2026-07-17.** 3.3 made every card lead with a point. Then Stuart looked at the two pages meant to *teach* the stack and scored them 55/100: the graphics were mediocre, the one page that should show how the pieces fit was a wall of text, and two diagrams were literally unreadable. 3.4 is the fix β the harness's invisible work, finally drawn, and drawn so you can actually read it.
|
|
46
|
+
|
|
47
|
+
- **The animation that shows the whole argument** β one prompt, sent two ways. Plain Claude Code: a bare wire to one expensive model, no grounding, no memory, no gates. The same model **wrapped in the harness**: it grounds the prompt, routes it to the cheapest model that can do the job, hands back what your project already decided, **inspects the write and can refuse it**, and checks it on the way out. The left lane finishes first β and that's the problem.
|
|
48
|
+
- **MetaHarness, as a picture** β the old card was rectangles with words in them. It's now the thesis at a glance: the model a **frozen** cyan crystal that never moves, seven policy surfaces evolving in a warm orbit around it, the kept branch merging back and the pruned one dying mid-air. *Freeze the model, evolve the harness.* Every number traces to an accepted ADR (28.5% cheaper at 98.1% bar-compliance β ADR-073/075/076).
|
|
49
|
+
- **Diagrams you can actually read** β two of them shipped with labels rendering at **8px and 3px**: present, un-clipped, and invisible. An SVG sized in one coordinate system, crushed into a narrower column, silently shrinks its own text and no error ever fires. There's now a gate for exactly that (`scripts/check-legibility.mjs`) β it measures the *effective* pixel size in the live page, proven to catch the known-bad case before it was trusted. Every diagram label now clears 12px on a phone.
|
|
50
|
+
- **The tips page has a door** β its only link wore the same style as a status readout beside it, and on a phone was hidden entirely β the page was **unreachable under 640px**. There's now an unmissable button where it belongs.
|
|
51
|
+
- **Real generated imagery** β three commissioned stills built from the console's own palette, so they belong to the page instead of sitting on top of it.
|
|
52
|
+
|
|
53
|
+
<details>
|
|
54
|
+
<summary><b>Earlier — what 3.3 proved</b> · it stopped reciting facts and started making a point. <i>Expand for the receipts.</i></summary>
|
|
55
|
+
|
|
56
|
+
## 3.3 β it stopped reciting facts and started making a point
|
|
57
|
+
|
|
58
|
+
**Shipped 2026-07-17.** 3.2 made the stack visible. Visible turned out not to be the same as *useful*: the console could tell you 21 things and leave you no smarter. Stuart, looking at his own product: *"I have no idea what message it's supposed to tell meβ¦ it seems to be facts without purpose."* 3.3 is the answer to that β every card now leads with a **verdict**, not a census.
|
|
59
|
+
|
|
60
|
+
- **A second page β [how to actually use this](console/tips.html)** β the question nobody could answer: *there are plugins, there are commands, and there's justβ¦ typing. Which am I supposed to use?* One page, three depths, and the first depth is **doing nothing**. Built on rUv's own doctrine: *"I don't have to load 350 skills that 99% of which I never use β the system is smart enough to know which one this task needs."*
|
|
61
|
+
- **MetaHarness, explained where you decide** β the card asked you to switch on a word you'd never seen. It now shows the architecture instead: the model **frozen** at the centre, seven policy surfaces evolving around it, four pillars underneath. rUv called this exact risk in ADR-076 (*"'Meta-harness' is a newer termβ¦ must define it in the first screen so it doesn't read as jargon"*); we'd shipped the jargon and skipped the mitigation. Every element traces to an accepted ADR.
|
|
62
|
+
- **The wiring card was lying to you** β it warned about 21 npx call sites. 18 were inside *clones of rUv's own repos* (one a `tests/init-test` fixture), and two were `echo` strings printing advice. Real exposure: **zero**. It now says so: *"Every rUv tool here resolves to one known version."* A card that shows amber for a problem you don't have is worse than no card.
|
|
63
|
+
- **The money leads** β the router card buried *$15.17 saved* as the dimmest text on the row while `FRONTIER β 0` read like missing data. It's the punchline: the expensive model never had to fire. Now it says that, in that order.
|
|
64
|
+
- **What caught Claude** β a new card. 21 gates read every move before it lands; 6 can stop one. The walls now record what they *catch*, not just what they pass β because the ledger held 13 receipts, 13 passing, while the design wall had refused a commit minutes earlier and written nothing down.
|
|
65
|
+
- **The console loads in 0.2s** (was ~14s) β a fleet-wide scan of 107 memory stores sat on the critical path of first paint. It hydrates late now.
|
|
66
|
+
|
|
67
|
+
</details>
|
|
68
|
+
|
|
69
|
+
<details>
|
|
70
|
+
<summary><b>Earlier — what 3.2 proved</b> · the invisible stack, made visible. <i>Expand for the receipts.</i></summary>
|
|
71
|
+
|
|
72
|
+
## 3.2 β the invisible stack, made visible
|
|
73
|
+
|
|
74
|
+
**Shipped 2026-07-16.** rUv's tools do their best work invisibly β which meant nobody could see them working, working stale, or working in conflict. The 3.x line makes the machinery visible, and everything it shows you is measured, never projected.
|
|
75
|
+
|
|
76
|
+
- **A living console** β `/rvbc` puts your whole stack on one page: what's installed, how it's wired, what your AI learned. Every warning arrives paired with a one-click, undoable fix, and the page re-checks itself after every change so you always see the *after* state.
|
|
77
|
+
- **Your brain, visible** β a live **Brain Activity** card: every memory stored, every lesson distilled, clickable down to the verbatim task β what-failed β what-works cards from your own AgentDB (ADR-0018).
|
|
78
|
+
- **Receipts, not estimates** β the routing dashboard recomputes from real routing receipts against *your* frontier; subscriptions price at $0; a providers row shows exactly which license pays for what.
|
|
79
|
+
- **No model fact ships from memory** β the 3.0 live-verification wall: every model/version claim checks the live catalog in CI (ADR-0016), and router profiles self-optimize from rUv's bench plus live prices (ADR-0015).
|
|
80
|
+
- **It learns YOU** β a recursive per-user learning loop (ADR-017): patterns from how you actually work, shared across your projects, isolated where they must be.
|
|
81
|
+
|
|
82
|
+

|
|
83
|
+
|
|
84
|
+
**Open it any time with `/rvbc`** (RuvNet Brain Console) β and the first time you load the Brain, it offers to open it for you.
|
|
85
|
+
|
|
86
|
+
> New gate with this release: **narrative versions are tested.** If any public page says "What's new in X" where X isn't the shipping version, CI fails β because this README sat on 2.5 while 3.1 shipped, and nobody's eyes are a gate.
|
|
87
|
+
|
|
88
|
+
</details>
|
|
89
|
+
|
|
90
|
+
<details>
|
|
91
|
+
<summary><b>Earlier — what 2.5 proved</b> · it uses rUv's real tools (a CI gate makes silent hand-rolls impossible), every scheduled job must prove it ran, and no subagent inherits an expensive model by accident. <i>Expand for the receipts.</i></summary>
|
|
92
|
+
|
|
93
|
+
## 2.5 β the receipts
|
|
40
94
|
|
|
41
95
|
**Shipped 2026-07-13. Two hard lessons, both fixed at the root.**
|
|
42
96
|
|
|
@@ -68,10 +122,12 @@ So 2.5.1 makes it a **wall, not advice**: a `PreToolUse` gate that **blocks any
|
|
|
68
122
|
|
|
69
123
|

|
|
70
124
|
|
|
125
|
+
</details>
|
|
126
|
+
|
|
71
127
|
<details>
|
|
72
128
|
<summary><b>Earlier — what 2.0 proved</b> · the release where the brain stopped taking its own word for anything: 32 verified repos, a 120-question fail-closed eval gate, ~90% cheaper per-turn injection, and an 8-dimension evidence-backed scorecard (55 → 83 in two days). <i>Expand for the receipts.</i></summary>
|
|
73
129
|
|
|
74
|
-
###
|
|
130
|
+
### 2.0 β the receipts
|
|
75
131
|
|
|
76
132
|
**2.0 is the release where the brain got bigger β and, more importantly, stopped taking its own word for anything.** Every number below regenerates from an artifact on disk; the claims ledger (`node scripts/claims-verify.mjs`) re-checks the advertised ones in CI:
|
|
77
133
|
|
|
@@ -159,7 +215,7 @@ WITHOUT the brain β drift | WITH RuvNet Brain β grounded
|
|
|
159
215
|
npx ruvnet-brain
|
|
160
216
|
```
|
|
161
217
|
|
|
162
|
-
That single command runs the whole setup, narrating _what it's doing and why_ at each step: it downloads the brain
|
|
218
|
+
That single command runs the whole setup, narrating _what it's doing and why_ at each step: it downloads the brain from the [latest GitHub Release](https://github.com/stuinfla/ruvnet-brain/releases/latest), unpacks it to `~/.cache/ruvnet-brain/kb`, installs its local reader (no cloud calls, no API keys), and wires the Claude Code plugin β the `search_ruvnet` MCP tool + the `UserPromptSubmit` grounding hook β at user scope. The installer and the `search_ruvnet` tool run on **macOS, Linux, and Windows**; the grounding/enforcement **hooks are POSIX shell**, so they fire on macOS, Linux, and Windows-via-WSL/Git-Bash (on native Windows without WSL the search tool still works, but the auto-grounding hooks don't fire β a Node port of the hooks is on the roadmap). It's safe to re-run, and the brain itself always fetches the current Release regardless of which install path you use β you install once; you don't keep re-downloading.
|
|
163
219
|
|
|
164
220
|
> Want the bleeding-edge installer, even ahead of the last npm publish? `npx github:stuinfla/ruvnet-brain` always runs straight off the latest GitHub commit.
|
|
165
221
|
|
|
@@ -186,9 +242,12 @@ Registers the `search_ruvnet` MCP tool, the grounding skill, and the `UserPrompt
|
|
|
186
242
|
|
|
187
243
|
You install once. After that, three mechanisms keep you on the current brain without you having to remember an update command.
|
|
188
244
|
|
|
189
|
-
- **Consent-gated auto-update heartbeat** (the `SessionStart` hook, `plugin/scripts/session-start.sh`). The **first** time the plugin runs on a machine it asks you **once** whether it may keep itself updated in the background β a security-conscious opt-in, because self-update can change the model's own instructions. Your answer is remembered (`~/.cache/ruvnet-brain/.auto-update-pref`) and never asked again. On each session start it does a rate-limited (~15 min) 3s-capped check of the live GitHub `plugin.json`. If a newer plugin version exists **and** you opted in, it downloads it in the background through Claude Code's own trusted marketplace path β but the new version is **staged, not active**: Claude Code only loads plugins at process start, so **this session keeps running the version it started with** until you restart (`claude --continue` brings your conversation right back on the new version). If you declined, it just tells you the command to run. The
|
|
245
|
+
- **Consent-gated auto-update heartbeat** (the `SessionStart` hook, `plugin/scripts/session-start.sh`). The **first** time the plugin runs on a machine it asks you **once** whether it may keep itself updated in the background β a security-conscious opt-in, because self-update can change the model's own instructions. Your answer is remembered (`~/.cache/ruvnet-brain/.auto-update-pref`) and never asked again. On each session start it does a rate-limited (~15 min) 3s-capped check of the live GitHub `plugin.json`. If a newer plugin version exists **and** you opted in, it downloads it in the background through Claude Code's own trusted marketplace path β but the new version is **staged, not active**: Claude Code only loads plugins at process start, so **this session keeps running the version it started with** until you restart (`claude --continue` brings your conversation right back on the new version). If you declined, it just tells you the command to run. The knowledge bundle is handled more conservatively β **detect + notify only**, never auto-applied, because the bundle isn't cryptographically signed yet and applying it would overwrite executable tool files (SEC-0010 #6).
|
|
190
246
|
|
|
191
|
-
- **
|
|
247
|
+
- **Grounding receipt line** (the `UserPromptSubmit` gate, `plugin/scripts/ground-ruvnet.sh`). When the brain engages on a prompt, the answer ends with one dim line stating what it actually did β either it read rUv's real source and **names the file**, or it says plainly that it didn't:
|
|
248
|
+
`π§ RuvNet Brain jumped in Β· cited agentic-flow/docs/adr/ADR-076-reposition-agentic-flow-as-agentic-meta-harness.md Β· v3.2.16`
|
|
249
|
+
`π§ RuvNet Brain jumped in Β· guidance only, no source read Β· v3.2.16`
|
|
250
|
+
An unearned citation is worse than no citation, so the line may only name a path the tools genuinely returned β and on a prompt where nothing fires, it stays silent rather than manufacture a receipt. The version shown is the one **actually loaded in memory** for this session; if a newer one is staged awaiting a restart, the line says so plainly (`β¦ vX staged, restart to load`). So you never have to wonder whether the brain is on, which version is acting, or whether an answer was grounded or guessed.
|
|
192
251
|
|
|
193
252
|
- **Nightly publish β `releases/latest` chain** (`scripts/self-update.mjs --publish`, run by the `deploy/com.ruvnet.brain-nightly.plist` LaunchAgent at 03:15). The nightly rebuilds only the repos whose upstream changed, and **if anything was rebuilt** it bumps the product version, cuts a GitHub Release, and advances [`releases/latest`](https://github.com/stuinfla/ruvnet-brain/releases/latest). Plugin and knowledge bundle move under **one** version number, so the heartbeat above picks up both automatically. (The LaunchAgent is not auto-installed β enabling a system scheduler needs explicit owner approval.)
|
|
194
253
|
|
|
@@ -212,7 +271,7 @@ Plus: the **βtake the wheelβ behavioral pipeline** (below), a **4-level beha
|
|
|
212
271
|
|
|
213
272
|
## How it works
|
|
214
273
|
|
|
215
|
-
The expensive work happens **once, at build time**: every covered repo is deep-walked (whole files, full function bodies, plus a symbol index), embedded into **two** vector variants (MiniLM-384 for edge/portability, bge-768 for depth) stored on-disk in **RVF / HNSW**, and distilled into a concepts + capability layer of per-repo primers and cards. That's **
|
|
274
|
+
The expensive work happens **once, at build time**: every covered repo is deep-walked (whole files, full function bodies, plus a symbol index), embedded into **two** vector variants (MiniLM-384 for edge/portability, bge-768 for depth) stored on-disk in **RVF / HNSW**, and distilled into a concepts + capability layer of per-repo primers and cards. That's **132,131 source chunks**. At **query time**, `search_ruvnet` searches every repo's store at once, pools the hits, and runs them through **one cross-encoder rerank** on a common scale β so the truly relevant file wins regardless of which repo it lives in β then returns whole source files, each labeled by repo and path.
|
|
216
275
|
|
|
217
276
|

|
|
218
277
|
|
|
@@ -260,7 +319,7 @@ The brain answers **both** kinds of questions. **Name the repo or ask something
|
|
|
260
319
|
| [`ruv-fann`](https://github.com/ruvnet/ruv-FANN) | Fast neural nets (Rust/WASM) + ruv-swarm | [`daa`](https://github.com/ruvnet/daa) | Decentralized autonomous agents |
|
|
261
320
|
| [`synthlang`](https://github.com/ruvnet/synthlang) | Prompt compression (~75% token cut) | [`rupixel`](https://github.com/ruvnet/rupixel) | On-device visual embeddings |
|
|
262
321
|
| [`dspy.ts`](https://github.com/ruvnet/dspy.ts) | DSPy-style programmable LLM pipelines in TS | [`fact`](https://github.com/ruvnet/fact) | Fast-Access Cached Tools + circuit breaker |
|
|
263
|
-
| [`cve-bench`](https://github.com/ruvnet/cve-bench) | Security-fix benchmark | [`
|
|
322
|
+
| [`cve-bench`](https://github.com/ruvnet/cve-bench) | Security-fix benchmark | [`metaharness`](https://github.com/ruvnet/metaharness) | Harness scaffolding / metaharness |
|
|
264
323
|
| [`rvm`](https://github.com/ruvnet/rvm) | Proof-gated capability microhypervisor | [`rUv-dev`](https://github.com/ruvnet/rUv-dev) Β· [`open-claude-code`](https://github.com/ruvnet/open-claude-code) | Dev workflow + agent tooling |
|
|
265
324
|
|
|
266
325
|
> **Not in the public brain:** rUv's private **Cognitum One** repos (seed, v0-appliance, platform-docs) are fenced out of the download by design β verified zero-leak in every build. **Helix** (rUv's local-first health app) is a finished product, not a building block, so it's out too.
|
|
@@ -284,7 +343,7 @@ node plugin/test/run-tests.mjs # full plugin QA over real JSO
|
|
|
284
343
|
| **Context-scenario routing** | **7 / 8 (88%)** | full-scenario prompts route correctly |
|
|
285
344
|
| **L1βL4 behavioral harness** | **all pass** | route Β· deep-recall (returns _code_) Β· implement (cites the API) Β· orchestrate (the hook drives the full pipeline) |
|
|
286
345
|
| **Plugin QA** | **26 / 26** | manifests, hook firing, MCP `initialize`/`tools/list`, capability battery |
|
|
287
|
-
| **Clean-room install** | **3 / 3** | download the published
|
|
346
|
+
| **Clean-room install** | **3 / 3** | download the published bundle fresh β unzip β query β grounded, cited answers |
|
|
288
347
|
| **Unit tests** | **257 passing** Β· 10% of ALL source covered | `npm run test:cov` β the floor fails CI if it slips. 10% is the honest number over every shipped file; the previous "75%" measured a hand-picked 8-file subset |
|
|
289
348
|
| **Grounding proof** | `npx ruvnet-brain --doctor` | asks a real question, then checks the cited path really exists in the on-disk store; a citation that doesn't resolve is reported as **NOT grounded** |
|
|
290
349
|
| **Held-out eval** | **grounded 100/100** Β· routed 63/80 | `npm run eval` β 120 frozen, hash-pinned questions across 5 strata, never used for tuning, graded on ground truth, never by a model |
|
|
@@ -322,7 +381,7 @@ node forge-ask-all.mjs --dir . --q "How does RuVector implement HNSW vector sear
|
|
|
322
381
|
|
|
323
382
|
This project versions in the open (see the live badge up top for the exact plugin version; the downloadable knowledge bundle is a separate track) β we don't claim βdone,β βcomplete,β or βzero hallucinations.β Where it stands:
|
|
324
383
|
|
|
325
|
-
- β
**The grounding brain is real and proven** β 36 repos,
|
|
384
|
+
- β
**The grounding brain is real and proven** β 36 repos, 132,131 chunks, dual embeddings, cross-encoder rerank, plugin (MCP tool + enforcement hook + skill), all re-runnable.
|
|
326
385
|
- β
**Code-level depth** β the code-rich repos are indexed to full function bodies; βhow is it implemented?β returns the implementation. Verified in the shipped bundle (clean-room 3/3).
|
|
327
386
|
- β
**Routing holds** β named 47/48, described 26/28, scenario 7/8; behavioral L1βL4 all pass; private stores fenced out of the public bundle (zero-leak verified).
|
|
328
387
|
- β οΈ **Two routing residuals** (above) β surfaced, not hidden.
|
|
@@ -340,7 +399,7 @@ This project versions in the open (see the live badge up top for the exact plugi
|
|
|
340
399
|
- `explainer/` β the source of the [live explainer](https://isovision.ai/ruvnet-brain/).
|
|
341
400
|
- `SPEC.md` Β· `PROGRESS.md` β the master spec and the living, timestamped build log.
|
|
342
401
|
|
|
343
|
-
The brain binaries ship via the [Release](https://github.com/stuinfla/ruvnet-brain/releases/latest), not git β a fresh clone is lightweight; `npx` fetches the
|
|
402
|
+
The brain binaries ship via the [Release](https://github.com/stuinfla/ruvnet-brain/releases/latest), not git β a fresh clone is lightweight; `npx` fetches the full bundle.
|
|
344
403
|
|
|
345
404
|
---
|
|
346
405
|
|
package/bin/install.mjs
CHANGED
|
@@ -68,6 +68,7 @@ const FLAG_DEMO = argv.includes('--demo'); // guided, real (non-fabricated) walk
|
|
|
68
68
|
const FLAG_FEEDBACK = argv.includes('--feedback'); // prefill a GitHub Discussion (version + health, nothing private) and open it
|
|
69
69
|
// ββ freshness flags β invoke/schedule the SELF-UPDATER the bundle already ships (kb/forge-update.mjs) ββ
|
|
70
70
|
const FLAG_UPDATE = argv.includes('--update'); // one-shot: pull the latest Release bundle into the installed brain now
|
|
71
|
+
const FLAG_AUTO = argv.includes('--auto'); // with --update: also enroll in Evergreen auto-update, so it's never run by hand again
|
|
71
72
|
const FLAG_ENABLE_NIGHTLY = argv.includes('--enable-nightly'); // schedule that update nightly (macOS LaunchAgent)
|
|
72
73
|
const FLAG_DISABLE_NIGHTLY = argv.includes('--disable-nightly'); // remove the nightly schedule
|
|
73
74
|
const FLAG_NO_NIGHTLY_PROMPT = argv.includes('--no-nightly-prompt'); // don't offer nightly auto-updates at the end of an install
|
|
@@ -78,6 +79,8 @@ const FLAG_WITH_STACK = argv.includes('--with-stack'); // add missing Ruflo/RuVe
|
|
|
78
79
|
const FLAG_NO_STACK = argv.includes('--no-stack'); // skip the toolkit offer entirely
|
|
79
80
|
const FLAG_ENHANCE_CLAUDE_MD = argv.includes('--enhance-claude-md'); // add the CLAUDE.md section without prompting
|
|
80
81
|
const FLAG_NO_ENHANCE = argv.includes('--no-enhance'); // skip the CLAUDE.md offer entirely
|
|
82
|
+
const FLAG_STATUSLINE = argv.includes('--statusline'); // opt in to the status-bar version segment, non-interactively
|
|
83
|
+
const FLAG_NO_STATUSLINE = argv.includes('--no-statusline'); // decline the status-bar offer without prompting
|
|
81
84
|
// --version <tag> forces a specific Release tag (e.g. --version v0.5.0-dev)
|
|
82
85
|
const versionIdx = argv.indexOf('--version');
|
|
83
86
|
const FORCED_VERSION =
|
|
@@ -904,19 +907,34 @@ function runUpdate() {
|
|
|
904
907
|
printBanner('update');
|
|
905
908
|
const kbDir = resolvedKbDir();
|
|
906
909
|
info(`brain dir: ${c.bold(kbDir)}`);
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
+
let updateStatus = 1;
|
|
911
|
+
if (fs.existsSync(path.join(kbDir, 'forge-update.mjs'))) {
|
|
912
|
+
info(c.dim("running the bundle's own self-updater (backs up first, re-verifies, never half-applies)β¦\n"));
|
|
913
|
+
// Relative filename + matching cwd β same launch convention as smokeQuery(); stdio:'inherit'
|
|
914
|
+
// streams the updater's narration live and unedited.
|
|
915
|
+
const r = spawnSync(process.execPath, ['forge-update.mjs', '--apply'], { cwd: kbDir, stdio: 'inherit' });
|
|
916
|
+
updateStatus = r.error ? 1 : (r.status === null ? 1 : r.status);
|
|
910
917
|
}
|
|
911
|
-
|
|
912
|
-
//
|
|
913
|
-
//
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
+
// FALLBACK (2026-07-17). The bundle's self-updater is missing OR failed β e.g. an OLDER bundle whose
|
|
919
|
+
// canonicalManifestUrl points at the dead main/kb/.last-built.json path and 404s (the exact break a
|
|
920
|
+
// real user, Jan Lafko, hit). NEVER leave the user stranded at a 404: re-run THIS installer as a
|
|
921
|
+
// fresh install, which pulls the latest Release DIRECTLY (releases/latest) and never touches the
|
|
922
|
+
// manifest β so --update always succeeds and self-heals the stale SOURCE.json in one shot.
|
|
923
|
+
if (updateStatus !== 0 && !process.env.RUVNET_BRAIN_NO_UPDATE_FALLBACK) {
|
|
924
|
+
warn("\nthe bundle's own updater couldn't complete β falling back to a fresh install of the latest Release (this always works)β¦\n");
|
|
925
|
+
const self = fileURLToPath(import.meta.url);
|
|
926
|
+
const fr = spawnSync(process.execPath, [self, '--force'], { stdio: 'inherit',
|
|
927
|
+
env: { ...process.env, RUVNET_BRAIN_NO_UPDATE_FALLBACK: '1' } });
|
|
928
|
+
updateStatus = fr.error ? 1 : (fr.status === null ? 1 : fr.status);
|
|
918
929
|
}
|
|
919
|
-
|
|
930
|
+
// `--update --auto` = update now AND enroll in Evergreen, so this is the LAST time it's ever run by
|
|
931
|
+
// hand. Only enroll if the update itself succeeded β never promise "you're set forever" on a failed
|
|
932
|
+
// update. enableNightly() prints its own real verification (plist path + launchctl result).
|
|
933
|
+
if (updateStatus === 0 && FLAG_AUTO) {
|
|
934
|
+
info(c.dim("\n--auto set: enrolling in Evergreen auto-update so you never run this againβ¦\n"));
|
|
935
|
+
enableNightly(); // exits on its own with verified output; if it returns, fall through to the update verdict
|
|
936
|
+
}
|
|
937
|
+
process.exit(updateStatus); // exit with the updater's own verdict
|
|
920
938
|
}
|
|
921
939
|
|
|
922
940
|
function enableNightly() {
|
|
@@ -1532,6 +1550,180 @@ async function offerClaudeMd() {
|
|
|
1532
1550
|
}
|
|
1533
1551
|
}
|
|
1534
1552
|
|
|
1553
|
+
// ββ step: OPT-IN status-bar version segment β "which RuvNet Brain am I on?" at a glance βββββββββββ
|
|
1554
|
+
// Claude Code renders its status bar from `statusLine.command` in ~/.claude/settings.json (verified
|
|
1555
|
+
// live: code.claude.com/docs/en/statusline β `{ type: "command", command: "<script>", padding? }`;
|
|
1556
|
+
// Claude Code pipes JSON session data to the command's stdin, and whatever it prints to stdout
|
|
1557
|
+
// becomes the line). Users often already have a rich statusline (git branch, context %, other tool
|
|
1558
|
+
// versions) β silently replacing it would be exactly the clobber this installer refuses to do
|
|
1559
|
+
// anywhere else. So: detect first, and only ADD a statusLine when NONE exists. If one is already
|
|
1560
|
+
// wired, settings.json is never touched β we hand back the helper's path so it can be folded into
|
|
1561
|
+
// their own script instead. Asked once ever (pref file, same contract as .telemetry-consent): a
|
|
1562
|
+
// "no", or a silent non-interactive run with no explicit flag, never gets re-asked or half-applied.
|
|
1563
|
+
// .cjs (CommonJS), not .mjs: measured live on this machine (Node 22) β the ESM loader costs the
|
|
1564
|
+
// statusline ~4-5ms extra per invocation versus a plain `require()` script (median 50.4ms vs
|
|
1565
|
+
// 46.7ms across 30 interleaved runs of otherwise-identical logic), which is the difference between
|
|
1566
|
+
// sitting under the <50ms budget and blowing past it on every single prompt. `.cjs` forces
|
|
1567
|
+
// CommonJS unambiguously regardless of any stray package.json a user's HOME might contain.
|
|
1568
|
+
const STATUSLINE_HELPER_NAME = 'ruvnet-brain-statusline.cjs';
|
|
1569
|
+
const statuslineHelperPath = () => path.join(telemetryStateDir(), STATUSLINE_HELPER_NAME);
|
|
1570
|
+
const statuslinePrefPath = () => path.join(telemetryStateDir(), '.statusline-pref');
|
|
1571
|
+
const settingsJsonPath = () => path.join(os.homedir(), '.claude', 'settings.json');
|
|
1572
|
+
|
|
1573
|
+
// Self-contained, dependency-free script Claude Code's statusLine command runs on EVERY prompt β it
|
|
1574
|
+
// must stay well under budget, so it's a single sync read with no imports beyond node builtins.
|
|
1575
|
+
// Reads the version LIVE from the installed bundle's SOURCE.json (releaseTag) β never hardcoded, so
|
|
1576
|
+
// it always matches whatever is actually on disk. Degrades to silent empty output on ANY failure
|
|
1577
|
+
// (brain not installed, file unreadable, malformed JSON) β a missing/broken brain must never break
|
|
1578
|
+
// the rest of the user's statusline.
|
|
1579
|
+
const STATUSLINE_HELPER_SRC = `#!/usr/bin/env node
|
|
1580
|
+
// ruvnet-brain-statusline.cjs β one segment for Claude Code's statusLine command.
|
|
1581
|
+
// See https://code.claude.com/docs/en/statusline. Written by the installer's --statusline offer.
|
|
1582
|
+
// Prints "RuvNet Brain v<version>" read LIVE from the installed bundle's SOURCE.json β never
|
|
1583
|
+
// hardcoded. Any failure (brain missing, unreadable, malformed JSON) degrades to empty output so
|
|
1584
|
+
// a broken or missing brain can never break the rest of the status line. CommonJS on purpose β
|
|
1585
|
+
// measurably faster to start than ESM for a script this runs on every single prompt.
|
|
1586
|
+
const fs = require('fs');
|
|
1587
|
+
const os = require('os');
|
|
1588
|
+
const path = require('path');
|
|
1589
|
+
|
|
1590
|
+
try {
|
|
1591
|
+
const kbDir = process.env.RUVNET_BRAIN_KB || path.join(os.homedir(), '.cache', 'ruvnet-brain', 'kb');
|
|
1592
|
+
const j = JSON.parse(fs.readFileSync(path.join(kbDir, 'SOURCE.json'), 'utf8'));
|
|
1593
|
+
const raw = String(j.releaseTag || '');
|
|
1594
|
+
if (/^[A-Za-z0-9._-]{1,32}$/.test(raw)) {
|
|
1595
|
+
process.stdout.write('RuvNet Brain ' + (raw.startsWith('v') ? raw : 'v' + raw));
|
|
1596
|
+
}
|
|
1597
|
+
} catch { /* not installed / unreadable β print nothing, never break the statusline */ }
|
|
1598
|
+
`;
|
|
1599
|
+
|
|
1600
|
+
function writeStatuslineHelper() {
|
|
1601
|
+
const dst = statuslineHelperPath();
|
|
1602
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
1603
|
+
fs.writeFileSync(dst, STATUSLINE_HELPER_SRC, { mode: 0o755 });
|
|
1604
|
+
return dst;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
// Pure read β NEVER writes. Exported so detection is independently testable
|
|
1608
|
+
// (RUVNET_BRAIN_IMPORT_ONLY=1) with zero risk of mutating a real settings.json.
|
|
1609
|
+
export function detectStatusLine(settingsPath = settingsJsonPath()) {
|
|
1610
|
+
let raw;
|
|
1611
|
+
try {
|
|
1612
|
+
raw = fs.readFileSync(settingsPath, 'utf8');
|
|
1613
|
+
} catch {
|
|
1614
|
+
return { path: settingsPath, exists: false, hasStatusLine: false, command: null, parseError: false, json: {} };
|
|
1615
|
+
}
|
|
1616
|
+
try {
|
|
1617
|
+
const j = raw.trim() ? JSON.parse(raw) : {};
|
|
1618
|
+
const sl = j && typeof j === 'object' ? j.statusLine : null;
|
|
1619
|
+
const command = sl && typeof sl.command === 'string' ? sl.command : null;
|
|
1620
|
+
return { path: settingsPath, exists: true, hasStatusLine: Boolean(command), command, parseError: false, json: j };
|
|
1621
|
+
} catch {
|
|
1622
|
+
return { path: settingsPath, exists: true, hasStatusLine: false, command: null, parseError: true, json: null };
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// Same `.bak-<ISO timestamp>` convention used elsewhere in this project (see onboarding-console.mjs's
|
|
1627
|
+
// config backup) β always taken before an EXISTING file is touched, never before a brand-new one.
|
|
1628
|
+
function backupSettingsJson(settingsPath) {
|
|
1629
|
+
const backup = `${settingsPath}.bak-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
1630
|
+
fs.copyFileSync(settingsPath, backup);
|
|
1631
|
+
return backup;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
function writeSettingsStatusLine(detected, command) {
|
|
1635
|
+
const backup = detected.exists ? backupSettingsJson(detected.path) : null;
|
|
1636
|
+
const next = { ...(detected.json || {}), statusLine: { type: 'command', command } };
|
|
1637
|
+
fs.mkdirSync(path.dirname(detected.path), { recursive: true });
|
|
1638
|
+
fs.writeFileSync(detected.path, JSON.stringify(next, null, 2) + '\n');
|
|
1639
|
+
return backup;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// Only called after explicit consent. NEVER overwrites an existing statusLine β detectStatusLine()
|
|
1643
|
+
// is the single source of truth for "is one already there", checked fresh right before any write.
|
|
1644
|
+
function applyStatusline() {
|
|
1645
|
+
const helperPath = writeStatuslineHelper();
|
|
1646
|
+
ok(`installed the version-segment script β ${c.bold(helperPath)}`);
|
|
1647
|
+
const command = `node "${helperPath}"`;
|
|
1648
|
+
|
|
1649
|
+
const detected = detectStatusLine();
|
|
1650
|
+
|
|
1651
|
+
if (detected.parseError) {
|
|
1652
|
+
warn(`${detected.path} isn't valid JSON β leaving it untouched (never merging into a file I can't parse).`);
|
|
1653
|
+
info(`Add this to it yourself once it's fixed:`);
|
|
1654
|
+
info(` ${c.bold(`"statusLine": { "type": "command", "command": "${command}" }`)}`);
|
|
1655
|
+
return 'manual-parse-error';
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
if (detected.hasStatusLine) {
|
|
1659
|
+
ok(`you already have a status line (${c.dim(detected.command)}) β leaving it exactly as is.`);
|
|
1660
|
+
info(`To fold the brain version in, have your own script also run this and print the result:`);
|
|
1661
|
+
info(` ${c.bold(command)}`);
|
|
1662
|
+
return 'existing-preserved';
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
try {
|
|
1666
|
+
const backup = writeSettingsStatusLine(detected, command);
|
|
1667
|
+
if (backup) ok(`backed up your settings to ${c.bold(backup)} before editing`);
|
|
1668
|
+
ok(`status line set in ${c.bold(detected.path)} β restart Claude Code to see it`);
|
|
1669
|
+
return 'set';
|
|
1670
|
+
} catch (e) {
|
|
1671
|
+
warn(`couldn't write ${detected.path} (${e.message}) β add it yourself:`);
|
|
1672
|
+
info(` ${c.bold(`"statusLine": { "type": "command", "command": "${command}" }`)}`);
|
|
1673
|
+
return 'write-error';
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// Exported (testable under RUVNET_BRAIN_IMPORT_ONLY=1, like offerTelemetry). Never throws β the
|
|
1678
|
+
// caller also guards, because a finished install must never be broken by an optional offer.
|
|
1679
|
+
export async function offerStatusline() {
|
|
1680
|
+
if (TEST_MODE) return 'suppressed'; // tests: never prompt, never write, never touch settings.json
|
|
1681
|
+
const prefPath = statuslinePrefPath();
|
|
1682
|
+
if (fs.existsSync(prefPath)) {
|
|
1683
|
+
// asked once ever β respect the answer, never re-ask. (Delete the file to be asked again.)
|
|
1684
|
+
let pref = '';
|
|
1685
|
+
try { pref = fs.readFileSync(prefPath, 'utf8').trim().toLowerCase(); } catch { /* treat as declined */ }
|
|
1686
|
+
return pref === 'yes' ? 'already-on' : 'already-set';
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
if (FLAG_NO_STATUSLINE) {
|
|
1690
|
+
try { fs.mkdirSync(telemetryStateDir(), { recursive: true }); fs.writeFileSync(prefPath, 'no\n'); } catch { /* best-effort */ }
|
|
1691
|
+
return 'declined-flag';
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
step(
|
|
1695
|
+
'Optional: show the brain version in your status bar',
|
|
1696
|
+
"so you can always tell at a glance which RuvNet Brain version you're on",
|
|
1697
|
+
);
|
|
1698
|
+
info(`Adds a small ${c.bold('"RuvNet Brain vX.Y.Z"')} segment, read live from your installed brain β it`);
|
|
1699
|
+
info(`updates itself the moment the brain updates. ${c.bold('Never overwrites an existing status line.')}`);
|
|
1700
|
+
|
|
1701
|
+
const interactive = process.stdin.isTTY || FLAG_YES || FLAG_STATUSLINE;
|
|
1702
|
+
if (!interactive) {
|
|
1703
|
+
// No terminal to ask on, and no explicit flag either β skip WITHOUT recording an answer, so a
|
|
1704
|
+
// future interactive (or flagged) run still gets a real chance to ask.
|
|
1705
|
+
info(`No interactive terminal here, so I won't assume β skipping for now.`);
|
|
1706
|
+
info(`Add it any time: ${c.bold('npx ruvnet-brain --statusline')}`);
|
|
1707
|
+
return 'not-asked';
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
const yes = FLAG_STATUSLINE || (await ask('Add a RuvNet Brain version segment to your Claude Code status bar?', false));
|
|
1711
|
+
|
|
1712
|
+
try {
|
|
1713
|
+
fs.mkdirSync(telemetryStateDir(), { recursive: true });
|
|
1714
|
+
fs.writeFileSync(prefPath, yes ? 'yes\n' : 'no\n');
|
|
1715
|
+
} catch (e) {
|
|
1716
|
+
warn(`couldn't record the answer (${e.message})`);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
if (!yes) {
|
|
1720
|
+
info(`No problem β add it any time: ${c.bold('npx ruvnet-brain --statusline')}`);
|
|
1721
|
+
return 'declined';
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
return applyStatusline();
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1535
1727
|
// ββ final success block ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
1536
1728
|
function success({ cacheDir, isCustom, plugin, env, nightly }) {
|
|
1537
1729
|
const line = 'β'.repeat(64);
|
|
@@ -1646,6 +1838,10 @@ Usage:
|
|
|
1646
1838
|
node bin/install.mjs --no-stack Don't offer to add Ruflo / RuVector
|
|
1647
1839
|
node bin/install.mjs --enhance-claude-md Add a RuvNet-Brain section to ~/.claude/CLAUDE.md (no prompt)
|
|
1648
1840
|
node bin/install.mjs --no-enhance Don't offer the CLAUDE.md section
|
|
1841
|
+
node bin/install.mjs --statusline Add a "RuvNet Brain vX.Y.Z" segment to your Claude Code
|
|
1842
|
+
status bar (no prompt). Never overwrites an existing status line β if
|
|
1843
|
+
you already have one, this only prints a snippet to fold it into yours.
|
|
1844
|
+
node bin/install.mjs --no-statusline Don't offer the status-bar segment
|
|
1649
1845
|
node bin/install.mjs --yes, -y Accept every optional offer (good for scripted installs)
|
|
1650
1846
|
|
|
1651
1847
|
Env:
|
|
@@ -1755,6 +1951,9 @@ It is safe to re-run at any time. After installing, restart Claude Code so the g
|
|
|
1755
1951
|
// Anonymous usage counts β OPT-IN, asked once ever, right after the nightly offer. Same rule:
|
|
1756
1952
|
// an optional offer can never break a finished install.
|
|
1757
1953
|
try { await offerTelemetry(cacheDir); } catch { /* fail-private: unanswered = OFF */ }
|
|
1954
|
+
// Status-bar version segment β OPT-IN, asked once ever, never clobbers an existing statusline.
|
|
1955
|
+
// Same rule: an optional offer can never break a finished install.
|
|
1956
|
+
try { await offerStatusline(); } catch { /* non-fatal β a status-bar nicety must never break the install */ }
|
|
1758
1957
|
|
|
1759
1958
|
success({ cacheDir, isCustom, plugin, env, nightly });
|
|
1760
1959
|
})().catch((e) => {
|
|
@@ -88,6 +88,26 @@
|
|
|
88
88
|
"schedule": "daily 09:00",
|
|
89
89
|
"maxAgeHours": 26,
|
|
90
90
|
"required": true
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"label": "com.ruvnet.issue-watch",
|
|
94
|
+
"what": "Hourly GitHub-issues SLA watcher (stuinfla/ruvnet-brain) β pages ntfy when an open issue sits >4h with no comment from the repo owner",
|
|
95
|
+
"schedule": "hourly",
|
|
96
|
+
"maxAgeHours": 3,
|
|
97
|
+
"required": true
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"label": "com.ruvnet.issue-fix",
|
|
101
|
+
"what": "Every 10 min: auto-fixes newly opened GitHub issues (stuinfla/ruvnet-brain) β bounded headless `claude -p` per issue in a disposable git worktree, pushes an issue-fix/<N> branch + comment or posts an honest triage comment, never touches main, never closes an issue",
|
|
102
|
+
"schedule": "every 10 min",
|
|
103
|
+
"maxAgeHours": 1,
|
|
104
|
+
"required": true
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"label": "com.ruvnet.routing-flywheel",
|
|
108
|
+
"schedule": "nightly 04:45",
|
|
109
|
+
"maxAgeHours": 26,
|
|
110
|
+
"what": "nightly bounded live flywheel over routing policy (candidate+receipt only, never live routing)"
|
|
91
111
|
}
|
|
92
112
|
]
|
|
93
113
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvnet-brain",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.4.6-dev",
|
|
4
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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,10 @@
|
|
|
28
28
|
"gists:sync": "node scripts/ingest-gists.mjs && node kb/forge-big.mjs both --dir kb --name ruv-gists",
|
|
29
29
|
"test:integration": "vitest run tests/integration",
|
|
30
30
|
"substitution:check": "node scripts/no-silent-substitution.mjs",
|
|
31
|
-
"
|
|
31
|
+
"catalog:verify": "node scripts/verify-model-catalog.mjs",
|
|
32
|
+
"catalog:refresh": "node scripts/refresh-model-catalog.mjs",
|
|
33
|
+
"falsify": "node scripts/falsify.mjs",
|
|
34
|
+
"sbom": "npx --yes @cyclonedx/cyclonedx-npm --omit dev --output-file sbom/ruvnet-brain.cdx.json --mc-type application --validate"
|
|
32
35
|
},
|
|
33
36
|
"files": [
|
|
34
37
|
"bin/install.mjs",
|
|
@@ -79,6 +82,7 @@
|
|
|
79
82
|
"vitest": "^4.1.10"
|
|
80
83
|
},
|
|
81
84
|
"dependencies": {
|
|
85
|
+
"@metaharness/flywheel": "^0.1.7",
|
|
82
86
|
"@metaharness/router": "^0.3.2"
|
|
83
87
|
}
|
|
84
88
|
}
|
|
@@ -67,9 +67,18 @@ export function effectivePrices(candidates, profile) {
|
|
|
67
67
|
const prices = {};
|
|
68
68
|
for (const c of candidates) {
|
|
69
69
|
const covered = (c.subscription || []).some((h) => profile?.harnesses?.[h]?.subscription === true);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
// Blended $/Mtok β the axis Router minimises. The catalog stores prices as costPerMTok:{in,out}
|
|
71
|
+
// (see ~/.claude/model-router/catalog.json); this function originally only understood a bare
|
|
72
|
+
// number or costIn/costOut, so every {in,out}-priced metered model blended to $0 and the
|
|
73
|
+
// cost-optimal router was choosing over all-zero prices (found 2026-07-16 when the console
|
|
74
|
+
// panel showed DeepSeek as "$0 Β· yours"). All three shapes are handled now; an unpriced
|
|
75
|
+
// candidate is Infinity, never a fake $0 β a missing price must not read as free.
|
|
76
|
+
const p = c.costPerMTok;
|
|
77
|
+
const blended =
|
|
78
|
+
typeof p === 'number' ? p
|
|
79
|
+
: p && typeof p.in === 'number' && typeof p.out === 'number' ? (p.in + p.out) / 2
|
|
80
|
+
: typeof c.costIn === 'number' && typeof c.costOut === 'number' ? (c.costIn + c.costOut) / 2
|
|
81
|
+
: Infinity;
|
|
73
82
|
prices[c.id] = covered ? 0 : blended;
|
|
74
83
|
}
|
|
75
84
|
return prices;
|
package/scripts/route-cheap.mjs
CHANGED
|
@@ -38,7 +38,10 @@ export const PRICING = {
|
|
|
38
38
|
'deepseek/deepseek-v4-flash': { in: 0.077, out: 0.154 }, // verified 2026-07-12 OpenRouter /models live; successor to deepseek-chat (which resolves to legacy V3)
|
|
39
39
|
'x-ai/grok-4.5': { in: 2.0, out: 6.0 }, // verified 2026-07-12 OpenRouter /models live; mid-priced frontier-adjacent
|
|
40
40
|
};
|
|
41
|
-
|
|
41
|
+
// Frontier = the most capable model you'd otherwise reach for. Fable 5 leads the Claude 5 family
|
|
42
|
+
// (2Γ Opus 4.8 per token β see CLAUDE_TIERS below), so it is the honest "instead of" baseline: every
|
|
43
|
+
// $ the cascade saves is measured against what Fable 5 would have cost on the same tokens.
|
|
44
|
+
export const FRONTIER = { name: 'claude-fable-5', in: 10.0, out: 50.0 };
|
|
42
45
|
|
|
43
46
|
// Claude tiers β $/Mtok, verified live from the OpenRouter /models API 2026-07-13.
|
|
44
47
|
// These are NOT routed through here (Claude Code's own Agent/Task tool spawns them). They are priced
|