ruvnet-brain 4.6.4 → 4.7.1

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 (82) hide show
  1. package/README.md +1 -1
  2. package/bin/install.mjs +65 -54
  3. package/config/practical-rule-catalog.json +22 -22
  4. package/console/app.js +1 -1
  5. package/console/architecture.html +2 -2
  6. package/package.json +1 -2
  7. package/plugin/.claude-plugin/plugin.json +1 -1
  8. package/plugin/.codex-plugin/plugin.json +1 -1
  9. package/plugin/.grok-plugin/plugin.json +12 -0
  10. package/plugin/commands/checkpoint.md +22 -40
  11. package/plugin/commands/rnb-brief.md +18 -15
  12. package/plugin/hooks/codex-hooks.json +1 -28
  13. package/plugin/hooks/grok-hooks.json +95 -0
  14. package/plugin/hooks/hook-contracts.json +6 -127
  15. package/plugin/hooks/hooks.json +1 -40
  16. package/plugin/host-adapters/grok.json +11 -0
  17. package/plugin/mcp/managed-cli-interface.mjs +0 -56
  18. package/plugin/scripts/agentdb-recall-reader.mjs +29 -9
  19. package/plugin/scripts/agentdb-recall.mjs +35 -27
  20. package/plugin/scripts/automated-run.mjs +42 -0
  21. package/plugin/scripts/brain-footprint.mjs +1 -1
  22. package/plugin/scripts/checkpoint-keys.mjs +46 -0
  23. package/plugin/scripts/codex-hook-wrapper.mjs +13 -3
  24. package/plugin/scripts/completion-claim-evidence.mjs +1 -1
  25. package/plugin/scripts/continuation-gate.mjs +6 -0
  26. package/plugin/scripts/continuity-events.mjs +90 -2
  27. package/plugin/scripts/continuity-hook-policy.mjs +5 -14
  28. package/plugin/scripts/decision-gate.mjs +2 -2
  29. package/plugin/scripts/decision-record.mjs +11 -40
  30. package/plugin/scripts/grok-hook-adapter.mjs +155 -0
  31. package/plugin/scripts/hook-failures.mjs +16 -0
  32. package/plugin/scripts/hook-shim.mjs +1 -2
  33. package/plugin/scripts/learn-capture.mjs +60 -42
  34. package/plugin/scripts/learning-store.mjs +70 -60
  35. package/plugin/scripts/node-sqlite.mjs +1 -1
  36. package/plugin/scripts/ruflo-bin.mjs +71 -1
  37. package/plugin/scripts/ruflo-memory-hooks.mjs +204 -0
  38. package/plugin/scripts/runtime-preferences.mjs +1 -1
  39. package/plugin/scripts/session-snapshot-hook.mjs +85 -341
  40. package/plugin/scripts/session-start-core.mjs +2 -12
  41. package/plugin/scripts/signal-watch.mjs +1 -1
  42. package/plugin/scripts/substitution-wall.mjs +222 -0
  43. package/plugin/scripts/turn-outcome-capture.mjs +68 -74
  44. package/scripts/codex-host-execution-proof.mjs +1 -1
  45. package/scripts/console-engine.mjs +0 -32
  46. package/scripts/health-repair.mjs +1 -81
  47. package/scripts/hook-qualify-core.mjs +2 -2
  48. package/scripts/model-managed-workflow-service.mjs +9 -10
  49. package/scripts/model-native-qualification.mjs +17 -5
  50. package/scripts/model-routing-controller.mjs +9 -8
  51. package/scripts/model-routing-execution-adapters.mjs +24 -2
  52. package/scripts/model-weekly-cycle.mjs +13 -12
  53. package/scripts/no-silent-substitution.mjs +65 -1
  54. package/scripts/onboarding-console.mjs +8 -23
  55. package/scripts/product-integrity-contract.mjs +1 -1
  56. package/scripts/release-qualification-contract.mjs +21 -30
  57. package/scripts/remedy-registry.mjs +0 -18
  58. package/scripts/user-model-prompt-hook.mjs +11 -10
  59. package/scripts/wired-check.mjs +1 -5
  60. package/plugin/scripts/continuity-brief.mjs +0 -351
  61. package/plugin/scripts/continuity-journal.mjs +0 -553
  62. package/plugin/scripts/learn-flush.mjs +0 -96
  63. package/plugin/scripts/learning-observation.mjs +0 -51
  64. package/plugin/scripts/learning-queue.mjs +0 -135
  65. package/plugin/scripts/learning-worker-supervisor.mjs +0 -79
  66. package/plugin/scripts/project-capture-queue.mjs +0 -357
  67. package/plugin/scripts/project-memory-enrollment.mjs +0 -203
  68. package/plugin/scripts/project-progression-checkpoint.mjs +0 -145
  69. package/plugin/scripts/project-progression-contract.mjs +0 -496
  70. package/plugin/scripts/project-progression-hook.mjs +0 -233
  71. package/plugin/scripts/project-progression-outbox.mjs +0 -225
  72. package/plugin/scripts/project-progression-producer.mjs +0 -309
  73. package/plugin/scripts/project-progression-reader.mjs +0 -304
  74. package/plugin/scripts/project-progression-session-start.mjs +0 -320
  75. package/plugin/scripts/project-progression-sources.mjs +0 -246
  76. package/plugin/scripts/project-progression-store.mjs +0 -723
  77. package/plugin/scripts/project-progression-suspension.mjs +0 -60
  78. package/plugin/scripts/project-transition-hook.mjs +0 -267
  79. package/plugin/scripts/session-snapshot-contract.mjs +0 -105
  80. package/plugin/scripts/turn-transport-journal.mjs +0 -108
  81. package/scripts/qa/progression-validation-benchmark.mjs +0 -66
  82. package/scripts/session-snapshot-contract.mjs +0 -1
