ruvnet-brain 3.9.18-dev β†’ 3.9.51-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # 🧠 RuvNet Brain
6
6
 
7
- ### 🧠 RuvNet Brain β€” [![RuvNet Brain version 3.9.18-dev β€” updated 2026-07-23 04:06 EDT](https://img.shields.io/badge/version_3.9.18--dev-updated_2026--07--23_04:06_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 3.9.51-dev β€” updated 2026-07-23 04:06 EDT](https://img.shields.io/badge/version_3.9.51--dev-updated_2026--07--23_04:06_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
 
@@ -142,9 +142,12 @@ answers the question the owner has been asking for weeks β€” **"what is actually
142
142
  had zero call sites; the client referenced it only in comments. Built-tested-unwired is this
143
143
  project's signature failure, and it happened three times in one night.
144
144
 
145
- Honest limit: this is **3.6, not 4.0**. 4.0 requires levels 3–5 of ADR-028's proactivity ladder
146
- (contextual, anticipatory, compounding) and all three remain unbuilt β€” see `docs/4.0-READINESS.md`,
147
- which grades the current state at **L2** with evidence for every mark.
145
+ Honest limit: this is still a dev release, **not 4.0**. 4.0 requires levels 3–5 of ADR-028's ladder
146
+ (contextual, anticipatory, compounding) **shipped and independently graded β‰₯95**. The mechanisms now
147
+ exist β€” L3's delivery seam (the chokepoint), an L4 goal surface, and L5 cross-project promotion (lessons
148
+ promoted to your global brain, survival proven by isolation) β€” but the ladder still grades **L2–L3**,
149
+ because L3 is built rather than live (deploy-gated) and the five acceptance metrics aren't all measured.
150
+ See `docs/4.0-READINESS.md` and `docs/4.0-EXECUTIVE-BRIEFING.md` (last independent grade: 70/100 overall).
148
151
 
149
152
  <details>
150
153
  <summary><b>Earlier &#8212; what 3.5 shipped</b> &#183; it stopped waiting to be asked. <i>Expand for the receipts.</i></summary>
@@ -406,7 +409,7 @@ Plus: the **β€œtake the wheel” behavioral pipeline** (below), a **4-level beha
406
409
 
407
410
  ## How it works
408
411
 
409
- 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 **149,721 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.
412
+ 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 **149,729 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.
410
413
 
411
414
  ![RuvNet Brain architecture pipeline](assets/diagrams/architecture-pipeline.svg)
412
415
 
@@ -516,7 +519,7 @@ node forge-ask-all.mjs --dir . --q "How does RuVector implement HNSW vector sear
516
519
 
517
520
  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:
518
521
 
519
- - βœ… **The grounding brain is real and proven** β€” 54 public stores Β· 149,721 public source chunks (57 built stores incl. private), dual embeddings, cross-encoder rerank, plugin (MCP tool + enforcement hook + skill), all re-runnable.
522
+ - βœ… **The grounding brain is real and proven** β€” 54 public stores Β· 149,729 public source chunks (57 built stores incl. private), dual embeddings, cross-encoder rerank, plugin (MCP tool + enforcement hook + skill), all re-runnable.
520
523
  - βœ… **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).
521
524
  - βœ… **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).
522
525
  - ⚠️ **Two routing residuals** (above) β€” surfaced, not hidden.
package/bin/install.mjs CHANGED
@@ -1412,6 +1412,12 @@ function disableSpendGuard() {
1412
1412
  else ok('spend watchdog was already off β€” nothing to remove (safe to run any time)');
1413
1413
  }
1414
1414
 
