ruvnet-brain 1.14.0-dev β†’ 2.0.0

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 (3) hide show
  1. package/README.md +56 -14
  2. package/bin/install.mjs +303 -9
  3. package/package.json +10 -4
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # 🧠 RuvNet Brain
6
6
 
7
- ### 🧠 RuvNet Brain β€” [![RuvNet Brain version 1.14.0-dev β€” updated 2026-07-08 07:21 EDT](https://img.shields.io/badge/version_1.14.0--dev-updated_2026--07--08_07:21_EDT-1E90FF?style=for-the-badge&labelColor=0757BA)](https://github.com/stuinfla/ruvnet-brain/blob/main/plugin/.claude-plugin/plugin.json)
7
+ ### 🧠 RuvNet Brain β€” [![RuvNet Brain version 2.0.0 β€” updated 2026-07-10 08:15 EDT](https://img.shields.io/badge/version_2.0.0-updated_2026--07--10_08:15_EDT-1E90FF?style=for-the-badge&labelColor=0757BA)](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
 
@@ -14,12 +14,15 @@
14
14
  [![explainer](https://img.shields.io/badge/β–Ά%20see%20it%20live-isovision.ai%2Fruvnet--brain-e8a13a?style=flat-square)](https://isovision.ai/ruvnet-brain/)
15
15
  [![license](https://img.shields.io/badge/license-MIT-8ecae6?style=flat-square)](LICENSE)
16
16
  [![grounded](https://img.shields.io/badge/answers-cited%20rUv%20source-333?style=flat-square)](#testing--proof)
17
- [![coverage](https://img.shields.io/badge/coverage-75%25%20lines%20Β·%2072%25%20stmts-2e7d32?style=flat-square)](#testing--proof)
17
+ [![coverage](https://img.shields.io/badge/coverage-10%25%20of%20ALL%20source%20Β·%20honest-b58900?style=flat-square)](#testing--proof)
18
18
 
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
22
  > - **Brain Release** (the downloadable 512MB knowledge bundle, linked from the "download" badge above) β€” always resolves to [`releases/latest`](https://github.com/stuinfla/ruvnet-brain/releases/latest), currently `v0.5.0-dev`. Only moves when the underlying knowledge base is rebuilt β€” separate again from the two above.
23
+ > - **Update an installed brain once:** `npx ruvnet-brain --update` β€” runs the bundle's own self-updater (backs up first, re-verifies, fails loud instead of half-applying).
24
+ > - **Nightly auto-update is OFF by default:** `npx ruvnet-brain --enable-nightly` schedules it (macOS LaunchAgent, 03:47); `npx ruvnet-brain --disable-nightly` removes it; Linux/Windows get the cron line documented in the bundle's `forge-update.mjs`.
25
+ > - **Either way, 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.
23
26
 
24
27
  <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>
25
28
 
@@ -33,6 +36,45 @@
33
36
 
34
37
  ---
35
38
 
39
+ ## What's new in 2.0
40
+
41
+ **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:
42
+
43
+ | | v1 (0.x–1.x) | v2.0 |
44
+ |---|---|---|
45
+ | **Corpus** | 24 repos built | **32 repos** built (of 197 live ruvnet repos), each verified by a live retrieval query |
46
+ | **Depth** (flagship `ruvector`) | 18,491 passages Β· **0** full source bodies | **28,018 passages Β· 2,996 full bodies** β€” depth also restored to `agent-harness-generator` (8,896/715), `ruview` (7,434/765), `open-claude-code` (195/69) |
47
+ | **Corpus QA gate** | none | every store must prove *embeds correctly + reads correctly* β€” vector count == passage count, depth floors, a 3-passage self-retrieval round-trip per store β€” **72/72 store-variants PASS**, wired fail-closed into the nightly publish |
48
+ | **Retrieval eval** | 12 frozen questions | **120 frozen, hash-pinned questions** across 5 strata; promotion gated on Wilson lower bounds, fail-closed β€” it blocked a real release this morning, which is the feature working |
49
+ | **Token cost** | 6,183 bytes injected per hook turn Β· zero self-measurement | **684 bytes (~90% cut)** with an eval-PASS proving zero quality loss Β· a live token meter measuring real bytes/tokens per prompt class Β· ~27% faster repeat queries via the KB cache |
50
+ | **rUv's gists** | not indexed | **437 gists** indexed with per-chunk freshness/provenance banners, refreshed nightly with cost-disciplined skip |
51
+ | **Reliability** | claims were prose | **claims ledger** (5 marketing claims mechanically re-verified in CI) Β· integration tests in CI incl. Linux Β· a Windows CI job Β· an honest coverage denominator (all source files) |
52
+ | **Autonomy** | hooks asked questions to an empty room β€” the #1 real-user complaint | **`/loop` contract** β€” checkpoint / resume / done-criteria; hooks detect autonomous mode and stop asking β€” 10 mutation-verified tests |
53
+ | **Publishing** | manual npm token Β· a nightly release PATH bug | **self-renewing npm token** (launchd daemon, proven end-to-end) Β· PATH bug root-caused and cured |
54
+ | **Memory layer** | silent SQLite corruption Β· searches returning 0 Β· invisible flywheel patterns | **3 root-caused fixes** (an ABI-mismatched binary falling back to WAL-blind whole-file overwrites; keys that were never scored; a split-store display bug) β€” plus **6 exact patches queued upstream to ruflo** |
55
+
56
+ The depth jump wasn't tuning β€” it was two pipeline root-causes fixed for good: missing `--full` hints, and a day-one `v2/` skip-dir bug that had made two repos undepthable since the first build.
57
+
58
+ ### The scorecard β€” 8 dimensions, self-scored /100, every deduction evidenced β€” 2026-07-10 (evening re-score)
59
+
60
+ | Dimension | v1 (2026-07-09) | v2.0 (current) | Ξ” | What moved it |
61
+ |---|---:|---:|---:|---|
62
+ | End-user experience | 54 | 83 | +29 | One-command install now offers nightly self-updates (default yes); the page lives on isovision.ai; publishing renews itself |
63
+ | Knowledge corpus | 71 | 88 | +17 | 24β†’32 verified repos; a 72/72 embeds-and-reads QA gate; full source depth restored β€” flagship went 0β†’2,996 source bodies |
64
+ | Effectiveness (eval-proven retrieval) | 58 | 88 | +30 | 120-question Wilson-bound gate β€” it blocked a bad release, then passed the fix *above* the old baseline |
65
+ | Acting like rUv | 38 | 72 | +34 | Memory layer root-caused and fixed with proofs; 6 exact patches queued upstream; real multi-agent swarm operations |
66
+ | Developer smarter | 62 | 84 | +22 | `/brain-score` runs this same scorecard on any repo; honest tool announcements; per-answer source receipts |
67
+ | Token cost efficiency | 41 | 82 | +41 | ~90% smaller per-turn injection, eval-proven free; a live token meter β€” measured, not guessed |
68
+ | Engineering reliability | 61 | 86 | +25 | 312 tests green; a claims ledger re-verifies marketing claims in CI; fail-closed publishing; Windows CI added |
69
+ | Safety & privacy | 74 | 82 | +8 | Private-store fence held under full rebuild; secrets never transit chat; autonomy hard fence |
70
+ | **Overall** | **55** | **83** | **+28** | β€” |
71
+
72
+ These are self-scores under a hard rule: **every deduction requires specific evidence, and a known architectural flaw caps a dimension at ≀70 until the flaw is fixed** β€” *acting like rUv* spent the morning capped for its memory-layer flaw; the flaw was root-caused and fixed with proofs, the cap lifted, and it re-scored 72. Overall **55 β†’ 83 in two days**, each number regenerating from a stored receipt β€” and the same scoring that produced these blocked a release mid-day. **That's why they're credible: scores you can trust beat scores that flatter.**
73
+
74
+ **The honest small print, kept visible:** warm query is ~21 s on the large corpus (it grew with depth; candidate dedup/pruning is the next optimization) Β· the 8 newest repos are findable by name but don't yet have primers/capability cards for described-need routing (coming) Β· the Windows CI job is new and unproven until its first green run.
75
+
76
+ ---
77
+
36
78
  ## The one thing to understand first
37
79
 
38
80
  **Reuven Cohen (rUv) builds about nine months ahead of the state of the art.** His RuvNet building blocks β€” Ruflo, RuVector, AgentDB, agentic-flow, SPARC and ~20 more β€” are the working prototypes of what becomes mainstream AI tooling three quarters later. This is the actual front edge.
@@ -107,7 +149,7 @@ You install once. After that, three mechanisms keep you on the current brain wit
107
149
 
108
150
  - **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 512 MB 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).
109
151
 
110
- - **Stack watchdog status footer** (the `UserPromptSubmit` hook's always-on Gate 0, `plugin/scripts/ground-ruvnet.sh`). Every response ends with one dim status line β€” e.g. `🧠 RuvNet Brain v1.11.0-dev Β· Ruflo: yes Β· AgentDB memory: on` β€” read from **filesystem ground truth**, not impressions. The version shown is always the one **actually loaded in memory** for this session; if a newer version 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 project memory is wired.
152
+ - **Stack watchdog status footer** (the `UserPromptSubmit` hook's always-on Gate 0, `plugin/scripts/ground-ruvnet.sh`). Every response ends with one dim status line β€” e.g. `🧠 RuvNet Brain v2.0.0 Β· Ruflo: yes Β· AgentDB memory: on` β€” read from **filesystem ground truth**, not impressions. The version shown is always the one **actually loaded in memory** for this session; if a newer version 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 project memory is wired.
111
153
 
112
154
  - **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.)
113
155
 
@@ -115,9 +157,9 @@ You install once. After that, three mechanisms keep you on the current brain wit
115
157
 
116
158
  ## ✨ What the knowledge bundle knows β€” the stack _down to the code_
117
159
 
118
- Earlier bundles knew the **docs and architecture**. The current bundle re-indexes the code-rich repos to **full function bodies**, against each repo's real source layout β€” so β€œhow is this actually implemented?” returns the implementation, not a summary:
160
+ Earlier bundles knew only the **docs and architecture**. v0.5 began re-indexing the code-rich repos to **full function bodies**, against each repo's real source layout β€” so β€œhow is this actually implemented?” returns the implementation, not a summary. The table below is that v0.5 depth jump, kept as the before/after receipt; **2.0 went further still** β€” flagship `ruvector` alone now carries 28,018 passages and 2,996 full bodies (see [What's new in 2.0](#whats-new-in-20)):
119
161
 
120
- | Repo | Full-body code passages | |
162
+ | Repo | Full-body code passages (v0.5) | |
121
163
  |---|---:|---|
122
164
  | `agentic-flow` | 296 β†’ **984** | model-routing, ReasoningBank |
123
165
  | `ruv-fann` | 52 β†’ **779** | ruv-swarm, cuda-wasm, neuro-divergent |
@@ -131,7 +173,7 @@ Plus: the **β€œtake the wheel” behavioral pipeline** (below), a **4-level beha
131
173
 
132
174
  ## How it works
133
175
 
134
- 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 **90,842 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.
176
+ 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 **128,994 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.
135
177
 
136
178
  ![RuvNet Brain architecture pipeline](assets/diagrams/architecture-pipeline.svg)
137
179
 
@@ -165,7 +207,7 @@ The brain answers **both** kinds of questions. **Name the repo or ask something
165
207
 
166
208
  ## What it covers
167
209
 
168
- ~21 of rUv's **building-block** repos in the [ruvnet](https://github.com/ruvnet) org β€” the reusable pieces you'd actually compose into a system β€” each deep-walked, embedded in both variants, symbol-indexed, and given a capability card.
210
+ 32 of rUv's repos in the [ruvnet](https://github.com/ruvnet) org β€” the reusable **building blocks** you'd actually compose into a system β€” each deep-walked and embedded in both variants. The core blocks below also carry symbol indexes and capability cards (the 8 newest repos are findable by name; their capability cards are coming).
169
211
 
170
212
  ![The RuvNet stack the brain covers](primer/assets/diagrams/ruvnet-stack.svg)
171
213
 
@@ -204,17 +246,17 @@ node plugin/test/run-tests.mjs # full plugin QA over real JSO
204
246
  | **L1–L4 behavioral harness** | **all pass** | route Β· deep-recall (returns _code_) Β· implement (cites the API) Β· orchestrate (the hook drives the full pipeline) |
205
247
  | **Plugin QA** | **26 / 26** | manifests, hook firing, MCP `initialize`/`tools/list`, capability battery |
206
248
  | **Clean-room install** | **3 / 3** | download the published 512 MB bundle fresh β†’ unzip β†’ query β†’ grounded, cited answers |
207
- | **Unit tests** | **154 passing** Β· 75% lines | `npm run test:cov` β€” a CI floor fails the build if coverage slips |
249
+ | **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 |
208
250
  | **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** |
209
- | **Held-out eval** | **grounded 12/12** Β· routed 10/12 | `npm run eval` β€” 12 frozen questions never used for tuning, graded on ground truth, never by a model |
251
+ | **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 |
210
252
 
211
- <sub>The suite also carries **181 `it.todo` stubs** β€” a written backlog, each naming an untested behavior and what it would take to cover. They are deliberately **not** counted as tests: a stub proves nothing, and a number that flatters is worse than no number.</sub>
253
+ <sub>The suite also carries **169 `it.todo` stubs** β€” a written backlog, each naming an untested behavior and what it would take to cover. They are deliberately **not** counted as tests: a stub proves nothing, and a number that flatters is worse than no number.</sub>
212
254
 
213
255
  Two honest residuals, not hidden: one described question (_β€œroute to cheaper models to cut cost”_) still leans `ruflo` over `agentic-flow` (orchestration/cost overlap); one unnamed _β€œmethodology”_ question routes to `synthlang` instead of `sparc`. Proof reports land in [`PROOF.md`](PROOF.md), [`DESCRIBED-PROOF.md`](DESCRIBED-PROOF.md), and [`HELIX-DEMO-NOHELIX.md`](HELIX-DEMO-NOHELIX.md).
214
256
 
215
257
  ### The eval flywheel
216
258
 
217
- Most of the numbers above were tuned against. [`evals/held-out.json`](evals/held-out.json) was not: twelve questions phrased the way a newcomer would ask, each with the owning repo chosen **from first principles before the brain ever saw them**. `npm run eval` scores two things, both without asking a model's opinion:
259
+ Most of the numbers above were tuned against. [`evals/held-out.json`](evals/held-out.json) was not: **120 frozen, hash-pinned questions across 5 strata** (named, described, scenario, adversarial, provenance), phrased the way a newcomer would ask, each with the owning repo chosen **from first principles before the brain ever saw them**. `npm run eval` scores, among other things, two claims β€” both without asking a model's opinion:
218
260
 
219
261
  - **grounded** β€” the cited passage genuinely exists in the on-disk store. A path that doesn't resolve is a *fabricated* citation, and is counted as a failure however plausible it reads.
220
262
  - **routed** β€” the citation came from a repo that actually owns the capability.
@@ -223,7 +265,7 @@ Most of the numbers above were tuned against. [`evals/held-out.json`](evals/held
223
265
 
224
266
  **Why not let an LLM grade it?** Because on this very repo an LLM panel scored a **zero-citation answer 98/100**. Model-as-judge is blind to the one failure that matters here.
225
267
 
226
- Current: **grounded 12/12, routed 10/12.** The two routing misses are recorded, not tuned away β€” `"generate tests and find coverage gaps"` cites agentic-qe's real test generator, which lives *vendored inside the ruflo repo*, so the answer is right and the repo label is a corpus-attribution artifact; `"spend less money on model calls"` cites a genuine `agentic-qe/docs/guides/cheaper-model-eval-lanes.md`, the same cost/orchestration overlap noted above.
268
+ Current baseline (n=120, [`evals/baseline.json`](evals/baseline.json)): **grounded 100/100 Β· routed 63/80 Β· abstain 18/20 Β· banner 20/20**, promotion gated on Wilson lower bounds. The routing misses are recorded, not tuned away β€” the recurring shapes: `"generate tests and find coverage gaps"` cites agentic-qe's real test generator, which lives *vendored inside the ruflo repo*, so the answer is right and the repo label is a corpus-attribution artifact; `"spend less money on model calls"` cites a genuine `agentic-qe/docs/guides/cheaper-model-eval-lanes.md`, the same cost/orchestration overlap noted above.
227
269
 
228
270
  **Query it directly (CLI):**
229
271
 
@@ -239,9 +281,9 @@ node forge-ask-all.mjs --dir . --q "How does RuVector implement HNSW vector sear
239
281
 
240
282
  ## Honest status
241
283
 
242
- This is a **`-dev`** project (see the live version 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:
284
+ 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:
243
285
 
244
- - βœ… **The grounding brain is real and proven** β€” ~21 building-block repos, 90,842 chunks, dual embeddings, cross-encoder rerank, plugin (MCP tool + enforcement hook + skill), all re-runnable.
286
+ - βœ… **The grounding brain is real and proven** β€” 32 repos, 128,994 chunks, dual embeddings, cross-encoder rerank, plugin (MCP tool + enforcement hook + skill), all re-runnable.
245
287
  - βœ… **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).
246
288
  - βœ… **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).
247
289
  - ⚠️ **Two routing residuals** (above) β€” surfaced, not hidden.
package/bin/install.mjs CHANGED
@@ -65,13 +65,18 @@ const FLAG_DOCTOR = argv.includes('--doctor');
65
65
  const FLAG_NO_VERIFY = argv.includes('--no-verify');
66
66
  const FLAG_PIN = argv.includes('--pin'); // skip the latest-check, use the bundled default
67
67
  const FLAG_DEMO = argv.includes('--demo'); // guided, real (non-fabricated) walkthrough of the brain in action
68
+ // ── freshness flags β€” invoke/schedule the SELF-UPDATER the bundle already ships (kb/forge-update.mjs) ──
69
+ const FLAG_UPDATE = argv.includes('--update'); // one-shot: pull the latest Release bundle into the installed brain now
70
+ const FLAG_ENABLE_NIGHTLY = argv.includes('--enable-nightly'); // schedule that update nightly (macOS LaunchAgent)
71
+ const FLAG_DISABLE_NIGHTLY = argv.includes('--disable-nightly'); // remove the nightly schedule
72
+ const FLAG_NO_NIGHTLY_PROMPT = argv.includes('--no-nightly-prompt'); // don't offer nightly auto-updates at the end of an install
68
73
  // ── onboarding-experience flags (all optional; every offer is safe to decline) ──
69
74
  const FLAG_YES = argv.includes('--yes') || argv.includes('-y'); // accept every optional offer non-interactively
70
75
  const FLAG_WITH_STACK = argv.includes('--with-stack'); // add missing Ruflo/RuVector without prompting
71
76
  const FLAG_NO_STACK = argv.includes('--no-stack'); // skip the toolkit offer entirely
72
77
  const FLAG_ENHANCE_CLAUDE_MD = argv.includes('--enhance-claude-md'); // add the CLAUDE.md section without prompting
73
78
  const FLAG_NO_ENHANCE = argv.includes('--no-enhance'); // skip the CLAUDE.md offer entirely
74
- // --version <tag> forces a specific Release tag (e.g. --version v0.4.0-dev)
79
+ // --version <tag> forces a specific Release tag (e.g. --version v0.5.0-dev)
75
80
  const versionIdx = argv.indexOf('--version');
76
81
  const FORCED_VERSION =
77
82
  versionIdx !== -1 && argv[versionIdx + 1] && !argv[versionIdx + 1].startsWith('-')
@@ -310,7 +315,7 @@ async function obtainBundle(release) {
310
315
  const downloadUrl = (release && release.url) || fallbackUrl(RELEASE_VERSION);
311
316
  step(
312
317
  `Downloading the brain (${APPROX_SIZE})`,
313
- 'the brain is the embedded source of ~18 RuvNet repos β€” too big for git, so it ships as a Release',
318
+ 'the brain is the embedded source of 20+ RuvNet repos β€” too big for git, so it ships as a Release',
314
319
  );
315
320
  info(`version: ${c.bold((release && release.tag) || RELEASE_VERSION)}`);
316
321
  info(`from: ${downloadUrl}`);
@@ -663,6 +668,33 @@ function runDemo() {
663
668
  console.log(` Full health check: ${c.bold('npx ruvnet-brain --doctor')}\n`);
664
669
  }
665
670
 
671
+ // ── token meter one-liner for --doctor (ADR-0011 token_cost_efficiency) ──────────────────────────
672
+ // The hooks + MCP server append one JSON line per fire to .ruvnet-brain/token-ledger.jsonl in the
673
+ // project they run in (see scripts/token-report.mjs for the full breakdown). This summarizes what
674
+ // was MEASURED yesterday+today in the cwd --doctor is run from β€” measured bytes, estimated tokens
675
+ // (bytes/4, stated as an estimate). Fail-silent by design: a meter problem never reddens a checkup.
676
+ function meterSummaryLine() {
677
+ try {
678
+ const ledger = path.join(process.cwd(), '.ruvnet-brain', 'token-ledger.jsonl');
679
+ if (!fs.existsSync(ledger)) return 'meter: no data yet (this project has no .ruvnet-brain/token-ledger.jsonl β€” it appears after the first hook/MCP fire)';
680
+ const since = new Date();
681
+ since.setHours(0, 0, 0, 0);
682
+ since.setDate(since.getDate() - 1); // start of yesterday, local time
683
+ let count = 0;
684
+ let bytes = 0;
685
+ for (const line of fs.readFileSync(ledger, 'utf8').split('\n')) {
686
+ if (!line.trim()) continue;
687
+ let e;
688
+ try { e = JSON.parse(line); } catch { continue; }
689
+ if (!e?.ts || new Date(e.ts) < since) continue;
690
+ count++;
691
+ bytes += Number(e.bytes) || 0;
692
+ }
693
+ if (count === 0) return 'meter: no data yet (nothing measured in this project yesterday/today)';
694
+ return `meter: ${count} injections measured here yesterday+today β€” ${bytes} bytes β‰ˆ ${Math.round(bytes / 4)} tokens (full breakdown: node scripts/token-report.mjs)`;
695
+ } catch { return 'meter: ledger unreadable'; }
696
+ }
697
+
666
698
  // ── `--doctor`: a standalone health check the user can run any time ───────────────────────────────
667
699
  async function doctor() {
668
700
  printBanner('doctor');
@@ -710,6 +742,7 @@ async function doctor() {
710
742
  console.log(` ${c.yellow('! Grounding not verifiable')} on this bundle β€” it predates the citation verifier.`);
711
743
  console.log(' Re-run npx ruvnet-brain to refresh, then --doctor will prove it.');
712
744
  }
745
+ console.log(` ${c.dim(meterSummaryLine())}`);
713
746
  if (allGreen) {
714
747
  console.log(`\n ${c.bold('What this means for you:')}`);
715
748
  console.log(` β€’ ${c.bold('It works in EVERY project')} β€” user-level (global). Open Claude Code in any repo or VS Code`);
@@ -725,6 +758,240 @@ async function doctor() {
725
758
  );
726
759
  }
727
760
 
761
+ // ── `--update` / `--enable-nightly` / `--disable-nightly`: end-user freshness controls ────────────
762
+ // The brain bundle SHIPS its own self-updater (forge-update.mjs, right in the KB dir): it pulls the
763
+ // canonical Release bundle, backs the current copy up, extracts, and re-verifies with forge-guard β€”
764
+ // failing loud with no partial clobber. These flags never reimplement any of that; they only INVOKE
765
+ // it once (--update) or SCHEDULE it per-user (--enable-nightly). Nothing here ever publishes.
766
+ const NIGHTLY_LABEL = 'com.ruvnet.brain-update';
767
+ const resolvedKbDir = () =>
768
+ process.env.RUVNET_BRAIN_KB || path.join(os.homedir(), '.cache', 'ruvnet-brain', 'kb');
769
+ const nightlyPlistPath = () =>
770
+ path.join(os.homedir(), 'Library', 'LaunchAgents', `${NIGHTLY_LABEL}.plist`);
771
+ // RUVNET_BRAIN_TEST=1 β†’ write/remove the plist but NEVER call launchctl. Tests point HOME at a temp
772
+ // dir; bootstrapping a temp-dir plist into the user's real gui domain would mutate exactly the
773
+ // system state the tests promise not to touch.
774
+ const TEST_MODE = process.env.RUVNET_BRAIN_TEST === '1';
775
+ // RUVNET_BRAIN_IMPORT_ONLY=1 β†’ import this file for its EXPORTS (parseNightlyAnswer, offerNightly)
776
+ // without running the installer as an import side effect. An EXPLICIT env var, not an argv[1]
777
+ // path-identity check, because this repo has already watched a path-identity check fail silently
778
+ // (see smokeQuery's launch note) β€” and a silently-skipped installer main is the worst possible
779
+ // failure mode for a stranger's first contact. With the variable unset, behavior is unchanged.
780
+ const IMPORT_ONLY = process.env.RUVNET_BRAIN_IMPORT_ONLY === '1';
781
+ const xmlEscape = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
782
+
783
+ // The same nightly command, cron-flavored β€” the pattern forge-update.mjs documents in its header.
784
+ // 03:47 on purpose: an off-hour minute, so it never piles onto the :00 cron rush.
785
+ const cronExample = (kbDir) =>
786
+ `47 3 * * * cd ${kbDir} && ${process.execPath} forge-update.mjs --apply >> ${kbDir}/update.log 2>&1`;
787
+
788
+ function missingUpdaterHelp(kbDir) {
789
+ console.error(`\n${c.red('βœ— can\'t update:')} ${c.bold('forge-update.mjs')} is missing from ${kbDir}.`);
790
+ console.error(` Either no brain is installed there, or the bundle predates the self-updater.`);
791
+ console.error(` Fix: re-run the installer β€” ${c.bold('npx ruvnet-brain')} ${c.dim('(add --force if a brain is already present)')}`);
792
+ console.error(` β€” the current bundle ships forge-update.mjs; then this command will work.`);
793
+ }
794
+
795
+ function runUpdate() {
796
+ printBanner('update');
797
+ const kbDir = resolvedKbDir();
798
+ info(`brain dir: ${c.bold(kbDir)}`);
799
+ if (!fs.existsSync(path.join(kbDir, 'forge-update.mjs'))) {
800
+ missingUpdaterHelp(kbDir);
801
+ process.exit(1);
802
+ }
803
+ info(c.dim("running the bundle's own self-updater (backs up first, re-verifies, never half-applies)…\n"));
804
+ // Relative filename + matching cwd β€” same launch convention as smokeQuery(); stdio:'inherit'
805
+ // streams the updater's narration live and unedited.
806
+ const r = spawnSync(process.execPath, ['forge-update.mjs', '--apply'], { cwd: kbDir, stdio: 'inherit' });
807
+ if (r.error) {
808
+ console.error(`\n${c.red('βœ— update failed to launch:')} ${r.error.message}`);
809
+ process.exit(1);
810
+ }
811
+ process.exit(r.status === null ? 1 : r.status); // exit with the updater's own verdict
812
+ }
813
+
814
+ function enableNightly() {
815
+ printBanner('enable nightly updates');
816
+ const kbDir = resolvedKbDir();
817
+
818
+ if (process.platform !== 'darwin') {
819
+ info('The LaunchAgent scheduler is macOS-only (for now).');
820
+ info("On this platform, schedule the bundle's self-updater with cron β€” the pattern");
821
+ info('forge-update.mjs itself documents:');
822
+ console.log(`\n ${c.bold(cronExample(kbDir))}\n`);
823
+ info(`(${c.bold('crontab -e')}, paste the line, save. Remove the line to disable.)`);
824
+ return; // exit 0 β€” the user got the working recipe
825
+ }
826
+
827
+ info(`brain dir: ${c.bold(kbDir)}`);
828
+ if (!fs.existsSync(path.join(kbDir, 'forge-update.mjs'))) {
829
+ // Refuse to schedule a job that is guaranteed to fail every night β€” fail loud NOW instead.
830
+ missingUpdaterHelp(kbDir);
831
+ process.exit(1);
832
+ }
833
+
834
+ // Template the plist to THIS user's kb dir + node binary. Quotes guard paths with spaces;
835
+ // xmlEscape guards the XML (>> and && must survive as shell operators after plist parsing).
836
+ const logPath = path.join(kbDir, 'update.log');
837
+ const shellCmd = `cd "${kbDir}" && "${process.execPath}" forge-update.mjs --apply >> "${logPath}" 2>&1`;
838
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
839
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
840
+ <plist version="1.0">
841
+ <dict>
842
+ <key>Label</key>
843
+ <string>${NIGHTLY_LABEL}</string>
844
+ <key>ProgramArguments</key>
845
+ <array>
846
+ <string>/bin/sh</string>
847
+ <string>-c</string>
848
+ <string>${xmlEscape(shellCmd)}</string>
849
+ </array>
850
+ <key>StartCalendarInterval</key>
851
+ <dict>
852
+ <key>Hour</key>
853
+ <integer>3</integer>
854
+ <key>Minute</key>
855
+ <integer>47</integer>
856
+ </dict>
857
+ <key>RunAtLoad</key>
858
+ <false/>
859
+ </dict>
860
+ </plist>
861
+ `;
862
+ const plistPath = nightlyPlistPath();
863
+ try {
864
+ fs.mkdirSync(path.dirname(plistPath), { recursive: true });
865
+ fs.writeFileSync(plistPath, plist);
866
+ } catch (e) {
867
+ console.error(`\n${c.red('βœ— couldn\'t write the LaunchAgent:')} ${e.message}`);
868
+ process.exit(1);
869
+ }
870
+ ok(`wrote ${c.bold(plistPath)}`);
871
+ info(c.dim('runs nightly at 03:47 β€” an off-hour minute, so it never lands on the :00 rush'));
872
+
873
+ if (TEST_MODE) {
874
+ warn('RUVNET_BRAIN_TEST=1 β€” skipping launchctl bootout/bootstrap (plist written only)');
875
+ } else {
876
+ const uid = process.getuid();
877
+ // bootout first so re-running replaces the loaded job cleanly; failure just means "wasn't loaded".
878
+ spawnSync('launchctl', ['bootout', `gui/${uid}/${NIGHTLY_LABEL}`], { stdio: 'ignore' });
879
+ const boot = spawnSync('launchctl', ['bootstrap', `gui/${uid}`, plistPath], { encoding: 'utf8' });
880
+ if (boot.status === 0) ok('LaunchAgent loaded β€” your brain now updates while you sleep');
881
+ else {
882
+ warn(`launchctl bootstrap failed (${(boot.stderr || '').trim() || `exit ${boot.status}`}) β€” the plist is in place;`);
883
+ info(`load it yourself: ${c.bold(`launchctl bootstrap gui/${uid} ${plistPath}`)}`);
884
+ }
885
+ }
886
+
887
+ console.log(`\n ${c.bold('Verify it:')} launchctl list | grep ${NIGHTLY_LABEL}`);
888
+ console.log(` ${c.bold('Watch it:')} tail ${logPath} ${c.dim('(appears after the first nightly run)')}`);
889
+ console.log(` ${c.bold('Disable it:')} npx ruvnet-brain --disable-nightly`);
890
+ console.log(`\n ${c.dim('It only ever PULLS the published Release bundle (backup + re-verify built in) β€” it never publishes,')}`);
891
+ console.log(` ${c.dim('and a night with no new Release is a clean no-op.')}\n`);
892
+ }
893
+
894
+ function disableNightly() {
895
+ printBanner('disable nightly updates');
896
+ if (process.platform !== 'darwin') {
897
+ info('The LaunchAgent nightly is macOS-only, so nothing was scheduled here by this tool.');
898
+ info(`If you added the cron line yourself, remove it with: ${c.bold('crontab -e')}`);
899
+ return;
900
+ }
901
+ const plistPath = nightlyPlistPath();
902
+ const existed = fs.existsSync(plistPath);
903
+ if (TEST_MODE) {
904
+ warn('RUVNET_BRAIN_TEST=1 β€” skipping launchctl bootout (plist removal only)');
905
+ } else {
906
+ // Ignore failure: "not loaded" is exactly the state we want anyway.
907
+ spawnSync('launchctl', ['bootout', `gui/${process.getuid()}/${NIGHTLY_LABEL}`], { stdio: 'ignore' });
908
+ }
909
+ if (existed) {
910
+ try {
911
+ fs.rmSync(plistPath);
912
+ } catch (e) {
913
+ console.error(`\n${c.red('βœ— couldn\'t remove the LaunchAgent:')} ${e.message}`);
914
+ console.error(` Remove it yourself: rm ${plistPath}`);
915
+ process.exit(1);
916
+ }
917
+ ok(`nightly updates disabled β€” removed ${plistPath}`);
918
+ } else {
919
+ ok('nightly updates were already off β€” nothing to remove (safe to run any time)');
920
+ }
921
+ info(`re-enable any time: ${c.bold('npx ruvnet-brain --enable-nightly')}`);
922
+ }
923
+
924
+ // ── step: offer nightly auto-updates at the end of a successful install (recommended, default YES) ──
925
+ // Requirement: a default `npx ruvnet-brain` run must never leave the user unaware of nightly
926
+ // auto-updates β€” it VERY CLEARLY recommends them, asks, and DEFAULTS TO YES. Before this, the
927
+ // nightly LaunchAgent only ever installed via the explicit --enable-nightly flag.
928
+ //
929
+ // The answer parsing is exported so the default-yes contract is unit-testable without a TTY:
930
+ // ENTER (empty) and y/yes (any case) accept; ONLY an explicit n/no declines.
931
+ export function parseNightlyAnswer(answer) {
932
+ const s = String(answer ?? '').trim().toLowerCase();
933
+ return s !== 'n' && s !== 'no';
934
+ }
935
+
936
+ // Exported for the same reason: the decision matrix (TTY/non-TTY Γ— platform Γ— already-enabled Γ—
937
+ // suppression flags) is testable in-process under RUVNET_BRAIN_IMPORT_ONLY=1 without a real install.
938
+ // Returns a status string; never throws (the caller also guards β€” a finished install must never
939
+ // be broken by an optional offer).
940
+ export async function offerNightly() {
941
+ // Suppressed outright: --no-nightly-prompt (the user said don't ask) and RUVNET_BRAIN_TEST=1
942
+ // (tests must stay non-interactive and must never schedule anything).
943
+ if (FLAG_NO_NIGHTLY_PROMPT || TEST_MODE) return 'suppressed';
944
+ const kbDir = resolvedKbDir();
945
+ // A bundle that predates the self-updater has nothing to run nightly β€” don't offer a job that
946
+ // is guaranteed to fail (enableNightly would refuse it anyway).
947
+ if (!fs.existsSync(path.join(kbDir, 'forge-update.mjs'))) return 'no-updater';
948
+
949
+ step(
950
+ 'One last thing β€” keeping the brain fresh',
951
+ 'rUv ships constantly; a brain that updates itself stays current with zero effort from you',
952
+ );
953
+
954
+ if (process.platform !== 'darwin') {
955
+ info('The LaunchAgent scheduler is macOS-only (for now).');
956
+ info(`Update manually any time with: ${c.bold('npx ruvnet-brain --update')}`);
957
+ info(`(or schedule it yourself with the cron line documented in the brain's own forge-update.mjs)`);
958
+ return 'unsupported';
959
+ }
960
+
961
+ if (fs.existsSync(nightlyPlistPath())) {
962
+ ok('nightly auto-updates are already on β€” new repos and gists arrive while you sleep');
963
+ return 'already-on';
964
+ }
965
+
966
+ info(`${c.bold('Recommended:')} your brain updates itself while you sleep β€” new repos, new gists, zero effort.`);
967
+
968
+ if (!process.stdin.isTTY && !FLAG_YES) {
969
+ // No terminal to ask on (CI / piped install) β€” recommend clearly instead of prompting.
970
+ info(`No interactive terminal here, so I won't prompt. Enable it any time with one command:`);
971
+ info(` ${c.bold('npx ruvnet-brain --enable-nightly')}`);
972
+ return 'recommended';
973
+ }
974
+
975
+ let yes = true; // --yes accepts every optional offer, this one included
976
+ if (!FLAG_YES) {
977
+ // Not ask(): its parser treats anything but y/yes as no. Here the DEFAULT is yes β€” only an
978
+ // explicit n/no declines (parseNightlyAnswer holds that contract, and the tests hold it there).
979
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
980
+ const answer = await new Promise((resolve) =>
981
+ rl.question(` ${c.cyan('?')} Enable nightly auto-updates? ${c.dim('[Y/n]')} `, resolve),
982
+ );
983
+ rl.close();
984
+ yes = parseNightlyAnswer(answer);
985
+ }
986
+
987
+ if (!yes) {
988
+ info(`No problem β€” enable it any time with: ${c.bold('npx ruvnet-brain --enable-nightly')}`);
989
+ return 'declined';
990
+ }
991
+ enableNightly(); // prints its own real verification output (plist path, launchctl result, how to check)
992
+ return 'enabled';
993
+ }
994
+
728
995
  // ── tiny interactive yes/no β€” SAFE in non-TTY (returns the default; never blocks a piped install) ──
729
996
  function ask(question, def = false) {
730
997
  if (FLAG_YES) return Promise.resolve(true);
@@ -896,13 +1163,13 @@ async function offerClaudeMd() {
896
1163
  }
897
1164
 
898
1165
  // ── final success block ──────────────────────────────────────────────────────────────────────────
899
- function success({ cacheDir, isCustom, plugin, env }) {
1166
+ function success({ cacheDir, isCustom, plugin, env, nightly }) {
900
1167
  const line = '─'.repeat(64);
901
1168
  console.log(`\n${c.green(line)}`);
902
1169
  console.log(`${c.green(c.bold(' RuvNet Brain is installed.'))}`);
903
1170
  console.log(`${c.green(line)}`);
904
1171
  console.log(`\n What you now have:`);
905
- console.log(` β€’ the brain (embedded source of ~18 RuvNet repos) at:`);
1172
+ console.log(` β€’ the brain (embedded source of 20+ RuvNet repos) at:`);
906
1173
  console.log(` ${c.bold(cacheDir)}`);
907
1174
  console.log(
908
1175
  ` β€’ the Claude Code plugin ${plugin.wired ? c.green('wired at user scope') : c.yellow('(finish the 2 commands above)')} β€” search_ruvnet + grounding hook`,
@@ -949,9 +1216,21 @@ function success({ cacheDir, isCustom, plugin, env }) {
949
1216
  β€’ Want to see it answer, live, right now? ${c.bold('npx ruvnet-brain --demo')} β€” 2 real questions, real cited answers.`);
950
1217
 
951
1218
  console.log(`\n ${c.bold('Set it up your way:')} this default is ${c.bold('global')} β€” live in every VS Code project automatically,`);
952
- console.log(` which is what most people want. Want it different (project-only, moved, with the build stack added,`);
953
- console.log(` auto-updating nightly)? ${c.bold('Just tell Claude')} once it's on β€” the brain is smart enough to reconfigure`);
954
- console.log(` itself. You never have to learn its internals.`);
1219
+ console.log(` which is what most people want. Want it different (project-only, moved, with the build stack`);
1220
+ console.log(` added)? ${c.bold('Just tell Claude')} once it's on. You never have to learn its internals.`);
1221
+
1222
+ if (nightly === 'enabled' || nightly === 'already-on') {
1223
+ // The offer just above turned nightly on (or found it on) β€” don't contradict that here.
1224
+ console.log(`\n ${c.bold('Staying current:')} nightly auto-updates are ${c.green(c.bold('ON'))} β€” your brain refreshes itself at 03:47.`);
1225
+ console.log(` β€’ update right now anyway: ${c.bold('npx ruvnet-brain --update')} ${c.dim('Β· turn nightly off: --disable-nightly')}`);
1226
+ console.log(` ${c.dim('It only advances when a new Release is actually published β€” a quiet night is a clean no-op.')}`);
1227
+ } else {
1228
+ console.log(`\n ${c.bold('Staying current:')} nightly updates are ${c.bold('OFF right now')} β€” nothing runs on your machine unasked.`);
1229
+ console.log(` β€’ one-shot check + update now: ${c.bold('npx ruvnet-brain --update')}`);
1230
+ console.log(` β€’ nightly at 03:47 (macOS): ${c.bold('npx ruvnet-brain --enable-nightly')} ${c.dim('Β· off again: --disable-nightly')}`);
1231
+ console.log(` β€’ Linux/Windows: the cron line documented in the brain's own ${c.bold('forge-update.mjs')}`);
1232
+ console.log(` ${c.dim('Either way, your copy only advances when a new Release is actually published.')}`);
1233
+ }
955
1234
 
956
1235
  console.log(`\n ${c.dim('You can\'t break anything β€” the plugin is disable-able and only acts on RuvNet-shaped work.')}`);
957
1236
  console.log('');
@@ -969,7 +1248,14 @@ Usage:
969
1248
  npx github:stuinfla/ruvnet-brain Same, but from the bleeding-edge GitHub commit
970
1249
  npx ruvnet-brain --doctor Health-check an existing install (green/red per part)
971
1250
  npx ruvnet-brain --demo Guided walkthrough β€” 2 real questions, real cited answers
972
- node bin/install.mjs --version <tag> Install a specific Release tag (e.g. --version v0.4.0-dev)
1251
+ npx ruvnet-brain --update One-shot: pull the latest Release bundle into your installed brain
1252
+ (runs the bundle's own forge-update.mjs --apply: backup + re-verify)
1253
+ npx ruvnet-brain --enable-nightly Schedule that update nightly at 03:47 β€” macOS LaunchAgent;
1254
+ other platforms get the documented cron line. OFF by default.
1255
+ npx ruvnet-brain --disable-nightly Remove the nightly schedule (safe to run any time)
1256
+ (a default install RECOMMENDS nightly and asks, defaulting to yes)
1257
+ node bin/install.mjs --no-nightly-prompt Don't offer nightly auto-updates at the end of the install
1258
+ node bin/install.mjs --version <tag> Install a specific Release tag (e.g. --version v0.5.0-dev)
973
1259
  node bin/install.mjs --pin Skip the latest-check; use the bundled known-good version
974
1260
  node bin/install.mjs --local Install from a repo clone's dist/ruvnet-brain.zip
975
1261
  node bin/install.mjs --force Re-fetch and reinstall even if already present
@@ -989,9 +1275,13 @@ It is safe to re-run at any time. After installing, restart Claude Code so the g
989
1275
 
990
1276
  // ── main ─────────────────────────────────────────────────────────────────────────────────────────
991
1277
  (async () => {
1278
+ if (IMPORT_ONLY) return; // imported for its exports (tests) β€” never run the installer as a side effect
992
1279
  if (FLAG_HELP) return showHelp();
993
1280
  if (FLAG_DOCTOR) return await doctor();
994
1281
  if (FLAG_DEMO) return runDemo();
1282
+ if (FLAG_UPDATE) return runUpdate();
1283
+ if (FLAG_ENABLE_NIGHTLY) return enableNightly();
1284
+ if (FLAG_DISABLE_NIGHTLY) return disableNightly();
995
1285
 
996
1286
  printBanner('installer');
997
1287
  console.log(c.dim("I'll set up the brain and the Claude Code plugin, explaining each step as I go.\n"));
@@ -1068,8 +1358,12 @@ It is safe to re-run at any time. After installing, restart Claude Code so the g
1068
1358
  const env = detectEnvironment();
1069
1359
  try { await offerStack(env); } catch (e) { warn(`(toolkit check skipped: ${e && e.message})`); }
1070
1360
  try { await offerClaudeMd(); } catch { /* non-fatal β€” never let an offer break the install */ }
1361
+ // Stuart's requirement: a default install must end by clearly recommending nightly auto-updates
1362
+ // and asking, DEFAULTING TO YES (TTY + macOS + not already on). Non-fatal like every other offer.
1363
+ let nightly = 'skipped';
1364
+ try { nightly = await offerNightly(); } catch { /* never let the offer break a finished install */ }
1071
1365
 
1072
- success({ cacheDir, isCustom, plugin, env });
1366
+ success({ cacheDir, isCustom, plugin, env, nightly });
1073
1367
  })().catch((e) => {
1074
1368
  die(e && e.message ? e.message : String(e));
1075
1369
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvnet-brain",
3
- "version": "1.14.0-dev",
3
+ "version": "2.0.0",
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": {
@@ -9,16 +9,21 @@
9
9
  "scripts": {
10
10
  "test": "node plugin/test/run-tests.mjs",
11
11
  "version:check": "node scripts/sync-version.mjs --check",
12
+ "claims:verify": "node scripts/claims-verify.mjs",
12
13
  "version:sync": "node scripts/sync-version.mjs",
13
- "test:unit": "vitest run",
14
- "test:cov": "vitest run --coverage",
14
+ "test:unit": "vitest run tests/unit",
15
+ "test:cov": "vitest run tests/unit --coverage",
16
+ "test:all": "npm run test:unit && npm run test:integration && npm test",
15
17
  "metaharness:fix": "node scripts/fix-metaharness-memretrieve.mjs --apply",
16
18
  "metaharness:check": "node scripts/fix-metaharness-memretrieve.mjs --check",
17
19
  "eval": "node scripts/eval-brain.mjs",
18
20
  "eval:gate": "node scripts/eval-brain.mjs --gate",
19
21
  "eval:record": "node scripts/eval-brain.mjs --record",
20
22
  "embed:verifier": "node scripts/embed-verifier.mjs",
21
- "embed:check": "node scripts/embed-verifier.mjs --check"
23
+ "embed:check": "node scripts/embed-verifier.mjs --check",
24
+ "gists:index": "node scripts/ingest-gists.mjs --index-only",
25
+ "gists:sync": "node scripts/ingest-gists.mjs && node kb/forge-big.mjs both --dir kb --name ruv-gists",
26
+ "test:integration": "vitest run tests/integration"
22
27
  },
23
28
  "files": [
24
29
  "bin/install.mjs",
@@ -50,6 +55,7 @@
50
55
  "bugs": "https://github.com/stuinfla/ruvnet-brain/issues",
51
56
  "author": "Stuart Kerr",
52
57
  "devDependencies": {
58
+ "@metaharness/darwin": "~0.8.0",
53
59
  "@vitest/coverage-v8": "^4.1.10",
54
60
  "vitest": "^4.1.10"
55
61
  }