ruvnet-brain 4.7.16 → 4.7.18
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 +2 -2
- package/bin/install.mjs +120 -15
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/hooks/grok-hooks.json +1 -1
- package/plugin/hooks/hook-contracts.json +36 -18
- package/plugin/hooks/hooks.json +1 -1
- package/plugin/scripts/agentdb-recall-process.mjs +1 -1
- package/plugin/scripts/agentdb-recall-reader.mjs +1 -1
- package/plugin/scripts/brain-confirmation.mjs +8 -1
- package/plugin/scripts/brain-footprint.mjs +3 -1
- package/plugin/scripts/continuity-hook-policy.mjs +9 -9
- package/plugin/scripts/decision-record.mjs +1 -1
- package/plugin/scripts/degradation-watch.mjs +13 -1
- package/plugin/scripts/developer-update-policy.mjs +31 -5
- package/plugin/scripts/developer-update.mjs +17 -5
- package/plugin/scripts/grok-hook-adapter.mjs +7 -3
- package/plugin/scripts/hook-failures.mjs +76 -8
- package/plugin/scripts/learn-capture.mjs +74 -9
- package/plugin/scripts/learning-store.mjs +17 -7
- package/plugin/scripts/nightly-scheduler.mjs +67 -12
- package/plugin/scripts/ruflo-bin.mjs +5 -1
- package/plugin/scripts/ruflo-memory-hooks.mjs +1 -1
- package/plugin/scripts/turn-outcome-capture.mjs +18 -18
- package/scripts/agentdb-fleet-doctor.mjs +1 -1
- package/scripts/corpus-dispatch-decision.mjs +5 -4
- package/scripts/corpus-watchdog.mjs +28 -2
- package/scripts/distill-project.mjs +1 -1
- package/scripts/falsify.mjs +1 -1
- package/scripts/health-repair.mjs +1 -1
- package/scripts/learning-replay-execution.mjs +3 -3
- package/scripts/learning-replay-fixture.mjs +3 -0
- package/scripts/lesson-migrate-agentdb.mjs +1 -1
- package/scripts/nightly-wrapper.sh +2 -2
- package/scripts/proxy/claude-proxied.sh +1 -0
- package/scripts/proxy/proxy-revert.sh +1 -0
- package/scripts/proxy/proxy-up.sh +1 -0
- package/scripts/proxy/proxy-verify.mjs +1 -1
- package/scripts/record-lesson.mjs +1 -1
- package/scripts/selfcheck.mjs +1 -1
- package/scripts/single-source-check.mjs +1 -1
- package/scripts/stack-sync.mjs +11 -3
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
|
|
|
@@ -255,7 +255,7 @@ uses the protected release workflow; a consumer update downloads an available ge
|
|
|
255
255
|
does not rebuild upstream knowledge. Check `--doctor`, `SOURCE.json` and refresh receipts
|
|
256
256
|
before treating an installation as current.
|
|
257
257
|
|
|
258
|
-
The **4.6.0 published-corpus baseline** is **
|
|
258
|
+
The **4.6.0 published-corpus baseline** is **205 public stores · 165,488 public source chunks**.
|
|
259
259
|
Those are bundle census values, not a claim that every upstream repository is fresh today.
|
|
260
260
|
Later corpus publication may advance the knowledge generation independently of code changes.
|
|
261
261
|
|
package/bin/install.mjs
CHANGED
|
@@ -54,6 +54,8 @@ import {
|
|
|
54
54
|
installNightlyRunner,
|
|
55
55
|
installScheduler,
|
|
56
56
|
nightlyArtifact,
|
|
57
|
+
nightlyModuleCurrency,
|
|
58
|
+
refreshNightlyModules,
|
|
57
59
|
removeScheduler,
|
|
58
60
|
readNightlyRegistration,
|
|
59
61
|
resolveNightlyProofBundle,
|
|
@@ -2026,25 +2028,53 @@ export function codexMarketplaceRows(value) {
|
|
|
2026
2028
|
return Array.isArray(value) ? value : [];
|
|
2027
2029
|
}
|
|
2028
2030
|
|
|
2029
|
-
|
|
2031
|
+
// Codex refuses EVERY plugin command while any configured marketplace snapshot fails to load, including one that is
|
|
2032
|
+
// not the Brain's. Measured 2026-10-23: a 7:21 p.m. ET update failed host convergence on "`claude-code-plugins` at
|
|
2033
|
+
// ~/.codex/.tmp/marketplaces/claude-code-plugins: marketplace root does not contain a supported manifest" while Codex
|
|
2034
|
+
// was refreshing that Git snapshot; the same step passed at 5 a.m. Reproduced with a snapshot missing its manifest:
|
|
2035
|
+
// `codex plugin list` exits 1, and `codex plugin marketplace upgrade <name>` repairs it (codex-cli 0.164.0).
|
|
2036
|
+
const CODEX_SNAPSHOT_FAILURE = /failed to load configured marketplace snapshot/i;
|
|
2037
|
+
export function codexBrokenMarketplaces(text) {
|
|
2038
|
+
return [...String(text || '').matchAll(/^- `([^`]+)` at /gm)].map((m) => m[1]);
|
|
2039
|
+
}
|
|
2040
|
+
const sleepSync = (ms) => { if (ms > 0) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); };
|
|
2041
|
+
|
|
2042
|
+
export function runCodexJson(args, {
|
|
2030
2043
|
codexBin = process.env.CODEX_BIN || 'codex',
|
|
2031
2044
|
codexHome = codexHomeDir(),
|
|
2032
2045
|
cwd = process.cwd(),
|
|
2046
|
+
run = runHostCli,
|
|
2047
|
+
waitMs = 5_000,
|
|
2048
|
+
// Refreshing another tool's marketplace is a network write; only an install/update that is about to change the
|
|
2049
|
+
// Brain's Codex plugin opts in. --doctor and listings report the broken snapshot and change nothing (review 2026-10-24).
|
|
2050
|
+
repair = false,
|
|
2033
2051
|
} = {}) {
|
|
2034
|
-
const
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2052
|
+
const options = { stdio: 'pipe', shell: false, cwd, env: { ...process.env, CODEX_HOME: codexHome }, encoding: 'utf8',
|
|
2053
|
+
timeout: 30_000, maxBuffer: 20 * 1024 * 1024 };
|
|
2054
|
+
const snapshotFailure = (res) => !res.missingBinary && res.status !== 0 && CODEX_SNAPSHOT_FAILURE.test(String(res.stderr || res.stdout || ''));
|
|
2055
|
+
let r = run(codexBin, args, options);
|
|
2056
|
+
// One wait covers a refresh Codex is finishing; then refresh only the OTHER Git marketplaces it named (the Brain's
|
|
2057
|
+
// own local snapshot is repaired by prepareCodexMarketplace), once, and ask again. Bounded: at most three asks.
|
|
2058
|
+
const repaired = [];
|
|
2059
|
+
if (snapshotFailure(r)) { sleepSync(waitMs); r = run(codexBin, args, options); }
|
|
2060
|
+
if (repair && snapshotFailure(r)) {
|
|
2061
|
+
for (const name of codexBrokenMarketplaces(r.stderr || r.stdout).filter((n) => n !== CODEX_MARKETPLACE)) {
|
|
2062
|
+
const up = run(codexBin, ['plugin', 'marketplace', 'upgrade', name, '--json'], options);
|
|
2063
|
+
repaired.push(`${name}: ${up.status === 0 ? 'refreshed' : `refresh failed (exit ${up.status})`}`);
|
|
2064
|
+
}
|
|
2065
|
+
if (repaired.length) r = run(codexBin, args, options);
|
|
2066
|
+
}
|
|
2043
2067
|
if (r.missingBinary) return { ok: false, error: r.message };
|
|
2044
2068
|
if (r.error || r.status !== 0) {
|
|
2045
|
-
|
|
2046
|
-
|
|
2069
|
+
let detail = String(r.stderr || r.stdout || r.error?.message || `exit ${r.status}`).trim();
|
|
2070
|
+
if (snapshotFailure(r)) {
|
|
2071
|
+
const names = codexBrokenMarketplaces(r.stderr || r.stdout);
|
|
2072
|
+
detail += ` — Codex cannot load its ${names.map((n) => `\`${n}\``).join(', ') || 'configured'} marketplace snapshot`
|
|
2073
|
+
+ `${repaired.length ? ` (tried: ${repaired.join('; ')})` : ''}; repair with: ${names.map((n) => `codex plugin marketplace upgrade ${n}`).join(' && ') || 'codex plugin marketplace upgrade'}`;
|
|
2074
|
+
}
|
|
2075
|
+
return { ok: false, error: detail, ...(repaired.length ? { repaired } : {}) };
|
|
2047
2076
|
}
|
|
2077
|
+
if (repaired.length && !TEST_MODE) info(` Codex marketplace snapshot repaired before the Brain step (${repaired.join('; ')})`);
|
|
2048
2078
|
try { return { ok: true, value: JSON.parse(r.stdout || 'null') }; }
|
|
2049
2079
|
catch { return { ok: false, error: `Codex returned non-JSON output for ${args.join(' ')}` }; }
|
|
2050
2080
|
}
|
|
@@ -2103,7 +2133,7 @@ export function wireCodexPlugin({
|
|
|
2103
2133
|
aliasHome,
|
|
2104
2134
|
} = {}) {
|
|
2105
2135
|
if (!fs.existsSync(codexDir)) return { host: false, action: 'no-host' };
|
|
2106
|
-
const options = { codexBin, codexHome, cwd };
|
|
2136
|
+
const options = { codexBin, codexHome, cwd, repair: true };
|
|
2107
2137
|
// Codex loads every configured marketplace before it can report plugin state. Repair only an
|
|
2108
2138
|
// absent/malformed Brain-owned snapshot before that probe, or the probe can fail before its own
|
|
2109
2139
|
// repair path is reachable. A healthy current cache stays byte-untouched, and plugin enablement
|
|
@@ -3179,8 +3209,14 @@ async function doctorRun({ json }) {
|
|
|
3179
3209
|
const brainHome = process.env.RUVNET_BRAIN_HOME || path.dirname(cacheDir);
|
|
3180
3210
|
const nightlyHealth = schedulerStatus({ platform: process.platform, env: process.env,
|
|
3181
3211
|
brainHome, kbDir: cacheDir });
|
|
3212
|
+
// Does the scheduled job run the code installed now? Its module snapshot is refreshed by --update, its runner only
|
|
3213
|
+
// by --enable-nightly (measured stale 2026-10-22). Read-only; `unregistered` is already covered by the state lines.
|
|
3214
|
+
let nightlyCode = { state: 'unregistered' };
|
|
3215
|
+
try { nightlyCode = nightlyModuleCurrency({ brainHome, runnerSource: path.join(REPO_ROOT, 'bin', 'nightly-refresh.mjs') }); } catch (error) { nightlyCode = { state: 'unknown', why: error.message }; }
|
|
3182
3216
|
if (nightlyHealth.state === 'on') {
|
|
3183
3217
|
ok(`nightly scheduler: ${nightlyHealth.evidence}`);
|
|
3218
|
+
if (nightlyCode.state === 'current') ok('nightly runs current code: the registered update modules are the installed set');
|
|
3219
|
+
else if (nightlyCode.fix) warn(`nightly runs ${nightlyCode.state === 'runner-changed' ? 'an older runner' : 'stale update modules'} — fix: ${c.bold(nightlyCode.fix)}`);
|
|
3184
3220
|
if (nightlyHealth.runHealth?.state === 'ok' || nightlyHealth.runHealth?.state === 'running') {
|
|
3185
3221
|
ok(`nightly execution: ${nightlyHealth.runHealth.evidence}`);
|
|
3186
3222
|
} else if (nightlyHealth.runHealth?.state === 'failed') {
|
|
@@ -3436,6 +3472,10 @@ async function doctorRun({ json }) {
|
|
|
3436
3472
|
: codexReadinessFailed ? 'MCP readiness blocked' : 'lifecycle hooks unhealthy') : 'wired', 'npx ruvnet-brain')] : []),
|
|
3437
3473
|
check('nightly', 'Nightly', nightlyFailed, `${nightlyHealth.state}${nightlyHealth.runHealth?.state ? `, last run ${nightlyHealth.runHealth.state}` : ''}`,
|
|
3438
3474
|
nightlyHealth.state === 'degraded' ? 'npx ruvnet-brain --enable-nightly' : 'npx ruvnet-brain --update'),
|
|
3475
|
+
// Scheduler ON but running yesterday's code: an advisory naming the exact fix, never a failing verdict.
|
|
3476
|
+
...(nightlyHealth.state === 'on' && nightlyCode.state !== 'unregistered' ? [check('nightly-code', 'Nightly code', nightlyCode.state !== 'current',
|
|
3477
|
+
nightlyCode.state === 'current' ? 'registered update modules match the installed set' : `${nightlyCode.state}: registered ${String(nightlyCode.registered || '?').slice(0, 12)}, installed ${String(nightlyCode.current || '?').slice(0, 12)}`,
|
|
3478
|
+
nightlyCode.fix || 'npx ruvnet-brain@latest --update', { advisory: true })] : []),
|
|
3439
3479
|
check('host-convergence', 'Hosts sync', !hostConvergence.healthy, hostConvergence.state, 'npx ruvnet-brain --update'),
|
|
3440
3480
|
...(rufloOperational ? [rufloCheckLine(rufloOperational)] : []),
|
|
3441
3481
|
...(agentdbLine ? [agentdbLine] : []),
|
|
@@ -3862,7 +3902,9 @@ export function syncHostsAfterUpdate(cacheDir = resolvedKbDir(), {
|
|
|
3862
3902
|
const fail = (detail = {}) => {
|
|
3863
3903
|
runtimeTransaction.rollback();
|
|
3864
3904
|
hostLock.release();
|
|
3865
|
-
|
|
3905
|
+
// Three paths below returned no error at all, so the refresh receipt recorded `state: null, error: null` and the
|
|
3906
|
+
// failure could not be diagnosed afterwards (2026-10-23 7:21 p.m. ET). Every failure now carries its reason.
|
|
3907
|
+
return { ok: false, results, ...detail, error: detail.error || hostFailureReason(results) };
|
|
3866
3908
|
};
|
|
3867
3909
|
|
|
3868
3910
|
try {
|
|
@@ -4023,6 +4065,18 @@ export function classifyHostConvergence(receipt, expectedVersion = PACKAGE_VERSI
|
|
|
4023
4065
|
}
|
|
4024
4066
|
|
|
4025
4067
|
/** Activation and host readiness are independent; never claim a rollback from a host refusal. */
|
|
4068
|
+
/** Why host synchronization stopped, from the per-host results it had gathered; null when nothing says. */
|
|
4069
|
+
export function hostFailureReason(results = {}) {
|
|
4070
|
+
const firstLines = (text) => String(text || '').trim().split('\n').slice(0, 3).join(' ');
|
|
4071
|
+
if (results.claude?.host && !results.claude.wired) {
|
|
4072
|
+
return `Claude Code plugin ${results.claude.action || 'wiring failed'}${results.claude.version ? ` (installed ${results.claude.version}, required ${results.claude.expectedVersion || PACKAGE_VERSION})` : ''}`;
|
|
4073
|
+
}
|
|
4074
|
+
if (results.codex && !['unchanged', 'installed', 'updated', 'disabled'].includes(results.codex.action)) {
|
|
4075
|
+
return `Codex plugin ${results.codex.action || 'failed'}${results.codex.error ? `: ${firstLines(results.codex.error)}` : ''}`;
|
|
4076
|
+
}
|
|
4077
|
+
return null;
|
|
4078
|
+
}
|
|
4079
|
+
|
|
4026
4080
|
export function hostSynchronizationFailureMessage(convergence, active, expectedVersion = PACKAGE_VERSION) {
|
|
4027
4081
|
const verified = active?.version === expectedVersion && Number.isSafeInteger(active.generation) && active.generation > 0
|
|
4028
4082
|
&& active.codeRoot === `versions/${expectedVersion}`;
|
|
@@ -4081,11 +4135,36 @@ function refuseOverSetAsideBrain() {
|
|
|
4081
4135
|
return true;
|
|
4082
4136
|
}
|
|
4083
4137
|
|
|
4138
|
+
/**
|
|
4139
|
+
* A SOURCE CHECKOUT older than the runtime already installed cannot apply the current release: it downloads ~736 MB
|
|
4140
|
+
* and then refuses on "staged ReleaseCoverage failed integrity — release version 4.7.17 differs from 4.5.2" (measured
|
|
4141
|
+
* 2026-10-23: `npx ruvnet-brain --update` run inside a stale checkout resolves that checkout's own bin). Say so
|
|
4142
|
+
* before the download, with the command that works. Only a checkout: a published installer can legitimately be
|
|
4143
|
+
* older than active.json (a plugin seeded from main during a release window, a release candidate installed from a
|
|
4144
|
+
* checkout), and the nightly and self-heal `@latest` updates must keep working then (review, 2026-10-24).
|
|
4145
|
+
*/
|
|
4146
|
+
export function olderInstallerRefusal({ brainHome, packageVersion = PACKAGE_VERSION, repoRoot = REPO_ROOT } = {}) {
|
|
4147
|
+
let installed;
|
|
4148
|
+
try { installed = JSON.parse(fs.readFileSync(path.join(brainHome, 'active.json'), 'utf8'))?.version; } catch { return null; }
|
|
4149
|
+
if (!/^\d+\.\d+\.\d+$/.test(String(installed || ''))) return null;
|
|
4150
|
+
try { if (cmpVersion(installed, packageVersion) <= 0) return null; } catch { return null; }
|
|
4151
|
+
if (!fs.existsSync(path.join(repoRoot, '.git'))) return null;
|
|
4152
|
+
return `this is ruvnet-brain ${packageVersion}, a source checkout at ${repoRoot}, older than the ${installed} already `
|
|
4153
|
+
+ 'installed here, and an older checkout cannot apply the current release. '
|
|
4154
|
+
+ 'Run npx ruvnet-brain@latest --update from any other folder, or bring this checkout up to date first (git pull).';
|
|
4155
|
+
}
|
|
4156
|
+
|
|
4084
4157
|
async function runUpdate() {
|
|
4085
4158
|
printBanner('update');
|
|
4086
4159
|
refuseUnmountedBrain();
|
|
4087
4160
|
const kbDir = resolvedKbDir();
|
|
4088
4161
|
const brainHome = process.env.RUVNET_BRAIN_HOME || path.dirname(kbDir);
|
|
4162
|
+
const olderInstaller = olderInstallerRefusal({ brainHome });
|
|
4163
|
+
if (olderInstaller) {
|
|
4164
|
+
console.error(`\n${c.red('✗ update stopped before downloading:')} ${olderInstaller}\n`);
|
|
4165
|
+
process.exitCode = 1;
|
|
4166
|
+
return;
|
|
4167
|
+
}
|
|
4089
4168
|
if (FLAG_HOST_SYNC_ONLY) {
|
|
4090
4169
|
info(c.dim('repairing host shells and managed model catalog without mutating KB bytes…\n'));
|
|
4091
4170
|
const convergence = syncHostsAfterUpdate(kbDir);
|
|
@@ -4404,6 +4483,7 @@ async function runUpdate() {
|
|
|
4404
4483
|
info(c.dim("\n--auto set: enrolling in Evergreen auto-update so you never run this again…\n"));
|
|
4405
4484
|
enableNightly(); // exits on its own with verified output; if it returns, fall through to the update verdict
|
|
4406
4485
|
}
|
|
4486
|
+
if (updateStatus === 0) reconcileNightlyModules({ brainHome, refreshReceipt });
|
|
4407
4487
|
let retention;
|
|
4408
4488
|
try {
|
|
4409
4489
|
retention = pruneLifecycleEvidence({ brainHome, kbDir, preserveRefreshRunIds: [refreshReceipt.runId],
|
|
@@ -4443,6 +4523,31 @@ async function runUpdate() {
|
|
|
4443
4523
|
await printConfirmation({ footprint: footprint?.after || null });
|
|
4444
4524
|
}
|
|
4445
4525
|
|
|
4526
|
+
/**
|
|
4527
|
+
* After a successful --update: the nightly runs a content-addressed SNAPSHOT of the update modules (installNightlyRunner),
|
|
4528
|
+
* which until now only --enable-nightly refreshed, so a fix shipped by --update stayed invisible to the 03:30 job
|
|
4529
|
+
* (measured 2026-10-22: the 4.7.15 developer-update fix). A stale registration is re-pointed at the installed modules;
|
|
4530
|
+
* a changed RUNNER would change the LaunchAgent command, which only explicit enablement may do, so that case is an
|
|
4531
|
+
* advisory naming the fix. Advisory and fail-open: it never fails an otherwise good update.
|
|
4532
|
+
*/
|
|
4533
|
+
export function reconcileNightlyModules({ brainHome, refreshReceipt = null, runnerSource = path.join(REPO_ROOT, 'bin', 'nightly-refresh.mjs') } = {}) {
|
|
4534
|
+
const advise = (status, detail) => { // the advisory write itself must never escape this step
|
|
4535
|
+
try { if (refreshReceipt) recordRefreshAdvisory(refreshReceipt, 'nightly-modules', status, detail); } catch { /* the receipt is evidence, not the gate */ }
|
|
4536
|
+
return { status, ...detail };
|
|
4537
|
+
};
|
|
4538
|
+
try {
|
|
4539
|
+
const nightly = refreshNightlyModules({ brainHome, runnerSource });
|
|
4540
|
+
if (nightly.state === 'refreshed') ok(`nightly scheduler now runs the installed update modules (${nightly.registered.slice(0, 16)}…)`);
|
|
4541
|
+
else if (nightly.state === 'runner-changed') warn(`nightly scheduler runs an older runner; the LaunchAgent changes only on explicit enablement — run ${c.bold(nightly.fix)}`);
|
|
4542
|
+
else if (nightly.state === 'refused') warn(`nightly module snapshot was not refreshed: ${nightly.reason} — run ${c.bold(nightly.fix)}`);
|
|
4543
|
+
return advise(['runner-changed', 'refused'].includes(nightly.state) ? 'SKIP' : 'PASS',
|
|
4544
|
+
{ state: nightly.state, registered: nightly.registered ?? null, current: nightly.current ?? null, ...(nightly.reason ? { reason: nightly.reason } : {}), ...(nightly.fix ? { fix: nightly.fix } : {}) });
|
|
4545
|
+
} catch (error) {
|
|
4546
|
+
warn(`nightly module snapshot was not refreshed (${error.message}); the scheduler registration was left as it was`);
|
|
4547
|
+
return advise('FAIL', { state: 'error', reason: error.message });
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4550
|
+
|
|
4446
4551
|
function enableNightly() {
|
|
4447
4552
|
printBanner('enable nightly updates');
|
|
4448
4553
|
const kbDir = resolvedKbDir();
|
|
@@ -5612,7 +5717,7 @@ const rufloProbeRun = (args, cli = 'ruflo', timeoutMs = 10_000) => {
|
|
|
5612
5717
|
// (verified live: ~/.npm-global/lib/node_modules/ruflo/node_modules/@claude-flow/cli/dist/src/
|
|
5613
5718
|
// services/daemon-autostart.js:85) — a read-only health probe must not leave one running.
|
|
5614
5719
|
const result = spawnSync(cli, args, { cwd: process.cwd(), encoding: 'utf8', timeout: timeoutMs, shell: IS_WIN && /\.(?:cmd|bat)$/i.test(cli),
|
|
5615
|
-
env: { ...process.env, RUFLO_DAEMON_AUTOSTART: '0' } });
|
|
5720
|
+
env: { ...process.env, RUFLO_DAEMON_AUTOSTART: '0', CLAUDE_FLOW_AUTO_UPDATE: 'false' } });
|
|
5616
5721
|
// A spawn failure or a timeout is marked, so it can never pass for an (empty, "healthy") answer.
|
|
5617
5722
|
return `${result.stdout || ''}\n${result.stderr || ''}${result.error ? `\n[ruflo-probe-error] ${result.error.code || result.error.message}` : ''}`;
|
|
5618
5723
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvnet-brain",
|
|
3
|
-
"version": "4.7.
|
|
3
|
+
"version": "4.7.18",
|
|
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 77 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 a UserPromptSubmit retrieve-and-inject grounding hook and a PreToolUse write gate that refuses ungrounded rUv-product code until search_ruvnet has been consulted (ADR-0012 / ADR-067).",
|
|
4
|
-
"version": "4.7.
|
|
4
|
+
"version": "4.7.18",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Stuart Kerr"
|
|
7
7
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "RuvNet Brain lifecycle plane for Grok CLI. Grok selects this file through plugin/.grok-plugin/plugin.json (measured on grok 1.0.50: a plugin directory carrying that manifest registers ONLY this file, so the Claude registry plugin/hooks/hooks.json is not also run). Every command enters plugin/scripts/grok-hook-adapter.mjs, which normalizes the Grok payload, withholds Grok's non-Claude transcript, skips the session-end Stop fire, runs the SAME hook-shim.mjs bodies as Claude and Codex, and returns Grok-valid output. Registered: decision-gate write route (PreToolUse, the only refuser), session-snapshot and grounding-stamp (PostToolUse), grounding-turn-mark (UserPromptSubmit, a marker file only), continuation-gate + session-snapshot + grounding-turn-gate (Stop), session-snapshot (PreCompact, SessionEnd). Deliberately NOT registered on Grok: session-start, ground-ruvnet, unprompted-speech and capacity-aware-parallel-work. Grok ignores SessionStart stdout and discards an allowing UserPromptSubmit hook's stdout (~/.grok/docs/user-guide/10-hooks.md:113, :507, :531), so they would spend time on every session or prompt and deliver nothing, and session-start would consume the once-only hook-failure summary where nobody sees it.
|
|
2
|
+
"description": "RuvNet Brain lifecycle plane for Grok CLI. Grok selects this file through plugin/.grok-plugin/plugin.json (measured on grok 1.0.50: a plugin directory carrying that manifest registers ONLY this file, so the Claude registry plugin/hooks/hooks.json is not also run). Every command enters plugin/scripts/grok-hook-adapter.mjs, which normalizes the Grok payload, withholds Grok's non-Claude transcript, skips the session-end Stop fire, runs the SAME hook-shim.mjs bodies as Claude and Codex, and returns Grok-valid output. Registered (9 handlers): decision-gate write route (PreToolUse, the only refuser registered on Grok), session-snapshot and grounding-stamp (PostToolUse), grounding-turn-mark (UserPromptSubmit, a marker file only), continuation-gate + session-snapshot + grounding-turn-gate (Stop), session-snapshot (PreCompact, SessionEnd). Deliberately NOT registered on Grok: session-start, ground-ruvnet, unprompted-speech and capacity-aware-parallel-work. Grok ignores SessionStart stdout and discards an allowing UserPromptSubmit hook's stdout (~/.grok/docs/user-guide/10-hooks.md:113, :507, :531), so they would spend time on every session or prompt and deliver nothing, and session-start would consume the once-only hook-failure summary where nobody sees it. SessionEnd: \"A plugin's SessionEnd hook runs for at most that default [1.5 s], whatever its timeout\" (10-hooks.md:644-645), so the 3 s timeout below is not honoured; the adapter budgets 1400 ms and hands it to the body as RUVNET_CODEX_BUDGET_MS, which detaches the checkpoint (the Codex 3 s SessionEnd path) instead of an inline store+readback Grok would kill partway. UNVERIFIED (docs silent, not measured): the payload tool_name of an MCP search_ruvnet call and whether grounding-stamp accepts Grok's toolResult shape; PreCompact delivery (no compaction was driven); Windows expansion of ${GROK_PLUGIN_ROOT} (10-hooks.md:581 describes it; not measured).",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"PreToolUse": [
|
|
5
5
|
{
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"class": "continuation nudge",
|
|
29
29
|
"hosts": [
|
|
30
30
|
"claude",
|
|
31
|
-
"codex"
|
|
31
|
+
"codex",
|
|
32
|
+
"grok"
|
|
32
33
|
],
|
|
33
34
|
"responsibility": "May request one continuation for one explicitly authorized, project-scoped objective read from the user's own work ledger (ADR-043). Never a refusal, never a second task store. Amended 2026-09-30 (ADR-074 class `completion`, no new registration): the same Stop body also requests ONE correction when the final answer claims work is done without a verification command run after the last state change this turn (Claude transcript), a named check, and a NOT-verified disclosure — on Codex, whose rollout format is not parsed, only the answer-side half is enforced and the transcript half is reported UNKNOWN. It also records first-person promises from the final answer into the SAME per-project ledger (Claude only; capped; closed only by a later evidence-passing completion claim, never by --done) and requests continuation on them under the same loop guards and cooldown. Amended 2026-10-11 (ADR-106, no new registration): the same body asks ONCE when a Claude answer ends by asking permission for, or postponing, work a standing instruction or open promise already authorized and names no genuine blocker class (no-deferral); Codex is not enforced because its prompts are not parsed."
|
|
34
35
|
},
|
|
@@ -38,7 +39,8 @@
|
|
|
38
39
|
"class": "continuity capture",
|
|
39
40
|
"hosts": [
|
|
40
41
|
"claude",
|
|
41
|
-
"codex"
|
|
42
|
+
"codex",
|
|
43
|
+
"grok"
|
|
42
44
|
],
|
|
43
45
|
"responsibility": "Write the redacted turn outcome with one bounded `ruflo memory store` in namespace turns and prove it by exact-key readback; a refusal is a hook-failures row. No journal, outbox or replay."
|
|
44
46
|
},
|
|
@@ -48,7 +50,8 @@
|
|
|
48
50
|
"class": "continuity capture",
|
|
49
51
|
"hosts": [
|
|
50
52
|
"claude",
|
|
51
|
-
"codex"
|
|
53
|
+
"codex",
|
|
54
|
+
"grok"
|
|
52
55
|
],
|
|
53
56
|
"responsibility": "Store one append-only project-state-auto-<ms> checkpoint through `ruflo memory store` and read it back by exact key; start turn distillation (`ruflo memory distill run`)."
|
|
54
57
|
},
|
|
@@ -58,7 +61,8 @@
|
|
|
58
61
|
"class": "continuity capture",
|
|
59
62
|
"hosts": [
|
|
60
63
|
"claude",
|
|
61
|
-
"codex"
|
|
64
|
+
"codex",
|
|
65
|
+
"grok"
|
|
62
66
|
],
|
|
63
67
|
"responsibility": "Store one append-only project-state-auto-<ms> checkpoint through `ruflo memory store` and read it back by exact key; start turn distillation (`ruflo memory distill run`)."
|
|
64
68
|
},
|
|
@@ -88,7 +92,8 @@
|
|
|
88
92
|
"class": "consequential-action authorization",
|
|
89
93
|
"hosts": [
|
|
90
94
|
"claude",
|
|
91
|
-
"codex"
|
|
95
|
+
"codex",
|
|
96
|
+
"grok"
|
|
92
97
|
],
|
|
93
98
|
"responsibility": "The ONE process that may refuse a Write/Edit/MultiEdit/NotebookEdit/apply_patch (ADR-067): composes protect-state, hijack-ruvnet, ground-before-write (ADR-0012), adr-currency and duplicate-code (2026-09-30: a new code file or large new export duplicating existing code in this checkout, refused at most once per path per session) into one decision with one reason. Fails open on its own errors. apply_patch is Codex's raw write-tool name, added to the shared matcher 2026-09-12 after a real apply_patch call was measured to fire PreToolUse live (codex-cli 0.154.0); codex-hook-adapter.mjs normalizes it to the same Edit shape decision-gate.mjs's policies already read on Claude."
|
|
94
99
|
},
|
|
@@ -98,7 +103,8 @@
|
|
|
98
103
|
"class": "grounding receipt",
|
|
99
104
|
"hosts": [
|
|
100
105
|
"claude",
|
|
101
|
-
"codex"
|
|
106
|
+
"codex",
|
|
107
|
+
"grok"
|
|
102
108
|
],
|
|
103
109
|
"responsibility": "After a SUCCESSFUL search_ruvnet, records which rUv products were consulted (a 24h stamp per term named in the query). It is the key that opens ground-before-write; without it the write gate would refuse every rUv-product write forever. Extended to Codex 2026-09-12: a real MCP search_ruvnet call fired PostToolUse with tool_name mcp__ruvnet_brain__search_ruvnet, which the existing matcher already recognized unchanged."
|
|
104
110
|
},
|
|
@@ -108,7 +114,8 @@
|
|
|
108
114
|
"class": "grounding turn marker",
|
|
109
115
|
"hosts": [
|
|
110
116
|
"claude",
|
|
111
|
-
"codex"
|
|
117
|
+
"codex",
|
|
118
|
+
"grok"
|
|
112
119
|
],
|
|
113
120
|
"responsibility": "Records that ground-ruvnet's Gate 1 (the same regex, proven byte-identical by test) matched this turn's prompt, so grounding-turn-gate has something to check at Stop — Stop's own payload carries no prompt text. Amended 2026-09-30: also arms when the prompt asks a capability/feasibility/architecture question about any tool or platform, recording the subjects it named; a marker not yet consumed is merged without moving its mtime (a queued mid-turn prompt re-dating it was a measured false-alarm cause). Writes only a marker file under ~/.cache/ruvnet-brain/grounding-turn/; never blocks, never speaks to the model."
|
|
114
121
|
},
|
|
@@ -118,7 +125,8 @@
|
|
|
118
125
|
"class": "answered-without-search enforcement",
|
|
119
126
|
"hosts": [
|
|
120
127
|
"claude",
|
|
121
|
-
"codex"
|
|
128
|
+
"codex",
|
|
129
|
+
"grok"
|
|
122
130
|
],
|
|
123
131
|
"responsibility": "The 'answered without searching' gate (2026-09-12): if grounding-turn-mark's marker shows Gate 1 fired this turn and grounding-stamp's own stamp evidence shows no search_ruvnet call happened since, forces continuation via the same hookSpecificOutput.additionalContext contract continuation-gate already uses on both hosts. A separate registration from continuation-gate — see grounding-turn-gate.mjs's header for why folding it into that file's ledger/cooldown semantics would corrupt them rather than extend them. Consumes its marker unconditionally so a stale one can never pressure an unrelated later turn. Amended 2026-09-30 (ADR-0030 #1): on Claude, 'was search_ruvnet called' is read from the transcript, and on an armed turn each capability claim in the final answer must be bound to a relevant strong source read this turn after the last weak one, else ONE correction naming the claim and what was read; on Codex (rollout not parsed) only rUv-term claims are judged, from stamps, and the rest are UNKNOWN. Gates #2/#3 are shadow-logged, never delivered."
|
|
124
132
|
},
|
|
@@ -128,7 +136,8 @@
|
|
|
128
136
|
"class": "normalized continuity observation",
|
|
129
137
|
"hosts": [
|
|
130
138
|
"claude",
|
|
131
|
-
"codex"
|
|
139
|
+
"codex",
|
|
140
|
+
"grok"
|
|
132
141
|
],
|
|
133
142
|
"responsibility": "When learning is opted in, write one minimized workflow observation with `ruflo memory store` + exact-key readback; a refusal is a hook-failures row. No queue."
|
|
134
143
|
}
|
|
@@ -204,7 +213,8 @@
|
|
|
204
213
|
"event": "Stop",
|
|
205
214
|
"hosts": [
|
|
206
215
|
"claude",
|
|
207
|
-
"codex"
|
|
216
|
+
"codex",
|
|
217
|
+
"grok"
|
|
208
218
|
],
|
|
209
219
|
"mode": "advisory",
|
|
210
220
|
"offBehavior": "run",
|
|
@@ -216,7 +226,8 @@
|
|
|
216
226
|
"event": "Stop",
|
|
217
227
|
"hosts": [
|
|
218
228
|
"claude",
|
|
219
|
-
"codex"
|
|
229
|
+
"codex",
|
|
230
|
+
"grok"
|
|
220
231
|
],
|
|
221
232
|
"mode": "advisory",
|
|
222
233
|
"offBehavior": "run",
|
|
@@ -228,7 +239,8 @@
|
|
|
228
239
|
"event": "PreCompact",
|
|
229
240
|
"hosts": [
|
|
230
241
|
"claude",
|
|
231
|
-
"codex"
|
|
242
|
+
"codex",
|
|
243
|
+
"grok"
|
|
232
244
|
],
|
|
233
245
|
"mode": "advisory",
|
|
234
246
|
"offBehavior": "run",
|
|
@@ -240,7 +252,8 @@
|
|
|
240
252
|
"event": "SessionEnd",
|
|
241
253
|
"hosts": [
|
|
242
254
|
"claude",
|
|
243
|
-
"codex"
|
|
255
|
+
"codex",
|
|
256
|
+
"grok"
|
|
244
257
|
],
|
|
245
258
|
"mode": "advisory",
|
|
246
259
|
"offBehavior": "run",
|
|
@@ -276,7 +289,8 @@
|
|
|
276
289
|
"event": "PreToolUse",
|
|
277
290
|
"hosts": [
|
|
278
291
|
"claude",
|
|
279
|
-
"codex"
|
|
292
|
+
"codex",
|
|
293
|
+
"grok"
|
|
280
294
|
],
|
|
281
295
|
"mode": "blocking",
|
|
282
296
|
"offBehavior": "run",
|
|
@@ -288,7 +302,8 @@
|
|
|
288
302
|
"event": "PostToolUse",
|
|
289
303
|
"hosts": [
|
|
290
304
|
"claude",
|
|
291
|
-
"codex"
|
|
305
|
+
"codex",
|
|
306
|
+
"grok"
|
|
292
307
|
],
|
|
293
308
|
"mode": "advisory",
|
|
294
309
|
"offBehavior": "silence",
|
|
@@ -300,7 +315,8 @@
|
|
|
300
315
|
"event": "UserPromptSubmit",
|
|
301
316
|
"hosts": [
|
|
302
317
|
"claude",
|
|
303
|
-
"codex"
|
|
318
|
+
"codex",
|
|
319
|
+
"grok"
|
|
304
320
|
],
|
|
305
321
|
"mode": "advisory",
|
|
306
322
|
"offBehavior": "silence",
|
|
@@ -312,7 +328,8 @@
|
|
|
312
328
|
"event": "Stop",
|
|
313
329
|
"hosts": [
|
|
314
330
|
"claude",
|
|
315
|
-
"codex"
|
|
331
|
+
"codex",
|
|
332
|
+
"grok"
|
|
316
333
|
],
|
|
317
334
|
"mode": "advisory",
|
|
318
335
|
"offBehavior": "silence",
|
|
@@ -324,7 +341,8 @@
|
|
|
324
341
|
"event": "PostToolUse",
|
|
325
342
|
"hosts": [
|
|
326
343
|
"claude",
|
|
327
|
-
"codex"
|
|
344
|
+
"codex",
|
|
345
|
+
"grok"
|
|
328
346
|
],
|
|
329
347
|
"mode": "advisory",
|
|
330
348
|
"offBehavior": "run",
|
package/plugin/hooks/hooks.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "RuvNet Brain lifecycle plane. The broad legacy routing, learning, and release interceptors remain retired. Lifecycle memory only CALLS ruflo (owner ruling 2026-10-12, ADR-106): there is no hand-rolled store, queue, journal or outbox. SessionStart restores the newest project-state-current-* checkpoint (and a newer project-state-auto-* one) and lists lesson keys by exact-key `ruflo memory list|retrieve`, and summarizes hook failures once. UserPromptSubmit runs the single unprompted-speech chokepoint, ground-ruvnet grounding injection (with one bounded AgentDB recall search), a capacity-aware context hint, and grounding-turn-mark. PreToolUse runs decision-gate's write route, the ONE process that may refuse a Write/Edit/MultiEdit/NotebookEdit/apply_patch (ADR-067, ADR-0012); PostToolUse on a successful search_ruvnet mints the grounding stamp that opens that gate, and session-snapshot PostToolUse writes an opted-in learning observation with `ruflo memory store`. Stop runs the continuation gate, writes the turn outcome with `ruflo memory store` in namespace turns proven by exact-key readback, and runs grounding-turn-gate. PreCompact and SessionEnd store one append-only project-state-auto-<ms> checkpoint with exact-key readback and start `ruflo memory distill run`. Every body is advisory and fails open except decision-gate; a refused or unverified ruflo write, or a body stopped by the shim watchdog, is one row in hook-failures.jsonl, summarized at the next SessionStart.",
|
|
2
|
+
"description": "RuvNet Brain lifecycle plane. The broad legacy routing, learning, and release interceptors remain retired. Lifecycle memory only CALLS ruflo (owner ruling 2026-10-12, ADR-106): there is no hand-rolled store, queue, journal or outbox. SessionStart restores the newest project-state-current-* checkpoint (and a newer project-state-auto-* one) and lists lesson keys by exact-key `ruflo memory list|retrieve`, and summarizes hook failures once. UserPromptSubmit runs the single unprompted-speech chokepoint, ground-ruvnet grounding injection (with one bounded AgentDB recall search), a capacity-aware context hint, and grounding-turn-mark. PreToolUse runs decision-gate's write route, the ONE process that may refuse a Write/Edit/MultiEdit/NotebookEdit/apply_patch (ADR-067, ADR-0012); PostToolUse on a successful search_ruvnet mints the grounding stamp that opens that gate, and session-snapshot PostToolUse writes an opted-in learning observation with `ruflo memory store`. Stop runs the continuation gate, writes the turn outcome with `ruflo memory store` in namespace turns proven by exact-key readback, and runs grounding-turn-gate. PreCompact and SessionEnd store one append-only project-state-auto-<ms> checkpoint with exact-key readback and start `ruflo memory distill run`. Every body is advisory and fails open except decision-gate (refuses a write) and unprompted-speech (exit 2 for an opted-in lesson block at UserPromptSubmit); a refused or unverified ruflo write, or a body stopped by the shim watchdog, is one row in hook-failures.jsonl, summarized at the next SessionStart.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
@@ -33,7 +33,7 @@ export function searchOnce({ ruflo, store, args, deadline, env, scratch, operati
|
|
|
33
33
|
catch { cleanup(); resolve({ rows: [], state: 'unavailable' }); return; }
|
|
34
34
|
let child;
|
|
35
35
|
try {
|
|
36
|
-
child = spawn(inv.executable, inv.args, { cwd, env: { ...env, RUFLO_DAEMON_AUTOSTART: '0', CLAUDE_FLOW_MEMORY_PATH: undefined },
|
|
36
|
+
child = spawn(inv.executable, inv.args, { cwd, env: { ...env, RUFLO_DAEMON_AUTOSTART: '0', CLAUDE_FLOW_AUTO_UPDATE: 'false', CLAUDE_FLOW_MEMORY_PATH: undefined },
|
|
37
37
|
stdio: ['ignore', 'pipe', 'ignore'], detached: process.platform !== 'win32', windowsHide: true });
|
|
38
38
|
} catch { cleanup(); resolve({ rows: [], state: 'unavailable' }); return; }
|
|
39
39
|
let out = '', outputBytes = 0;
|
|
@@ -8,7 +8,7 @@ import { resolveRuflo, rufloInvocationOrPlain } from './ruflo-bin.mjs';
|
|
|
8
8
|
const call = (bin, args, deadline) => new Promise((resolve) => {
|
|
9
9
|
const timeout = Math.max(1, deadline - Date.now());
|
|
10
10
|
const inv = rufloInvocationOrPlain(bin, args, { env: process.env });
|
|
11
|
-
execFile(inv.executable, inv.args, { timeout, killSignal: 'SIGKILL', encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, env: { ...process.env, RUFLO_DAEMON_AUTOSTART: '0' } },
|
|
11
|
+
execFile(inv.executable, inv.args, { timeout, killSignal: 'SIGKILL', encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, env: { ...process.env, RUFLO_DAEMON_AUTOSTART: '0', CLAUDE_FLOW_AUTO_UPDATE: 'false' } },
|
|
12
12
|
(error, stdout) => resolve(error ? null : String(stdout || '')));
|
|
13
13
|
});
|
|
14
14
|
|
|
@@ -181,8 +181,15 @@ export function confirm({ footprint, env = process.env, home = os.homedir(), now
|
|
|
181
181
|
// Footprint
|
|
182
182
|
const parts = Object.entries(footprint.breakdown).sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k} ${formatBytes(v)}`);
|
|
183
183
|
const foreign = footprint.unowned.reduce((n, i) => n + (i.bytes || 0), 0);
|
|
184
|
+
// `--clean` is the fix only when it can remove something. Measured 2026-10-24: 3.01 GB of a 2.23 GB budget with
|
|
185
|
+
// zero cruft, the line said "fix: --clean", and --clean freed nothing — the bytes were a 1.3 GB npm install ruflo's
|
|
186
|
+
// self-update left in ruvnet-brain/ruflo-cwd, counted as Brain state. Name where the bytes are instead.
|
|
187
|
+
const reclaimable = footprint.cruft.some((i) => ['remove', 'remove-if-proven', 'rotate', 'truncate', 'collect', 'prune'].includes(i.action));
|
|
188
|
+
const largest = footprint.items.filter((i) => i.kind === 'state' && i.bytes).sort((a, b) => b.bytes - a.bytes).slice(0, 2);
|
|
189
|
+
const footprintFix = reclaimable || !largest.length ? CLEAN
|
|
190
|
+
: `${CLEAN} has nothing to remove; the largest Brain folders are ${largest.map((i) => `${i.path} (${formatBytes(i.bytes)})`).join(' and ')} — see what is filling them`;
|
|
184
191
|
lines.push(line('footprint', 'Footprint', footprint.withinBudget ? 'ok' : 'fail',
|
|
185
|
-
`${formatBytes(footprint.totalBytes)} of ${formatBytes(footprint.budgetBytes)} budget (${parts.join(' · ')})${foreign ? `; not counted: ${formatBytes(foreign)} owned by other tools` : ''}`,
|
|
192
|
+
`${formatBytes(footprint.totalBytes)} of ${formatBytes(footprint.budgetBytes)} budget (${parts.join(' · ')})${foreign ? `; not counted: ${formatBytes(foreign)} owned by other tools` : ''}`, footprintFix));
|
|
186
193
|
|
|
187
194
|
// No cruft
|
|
188
195
|
const cruft = footprint.cruft;
|
|
@@ -25,6 +25,7 @@ import path from 'node:path';
|
|
|
25
25
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
26
26
|
import { isKbTree, kbCopyProof } from './kb-copy-proof.mjs';
|
|
27
27
|
import { brainLocation } from './brain-location.mjs';
|
|
28
|
+
import { RUFLO_CWD_ARTIFACTS } from './ruflo-bin.mjs';
|
|
28
29
|
import { assessMoveLeftovers, cachedKept, cmpVersion, isVolumeMetadata, keptCopyFix, physical, pidAlive, readProofCache, rememberKept, removeWithin, rotate,
|
|
29
30
|
treeBytes, truncateToTail } from './footprint-io.mjs';
|
|
30
31
|
|
|
@@ -59,7 +60,8 @@ export const LOG_FILES = Object.freeze(['evidence.jsonl', 'token-ledger.jsonl',
|
|
|
59
60
|
'capability-live-evidence.jsonl', 'design-grades.jsonl', 'grounding-overrides.jsonl']);
|
|
60
61
|
const TEXT_LOG = /^\.(?:last-[A-Za-z0-9-]+|seed)\.log$/;
|
|
61
62
|
const RECOVERY_LEFTOVER = /(?:\.(?:bak|retired|dead)-\d{6,}|^bootstrap-backup-\d)/;
|
|
62
|
-
|
|
63
|
+
// One list with ruflo-bin.mjs, which clears the same names before each call (incl. a self-update's npm install, 1.3 GB on 2026-10-22).
|
|
64
|
+
const RUFLO_DEBRIS = new Set(RUFLO_CWD_ARTIFACTS);
|
|
63
65
|
// Directories in the brain home that hold data another tool owns: measured, shown, never budgeted or removed.
|
|
64
66
|
const FOREIGN = { 'ruvector-mcp': 'RuVector MCP server working store (its own live memory)',
|
|
65
67
|
'worktree-recovery': 'hand-made recovery archives, not created by the Brain' };
|
|
@@ -134,7 +134,7 @@ export const CONTINUITY_EVENTS = Object.freeze({
|
|
|
134
134
|
// The "answered without searching" gate, half 1 of 2 (2026-09-12) — see grounding-turn-gate.mjs's
|
|
135
135
|
// header for the full rationale. Records that ground-ruvnet's Gate 1 fired for this turn, since
|
|
136
136
|
// Stop's own payload carries no prompt text for grounding-turn-gate to test.
|
|
137
|
-
registration('grounding-turn-mark', '*', ['claude', 'codex']),
|
|
137
|
+
registration('grounding-turn-mark', '*', ['claude', 'codex', 'grok']),
|
|
138
138
|
]),
|
|
139
139
|
// The write gate and its key (ADR-0012 / ADR-067), re-registered 2026-09-11 — see the header.
|
|
140
140
|
// Extended to Codex 2026-09-12: a real `apply_patch` write was measured to fire PreToolUse on
|
|
@@ -143,7 +143,7 @@ export const CONTINUITY_EVENTS = Object.freeze({
|
|
|
143
143
|
// Claude's `Edit` shape for decision-gate.mjs's own policies (protect-state, hijack-ruvnet,
|
|
144
144
|
// ground-before-write, adr-currency all then see tool_name:"Edit" exactly as on Claude).
|
|
145
145
|
PreToolUse: Object.freeze([
|
|
146
|
-
registration('decision-gate', '^(Write|Edit|MultiEdit|NotebookEdit|apply_patch)$', ['claude', 'codex']),
|
|
146
|
+
registration('decision-gate', '^(Write|Edit|MultiEdit|NotebookEdit|apply_patch)$', ['claude', 'codex', 'grok']),
|
|
147
147
|
]),
|
|
148
148
|
// Extended to Codex 2026-09-12: a real MCP `search_ruvnet` call was measured to fire PostToolUse
|
|
149
149
|
// on codex-cli 0.154.0 with tool_name "mcp__ruvnet_brain__search_ruvnet" — the matcher below
|
|
@@ -153,18 +153,18 @@ export const CONTINUITY_EVENTS = Object.freeze({
|
|
|
153
153
|
// "Searched N RuvNet repos" banner and query text the script already looks for).
|
|
154
154
|
PostToolUse: Object.freeze([
|
|
155
155
|
// Learning observation (consent-gated `ruflo memory store`) runs inside session-snapshot's PostToolUse body.
|
|
156
|
-
registration('session-snapshot', '*', ['claude', 'codex']),
|
|
157
|
-
registration('grounding-stamp', '^(?:.*__)?search_ruvnet$', ['claude', 'codex']),
|
|
156
|
+
registration('session-snapshot', '*', ['claude', 'codex', 'grok']),
|
|
157
|
+
registration('grounding-stamp', '^(?:.*__)?search_ruvnet$', ['claude', 'codex', 'grok']),
|
|
158
158
|
]),
|
|
159
159
|
Stop: Object.freeze([
|
|
160
|
-
registration('continuation-gate', '*', ['claude', 'codex']),
|
|
160
|
+
registration('continuation-gate', '*', ['claude', 'codex', 'grok']),
|
|
161
161
|
// Codex added 2026-09-29 (owner requirement: every turn's outcome recorded on BOTH hosts —
|
|
162
162
|
// turn-outcome-capture.mjs). Codex SessionEnd carries no last_assistant_message, so Stop is the
|
|
163
163
|
// only boundary that can record a Codex turn. Evidence: codex-cli 0.158.0's own
|
|
164
164
|
// stop.command.input schema (last_assistant_message, turn_id) and the two Codex Stop handlers
|
|
165
165
|
// already registered above/below. NOT yet live-observed firing — see the probe box: the capture
|
|
166
166
|
// fails open, so an unfired registration costs nothing but must not be read as proof.
|
|
167
|
-
registration('session-snapshot', '*', ['claude', 'codex']),
|
|
167
|
+
registration('session-snapshot', '*', ['claude', 'codex', 'grok']),
|
|
168
168
|
// The "answered without searching" gate, half 2 of 2 (2026-09-12). Forces continuation
|
|
169
169
|
// (hookSpecificOutput.additionalContext — the same contract continuation-gate.mjs already uses
|
|
170
170
|
// and codex-hook-adapter.mjs already translates to Codex's decision:block on both hosts) when
|
|
@@ -172,13 +172,13 @@ export const CONTINUITY_EVENTS = Object.freeze({
|
|
|
172
172
|
// (grounding-stamp.sh) postdates it. A separate registration from continuation-gate on purpose —
|
|
173
173
|
// see grounding-turn-gate.mjs's header for why folding it in would corrupt that file's ledger
|
|
174
174
|
// semantics rather than extend them.
|
|
175
|
-
registration('grounding-turn-gate', '*', ['claude', 'codex']),
|
|
175
|
+
registration('grounding-turn-gate', '*', ['claude', 'codex', 'grok']),
|
|
176
176
|
]),
|
|
177
177
|
PreCompact: Object.freeze([
|
|
178
|
-
registration('session-snapshot', '*', ['claude', 'codex']),
|
|
178
|
+
registration('session-snapshot', '*', ['claude', 'codex', 'grok']),
|
|
179
179
|
]),
|
|
180
180
|
SessionEnd: Object.freeze([
|
|
181
|
-
registration('session-snapshot', '*', ['claude', 'codex']),
|
|
181
|
+
registration('session-snapshot', '*', ['claude', 'codex', 'grok']),
|
|
182
182
|
]),
|
|
183
183
|
});
|
|
184
184
|
|
|
@@ -138,7 +138,7 @@ function runRuflo(ruflo, db, args, { spawn = spawnSync } = {}) {
|
|
|
138
138
|
try {
|
|
139
139
|
const { executable, args: argv } = rufloInvocation(ruflo, args);
|
|
140
140
|
const r = spawn(executable, argv, { cwd, encoding: 'utf8', timeout: 60_000, windowsHide: true,
|
|
141
|
-
env: { ...process.env, RUFLO_DAEMON_AUTOSTART: '0', CLAUDE_FLOW_MEMORY_PATH: undefined } });
|
|
141
|
+
env: { ...process.env, RUFLO_DAEMON_AUTOSTART: '0', CLAUDE_FLOW_AUTO_UPDATE: 'false', CLAUDE_FLOW_MEMORY_PATH: undefined } });
|
|
142
142
|
return { status: Number.isInteger(r.status) ? r.status : 1, stdout: String(r.stdout || ''), output: `${r.stderr || ''}\n${r.stdout || ''}${r.error ? `\n${r.error.message}` : ''}` };
|
|
143
143
|
} finally { fs.rmSync(cwd, { recursive: true, force: true }); }
|
|
144
144
|
}
|