1415
+ // Default STATE_PATH from scripts/upgrade-notice.mjs, duplicated (not imported β€” see the cmpTag()
1416
+ // comment above this file's own rule on why) so machineFootprint()/uninstallAll() can find and
1417
+ // remove it without a module this file may not statically depend on.
1418
+ const upgradeNoticeStatePath = () =>
1419
+ process.env.RUVNET_UPGRADE_NOTICE_FILE || path.join(os.homedir(), '.config', 'ruvnet-brain', 'upgrade-notice.json');
1420
+
1415
1421
  /**
1416
1422
  * Everything this installer can leave on a machine, DERIVED from disk β€” never asserted.
1417
1423
  *
@@ -1435,7 +1441,14 @@ export function machineFootprint() {
1435
1441
  if (cmds) items.push({
1436
1442
  label: 'Claude Code plugin',
1437
1443
  path: path.dirname(cmds),
1438
- undo: 'claude plugin uninstall ruvnet-brain@ruvnet-brain',
1444
+ // `claude plugin uninstall` removes the INSTALLED plugin only β€” it leaves the marketplace itself
1445
+ // registered (a separate git clone + registry entry: `claude plugin marketplace list` still
1446
+ // shows it, and wirePlugin() above is the thing that added it via `claude plugin marketplace add
1447
+ // stuinfla/ruvnet-brain`). Verified live against `claude plugin marketplace --help` (marketplace
1448
+ // name = "ruvnet-brain", from this repo's own .claude-plugin/marketplace.json) that `remove` takes
1449
+ // that same short name. Without this second command someone who ran every undo we printed would
1450
+ // still have us registered as a marketplace.
1451
+ undo: 'claude plugin uninstall ruvnet-brain@ruvnet-brain && claude plugin marketplace remove ruvnet-brain',
1439
1452
  });
1440
1453
  const cmdPath = path.join(os.homedir(), '.claude', 'CLAUDE.md');
1441
1454
  try {
@@ -1457,12 +1470,21 @@ export function machineFootprint() {
1457
1470
  add('Status-bar preference', path.join(telemetryStateDir(), '.statusline-pref'), 'delete this file');
1458
1471
  add('Model-router files', path.join(os.homedir(), '.claude', 'model-router'),
1459
1472
  'rm -rf ~/.claude/model-router');
1460
- // Config entries live INSIDE files the user owns, so they are reported as edits to review rather
1461
- // than as paths to delete β€” deleting someone's settings.json over one key would be indefensible.
1473
+ // GAP FIX: this used to be a loose `.includes('ruvnet-brain')` substring test against the WHOLE
1474
+ // settings.json file β€” which (a) could also fire on something unrelated (e.g. a marketplace
1475
+ // autoUpdate setting that merely mentions our name) and get mislabeled as "the statusLine entry",
1476
+ // and (b) meant this was ALWAYS reported as a manual edit, never auto-removed, even though this
1477
+ // installer is the one that wrote the key and knows exactly what it wrote. detectStatusLine() now
1478
+ // checks the actual parsed statusLine.command against the exact string writeSettingsStatusLine()
1479
+ // writes β€” precise enough that removeSettingsStatusLine() (called from uninstallAll()) can safely
1480
+ // reverse just this key, the same way removeClaudeMdBlock() reverses just its own block. A status
1481
+ // line a user has since edited or folded into their own script no longer matches and is correctly
1482
+ // left off this list entirely (nothing to claim, nothing to undo).
1462
1483
  try {
1463
- const settings = path.join(os.homedir(), '.claude', 'settings.json');
1464
- if (fs.existsSync(settings) && fs.readFileSync(settings, 'utf8').includes('ruvnet-brain')) {
1465
- items.push({ label: 'A statusLine entry in your settings.json', path: settings, undo: 'remove the "statusLine" entry that points at ruvnet-brain' });
1484
+ const settings = settingsJsonPath();
1485
+ const detected = detectStatusLine(settings);
1486
+ if (detected.hasStatusLine && !detected.parseError && detected.command === `node "${statuslineHelperPath()}"`) {
1487
+ items.push({ label: 'The statusLine entry in settings.json', path: settings, undo: 'npx ruvnet-brain --uninstall (removes just this key; settings.json is backed up first)' });
1466
1488
  }
1467
1489
  } catch { /* unreadable β€” do not claim it */ }
1468
1490
  try {
@@ -1471,6 +1493,14 @@ export function machineFootprint() {
1471
1493
  items.push({ label: 'The search_ruvnet MCP server registration', path: claudeJson, undo: 'claude mcp remove ruvnet-brain --scope user' });
1472
1494
  }
1473
1495
  } catch { /* unreadable β€” do not claim it */ }
1496
+ // GAP FIX: recordNotified() (scripts/upgrade-notice.mjs, invoked from main() and runDemo() below)
1497
+ // writes this file the first time a "what's new" notice is shown β€” a real disk artifact this
1498
+ // installer's own code path creates, that was never listed here and never removed on --uninstall.
1499
+ // Path duplicated rather than imported (see the cmpTag() comment above: this file must not import
1500
+ // from scripts/ at module scope, since scripts/upgrade-notice.mjs isn't in the npm `files` list β€”
1501
+ // it only reaches a user via a repo clone or `npx github:...`, never a plain npm publish; harmless
1502
+ // no-op existence check either way). Env override name matches STATE_PATH there exactly.
1503
+ add('Upgrade-notice state (release-notice tracking)', upgradeNoticeStatePath(), 'delete this file');
1474
1504
 
