ruvnet-brain 1.16.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 +52 -13
  2. package/bin/install.mjs +131 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # 🧠 RuvNet Brain
6
6
 
7
- ### 🧠 RuvNet Brain β€” [![RuvNet Brain version 1.16.0-dev β€” updated 2026-07-09 04:53 EDT](https://img.shields.io/badge/version_1.16.0--dev-updated_2026--07--09_04:53_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
 
@@ -36,6 +36,45 @@
36
36
 
37
37
  ---
38
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
+
39
78
  ## The one thing to understand first
40
79
 
41
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.
@@ -110,7 +149,7 @@ You install once. After that, three mechanisms keep you on the current brain wit
110
149
 
111
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).
112
151
 
113
- - **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.
114
153
 
115
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.)
116
155
 
@@ -118,9 +157,9 @@ You install once. After that, three mechanisms keep you on the current brain wit
118
157
 
119
158
  ## ✨ What the knowledge bundle knows β€” the stack _down to the code_
120
159
 
121
- 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)):
122
161
 
123
- | Repo | Full-body code passages | |
162
+ | Repo | Full-body code passages (v0.5) | |
124
163
  |---|---:|---|
125
164
  | `agentic-flow` | 296 β†’ **984** | model-routing, ReasoningBank |
126
165
  | `ruv-fann` | 52 β†’ **779** | ruv-swarm, cuda-wasm, neuro-divergent |
@@ -134,7 +173,7 @@ Plus: the **β€œtake the wheel” behavioral pipeline** (below), a **4-level beha
134
173
 
135
174
  ## How it works
136
175
 
137
- 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.
138
177
 
139
178
  ![RuvNet Brain architecture pipeline](assets/diagrams/architecture-pipeline.svg)
140
179
 
@@ -168,7 +207,7 @@ The brain answers **both** kinds of questions. **Name the repo or ask something
168
207
 
169
208
  ## What it covers
170
209
 
171
- ~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).
172
211
 
173
212
  ![The RuvNet stack the brain covers](primer/assets/diagrams/ruvnet-stack.svg)
174
213
 
@@ -207,17 +246,17 @@ node plugin/test/run-tests.mjs # full plugin QA over real JSO
207
246
  | **L1–L4 behavioral harness** | **all pass** | route Β· deep-recall (returns _code_) Β· implement (cites the API) Β· orchestrate (the hook drives the full pipeline) |
208
247
  | **Plugin QA** | **26 / 26** | manifests, hook firing, MCP `initialize`/`tools/list`, capability battery |
209
248
  | **Clean-room install** | **3 / 3** | download the published 512 MB bundle fresh β†’ unzip β†’ query β†’ grounded, cited answers |
210
- | **Unit tests** | **208 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 |
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 |
211
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** |
212
- | **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 |
213
252
 
214
- <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>
215
254
 
216
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).
217
256
 
218
257
  ### The eval flywheel
219
258
 
220
- 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:
221
260
 
222
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.
223
262
  - **routed** β€” the citation came from a repo that actually owns the capability.
@@ -226,7 +265,7 @@ Most of the numbers above were tuned against. [`evals/held-out.json`](evals/held
226
265
 
227
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.
228
267
 
229
- 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.
230
269
 
231
270
  **Query it directly (CLI):**
232
271
 
@@ -242,9 +281,9 @@ node forge-ask-all.mjs --dir . --q "How does RuVector implement HNSW vector sear
242
281
 
243
282
  ## Honest status
244
283
 
245
- 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:
246
285
 
247
- - βœ… **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.
248
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).
249
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).
250
289
  - ⚠️ **Two routing residuals** (above) β€” surfaced, not hidden.
package/bin/install.mjs CHANGED
@@ -69,13 +69,14 @@ const FLAG_DEMO = argv.includes('--demo'); // guided, real (non-fabricated) walk
69
69
  const FLAG_UPDATE = argv.includes('--update'); // one-shot: pull the latest Release bundle into the installed brain now
