ruvnet-brain 4.0.2 โ†’ 4.0.4

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 4.0.2 โ€” updated 2026-07-30 03:24 EDT](https://img.shields.io/badge/version_4.0.2-updated_2026--07--30_03:24_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 4.0.4 โ€” updated 2026-07-30 03:24 EDT](https://img.shields.io/badge/version_4.0.4-updated_2026--07--30_03:24_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
 
package/bin/install.mjs CHANGED
@@ -604,7 +604,7 @@ function installReader(cacheDir) {
604
604
  // though installation was green. Persist the exact runtime shipped by the installer underneath the
605
605
  // installed KB: Node then resolves optional reader dependencies from `<cacheDir>/node_modules`, and
606
606
  // both Claude Code and Codex get one stable path with no source clone or second package install.
607
- export function installConsoleRuntime(cacheDir, sourceRoot = REPO_ROOT) {
607
+ export function beginConsoleRuntimeTransaction(cacheDir, sourceRoot = REPO_ROOT) {
608
608
  const runtime = path.join(cacheDir, '.console-runtime');
609
609
  const staged = `${runtime}.tmp-${process.pid}`;
610
610
  const prior = `${runtime}.prior-${process.pid}`;
@@ -631,16 +631,103 @@ export function installConsoleRuntime(cacheDir, sourceRoot = REPO_ROOT) {
631
631
  fs.cpSync(source, target, { recursive: true, force: true, preserveTimestamps: true });
632
632
  }
633
633
 
634
- if (fs.existsSync(runtime)) fs.renameSync(runtime, prior);
634
+ let packageManifest;
635
635
  try {
636
- fs.renameSync(staged, runtime);
637
- fs.rmSync(prior, { recursive: true, force: true });
636
+ packageManifest = JSON.parse(fs.readFileSync(path.join(staged, 'package.json'), 'utf8'));
638
637
  } catch (error) {
639
- if (fs.existsSync(prior) && !fs.existsSync(runtime)) fs.renameSync(prior, runtime);
640
638
  fs.rmSync(staged, { recursive: true, force: true });
639
+ throw new Error(`console runtime identity is invalid: ${error.message}`);
640
+ }
641
+ if (!packageManifest.version || packageManifest.version !== PACKAGE_VERSION) {
642
+ fs.rmSync(staged, { recursive: true, force: true });
643
+ throw new Error(`console runtime version ${packageManifest.version || '(missing)'} does not match candidate ${PACKAGE_VERSION}`);
644
+ }
645
+ const stagedEntry = path.join(staged, 'scripts', 'onboarding-console.mjs');
646
+ for (const syntaxTarget of [stagedEntry, path.join(staged, 'bin', 'install.mjs')]) {
647
+ const checked = spawnSync(process.execPath, ['--check', syntaxTarget], { encoding: 'utf8' });
648
+ if (checked.error || checked.status !== 0) {
649
+ fs.rmSync(staged, { recursive: true, force: true });
650
+ throw new Error(`console runtime failed syntax verification: ${path.relative(staged, syntaxTarget)}`);
651
+ }
652
+ }
653
+ const identity = {
654
+ product: 'ruvnet-brain-console-runtime',
655
+ schema: 1,
656
+ apiContract: 1,
657
+ runtimeVersion: packageManifest.version,
658
+ entrypoint: 'scripts/onboarding-console.mjs',
659
+ sourceSha256: crypto.createHash('sha256').update(fs.readFileSync(stagedEntry)).digest('hex'),
660
+ };
661
+ fs.writeFileSync(path.join(staged, 'runtime-identity.json'), `${JSON.stringify(identity, null, 2)}\n`, { mode: 0o600 });
662
+
663
+ let state = 'staged';
664
+ const entry = path.join(runtime, identity.entrypoint);
665
+ return {
666
+ identity,
667
+ entry,
668
+ activate() {
669
+ if (state !== 'staged') throw new Error(`console runtime transaction cannot activate from ${state}`);
670
+ if (fs.existsSync(runtime)) fs.renameSync(runtime, prior);
671
+ try {
672
+ fs.renameSync(staged, runtime);
673
+ state = 'active';
674
+ return entry;
675
+ } catch (error) {
676
+ if (fs.existsSync(prior) && !fs.existsSync(runtime)) fs.renameSync(prior, runtime);
677
+ fs.rmSync(staged, { recursive: true, force: true });
678
+ state = 'rolled-back';
679
+ throw error;
680
+ }
681
+ },
682
+ commit() {
683
+ if (state !== 'active') throw new Error(`console runtime transaction cannot commit from ${state}`);
684
+ fs.rmSync(prior, { recursive: true, force: true });
685
+ state = 'committed';
686
+ return entry;
687
+ },
688
+ rollback() {
689
+ if (state === 'staged') fs.rmSync(staged, { recursive: true, force: true });
690
+ if (state === 'active') {
691
+ fs.rmSync(runtime, { recursive: true, force: true });
692
+ if (fs.existsSync(prior)) fs.renameSync(prior, runtime);
693
+ }
694
+ fs.rmSync(staged, { recursive: true, force: true });
695
+ fs.rmSync(prior, { recursive: true, force: true });
696
+ state = 'rolled-back';
697
+ },
698
+ };
699
+ }
700
+
701
+ export function installConsoleRuntime(cacheDir, sourceRoot = REPO_ROOT) {
702
+ const transaction = beginConsoleRuntimeTransaction(cacheDir, sourceRoot);
703
+ try {
704
+ transaction.activate();
705
+ return transaction.commit();
706
+ } catch (error) {
707
+ transaction.rollback();
641
708
  throw error;
642
709
  }
643
- return path.join(runtime, 'scripts', 'onboarding-console.mjs');
710
+ }
711
+
712
+ export function consoleRestartState(identity, {
713
+ receiptDir = path.join(process.env.RUVNET_BRAIN_HOME || path.join(os.homedir(), '.cache', 'ruvnet-brain'), 'console-instances'),
714
+ } = {}) {
715
+ let receipts = [];
716
+ try {
717
+ receipts = fs.readdirSync(receiptDir)
718
+ .filter((name) => name.endsWith('.json'))
719
+ .map((name) => {
720
+ try { return JSON.parse(fs.readFileSync(path.join(receiptDir, name), 'utf8')); }
721
+ catch { return null; }
722
+ })
723
+ .filter((receipt) => receipt?.product === 'ruvnet-brain-console' && receipt.schema === 1);
724
+ } catch { /* no running Console receipts is the ordinary ready state */ }
725
+ const staleInstances = receipts.filter((receipt) => receipt.sourceSha256 !== identity.sourceSha256).length;
726
+ return {
727
+ state: staleInstances > 0 ? 'pending-console-restart' : 'ready',
728
+ instanceReceipts: receipts.length,
729
+ staleInstances,
730
+ };
644
731
  }
645
732
 
646
733
  // โ”€โ”€ plugin presence: the ONLY reliable proof the slash commands will exist โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -798,6 +885,7 @@ function codexManagedBlock(serverPath) {
798
885
  '[mcp_servers.ruvnet-brain]',
799
886
  'command = "node"',
800
887
  `args = [${JSON.stringify(serverPath)}]`,
888
+ 'startup_timeout_sec = 30',
801
889
  CODEX_BLOCK_END,
802
890
  ].join('\n');
803
891
  }
@@ -855,7 +943,11 @@ function removeCodexWiring() {
855
943
  // What the doctor asserts from disk โ€” never from the fact that we once ran. "Wired" means our entry
856
944
  // is in the config AND the server.mjs it points at is really there, because a registration pointing
857
945
  // at a deleted file is worse than no registration: Codex fails at spawn time with nothing to read.
858
- export function codexStatus({ configPath = codexConfigPath(), codexDir = codexHomeDir() } = {}) {
946
+ export function codexStatus({
947
+ configPath = codexConfigPath(),
948
+ codexDir = codexHomeDir(),
949
+ brainHome = path.join(path.dirname(codexDir), '.cache', 'ruvnet-brain'),
950
+ } = {}) {
859
951
  let host = false;
860
952
  try { host = fs.existsSync(codexDir); } catch { /* unreadable โ€” treat as absent */ }
861
953
  if (!host) return { host: false, wired: false, serverPath: null, serverExists: false };
@@ -866,7 +958,63 @@ export function codexStatus({ configPath = codexConfigPath(), codexDir = codexHo
866
958
  try { serverPath = m ? JSON.parse(m[1]) : null; } catch { /* malformed entry โ€” treat as absent */ }
867
959
  let serverExists = false;
868
960
  try { serverExists = serverPath ? fs.existsSync(serverPath) : false; } catch { /* unreadable */ }
869
- return { host: true, wired: Boolean(serverPath) && serverExists, serverPath, serverExists };
961
+ const section = m ? text.slice(m.index).split(/\n[ \t]*\[/, 1)[0] : '';
962
+ const timeoutMatch = /(?:^|\n)[ \t]*startup_timeout_sec\s*=\s*(\d+)/.exec(section);
963
+ const startupTimeoutSec = timeoutMatch ? Number(timeoutMatch[1]) : null;
964
+ const readinessReceipt = (() => {
965
+ try { return JSON.parse(fs.readFileSync(path.join(brainHome, 'mcp-readiness.json'), 'utf8')); }
966
+ catch { return null; }
967
+ })();
968
+ let live = false;
969
+ if (readinessReceipt?.state === 'ready'
970
+ && Number.isInteger(readinessReceipt.pid)
971
+ && Number.isInteger(readinessReceipt.workerPid)) {
972
+ try {
973
+ process.kill(readinessReceipt.pid, 0);
974
+ process.kill(readinessReceipt.workerPid, 0);
975
+ live = true;
976
+ } catch { /* stale process or worker receipt */ }
977
+ }
978
+ const readiness = live ? 'ready' : readinessReceipt?.state === 'degraded' ? 'degraded' : 'registered';
979
+ return {
980
+ host: true,
981
+ wired: Boolean(serverPath) && serverExists,
982
+ serverPath,
983
+ serverExists,
984
+ startupTimeoutSec,
985
+ readiness,
986
+ live,
987
+ readinessReceipt,
988
+ };
989
+ }
990
+
991
+ export function codexMcpGuidance(status) {
992
+ if (!status?.wired) {
993
+ return { healthy: false, blocking: true, summary: 'Codex MCP is not registered to a usable server.', detail: null };
994
+ }
995
+ if (status.live && status.readiness === 'ready') {
996
+ return {
997
+ healthy: true,
998
+ blocking: false,
999
+ summary: `Codex MCP ready and live; discovery uses a ${status.startupTimeoutSec ?? 'configured'}s startup deadline.`,
1000
+ detail: 'A running MCP shell completed worker initialize and warmup.',
1001
+ };
1002
+ }
1003
+ if (status.readiness === 'degraded') {
1004
+ const receipt = status.readinessReceipt || {};
1005
+ return {
1006
+ healthy: false,
1007
+ blocking: true,
1008
+ summary: 'Codex MCP is registered but degraded.',
1009
+ detail: `${receipt.phase || 'startup'}: ${receipt.error || 'worker readiness failed'}`,
1010
+ };
1011
+ }
1012
+ return {
1013
+ healthy: false,
1014
+ blocking: false,
1015
+ summary: `Codex MCP is registered with a ${status.startupTimeoutSec ?? 'missing'}s startup deadline; live readiness is not yet proven.`,
1016
+ detail: 'The first real search establishes worker readiness without removing search_ruvnet from discovery.',
1017
+ };
870
1018
  }
871
1019
 
872
1020
  // Atomic file replacement: produce the new bytes BESIDE the target, then rename() over it. Against
@@ -1678,11 +1826,14 @@ async function doctor() {
1678
1826
  // from disk (our entry present AND the server.mjs it names really there), never asserted from the
1679
1827
  // fact that an install once ran.
1680
1828
  const cx = codexStatus();
1829
+ let codexMcp = null;
1681
1830
  let codexLifecycle = null;
1682
1831
  if (!cx.host) {
1683
1832
  console.log(` ${c.dim('Codex: no host detected (no ~/.codex) โ€” nothing to wire.')}`);
1684
1833
  } else if (cx.wired) {
1685
- console.log(` ${c.green('โœ“ Codex: wired.')} search_ruvnet is registered in ~/.codex/config.toml and its server exists.`);
1834
+ codexMcp = codexMcpGuidance(cx);
1835
+ console.log(` ${codexMcp.healthy ? c.green('โœ“') : codexMcp.blocking ? c.yellow('!') : c.dim('โ—‹')} ${codexMcp.summary}`);
1836
+ if (codexMcp.detail) console.log(` ${codexMcp.detail}`);
1686
1837
  codexLifecycle = await codexLifecycleStatus();
1687
1838
  printCodexLifecycle(codexLifecycle);
1688
1839
  } else {
@@ -1751,10 +1902,12 @@ async function doctor() {
1751
1902
  && !codexLifecycleGuidance(codexLifecycle).intentional,
1752
1903
  );
1753
1904
  const codexWiringFailed = Boolean(cx.host && !cx.wired);
1905
+ const codexReadinessFailed = Boolean(codexMcp?.blocking);
1754
1906
  const failed = (hookResult ? hookResult.exitCode !== 0 : !allGreen)
1755
1907
  || groundingUnprovenPersisted
1756
1908
  || codexLifecycleFailed
1757
1909
  || codexWiringFailed
1910
+ || codexReadinessFailed
1758
1911
  || Boolean(rufloOperational && !rufloOperational.healthy);
1759
1912
  if (failed && !hookResult && !groundingUnprovenPersisted) {
1760
1913
  console.log(` ${c.red('โœ— FAILING')} โ€” the warnings above are real. Re-run ${c.bold('npx ruvnet-brain')} to repair.`);
@@ -2018,32 +2171,61 @@ function missingUpdaterHelp(kbDir) {
2018
2171
  console.error(` โ€” the current bundle ships forge-update.mjs; then this command will work.`);
2019
2172
  }
2020
2173
 
2021
- function syncHostsAfterUpdate() {
2174
+ export function syncHostsAfterUpdate(cacheDir = resolvedKbDir(), {
2175
+ sourceRoot = REPO_ROOT,
2176
+ brainHome = process.env.RUVNET_BRAIN_HOME || path.join(os.homedir(), '.cache', 'ruvnet-brain'),
2177
+ consoleReceiptDir = path.join(brainHome, 'console-instances'),
2178
+ wireClaude = wirePlugin,
2179
+ wireCodexHost: detectCodexHost = wireCodexHost,
2180
+ wireCodexPlugin: installCodexPlugin = wireCodexPlugin,
2181
+ runStableSpine = (apply) => spawnSync(
2182
+ process.execPath,
2183
+ [apply, '--auto', '--expected-version', PACKAGE_VERSION],
2184
+ { stdio: 'inherit', env: process.env },
2185
+ ),
2186
+ } = {}) {
2022
2187
  const results = {};
2023
- results.claude = wirePlugin({ expectedVersion: PACKAGE_VERSION, requireManaged: true });
2024
- if (results.claude.host && !results.claude.wired) return { ok: false, results };
2188
+ let runtimeTransaction;
2025
2189
  try {
2026
- results.codexHost = wireCodexHost();
2190
+ runtimeTransaction = beginConsoleRuntimeTransaction(cacheDir, sourceRoot);
2191
+ } catch (error) {
2192
+ return { ok: false, results, error: `Console runtime staging failed: ${error.message}` };
2193
+ }
2194
+ const fail = (detail = {}) => {
2195
+ runtimeTransaction.rollback();
2196
+ return { ok: false, results, ...detail };
2197
+ };
2198
+
2199
+ try {
2200
+ results.claude = wireClaude === wirePlugin
2201
+ ? wirePlugin({ expectedVersion: PACKAGE_VERSION, requireManaged: true })
2202
+ : wireClaude({ expectedVersion: PACKAGE_VERSION, requireManaged: true });
2203
+ if (results.claude.host && !results.claude.wired) return fail();
2204
+ results.codexHost = detectCodexHost();
2027
2205
  if (results.codexHost.host) {
2028
- results.codex = wireCodexPlugin({ expectedVersion: PACKAGE_VERSION });
2206
+ results.codex = installCodexPlugin({ expectedVersion: PACKAGE_VERSION });
2029
2207
  if (!['unchanged', 'installed', 'updated', 'disabled'].includes(results.codex.action)) {
2030
- return { ok: false, results };
2208
+ return fail();
2031
2209
  }
2032
2210
  }
2033
2211
  } catch (error) {
2034
2212
  results.codex = { action: 'failed', error: error?.message || String(error) };
2035
- return { ok: false, results };
2213
+ return fail();
2036
2214
  }
2037
2215
 
2038
- const apply = path.join(__dirname, '..', 'plugin', 'scripts', 'update-apply.mjs');
2039
- if (!fs.existsSync(apply)) return { ok: false, results, error: 'Stable Spine updater missing from package' };
2040
- const applied = spawnSync(process.execPath, [apply, '--auto', '--expected-version', PACKAGE_VERSION], { stdio: 'inherit', env: process.env });
2216
+ const apply = path.join(sourceRoot, 'plugin', 'scripts', 'update-apply.mjs');
2217
+ if (!fs.existsSync(apply)) return fail({ error: 'Stable Spine updater missing from package' });
2218
+ const applied = runStableSpine(apply);
2041
2219
  const okApplied = !applied.error && applied.status === 0;
2042
2220
  if (okApplied) {
2043
- const home = process.env.RUVNET_BRAIN_HOME || path.join(os.homedir(), '.cache', 'ruvnet-brain');
2044
- const receiptPath = path.join(home, 'host-convergence.json');
2221
+ const receiptPath = path.join(brainHome, 'host-convergence.json');
2045
2222
  try {
2046
- fs.mkdirSync(home, { recursive: true });
2223
+ runtimeTransaction.activate();
2224
+ results.consoleRuntime = {
2225
+ ...runtimeTransaction.identity,
2226
+ ...consoleRestartState(runtimeTransaction.identity, { receiptDir: consoleReceiptDir }),
2227
+ };
2228
+ fs.mkdirSync(brainHome, { recursive: true });
2047
2229
  const tmp = `${receiptPath}.tmp-${process.pid}`;
2048
2230
  fs.writeFileSync(tmp, JSON.stringify({
2049
2231
  desiredVersion: PACKAGE_VERSION,
@@ -2052,13 +2234,16 @@ function syncHostsAfterUpdate() {
2052
2234
  claude: { state: results.claude.host ? 'ready' : 'absent', version: results.claude.version || null },
2053
2235
  codex: { state: results.codex?.action === 'disabled' ? 'disabled' : (results.codexHost?.host ? 'ready' : 'absent'), version: results.codex?.version || null },
2054
2236
  },
2237
+ consoleRuntime: results.consoleRuntime,
2055
2238
  }, null, 2));
2056
2239
  fs.renameSync(tmp, receiptPath);
2240
+ runtimeTransaction.commit();
2057
2241
  } catch (error) {
2058
- return { ok: false, results, applyStatus: applied.status, error: `host convergence receipt failed: ${error.message}` };
2242
+ return fail({ applyStatus: applied.status, error: `Console runtime convergence failed: ${error.message}` });
2059
2243
  }
2060
2244
  }
2061
- return { ok: okApplied, results, applyStatus: applied.status };
2245
+ if (!okApplied) return fail({ applyStatus: applied.status, error: applied.error?.message || 'Stable Spine activation failed' });
2246
+ return { ok: true, results, applyStatus: applied.status };
2062
2247
  }
2063
2248
 
2064
2249
  function runUpdate() {
@@ -2103,7 +2288,7 @@ function runUpdate() {
2103
2288
  }
2104
2289
  if (updateStatus === 0) {
2105
2290
  info(c.dim('\nsynchronizing every detected host to this exact published versionโ€ฆ\n'));
2106
- const convergence = syncHostsAfterUpdate();
2291
+ const convergence = syncHostsAfterUpdate(kbDir);
2107
2292
  if (!convergence.ok) {
2108
2293
  warn(`host synchronization is incomplete โ€” runtime stays on the prior verified generation${convergence.error ? ` (${convergence.error})` : ''}`);
2109
2294
  updateStatus = 1;
@@ -2477,15 +2662,14 @@ function printFootprint({ heading = 'What this put on your machine' } = {}) {
2477
2662
  }
2478
2663
 
2479
2664
  function showWhatsNew() {
2480
- const notes = path.join(REPO_ROOT, 'docs', 'RELEASE-NOTES-4.0.md');
2481
- if (!fs.existsSync(notes)) {
2482
- console.error('RuvNet Brain release notes are missing from this artifact.');
2665
+ const executable = path.join(REPO_ROOT, 'plugin', 'scripts', 'whats-new.mjs');
2666
+ const result = spawnSync(process.execPath, [executable], { stdio: 'inherit' });
2667
+ if (result.error) {
2668
+ console.error(`RuvNet Brain What's New failed: ${result.error.message}`);
2483
2669
  process.exitCode = 1;
2484
2670
  return;
2485
2671
  }
2486
- const text = fs.readFileSync(notes, 'utf8');
2487
- process.stdout.write(text);
2488
- if (!text.endsWith('\n')) process.stdout.write('\n');
2672
+ process.exitCode = result.status === 0 ? 0 : 1;
2489
2673
  }
2490
2674
 
2491
2675
  /**
@@ -1,6 +1,6 @@
1
1
  # RuvNet-Brain 4.0 line โ€” what's new (the major-release highlights)
2
2
 
3
- Updated: 2026-07-30
3
+ Updated: 2026-08-01
4
4
 
5
5
  > **Source of truth** for the `/whats-new` command and the first-run upgrade message. Curated, honest,
6
6
  > major-only โ€” not the point-release churn. If a claim here isn't true of the shipping build, it does not
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvnet-brain",
3
- "version": "4.0.2",
3
+ "version": "4.0.4",
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": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ruvnet-brain",
3
3
  "description": "RuvNet brain transplant for Claude Code โ€” grounds every RuvNet decision in real source across 69 rUv repositories, prefers Ruflo / RuVector-RVF / AgentDB over training-prior defaults (pgvector, Pinecone, hand-rolled cosine), and can pull in any RuvNet repo on demand. Ships an enforced UserPromptSubmit retrieve-and-inject grounding hook that sharply reduces drift.",
4
- "version": "4.0.2",
4
+ "version": "4.0.4",
5
5
  "author": {
6
6
  "name": "Stuart Kerr"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvnet-brain",
3
- "version": "4.0.2",
3
+ "version": "4.0.4",
4
4
  "description": "Source-grounded RuvNet knowledge, lifecycle enforcement, and learning for Codex.",
5
5
  "author": {
6
6
  "name": "Stuart Kerr"
@@ -9,14 +9,15 @@ The user wants the headline story of the **major** release they're on โ€” the 4.
9
9
  **not** the "3.9.x โ†’ 3.9.y" point-release churn. Deliver it warmly and honestly, then offer the Console.
10
10
 
11
11
  **First, ground โ€” never recite this from memory (it drifts every release):**
12
- 1. Read the running version from `${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json`. State it honestly.
12
+ 1. Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/whats-new.mjs"`. This reads the running version and curated
13
+ notes from the same immutable installed payload. If it exits nonzero, report that exact failure;
14
+ do not substitute a checkout, download, or another installed version. State the version honestly.
13
15
  **Do NOT claim "you're on 4.0"** unless the version literally starts with `4.` โ€” per ADR-042 the
14
16
  number stays `3.9.x-dev` until the 4.0 line is field-verified. The honest framing is: *"these are the
15
17
  4.0-line enhancements, and you already have them โ€” the version stamps to 4.0 once they're proven in
16
18
  real use."*
17
- 2. Read `docs/RELEASE-NOTES-4.0.md` (the curated highlights + its VERSION STATUS banner). Its content is
18
- the source of truth for what follows โ€” summarize it, don't invent beside it, and carry its honest
19
- version framing.
19
+ 2. Treat the executable's notes as the source of truth โ€” summarize them, don't invent beside them,
20
+ and carry their honest version framing.
20
21
 
21
22
  **Then tell them what the 4.0 line is โ€” the honest headline (adapt to the notes file; do NOT overclaim):**
22
23
 
@@ -49,8 +50,7 @@ word โ€” or type `/rvbc`."* If they say yes, follow `rvbc.md` in this same direc
49
50
  warm heads-up about the ~20s scan).
50
51
 
51
52
  **Honesty rules for this command (same as the product):**
52
- - If `docs/RELEASE-NOTES-4.0.md` is missing, say the notes aren't written yet and summarize from what
53
- you can verify โ€” never fabricate a highlight.
53
+ - If the installed executable fails, report its failure and do not fabricate a highlight.
54
54
  - Never claim a metric ("40% better", "95/100") the release has not independently earned. The self-
55
55
  measurement is new and still filling; say so plainly.
56
56
  - This is the MAJOR story only. If the user wants the point-release detail, point them at the repo's
@@ -0,0 +1,88 @@
1
+ # RuvNet-Brain 4.0 line โ€” what's new (the major-release highlights)
2
+
3
+ Updated: 2026-08-01
4
+
5
+ > **Source of truth** for the `/whats-new` command and the first-run upgrade message. Curated, honest,
6
+ > major-only โ€” not the point-release churn. If a claim here isn't true of the shipping build, it does not
7
+ > belong here. The self-measurement claims are deliberately hedged: they are *new and filling*, not
8
+ > *proven*.
9
+ >
10
+ > **VERSION STATUS:** the public release line is now 4.x. Publication does not erase the remaining
11
+ > acceptance obligations: the exact public artifact must still pass the fail-closed release contract
12
+ > through fresh Claude Code and Codex hosts. Anything not proven through that boundary is listed as a
13
+ > limitation, never converted into a capability claim.
14
+
15
+ **One line:** the 4.0 line is where the brain got **honest, legible, fast, and self-measuring** โ€” and
16
+ it's landing now.
17
+
18
+ ## The big things
19
+
20
+ ### Release proof is fail-closed
21
+ The 4.0 release path now separates a clean candidate seal from a post-publication seal. Dirty
22
+ lineage, zero/skipped/todo tests, open issues, red or pending exact-SHA workflows, a missing
23
+ `ruvnet-brain` self-RVF store, weak query-deadline margin, missing independent graders,
24
+ host/artifact mismatches, and public-byte drift are release failures rather than warnings.
25
+ `npm run release:proof -- --status --quick` shows the current live blockers.
26
+
27
+ ### 1. The Console is the front door
28
+ Type `/rvbc` and your whole RuvNet stack is on one live local page: what's installed, what the AI has
29
+ actually learned from *your* projects (real memories + distilled lessons, drill-down to the verbatim
30
+ cards), which subscription pays for what, and one-click **reversible** fixes for anything stale. New in
31
+ 4.0:
32
+ - a plain-English **explainer on every card** (no more guessing what "trust & provenance" means),
33
+ - every suggestion carries its **blast radius** โ€” *just this project* vs *every project ยท this machine*,
34
+ - **safe on/off checkboxes** that appear *only* where the undo is proven,
35
+ - a **terminal-first install** โ€” the granular "here's exactly what I'll do, uncheck any of it" flow runs
36
+ in your terminal, where an `npx` user expects it.
37
+
38
+ ### 2. It will not lie about your machine
39
+ Every number is measured live from your setup. **"We couldn't check" never renders as "off."** One
40
+ project's state can never leak into another project's view (a real bug 4.0 fixed in the console itself).
41
+ Empty-first, honest-always.
42
+
43
+ ### 3. Fast โ€” and it tells you when it's ready
44
+ The console and tips page paint in **well under a second** (measured, with a QE suite that runs every
45
+ time). On a first scan it shows a **countdown** and then says *"it's live โ€” take a look at your page,"*
46
+ so you're never staring at a blank screen wondering if it hung.
47
+
48
+ ### 4. It measures itself now
49
+ The brain records when it offered help and whether you acted on it โ€” so over time it can **prove** it's
50
+ improving instead of asserting it. **Honest caveat:** this instrumentation is *new* and has only just
51
+ started collecting. 4.0 is not a claim of "proven better in the field" โ€” it is the release that makes
52
+ that proof *possible*, and the evidence accrues as you use it.
53
+
54
+ ### 5. It learns across your projects
55
+ A lesson proven in one project can be **promoted to your global brain** and applied everywhere โ€” and it
56
+ now **survives an update** (tested against the real updater, not argued).
57
+
58
+ ### 6. Runs on your account, cheapest capable model
59
+ The QE suite and model routing use **your Claude account, not an API key**, at the least-powerful model
60
+ that does the job. Nothing bills silently.
61
+
62
+ ### 7. Claude Code and Codex share one active runtime
63
+ Both hosts are wired to the same Stable Spine generation: the MCP search shell, lifecycle hooks,
64
+ skills and update behavior advance as one versioned runtime instead of being installed as unrelated
65
+ copies.
66
+
67
+ ### 8. Source grounding is an RVF-native product surface
68
+ The Brain searches per-repository RuVector RVF stores, joins hits to their source passages, and
69
+ returns cited repository paths. Model memory is not accepted as evidence for rUv-stack claims.
70
+
71
+ ### 9. Quality and harness workflows are explicit
72
+ Agentic-QE is the testing fleet. Harness scoring evaluates the orchestration layer, and cost-aware
73
+ routing can select cheaper capable models when the required provider access is configured. These are
74
+ named workflows a user can request, not silent substitutes.
75
+
76
+ ## What 4.0 deliberately does NOT claim
77
+ Stated up front because overclaiming is the one thing this product cannot do:
78
+ - **Not** "proven X% better" โ€” the outcome ledger is still filling (see #4).
79
+ - **Not** "fully proactive / anticipatory" โ€” the brain still mostly speaks when you open the console or
80
+ ask; the in-session, unprompted surface is the next frontier, not a shipped 4.0 guarantee.
81
+ - **Not** independently graded โ‰ฅ95 on the exact public artifact. A score is not a release substitute.
82
+ - **Not** fully accepted while any critical exact-artifact, host, retrieval, version-convergence or
83
+ recovery invariant is FAIL, UNKNOWN, skipped or mocked-only.
84
+
85
+ ## For upgraders
86
+ On the first real major-version transition, the installer/session experience presents the concise
87
+ highlights once. Run `npx ruvnet-brain --whats-new` at any time to read this full list again, or
88
+ `npx ruvnet-brain --what-changed` to inspect the exact machine footprint and undo path for each piece.