package/README.md CHANGED
@@ -9,7 +9,7 @@ Created: 2026-06-29 22:36:38 EDT
9
9
 
10
10
  ### Discover the stack. Read the source. Build with confidence.
11
11
 
12
- [![RuvNet Brain version 4.6.2 — README updated 2026-10-10 EDT](https://img.shields.io/badge/version_4.6.4-updated_2026--10--10_EDT-1E90FF?style=for-the-badge&labelColor=0757BA)](plugin/.claude-plugin/plugin.json)
12
+ [![RuvNet Brain version 4.6.2 — README updated 2026-10-10 EDT](https://img.shields.io/badge/version_4.7.1-updated_2026--10--10_EDT-1E90FF?style=for-the-badge&labelColor=0757BA)](plugin/.claude-plugin/plugin.json)
13
13
 
14
14
  **A source-grounded companion for Claude Code and Codex that helps you find and use Reuven Cohen's (rUv's) RuvNet ecosystem.**
15
15
 
package/bin/install.mjs CHANGED
@@ -89,7 +89,6 @@ import { installTerminalLaunchers } from '../scripts/model-terminal-launchers.mj
89
89
  import { readConsoleReceipts, replaceStaleConsoles } from '../scripts/console-instances.mjs';
90
90
  import { moveBrain, MoveRefused } from '../scripts/move-brain.mjs';
91
91
  import { brainLocation } from '../plugin/scripts/brain-location.mjs';
92
- import { cleanLegacyRufloDebris } from '../plugin/scripts/project-progression-store.mjs';
93
92
  import { resolveProjectStore } from '../plugin/scripts/project-store-resolver.mjs';
94
93
  import { runHostCli, waitForHostCli } from '../scripts/host-cli.mjs';
95
94
  import {
@@ -1074,9 +1073,9 @@ export function serverDependencies(source, seen = new Set()) {
1074
1073
  try { src = fs.readFileSync(file, 'utf8'); } catch { return; }
1075
1074
  // Lazy [^'"]* (no \n exclusion) so a multi-line named import — `import {\n a,\n} from './x.mjs';`
1076
1075
  // — is still seen: the exclusion used to stop at the first newline, so this walker silently
1077
- // missed any dependency imported that way. Caught live 2026-09-27: session-snapshot-hook.mjs's
1078
- // multi-line import of project-progression-hook.mjs never reached the Codex MCP server package,
1079
- // reproducing the exact packaging-boundary failure this function's own header warns about.
1076
+ // missed any dependency imported that way. Caught live 2026-09-27: a multi-line import in the
1077
+ // lifecycle hook never reached the Codex MCP server package, reproducing the exact
1078
+ // packaging-boundary failure this function's own header warns about.
1080
1079
  for (const m of src.matchAll(/^\s*(?:import|export)[^'"]*?from\s*['"](\.[^'"]+)['"]/gm)) {
1081
1080
  const spec = specPrefix ? path.join(specPrefix, m[1]) : m[1];
1082
1081
  const from = path.resolve(path.dirname(file), m[1]);
@@ -1309,30 +1308,6 @@ export function withLiveConsoleState(recorded, { receiptDir, alive, probe } = {}
1309
1308
  return { ...recorded, consoleRuntime: live };
1310
1309
  }
1311
1310
 
1312
- /**
1313
- * 4.3.40's ruflo leftovers inside this project's `.swarm` (cleanLegacyRufloDebris). --update removes
1314
- * them; --doctor only reports (dryRun). Either way a REFUSED artifact (unexpected contents, a symlink) is
1315
- * printed, never dropped silently. Not a project (or no .swarm): nothing to say.
1316
- */
1317
- export function reportLegacyRufloDebris({ projectDir = process.cwd(), dryRun = false } = {}) {
1318
- let storeDir;
1319
- try { storeDir = path.dirname(resolveProjectStore({ projectDir }).canonicalAgentDbPath); } catch { return null; }
1320
- if (!fs.existsSync(storeDir)) return null;
1321
- const result = cleanLegacyRufloDebris(storeDir, { dryRun });
1322
- for (const entry of result.removed) {
1323
- // Dry run applies the same checks (allowlist, mirror proof, in-use window); only the final
1324
- // unchanged-since-proof comparison can still keep it at --update time.
1325
- if (dryRun) info(`legacy ruflo debris from 4.3.40 in ${entry}: passes every check; --update removes it unless it is written to before then`);
1326
- else ok(`removed legacy ruflo debris from 4.3.40: ${entry}`);
1327
- }
1328
- for (const { path: entry, reason, kept } of result.refused) {
1329
- // A KEPT nested AgentDB holds rows (or may still be written): never suggest deleting it by hand.
1330
- if (kept) warn(`left 4.3.40's nested ruflo store in place — ${entry}: ${reason}. It is checked again on every update.`);
1331
- else warn(`left legacy ruflo debris in place — ${entry}: ${reason}. Inspect it; remove it yourself if it is ruflo's.`);
1332
- }
1333
- return result;
1334
- }
1335
-
1336
1311
  export function consoleRestartState(identity, {
1337
1312
  receiptDir = path.join(process.env.RUVNET_BRAIN_HOME || path.join(os.homedir(), '.cache', 'ruvnet-brain'), 'console-instances'),
1338
1313
  alive, probe,
@@ -1781,12 +1756,62 @@ function legacyBrainWrapperOwnership(wrapperPath, home) {
1781
1756
  || (typeof process.getuid === 'function' && stat.uid !== process.getuid())) {
1782
1757
  return { state: 'conflict', reason: 'existing bridge is not an unshared regular file' };
1783
1758
  }
1759
+ let installed;
1784
1760
  try {
1785
- if (fs.readFileSync(wrapperPath).equals(fs.readFileSync(source))) return { state: 'verified' };
1761
+ installed = fs.readFileSync(wrapperPath);
1762
+ if (installed.equals(fs.readFileSync(source))) return { state: 'verified' };
1786
1763
  } catch { /* Missing proof is not ownership. */ }
1764
+ // An UPGRADE: the bridge is still byte-identical to a wrapper an earlier Brain generation shipped.
1765
+ // Comparing only against the NEW source made every release that changed the wrapper bytes refuse
1766
+ // its own previous install (4.6.4 -> 4.7.0, hook-wrapper-ownership-conflict). The proof is a
1767
+ // generation the Stable Spine keeps under the already-verified namespace: versions/<v>/ (the
1768
+ // active one named by active.json, its previous, and any retained one). Every directory on that
1769
+ // path must be a real, owner-uid directory and the candidate a regular file; nothing is followed.
1770
+ if (installed && shippedByRetainedGeneration(installed, path.join(home, '.cache', 'ruvnet-brain', 'versions'))) {
1771
+ return { state: 'prior-generation' };
1772
+ }
1787
1773
  return { state: 'conflict', reason: 'existing bridge differs from the shipped Brain source' };
1788
1774
  }
1789
1775
 
1776
+ function shippedByRetainedGeneration(bytes, versionsDir) {
1777
+ const ownedDir = (dir) => {
1778
+ try {
1779
+ const st = fs.lstatSync(dir);
1780
+ return st.isDirectory() && !st.isSymbolicLink()
1781
+ && (typeof process.getuid !== 'function' || st.uid === process.getuid());
1782
+ } catch { return false; }
1783
+ };
1784
+ if (!ownedDir(versionsDir)) return false;
1785
+ let names;
1786
+ try { names = fs.readdirSync(versionsDir); } catch { return false; }
1787
+ return names.some((name) => {
1788
+ // Staging remnants and hidden temporaries were never activated, so they prove nothing.
1789
+ if (name.startsWith('.') || name.includes('.staging-')) return false;
1790
+ const generation = path.join(versionsDir, name);
1791
+ const scripts = path.join(generation, 'scripts');
1792
+ const candidate = path.join(scripts, 'codex-hook-wrapper.mjs');
1793
+ if (!ownedDir(generation) || !ownedDir(scripts)) return false;
1794
+ try {
1795
+ const st = fs.lstatSync(candidate);
1796
+ if (!st.isFile() || st.isSymbolicLink() || st.size !== bytes.length) return false;
1797
+ return fs.readFileSync(candidate).equals(bytes);
1798
+ } catch { return false; }
1799
+ });
1800
+ }
1801
+
1802
+ // The bridge's previous bytes are kept beside it under ONE fixed name (the immediately prior
1803
+ // generation's wrapper), so a rollback is a rename and nothing accumulates release over release.
1804
+ // Written temp-then-rename: rename replaces a planted link at the backup name, never follows it.
1805
+ function backupPriorWrapper(wrapperPath) {
1806
+ const backup = `${wrapperPath}.prior`;
1807
+ const temporary = `${backup}.tmp-${process.pid}-${crypto.randomUUID()}`;
1808
+ try {
1809
+ fs.writeFileSync(temporary, fs.readFileSync(wrapperPath), { flag: 'wx', mode: fs.statSync(wrapperPath).mode & 0o777 });
1810
+ fs.renameSync(temporary, backup);
1811
+ } finally { fs.rmSync(temporary, { force: true }); }
1812
+ return backup;
1813
+ }
1814
+
1790
1815
  export function wireCodexHost({
1791
1816
  codexDir = codexHomeDir(),
1792
1817
  configPath = path.join(codexDir, 'config.toml'),
@@ -1870,6 +1895,7 @@ export function wireCodexHost({
1870
1895
  return { host: true, action: 'hook-wrapper-source-missing' };
1871
1896
  }
1872
1897
  let hookRetirement;
1898
+ let hookWrapperBackup = null;
1873
1899
  try {
1874
1900
  fs.mkdirSync(path.dirname(hookWrapperPath), { recursive: true });
1875
1901
  const wrapperStat = (() => { try { return fs.lstatSync(hookWrapperPath); } catch (error) {
@@ -1881,6 +1907,7 @@ export function wireCodexHost({
1881
1907
  const currentOwnership = legacyBrainWrapperOwnership(hookWrapperPath, path.dirname(codexDir));
1882
1908
  if (currentOwnership.state === 'conflict') throw new Error(currentOwnership.reason);
1883
1909
  hookRetirement = retireManagedHookRegistrations({ home: path.dirname(codexDir), codexDir, wrapperPath: hookWrapperPath });
1910
+ if (currentOwnership.state === 'prior-generation') hookWrapperBackup = backupPriorWrapper(hookWrapperPath);
1884
1911
  atomicReplace(hookWrapperPath, (tmp) => fs.copyFileSync(hookWrapperSource, tmp));
1885
1912
  } catch (error) {
1886
1913
  if (announce) warn(`could not install the Codex hook bridge at ${hookWrapperPath}: ${error.message}`);
@@ -1899,7 +1926,7 @@ export function wireCodexHost({
1899
1926
  info(` to hand it to us instead, delete that ${c.bold('[mcp_servers.ruvnet-brain]')} block and re-run this installer`);
1900
1927
  }
1901
1928
  return { host: true, action, serverPath, managedCliPath, runtimePreferencesPath,
1902
- hookWrapperPath, hookWrapperInstalled: true, hookOwnershipConflicts: hookRetirement.conflicts.length };
1929
+ hookWrapperPath, hookWrapperInstalled: true, hookWrapperBackup, hookOwnershipConflicts: hookRetirement.conflicts.length };
1903
1930
  }
1904
1931
  if (text !== before) {
1905
1932
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
@@ -1911,7 +1938,7 @@ export function wireCodexHost({
1911
1938
  info(` ${c.dim('only our marked block is written; every other section is byte-preserved')}`);
1912
1939
  }
1913
1940
  return { host: true, action, serverPath, managedCliPath, runtimePreferencesPath,
1914
- hookWrapperPath, hookWrapperInstalled: true, hookOwnershipConflicts: hookRetirement.conflicts.length,
1941
+ hookWrapperPath, hookWrapperInstalled: true, hookWrapperBackup, hookOwnershipConflicts: hookRetirement.conflicts.length,
1915
1942
  changed: text !== before };
1916
1943
  }
1917
1944
 
@@ -2386,7 +2413,9 @@ export function retireManagedHookRegistrations({ home = os.homedir(), codexDir =
2386
2413
  let removed = 0;
2387
2414
  const changedFiles = [];
2388
2415
  const conflicts = [];
2389
- const verifiedWrapper = legacyBrainWrapperOwnership(wrapperPath, path.dirname(codexDir)).state === 'verified';
2416
+ // A bridge a retained Brain generation shipped is as much the Brain's as the new source.
2417
+ const verifiedWrapper = ['verified', 'prior-generation']
2418
+ .includes(legacyBrainWrapperOwnership(wrapperPath, path.dirname(codexDir)).state);
2390
2419
  const tokensOf = (command) => (command.match(/"[^"]*"|'[^']*'|[^\s]+/g) || [])
2391
2420
  .map((token) => token.replace(/^["']|["']$/g, ''));
2392
2421
  const registry = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'plugin', 'hooks', 'codex-hooks.json'), 'utf8'));
@@ -3127,7 +3156,6 @@ async function doctorRun({ json }) {
3127
3156
  warn(`host convergence receipt is invalid: ${error.message}`);
3128
3157
  }
3129
3158
  }
3130
- try { reportLegacyRufloDebris({ dryRun: true }); } catch (error) { warn(`legacy ruflo debris check failed: ${error.message}`); }
3131
3159
  const brainHome = process.env.RUVNET_BRAIN_HOME || path.dirname(cacheDir);
3132
3160
  const nightlyHealth = schedulerStatus({ platform: process.platform, env: process.env,
3133
3161
  brainHome, kbDir: cacheDir });
@@ -3304,26 +3332,9 @@ async function doctorRun({ json }) {
3304
3332
  c.dim('\n Heads-up: a window that was ALREADY open when you installed needs a restart to pick it up;\n newly-opened windows are fine.\n'),
3305
3333
  );
3306
3334
 
3307
- // ── AGENTDB RECORDING (ADR-100 §4) — positive confirmation, or the loud reason it is not ────────
3308
- // For the project --doctor is run from. Read-only: the outbox and the store are inspected, nothing
3309
- // is written. A project without `.swarm` has not adopted the store and gets no line. It is a LINE OF THE
3310
- // ONE VERDICT (re-review S2: it used to be narration only, so --json never saw it): advisory '!' when
3311
- // stuck — recording is the project's opt-in memory, not the Brain's health — ✓ when proven, ○ otherwise.
3312
- let agentdbLine = null;
3313
- try {
3314
- const { ContinuityJournal, recordingLine } = await import('../plugin/scripts/continuity-journal.mjs');
3315
- const { resolveProjectStore } = await import('../plugin/scripts/project-store-resolver.mjs');
3316
- const journal = new ContinuityJournal({ projectRoot: resolveProjectStore({ projectDir: process.cwd() }).projectRoot });
3317
- if (fs.existsSync(journal.swarm)) {
3318
- const status = journal.status();
3319
- const detail = recordingLine(status).replace(/^AgentDB: /, '');
3320
- agentdbLine = { id: 'agentdb', label: 'AgentDB', detail,
3321
- state: status.stuck ? 'warn' : status.lastCommitAt && !status.notApplicable ? 'ok' : 'unknown',
3322
- fix: status.stuck ? (status.problem === 'stuck-pending' ? 'ruflo doctor --fix (then the next session drains the outbox)' : 'node <plugin>/scripts/continuity-brief.mjs --clear') : null };
3323
- }
3324
- } catch (error) {
3325
- agentdbLine = { id: 'agentdb', label: 'AgentDB', state: 'unknown', detail: `recording status unavailable: ${error.message}`, fix: null };
3326
- }
3335
+ // AgentDB recording is proven per write: a refused or unverified ruflo write is a hook-failures row,
3336
+ // summarized at SessionStart. There is no local outbox to inspect here any more.
3337
+ const agentdbLine = null;
3327
3338
 
3328
3339
  const { turnRecordingStatus } = await import('../plugin/scripts/turn-outcome-capture.mjs');
3329
3340
  const turnStatus = turnRecordingStatus({ projectDir: process.cwd() });
@@ -4337,7 +4348,6 @@ async function runUpdate() {
4337
4348
  warn(hostSynchronizationFailureMessage(convergence, active));
4338
4349
  updateStatus = 1;
4339
4350
  } else if (convergence.convergence?.notice) info(convergence.convergence.notice);
4340
- try { reportLegacyRufloDebris(); } catch (error) { warn(`legacy ruflo debris cleanup failed: ${error.message}`); }
4341
4351
  recordRefreshPhase(refreshReceipt, 'host-convergence', convergence.ok && convergence.convergence?.healthy === true ? 'PASS' : 'FAIL', {
4342
4352
  state: convergence.convergence?.state || null, error: convergence.error || null,
4343
4353
  ...(convergence.convergence?.openSessions ? { openSessions: convergence.convergence.openSessions } : {}),
@@ -4929,6 +4939,7 @@ function uninstallAll() {
4929
4939
  // ADR-054 §5: a reinstall must never inherit an invisible OFF. See brainOffSentinelPath().
4930
4940
  ['brain on/off switch', brainOffSentinelPath()],
4931
4941
  ['legacy Codex lifecycle hook wrapper', codexHookWrapperPath()],
4942
+ ['prior Codex hook wrapper backup', `${codexHookWrapperPath()}.prior`],
4932
4943
  ['Codex MCP server files', codexServerDir()],
4933
4944
  ]) {
4934
4945
  if (!fs.existsSync(target)) continue;