1475
1505
  return items;
1476
1506
  }
@@ -1551,7 +1581,10 @@ function uninstallAll() {
1551
1581
  // ours to delete, so they are handed over as commands.
1552
1582
  const AUTO = new Set(['Brain bundle (knowledge base)', 'Nightly updater (LaunchAgent)',
1553
1583
  'Spend watchdog (LaunchAgent)', 'Spend watchdog script', 'CLAUDE.md block (6 lines, between markers)',
1554
- 'Model-router files', 'Status-bar version script', 'Status-bar preference', 'Usage-counts preference']);
1584
+ 'Model-router files', 'Status-bar version script', 'Status-bar preference', 'Usage-counts preference',
1585
+ // Two gaps closed here: the statusLine KEY is now removable in place (we know exactly what we
1586
+ // wrote β€” see removeSettingsStatusLine()), and the upgrade-notice tracker is a file we fully own.
1587
+ 'The statusLine entry in settings.json', 'Upgrade-notice state (release-notice tracking)']);
1555
1588
  const willRemove = before.filter((it) => AUTO.has(it.label));
1556
1589
  const manual = before.filter((it) => !AUTO.has(it.label));
1557
1590
 
@@ -1570,6 +1603,14 @@ function uninstallAll() {
1570
1603
  const claudeMd = removeClaudeMdBlock();
1571
1604
  if (claudeMd === 'removed') ok('removed our block from ~/.claude/CLAUDE.md (your content untouched, backup saved)');
1572
1605
 
1606
+ // GAP FIX: this used to be permanently "manual" β€” machineFootprint() treated ANY settings.json edit
1607
+ // as something only the user could safely touch. That blanket rule was right for edits we cannot
1608
+ // attribute with confidence, but wrong for this one specific key: we wrote it ourselves and know
1609
+ // the exact string we wrote, so we can reverse exactly that (never a status line the user has since
1610
+ // customized β€” removeSettingsStatusLine() checks the live command before touching anything).
1611
+ const statusLine = removeSettingsStatusLine();
1612
+ if (statusLine === 'removed') ok('removed the statusLine entry from ~/.claude/settings.json (your other settings untouched, backup saved)');
1613
+
1573
1614
  // NEVER rm -rf A PATH WE HAVE NOT PROVEN IS OURS. resolvedKbDir() honours $RUVNET_BRAIN_KB, which
1574
1615
  // the docs encourage for custom install locations β€” so `RUVNET_BRAIN_KB=$HOME npx ruvnet-brain
1575
1616
  // --uninstall` would have recursively deleted the user's home directory. Found by adversarial
@@ -1600,6 +1641,11 @@ function uninstallAll() {
1600
1641
  ['status-bar script', path.join(os.homedir(), '.cache', 'ruvnet-brain', 'ruvnet-brain-statusline.cjs')],
1601
1642
  ['status-bar preference', path.join(telemetryStateDir(), '.statusline-pref')],
1602
1643
  ['usage-counts preference', telemetryConsentPath()],
1644
+ // GAP FIX: written by recordNotified() (scripts/upgrade-notice.mjs) whenever the "what's new"
1645
+ // notice fires from main()/runDemo() below β€” a single-purpose file under a directory this
1646
+ // installer alone writes to, safe to remove outright (not the whole ~/.config/ruvnet-brain/ dir,
1647
+ // which can also hold lessons.json/settings.json this installer never creates and must not touch).
1648
+ ['upgrade-notice state', upgradeNoticeStatePath()],
1603
1649
  ]) {
1604
1650
  if (!fs.existsSync(target)) continue;
1605
1651
  try { fs.rmSync(target, { recursive: true, force: true }); ok(`removed the ${label}`); }
@@ -1932,6 +1978,91 @@ export async function offerTelemetry(cacheDir) {
1932
1978
  * So: persistent background jobs and global-config edits require their OWN explicit flag. There is
1933
1979
  * no combination of `-y` alone that installs a daemon.
1934
1980
  */
1981
+ /**
1982
+ * THE PLAN β€” everything this run may do, stated BEFORE the first thing is done.
1983
+ *
1984
+ * WHY THIS EXISTS (real user feedback, 2026-07-24, relayed by the owner): people were not running
1985
+ * `npx ruvnet-brain` *because they could not tell what it would do.* One of them, a sophisticated
1986
+ * user, put the general objection precisely: he dislikes "the virus/plugin approach… it all works in
1987
+ * memory, with invisible hooks and all."
1988
+ *
1989
+ * The installer already asked consent for every high-impact step β€” watchdog, nightly updates,
1990
+ * telemetry, stack tools, statusline. That was necessary and NOT sufficient: consent granted one
1991
+ * question at a time, after the run has already started, never tells you the SHAPE of what you
1992
+ * agreed to. You cannot decline a thing you have not yet been told is coming, and a person deciding
1993
+ * whether to paste a command into their terminal is deciding about the whole run, not about step 4.
1994
+ *
1995
+ * So: the whole list, up front, with what each one costs you and how to undo it. Steps marked [?] are
1996
+ * asked individually as before β€” this screen does not replace those prompts, it makes them
1997
+ * predictable. Nothing here mutates anything; it prints and waits.
1998
+ *
1999
+ * TRUTHFULNESS RULE: every line below names a real step this file performs and a real reversal
2000
+ * command. If a step is added to the installer and not to this list, the list becomes a lie about
2001
+ * the installer β€” which is worse than having no list. Keep them together.
2002
+ */
2003
+ async function printPlanAndConfirm() {
2004
+ const H = os.homedir();
2005
+ const short = (p) => p.replace(H, '~');
2006
+
2007
+ const steps = [
2008
+ { auto: true, name: 'Download the knowledge base',
2009
+ // short() already renders $HOME as "~"; prefixing another one produced "~~/.cache/…".
2010
+ cost: short(resolveCacheDir().cacheDir),
2011
+ what: 'Real rUv source, on your disk, so answers cite files instead of guessing.',
2012
+ undo: 'npx ruvnet-brain --uninstall' },
2013
+ { auto: true, name: 'Register the Claude Code plugin + MCP server',
2014
+ cost: `an entry in ${short(path.join(H, '.claude'))}`,
2015
+ what: 'Gives Claude a search_ruvnet tool. Adds no hooks you have not agreed to.',
2016
+ undo: 'npx ruvnet-brain --uninstall' },
2017
+ { ask: true, name: 'Add rUv tools you are missing',
2018
+ cost: 'npm installs, only the ones you pick',
2019
+ what: 'So the brain can build with them, not just answer questions about them.',
2020
+ undo: 'npm uninstall -g <tool>' },
2021
+ { ask: true, name: 'Nightly auto-updates',
2022
+ cost: 'one LaunchAgent',
2023
+ what: 'Keeps the KB and plugin current. Recommended β€” rUv ships fast, and a stale brain is the main way this stops being useful.',
2024
+ undo: 'npx ruvnet-brain --disable-nightly' },
2025
+ { ask: true, name: 'Spend watchdog',
2026
+ cost: 'one LaunchAgent',
2027
+ what: 'Warns you if an agent fleet starts burning API credit unexpectedly.',
2028
+ undo: 'npx ruvnet-brain --disable-spend-guard' },
2029
+ { ask: true, name: 'Status-bar version segment',
2030
+ cost: 'one line in settings.json',
2031
+ what: 'Shows which brain version is live while you work.',
2032
+ undo: 'npx ruvnet-brain --no-statusline, or delete the statusLine entry' },
2033
+ { ask: true, name: 'Anonymous usage counts',
2034
+ cost: 'a counter ping',
2035
+ what: 'Installs and searches only β€” never your queries, your code, or your paths.',
2036
+ undo: 'npx ruvnet-brain --no-telemetry' },
2037
+ ];
2038
+
2039
+ console.log(` ${c.bold('Here is everything this will do.')} Nothing has happened yet.\n`);
2040
+ for (const s of steps) {
2041
+ const mark = s.auto ? c.green('βœ“') : c.cyan('?');
2042
+ console.log(` ${mark} ${c.bold(s.name)} ${c.dim('Β· ' + s.cost)}`);
2043
+ console.log(` ${s.what}`);
2044
+ console.log(` ${c.dim('undo: ' + s.undo)}\n`);
2045
+ }
2046
+ console.log(` ${c.green('βœ“')} happens automatically. ${c.cyan('?')} is asked first β€” and "no" is a complete answer.`);
2047
+ console.log(` ${c.dim('Every step is reversible, and `npx ruvnet-brain --what-changed` lists everything it touched.')}\n`);
2048
+
2049
+ // FLAG_YES means the caller already decided; a plan screen that blocks automation would break
2050
+ // agentic-kit and every scripted install. Print it, then proceed β€” the information is the point,
2051
+ // the pause is a courtesy to humans.
2052
+ if (FLAG_YES || FLAG_AUTO || !process.stdin.isTTY) {
2053
+ console.log(c.dim(' (non-interactive β€” continuing)\n'));
2054
+ return;
2055
+ }
2056
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
2057
+ const a = await new Promise((r) => rl.question(` ${c.cyan('?')} Continue? ${c.dim('[Y/n]')} `, r));
2058
+ rl.close();
2059
+ if (/^n/i.test(String(a).trim())) {
2060
+ console.log(`\n Stopped. Nothing was changed.\n`);
2061
+ process.exit(0);
2062
+ }
2063
+ console.log('');
2064
+ }
2065
+
1935
2066
  function ask(question, def = false, { blanketYes = true } = {}) {
1936
2067
  if (FLAG_YES && blanketYes) return Promise.resolve(true);
1937
2068
  if (!process.stdin.isTTY) return Promise.resolve(def);
@@ -2224,6 +2355,33 @@ function writeSettingsStatusLine(detected, command) {
2224
2355
  return backup;
2225
2356
  }
2226
2357
 
2358
+ // The uninstall-side mirror of writeSettingsStatusLine() above β€” this installer is the ONLY writer
2359
+ // that can safely reverse this specific edit, because it is the only one that knows the EXACT string
2360
+ // it wrote. Match on that exact command (never a loose "mentions ruvnet-brain" guess β€” see the
2361
+ // machineFootprint() comment this replaces) so a status line the user has since folded their own
2362
+ // script into, or edited by hand, is left completely alone. Same "refuse rather than guess"
2363
+ // discipline removeClaudeMdBlock() already applies to CLAUDE.md, and the same backup-first courtesy.
2364
+ function removeSettingsStatusLine() {
2365
+ const settingsPath = settingsJsonPath();
2366
+ const detected = detectStatusLine(settingsPath);
2367
+ if (!detected.exists || detected.parseError || !detected.hasStatusLine) return 'absent';
2368
+ const ours = `node "${statuslineHelperPath()}"`;
2369
+ if (detected.command !== ours) return 'not-ours'; // never touch a status line we didn't write
2370
+ try {
2371
+ const backup = backupSettingsJson(settingsPath);
2372
+ const next = { ...(detected.json || {}) };
2373
+ delete next.statusLine;
2374
+ const tmp = `${settingsPath}.ruvnet-tmp`;
2375
+ fs.writeFileSync(tmp, JSON.stringify(next, null, 2) + '\n');
2376
+ fs.renameSync(tmp, settingsPath);
2377
+ info(c.dim(` your original is saved at ${backup}`));
2378
+ return 'removed';
2379
+ } catch (e) {
2380
+ warn(`couldn't remove the statusLine entry (${e.message}) β€” remove it yourself from ${settingsPath}`);
2381
+ return 'error';
2382
+ }
2383
+ }
2384
+
2227
2385
  // Only called after explicit consent. NEVER overwrites an existing statusLine β€” detectStatusLine()
2228
2386
  // is the single source of truth for "is one already there", checked fresh right before any write.
2229
2387
  function applyStatusline() {
@@ -2491,6 +2649,8 @@ It is safe to re-run at any time. After installing, restart Claude Code so the g
2491
2649
  }
2492
2650
  }
2493
2651
 
2652
+ await printPlanAndConfirm();
2653
+
2494
2654
  const { cacheDir, isCustom } = resolveCacheDir();
2495
2655
 
2496
2656
  // ── "ALREADY PRESENT" IS THE WRONG QUESTION β€” ask "already CURRENT" ──────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvnet-brain",
3
- "version": "3.9.18-dev",
3
+ "version": "3.9.51-dev",
4
4
  "description": "One-command installer for RuvNet Brain \u2014 a portable, source-grounded brain over rUv's RuvNet building blocks, delivered as a Claude Code plugin so Claude uses the stack instead of fighting it.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,7 +31,8 @@
31
31
  "falsify": "node scripts/falsify.mjs",
32
32
  "sbom": "npx --yes @cyclonedx/cyclonedx-npm --omit dev --output-file sbom/ruvnet-brain.cdx.json --mc-type application --validate",
33
33
  "wired:check": "node scripts/wired-check.mjs --check",
34
- "doc:currency": "node scripts/doc-currency.mjs --check"
34
+ "doc:currency": "node scripts/doc-currency.mjs --check",
35
+ "status:check": "node scripts/status-honesty.mjs"
35
36
  },
36
37
  "files": [
37
38
  "bin/install.mjs",