70
70
  const FLAG_ENABLE_NIGHTLY = argv.includes('--enable-nightly'); // schedule that update nightly (macOS LaunchAgent)
71
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
72
73
  // ── onboarding-experience flags (all optional; every offer is safe to decline) ──
73
74
  const FLAG_YES = argv.includes('--yes') || argv.includes('-y'); // accept every optional offer non-interactively
74
75
  const FLAG_WITH_STACK = argv.includes('--with-stack'); // add missing Ruflo/RuVector without prompting
75
76
  const FLAG_NO_STACK = argv.includes('--no-stack'); // skip the toolkit offer entirely
76
77
  const FLAG_ENHANCE_CLAUDE_MD = argv.includes('--enhance-claude-md'); // add the CLAUDE.md section without prompting
77
78
  const FLAG_NO_ENHANCE = argv.includes('--no-enhance'); // skip the CLAUDE.md offer entirely
78
- // --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)
79
80
  const versionIdx = argv.indexOf('--version');
80
81
  const FORCED_VERSION =
81
82
  versionIdx !== -1 && argv[versionIdx + 1] && !argv[versionIdx + 1].startsWith('-')
@@ -314,7 +315,7 @@ async function obtainBundle(release) {
314
315
  const downloadUrl = (release && release.url) || fallbackUrl(RELEASE_VERSION);
315
316
  step(
316
317
  `Downloading the brain (${APPROX_SIZE})`,
317
- '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',
318
319
  );
319
320
  info(`version: ${c.bold((release && release.tag) || RELEASE_VERSION)}`);
320
321
  info(`from: ${downloadUrl}`);
@@ -667,6 +668,33 @@ function runDemo() {
667
668
  console.log(` Full health check: ${c.bold('npx ruvnet-brain --doctor')}\n`);
668
669
  }
669
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
+
670
698
  // ── `--doctor`: a standalone health check the user can run any time ───────────────────────────────
671
699
  async function doctor() {
672
700
  printBanner('doctor');
@@ -714,6 +742,7 @@ async function doctor() {
714
742
  console.log(` ${c.yellow('! Grounding not verifiable')} on this bundle β€” it predates the citation verifier.`);
715
743
  console.log(' Re-run npx ruvnet-brain to refresh, then --doctor will prove it.');
716
744
  }
745
+ console.log(` ${c.dim(meterSummaryLine())}`);
717
746
  if (allGreen) {
718
747
  console.log(`\n ${c.bold('What this means for you:')}`);
719
748
  console.log(` β€’ ${c.bold('It works in EVERY project')} β€” user-level (global). Open Claude Code in any repo or VS Code`);
@@ -743,6 +772,12 @@ const nightlyPlistPath = () =>
743
772
  // dir; bootstrapping a temp-dir plist into the user's real gui domain would mutate exactly the
744
773
  // system state the tests promise not to touch.
745
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';
746
781
  const xmlEscape = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
747
782
 
748
783
  // The same nightly command, cron-flavored β€” the pattern forge-update.mjs documents in its header.
@@ -886,6 +921,77 @@ function disableNightly() {
886
921
  info(`re-enable any time: ${c.bold('npx ruvnet-brain --enable-nightly')}`);
887
922
  }
888
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
+
889
995
  // ── tiny interactive yes/no β€” SAFE in non-TTY (returns the default; never blocks a piped install) ──
890
996
  function ask(question, def = false) {
891
997
  if (FLAG_YES) return Promise.resolve(true);
@@ -1057,13 +1163,13 @@ async function offerClaudeMd() {
1057
1163
  }
1058
1164
 
1059
1165
  // ── final success block ──────────────────────────────────────────────────────────────────────────
1060
- function success({ cacheDir, isCustom, plugin, env }) {
1166
+ function success({ cacheDir, isCustom, plugin, env, nightly }) {
1061
1167
  const line = '─'.repeat(64);
1062
1168
  console.log(`\n${c.green(line)}`);
1063
1169
  console.log(`${c.green(c.bold(' RuvNet Brain is installed.'))}`);
1064
1170
  console.log(`${c.green(line)}`);
1065
1171
  console.log(`\n What you now have:`);
1066
- console.log(` β€’ the brain (embedded source of ~18 RuvNet repos) at:`);
1172
+ console.log(` β€’ the brain (embedded source of 20+ RuvNet repos) at:`);
1067
1173
  console.log(` ${c.bold(cacheDir)}`);
1068
1174
  console.log(
1069
1175
  ` β€’ the Claude Code plugin ${plugin.wired ? c.green('wired at user scope') : c.yellow('(finish the 2 commands above)')} β€” search_ruvnet + grounding hook`,
@@ -1113,11 +1219,18 @@ function success({ cacheDir, isCustom, plugin, env }) {
1113
1219
  console.log(` which is what most people want. Want it different (project-only, moved, with the build stack`);
1114
1220
  console.log(` added)? ${c.bold('Just tell Claude')} once it's on. You never have to learn its internals.`);
1115
1221
 
1116
- console.log(`\n ${c.bold('Staying current:')} nightly updates are ${c.bold('OFF by default')} β€” nothing runs on your machine unasked.`);
1117
- console.log(` β€’ one-shot check + update now: ${c.bold('npx ruvnet-brain --update')}`);
1118
- console.log(` β€’ nightly at 03:47 (macOS): ${c.bold('npx ruvnet-brain --enable-nightly')} ${c.dim('Β· off again: --disable-nightly')}`);
1119
- console.log(` β€’ Linux/Windows: the cron line documented in the brain's own ${c.bold('forge-update.mjs')}`);
1120
- console.log(` ${c.dim('Either way, your copy only advances when a new Release is actually published.')}`);
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
+ }
1121
1234
 
1122
1235
  console.log(`\n ${c.dim('You can\'t break anything β€” the plugin is disable-able and only acts on RuvNet-shaped work.')}`);
1123
1236
  console.log('');
@@ -1140,7 +1253,9 @@ Usage:
1140
1253
  npx ruvnet-brain --enable-nightly Schedule that update nightly at 03:47 β€” macOS LaunchAgent;
1141
1254
  other platforms get the documented cron line. OFF by default.
1142
1255
  npx ruvnet-brain --disable-nightly Remove the nightly schedule (safe to run any time)
1143
- node bin/install.mjs --version <tag> Install a specific Release tag (e.g. --version v0.4.0-dev)
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)
1144
1259
  node bin/install.mjs --pin Skip the latest-check; use the bundled known-good version
1145
1260
  node bin/install.mjs --local Install from a repo clone's dist/ruvnet-brain.zip
1146
1261
  node bin/install.mjs --force Re-fetch and reinstall even if already present
@@ -1160,6 +1275,7 @@ It is safe to re-run at any time. After installing, restart Claude Code so the g
1160
1275
 
1161
1276
  // ── main ─────────────────────────────────────────────────────────────────────────────────────────
1162
1277
  (async () => {
1278
+ if (IMPORT_ONLY) return; // imported for its exports (tests) β€” never run the installer as a side effect
1163
1279
  if (FLAG_HELP) return showHelp();
1164
1280
  if (FLAG_DOCTOR) return await doctor();
1165
1281
  if (FLAG_DEMO) return runDemo();
@@ -1242,8 +1358,12 @@ It is safe to re-run at any time. After installing, restart Claude Code so the g
1242
1358
  const env = detectEnvironment();
1243
1359
  try { await offerStack(env); } catch (e) { warn(`(toolkit check skipped: ${e && e.message})`); }
1244
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 */ }
1245
1365
 
1246
- success({ cacheDir, isCustom, plugin, env });
1366
+ success({ cacheDir, isCustom, plugin, env, nightly });
1247
1367
  })().catch((e) => {
1248
1368
  die(e && e.message ? e.message : String(e));
1249
1369
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvnet-brain",
3
- "version": "1.16.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": {