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.
Files changed (43) hide show
  1. package/README.md +2 -2
  2. package/bin/install.mjs +120 -15
  3. package/package.json +1 -1
  4. package/plugin/.claude-plugin/plugin.json +1 -1
  5. package/plugin/.codex-plugin/plugin.json +1 -1
  6. package/plugin/hooks/grok-hooks.json +1 -1
  7. package/plugin/hooks/hook-contracts.json +36 -18
  8. package/plugin/hooks/hooks.json +1 -1
  9. package/plugin/scripts/agentdb-recall-process.mjs +1 -1
  10. package/plugin/scripts/agentdb-recall-reader.mjs +1 -1
  11. package/plugin/scripts/brain-confirmation.mjs +8 -1
  12. package/plugin/scripts/brain-footprint.mjs +3 -1
  13. package/plugin/scripts/continuity-hook-policy.mjs +9 -9
  14. package/plugin/scripts/decision-record.mjs +1 -1
  15. package/plugin/scripts/degradation-watch.mjs +13 -1
  16. package/plugin/scripts/developer-update-policy.mjs +31 -5
  17. package/plugin/scripts/developer-update.mjs +17 -5
  18. package/plugin/scripts/grok-hook-adapter.mjs +7 -3
  19. package/plugin/scripts/hook-failures.mjs +76 -8
  20. package/plugin/scripts/learn-capture.mjs +74 -9
  21. package/plugin/scripts/learning-store.mjs +17 -7
  22. package/plugin/scripts/nightly-scheduler.mjs +67 -12
  23. package/plugin/scripts/ruflo-bin.mjs +5 -1
  24. package/plugin/scripts/ruflo-memory-hooks.mjs +1 -1
  25. package/plugin/scripts/turn-outcome-capture.mjs +18 -18
  26. package/scripts/agentdb-fleet-doctor.mjs +1 -1
  27. package/scripts/corpus-dispatch-decision.mjs +5 -4
  28. package/scripts/corpus-watchdog.mjs +28 -2
  29. package/scripts/distill-project.mjs +1 -1
  30. package/scripts/falsify.mjs +1 -1
  31. package/scripts/health-repair.mjs +1 -1
  32. package/scripts/learning-replay-execution.mjs +3 -3
  33. package/scripts/learning-replay-fixture.mjs +3 -0
  34. package/scripts/lesson-migrate-agentdb.mjs +1 -1
  35. package/scripts/nightly-wrapper.sh +2 -2
  36. package/scripts/proxy/claude-proxied.sh +1 -0
  37. package/scripts/proxy/proxy-revert.sh +1 -0
  38. package/scripts/proxy/proxy-up.sh +1 -0
  39. package/scripts/proxy/proxy-verify.mjs +1 -1
  40. package/scripts/record-lesson.mjs +1 -1
  41. package/scripts/selfcheck.mjs +1 -1
  42. package/scripts/single-source-check.mjs +1 -1
  43. 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
- [![RuvNet Brain version 4.6.2 — README updated 2026-10-10 EDT](https://img.shields.io/badge/version_4.7.16-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.18-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
 
@@ -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 **203 public stores · 164,863 public source chunks**.
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
- function runCodexJson(args, {
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 r = runHostCli(codexBin, args, {
2035
- stdio: 'pipe',
2036
- shell: false,
2037
- cwd,
2038
- env: { ...process.env, CODEX_HOME: codexHome },
2039
- encoding: 'utf8',
2040
- timeout: 30_000,
2041
- maxBuffer: 20 * 1024 * 1024,
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
- const detail = String(r.stderr || r.stdout || r.error?.message || `exit ${r.status}`).trim();
2046
- return { ok: false, error: detail };
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
- return { ok: false, results, ...detail };
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.16",
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.16",
4
+ "version": "4.7.18",
5
5
  "author": {
6
6
  "name": "Stuart Kerr"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvnet-brain",
3
- "version": "4.7.16",
3
+ "version": "4.7.18",
4
4
  "description": "Source-grounded RuvNet knowledge, lifecycle enforcement, and learning for Codex.",
5
5
  "author": {
6
6
  "name": "Stuart Kerr"
@@ -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. UNVERIFIED (docs silent, not measured): whether Grok honours a SessionEnd timeout above its 1.5 s default (10-hooks.md:477-478); 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).",
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",
@@ -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` : ''}`, CLEAN));
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
- const RUFLO_DEBRIS = new Set(['.swarm', '.claude', '.claude-flow', 'ruvector.db']);
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
  }