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.
- package/README.md +52 -13
- package/bin/install.mjs +131 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
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
|
|
|
@@ -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
|
|
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**.
|
|
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 **
|
|
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
|

|
|
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
|
-
|
|
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
|

|
|
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** | **
|
|
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
|
|
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 **
|
|
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:
|
|
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
|
|
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
|
|
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** β
|
|
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.
|
|
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
|
|
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, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
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
|
|
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
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
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
|
-
|
|
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": "
|
|
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": {
|