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.
- package/README.md +1 -1
- package/bin/install.mjs +65 -54
- package/config/practical-rule-catalog.json +22 -22
- package/console/app.js +1 -1
- package/console/architecture.html +2 -2
- package/package.json +1 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.grok-plugin/plugin.json +12 -0
- package/plugin/commands/checkpoint.md +22 -40
- package/plugin/commands/rnb-brief.md +18 -15
- package/plugin/hooks/codex-hooks.json +1 -28
- package/plugin/hooks/grok-hooks.json +95 -0
- package/plugin/hooks/hook-contracts.json +6 -127
- package/plugin/hooks/hooks.json +1 -40
- package/plugin/host-adapters/grok.json +11 -0
- package/plugin/mcp/managed-cli-interface.mjs +0 -56
- package/plugin/scripts/agentdb-recall-reader.mjs +29 -9
- package/plugin/scripts/agentdb-recall.mjs +35 -27
- package/plugin/scripts/automated-run.mjs +42 -0
- package/plugin/scripts/brain-footprint.mjs +1 -1
- package/plugin/scripts/checkpoint-keys.mjs +46 -0
- package/plugin/scripts/codex-hook-wrapper.mjs +13 -3
- package/plugin/scripts/completion-claim-evidence.mjs +1 -1
- package/plugin/scripts/continuation-gate.mjs +6 -0
- package/plugin/scripts/continuity-events.mjs +90 -2
- package/plugin/scripts/continuity-hook-policy.mjs +5 -14
- package/plugin/scripts/decision-gate.mjs +2 -2
- package/plugin/scripts/decision-record.mjs +11 -40
- package/plugin/scripts/grok-hook-adapter.mjs +155 -0
- package/plugin/scripts/hook-failures.mjs +16 -0
- package/plugin/scripts/hook-shim.mjs +1 -2
- package/plugin/scripts/learn-capture.mjs +60 -42
- package/plugin/scripts/learning-store.mjs +70 -60
- package/plugin/scripts/node-sqlite.mjs +1 -1
- package/plugin/scripts/ruflo-bin.mjs +71 -1
- package/plugin/scripts/ruflo-memory-hooks.mjs +204 -0
- package/plugin/scripts/runtime-preferences.mjs +1 -1
- package/plugin/scripts/session-snapshot-hook.mjs +85 -341
- package/plugin/scripts/session-start-core.mjs +2 -12
- package/plugin/scripts/signal-watch.mjs +1 -1
- package/plugin/scripts/substitution-wall.mjs +222 -0
- package/plugin/scripts/turn-outcome-capture.mjs +68 -74
- package/scripts/codex-host-execution-proof.mjs +1 -1
- package/scripts/console-engine.mjs +0 -32
- package/scripts/health-repair.mjs +1 -81
- package/scripts/hook-qualify-core.mjs +2 -2
- package/scripts/model-managed-workflow-service.mjs +9 -10
- package/scripts/model-native-qualification.mjs +17 -5
- package/scripts/model-routing-controller.mjs +9 -8
- package/scripts/model-routing-execution-adapters.mjs +24 -2
- package/scripts/model-weekly-cycle.mjs +13 -12
- package/scripts/no-silent-substitution.mjs +65 -1
- package/scripts/onboarding-console.mjs +8 -23
- package/scripts/product-integrity-contract.mjs +1 -1
- package/scripts/release-qualification-contract.mjs +21 -30
- package/scripts/remedy-registry.mjs +0 -18
- package/scripts/user-model-prompt-hook.mjs +11 -10
- package/scripts/wired-check.mjs +1 -5
- package/plugin/scripts/continuity-brief.mjs +0 -351
- package/plugin/scripts/continuity-journal.mjs +0 -553
- package/plugin/scripts/learn-flush.mjs +0 -96
- package/plugin/scripts/learning-observation.mjs +0 -51
- package/plugin/scripts/learning-queue.mjs +0 -135
- package/plugin/scripts/learning-worker-supervisor.mjs +0 -79
- package/plugin/scripts/project-capture-queue.mjs +0 -357
- package/plugin/scripts/project-memory-enrollment.mjs +0 -203
- package/plugin/scripts/project-progression-checkpoint.mjs +0 -145
- package/plugin/scripts/project-progression-contract.mjs +0 -496
- package/plugin/scripts/project-progression-hook.mjs +0 -233
- package/plugin/scripts/project-progression-outbox.mjs +0 -225
- package/plugin/scripts/project-progression-producer.mjs +0 -309
- package/plugin/scripts/project-progression-reader.mjs +0 -304
- package/plugin/scripts/project-progression-session-start.mjs +0 -320
- package/plugin/scripts/project-progression-sources.mjs +0 -246
- package/plugin/scripts/project-progression-store.mjs +0 -723
- package/plugin/scripts/project-progression-suspension.mjs +0 -60
- package/plugin/scripts/project-transition-hook.mjs +0 -267
- package/plugin/scripts/session-snapshot-contract.mjs +0 -105
- package/plugin/scripts/turn-transport-journal.mjs +0 -108
- package/scripts/qa/progression-validation-benchmark.mjs +0 -66
- 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
|
-
[](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:
|
|
1078
|
-
//
|
|
1079
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
3308
|
-
//
|
|
3309
|
-
|
|
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;
|