ruvnet-brain 3.9.133-dev → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/.claude-plugin/marketplace.json +13 -0
  2. package/README.md +3 -3
  3. package/bin/install.mjs +284 -33
  4. package/kb/zip-extract.mjs +53 -14
  5. package/package.json +7 -1
  6. package/plugin/.claude-plugin/marketplace.json +13 -0
  7. package/plugin/.claude-plugin/plugin.json +23 -0
  8. package/plugin/.codex-plugin/plugin.json +21 -0
  9. package/plugin/.mcp.json +8 -0
  10. package/plugin/commands/brain-console.md +16 -0
  11. package/plugin/commands/configure.md +32 -0
  12. package/plugin/commands/rvbc.md +78 -0
  13. package/plugin/commands/rvcb.md +16 -0
  14. package/plugin/commands/whats-new.md +57 -0
  15. package/plugin/hooks/codex-hooks.json +160 -0
  16. package/plugin/hooks/hook-contracts.json +77 -0
  17. package/plugin/hooks/hooks.json +203 -0
  18. package/plugin/mcp/server.mjs +35 -6
  19. package/plugin/scripts/anticipate.sh +534 -0
  20. package/plugin/scripts/codex-hook-adapter.mjs +96 -0
  21. package/plugin/scripts/continuation-gate.mjs +267 -0
  22. package/plugin/scripts/design-wall.sh +137 -0
  23. package/plugin/scripts/detach.mjs +168 -0
  24. package/plugin/scripts/finalize-token-meter.mjs +25 -0
  25. package/plugin/scripts/gate-receipt.sh +35 -0
  26. package/plugin/scripts/ground-before-write.sh +199 -0
  27. package/plugin/scripts/ground-ruvnet.sh +507 -0
  28. package/plugin/scripts/grounding-stamp.sh +113 -0
  29. package/plugin/scripts/grounding-substance.mjs +595 -0
  30. package/plugin/scripts/hijack-ruvnet.sh +81 -0
  31. package/plugin/scripts/hook-input.mjs +558 -0
  32. package/plugin/scripts/hook-shim-bash.mjs +55 -0
  33. package/plugin/scripts/hook-shim.mjs +303 -0
  34. package/plugin/scripts/host-update.mjs +58 -0
  35. package/plugin/scripts/kling-preflight.sh +146 -0
  36. package/plugin/scripts/learn-capture.sh +154 -0
  37. package/plugin/scripts/learn-flush.mjs +138 -0
  38. package/plugin/scripts/lesson-hooks.sh +213 -0
  39. package/plugin/scripts/md-stamp.mjs +219 -0
  40. package/plugin/scripts/protect-brain-state.sh +84 -0
  41. package/plugin/scripts/route-dispatch.sh +147 -0
  42. package/plugin/scripts/routing-outcome-capture.mjs +89 -0
  43. package/plugin/scripts/session-start.sh +868 -0
  44. package/plugin/scripts/signal-watch.mjs +193 -0
  45. package/plugin/scripts/unprompted-runtime.mjs +377 -0
  46. package/plugin/scripts/update-apply.mjs +419 -0
  47. package/plugin/scripts/verify-interface.sh +53 -0
  48. package/plugin/scripts/version-bump-gate.sh +112 -0
  49. package/plugin/skills/brain-build/SKILL.md +123 -0
  50. package/plugin/skills/brain-console/SKILL.md +20 -0
  51. package/plugin/skills/brain-prompt/SKILL.md +83 -0
  52. package/plugin/skills/brain-score/SKILL.md +101 -0
  53. package/plugin/skills/ruvnet-brain/PLAYBOOK.md +117 -0
  54. package/plugin/skills/ruvnet-brain/SKILL.md +234 -0
  55. package/plugin/skills/rvbc/SKILL.md +20 -0
  56. package/plugin/skills/savings/SKILL.md +46 -0
  57. package/plugin/skills/whats-new/SKILL.md +22 -0
@@ -47,15 +47,28 @@ const SIG_EOCD64_LOC = 0x07064b50;
47
47
  const SIG_CENTRAL = 0x02014b50;
48
48
  const SIG_LOCAL = 0x04034b50;
49
49
 
50
+ export const ZIP_LIMITS = Object.freeze({
51
+ archiveBytes: 2 * 1024 ** 3,
52
+ entries: 100_000,
53
+ centralDirectoryBytes: 128 * 1024 ** 2,
54
+ entryBytes: 2 * 1024 ** 3,
55
+ totalBytes: 8 * 1024 ** 3,
56
+ compressionRatio: 1_000,
57
+ });
58
+
50
59
  // zlib.crc32 landed in Node 20.15.0 / 22.2.0. package.json engines allow >=18, so it is OPTIONAL —
51
60
  // present it is used, absent the size check still stands and the caller is told which happened.
52
61
  // Reimplementing CRC-32 here would be hand-rolling something the runtime ships.
53
62
  const crc32 = typeof zlib.crc32 === 'function' ? zlib.crc32 : null;
54
63
 
55
64
  class Tally extends Transform {
56
- constructor() { super(); this.bytes = 0; this.crc = 0; }
65
+ constructor(maxBytes) { super(); this.bytes = 0; this.crc = 0; this.maxBytes = maxBytes; }
57
66
  _transform(chunk, _enc, cb) {
58
67
  this.bytes += chunk.length;
68
+ if (this.bytes > this.maxBytes) {
69
+ cb(new Error(`uncompressed entry exceeded the ${this.maxBytes}-byte safety limit`));
70
+ return;
71
+ }
59
72
  if (crc32) this.crc = crc32(chunk, this.crc);
60
73
  this.push(chunk);
61
74
  cb();
@@ -181,10 +194,40 @@ export async function extractZip(zipPath, destDir) {
181
194
  try {
182
195
  const size = fs.fstatSync(fd).size;
183
196
  if (size === 0) throw new Error('archive is empty (0 bytes)');
197
+ if (size > ZIP_LIMITS.archiveBytes) {
198
+ throw new Error(`archive is ${size} bytes, above the ${ZIP_LIMITS.archiveBytes}-byte safety limit`);
199
+ }
184
200
  const { cdSize, cdOffset } = centralDirectoryLocation(fd, size);
201
+ if (cdSize > ZIP_LIMITS.centralDirectoryBytes) {
202
+ throw new Error(`central directory is ${cdSize} bytes, above the ${ZIP_LIMITS.centralDirectoryBytes}-byte safety limit`);
203
+ }
185
204
  if (cdOffset + cdSize > size) throw new Error(`central directory runs past end of file (offset ${cdOffset} + ${cdSize} > ${size})`);
186
205
  const entries = parseCentralDirectory(readAt(fd, cdSize, cdOffset));
187
206
  if (entries.length === 0) throw new Error('central directory contains no entries');
207
+ if (entries.length > ZIP_LIMITS.entries) {
208
+ throw new Error(`archive has ${entries.length} entries, above the ${ZIP_LIMITS.entries}-entry safety limit`);
209
+ }
210
+ let declaredTotal = 0;
211
+ for (const entry of entries) {
212
+ const unixType = (entry.externalAttrs >>> 16) & 0xf000;
213
+ if ((entry.versionMadeBy >> 8) === 3 && unixType === 0xa000) {
214
+ throw new Error(`entry "${entry.name}" is a symbolic link — downloaded bundles may contain regular files and directories only`);
215
+ }
216
+ if (!Number.isSafeInteger(entry.uncompSize) || entry.uncompSize < 0) {
217
+ throw new Error(`entry "${entry.name}" has an unsafe uncompressed size`);
218
+ }
219
+ if (entry.uncompSize > ZIP_LIMITS.entryBytes) {
220
+ throw new Error(`entry "${entry.name}" claims ${entry.uncompSize} bytes, above the ${ZIP_LIMITS.entryBytes}-byte safety limit`);
221
+ }
222
+ declaredTotal += entry.uncompSize;
223
+ if (!Number.isSafeInteger(declaredTotal) || declaredTotal > ZIP_LIMITS.totalBytes) {
224
+ throw new Error(`archive claims ${declaredTotal} uncompressed bytes, above the ${ZIP_LIMITS.totalBytes}-byte safety limit`);
225
+ }
226
+ const ratio = entry.uncompSize / Math.max(entry.compSize, 1);
227
+ if (ratio > ZIP_LIMITS.compressionRatio) {
228
+ throw new Error(`entry "${entry.name}" has compression ratio ${ratio.toFixed(1)}, above the ${ZIP_LIMITS.compressionRatio}:1 safety limit`);
229
+ }
230
+ }
188
231
 
189
232
  fs.mkdirSync(destDir, { recursive: true });
190
233
  let files = 0;
@@ -234,17 +277,6 @@ export async function extractZip(zipPath, destDir) {
234
277
  throw new Error(`entry "${e.name}" uses unsupported compression method ${e.method} (only stored=0 and deflate=8 are implemented)`);
235
278
  }
236
279
 
237
- const isSymlink = (e.versionMadeBy >> 8) === 3 && ((e.externalAttrs >>> 16) & 0xf000) === 0xa000;
238
- if (isSymlink) {
239
- // A symlink's payload is its target path — always tiny, so it is read whole rather than streamed.
240
- const comp = e.compSize === 0 ? Buffer.alloc(0) : readAt(fd, e.compSize, dataStart);
241
- const linkTarget = (e.method === 8 ? zlib.inflateRawSync(comp) : comp).toString('utf8');
242
- fs.rmSync(target, { recursive: true, force: true });
243
- fs.symlinkSync(linkTarget, target);
244
- files++;
245
- continue;
246
- }
247
-
248
280
  // A zero-length entry has no data range to stream (start > end would be a nonsense read), so
249
281
  // it is written directly. Its size/CRC assertions below still apply.
250
282
  if (e.compSize === 0) {
@@ -254,25 +286,32 @@ export async function extractZip(zipPath, destDir) {
254
286
  continue;
255
287
  }
256
288
 
257
- const tally = new Tally();
289
+ const tally = new Tally(Math.min(e.uncompSize, ZIP_LIMITS.entryBytes));
290
+ const partial = path.join(path.dirname(target), `.ruvnet-extract-${process.pid}-${entryIndex}.tmp`);
291
+ try { fs.rmSync(partial, { force: true }); } catch { /* absent */ }
258
292
  const stages = [fs.createReadStream(zipPath, { start: dataStart, end: dataStart + e.compSize - 1 })];
259
293
  if (e.method === 8) stages.push(zlib.createInflateRaw());
260
- stages.push(tally, fs.createWriteStream(target));
294
+ stages.push(tally, fs.createWriteStream(partial, { flags: 'wx' }));
261
295
 
262
296
  try {
263
297
  await pipeline(...stages);
264
298
  } catch (err) {
299
+ try { fs.rmSync(partial, { force: true }); } catch { /* best effort */ }
265
300
  throw new Error(`entry "${e.name}" failed to inflate (${err && err.code ? err.code : ''}${err && err.message ? ` ${err.message}` : ''}) — the archive is corrupt or truncated`);
266
301
  }
267
302
  if (tally.bytes !== e.uncompSize) {
303
+ fs.rmSync(partial, { force: true });
268
304
  throw new Error(`entry "${e.name}" is corrupt: expected ${e.uncompSize} bytes, extracted ${tally.bytes}`);
269
305
  }
270
306
  if (crc32 && (tally.crc >>> 0) !== (e.crc >>> 0)) {
307
+ fs.rmSync(partial, { force: true });
271
308
  // Zero-padded to 8 hex digits so the value is directly comparable to the one Info-ZIP's
272
309
  // own `unzip` prints for the same damaged entry ("bad CRC 811e0dd1 (should be 0f6994e4)").
273
310
  const hex = (n) => (n >>> 0).toString(16).padStart(8, '0');
274
311
  throw new Error(`entry "${e.name}" failed its CRC-32 check (expected ${hex(e.crc)}, got ${hex(tally.crc)}) — the archive is corrupt`);
275
312
  }
313
+ if (process.platform === 'win32') fs.rmSync(target, { recursive: true, force: true });
314
+ fs.renameSync(partial, target);
276
315
 
277
316
  // Unix permission bits, when the archive carries them. Skipped on Windows, which has no
278
317
  // POSIX mode and where chmod is a no-op at best.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvnet-brain",
3
- "version": "3.9.133-dev",
3
+ "version": "4.0.1",
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": {
@@ -49,11 +49,17 @@
49
49
  "cap:ab": "node scripts/rerank-cap-warm-ab.mjs"
50
50
  },
51
51
  "files": [
52
+ ".claude-plugin/marketplace.json",
52
53
  "bin/install.mjs",
54
+ "plugin/",
53
55
  "plugin/mcp/managed-cli-interface.mjs",
54
56
  "plugin/mcp/server.mjs",
55
57
  "plugin/scripts/codex-hook-wrapper.mjs",
58
+ "plugin/scripts/host-update.mjs",
59
+ "plugin/scripts/update-apply.mjs",
56
60
  "!plugin/README.md",
61
+ "!plugin/scripts/.ruvnet-brain/",
62
+ "!plugin/test/",
57
63
  "README.md",
58
64
  "LICENSE",
59
65
  "config/model-router/",
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "ruvnet-brain",
3
+ "owner": {
4
+ "name": "Stuart Kerr"
5
+ },
6
+ "plugins": [
7
+ {
8
+ "name": "ruvnet-brain",
9
+ "source": "./",
10
+ "description": "RuvNet brain transplant for Claude Code: source-grounded knowledge of the rUv ecosystem (19+ repos) + enforced grounding so Claude stops arguing about RuvNet and actually uses it + on-demand repo ingestion."
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "ruvnet-brain",
3
+ "description": "RuvNet brain transplant for Claude Code — grounds every RuvNet decision in real source across 69 rUv repositories, prefers Ruflo / RuVector-RVF / AgentDB over training-prior defaults (pgvector, Pinecone, hand-rolled cosine), and can pull in any RuvNet repo on demand. Ships an enforced UserPromptSubmit retrieve-and-inject grounding hook that sharply reduces drift.",
4
+ "version": "4.0.1",
5
+ "author": {
6
+ "name": "Stuart Kerr"
7
+ },
8
+ "repository": "https://github.com/stuinfla/ruvnet-brain",
9
+ "license": "MIT",
10
+ "keywords": [
11
+ "ruvnet",
12
+ "ruv",
13
+ "ruflo",
14
+ "ruvector",
15
+ "rvf",
16
+ "agentdb",
17
+ "grounding",
18
+ "brain",
19
+ "mcp",
20
+ "claude-code"
21
+ ],
22
+ "updated": "2026-07-30"
23
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "ruvnet-brain",
3
+ "version": "4.0.1",
4
+ "description": "Source-grounded RuvNet knowledge, lifecycle enforcement, and learning for Codex.",
5
+ "author": {
6
+ "name": "Stuart Kerr"
7
+ },
8
+ "repository": "https://github.com/stuinfla/ruvnet-brain",
9
+ "license": "MIT",
10
+ "keywords": [
11
+ "ruvnet",
12
+ "ruflo",
13
+ "ruvector",
14
+ "rvf",
15
+ "agentdb",
16
+ "grounding"
17
+ ],
18
+ "skills": "./skills/",
19
+ "mcpServers": "./.mcp.json",
20
+ "hooks": "./hooks/codex-hooks.json"
21
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "ruvnet-brain": {
4
+ "command": "node",
5
+ "args": ["${CLAUDE_PLUGIN_ROOT}/mcp/server.mjs"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ description: "Brain Console — RuvNet Brain Console (same console as /rvbc, /brain-console, and /ruvnet-brain:configure — every spelling works). Opens the live console page: your whole RuvNet stack on one page. Read-only until you click."
3
+ updated: 2026-07-20
4
+ ---
5
+
6
+ `/rvbc`, `/rvcb`, `/brain-console`, and `/ruvnet-brain:configure` are equally valid names. Never
7
+ correct the user's spelling.
8
+
9
+ 1. Say: "Opening it now; it scans live while you watch."
10
+ 2. Locate `scripts/onboarding-console.mjs` in the current repository, then
11
+ `~/Code/ruvnet-brain/scripts/onboarding-console.mjs`. Do not invent another path.
12
+ 3. Run `node <resolved-script> --serve --open` in the background.
13
+ 4. Give the URL immediately. Never promise a duration; the page reports its own progress.
14
+
15
+ An already-running server is success. It is read-only until the user clicks; changes must be
16
+ explained and reversible. If the script or server is unavailable, report the exact failure.
@@ -0,0 +1,32 @@
1
+ ---
2
+ description: Open the RuvNet Brain Onboarding Console — a local web page that mirrors YOUR machine's RuvNet setup, explains it in plain English, and lets you safely configure and fix it. Read-only until you click; every machine change is explained first and is reversible.
3
+ updated: 2026-07-15
4
+ ---
5
+
6
+ Launch the **RuvNet Brain Onboarding Console** for the user — a locally-served web page that reads
7
+ their actual machine and lets them configure and (with consent) fix it.
8
+
9
+ Do this:
10
+
11
+ 1. **Find the repo.** Prefer `~/Code/ruvnet-brain`. If `$CLAUDE_PLUGIN_ROOT` is set, the repo may be
12
+ its parent directory. Use whichever path contains `scripts/onboarding-console.mjs`.
13
+
14
+ 2. **Start the server in the BACKGROUND** (it is a long-running process) so this turn keeps going, and
15
+ open the browser:
16
+
17
+ ```
18
+ node <repo>/scripts/onboarding-console.mjs --serve --open
19
+ ```
20
+
21
+ Run it with `run_in_background: true`. It binds `127.0.0.1` only and prints a line like
22
+ `http://127.0.0.1:7411/`.
23
+
24
+ 3. **Give the user the link** as a clickable `http://127.0.0.1:<port>/`, with one warm sentence:
25
+ the page is **read-only until you click**, everything it can change to your computer is **explained
26
+ in plain English and confirmed first**, and **Settings save at your user level**
27
+ (`~/.claude/ruvnet-brain/config.json`) without touching how anything else on your machine runs.
28
+
29
+ 4. If the server errors (e.g. `sqlite3` not found, or a bad node version), report it plainly and offer
30
+ to fix it — don't leave them staring at a dead tab.
31
+
32
+ Do **not** narrate the machine yourself — the page is the mirror. Your job is just to get it open.
@@ -0,0 +1,78 @@
1
+ ---
2
+ description: "RvBC — RuvNet Brain Console. Opens the live console page: your whole RuvNet stack on one page — what's installed, what your AI learned, one-click reversible fixes. Read-only until you click. (Also: /rvcb, /brain-console — all the same thing.)"
3
+ updated: 2026-07-20
4
+ ---
5
+
6
+ Launch the **RuvNet Brain Console** for the user. Same console as `/ruvnet-brain:configure`,
7
+ `/rvcb`, and `/brain-console` — every spelling lands here, so never tell the user they typed it wrong.
8
+
9
+ ## The contract you are keeping
10
+
11
+ The page **opens immediately and scans itself while they watch.** The server never blocks: a cold or
12
+ new-project read is answered in milliseconds with "measuring…", the page keeps its skeletons and
13
+ narrates the wait, and each card fills in as its own measurement lands. A chip in the header says how
14
+ old the picture is and re-measures when clicked.
15
+
16
+ So there is nothing to wait for and nothing to warn them about. **Your entire job is: say one
17
+ sentence, start the server in the background, hand over the URL, and get out of the way.**
18
+
19
+ ## 1. SPEAK FIRST — one short sentence, before any tool call
20
+
21
+ Your FIRST output — before any tool call, any file read, any search — is one warm line saying you are
22
+ opening it now and it will scan itself live in their browser. Something in the spirit of:
23
+
24
+ > "Opening your console now — it'll come up right away and scan your setup live while you watch."
25
+
26
+ Say it like a person, not a status bar. **Do not** promise 20 seconds, a minute, or any duration: the
27
+ page carries its own timing now, and a number invented here is a number the page will contradict.
28
+
29
+ ## 2. Find the repo
30
+
31
+ Prefer `~/Code/ruvnet-brain`. If `$CLAUDE_PLUGIN_ROOT` is set, the repo may be its parent
32
+ directory. Use whichever path actually contains `scripts/onboarding-console.mjs`.
33
+
34
+ ## 3. Start it in the BACKGROUND and open the browser
35
+
36
+ ```
37
+ node <repo>/scripts/onboarding-console.mjs --serve --open
38
+ ```
39
+
40
+ **`run_in_background: true`, ALWAYS — never in the foreground.** This is a server: it does not exit,
41
+ so a foreground run hangs the tool call until the 120-second timeout and turns an instant page into
42
+ two minutes of dead air. It binds `127.0.0.1` only and prints `http://127.0.0.1:7411/` within about a
43
+ third of a second.
44
+
45
+ **Then post the URL immediately.** Do not poll the log, do not wait for a scan, do not check whether
46
+ the data has landed — the page reports its own progress and none of it is visible from here.
47
+
48
+ **If it reports it is already running**, that is a success, not an error: open the URL yourself with
49
+ `open "http://127.0.0.1:<port>/"`. One honest caveat, worth half a sentence if it comes up — a
50
+ console that is already running is serving the project it was *launched* from, so if they want this
51
+ project's view they can stop that one (`^C` in its terminal) and run this again.
52
+
53
+ ## 4. Hand it over and stop
54
+
55
+ Give them the URL as a clickable line, and one warm sentence: the page is **read-only until you
56
+ click**, every machine change is **explained first and reversible**, and Settings save at the user
57
+ level only. Worth naming once: the brain's on/off switch is the first card on the page, and the
58
+ header chip says how fresh the reading is and re-measures if they click it.
59
+
60
+ ## 5. If it errors
61
+
62
+ Report it plainly — the real error, not a guess — and offer to fix it. Never leave them staring at
63
+ a dead tab wondering whether it is still loading.
64
+
65
+ ---
66
+
67
+ ## Things NOT to do before the page is open
68
+
69
+ Each of these has cost a real user real minutes of silence, and not one of them is needed to open a
70
+ local web page:
71
+
72
+ - **No knowledge search, no memory recall, no source grounding, no repo exploration.** A cold
73
+ embedding-model load alone is allowed up to 180 seconds.
74
+ - **No model selection or routing work.**
75
+ - **No reading this repo to "check" anything first.** Find the script, run it.
76
+
77
+ Do **not** narrate the machine yourself afterwards; the page is the mirror. Your job is to open it,
78
+ say one sentence, and get out of the way.
@@ -0,0 +1,16 @@
1
+ ---
2
+ description: "RvCB — RuvNet Brain Console (same console as /rvbc, /brain-console, and /ruvnet-brain:configure — every spelling works). Opens the live console page: your whole RuvNet stack on one page. Read-only until you click."
3
+ updated: 2026-07-20
4
+ ---
5
+
6
+ `/rvbc`, `/rvcb`, `/brain-console`, and `/ruvnet-brain:configure` are equally valid names. Never
7
+ correct the user's spelling.
8
+
9
+ 1. Say: "Opening it now; it scans live while you watch."
10
+ 2. Locate `scripts/onboarding-console.mjs` in the current repository, then
11
+ `~/Code/ruvnet-brain/scripts/onboarding-console.mjs`. Do not invent another path.
12
+ 3. Run `node <resolved-script> --serve --open` in the background.
13
+ 4. Give the URL immediately. Never promise a duration; the page reports its own progress.
14
+
15
+ An already-running server is success. It is read-only until the user clicks; changes must be
16
+ explained and reversible. If the script or server is unavailable, report the exact failure.
@@ -0,0 +1,57 @@
1
+ ---
2
+ description: "What's new in RuvNet Brain — the big things in the current MAJOR release (4.0), in plain English. Not the point-release churn — the headline changes since 3.x. Ends by offering to open the Console so you can see it live."
3
+ updated: 2026-07-25
4
+ ---
5
+
6
+ # RuvNet-Brain: what's new
7
+
8
+ The user wants the headline story of the **major** release they're on — the 4.0-vs-3.0 kind of change,
9
+ **not** the "3.9.x → 3.9.y" point-release churn. Deliver it warmly and honestly, then offer the Console.
10
+
11
+ **First, ground — never recite this from memory (it drifts every release):**
12
+ 1. Read the running version from `${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json`. State it honestly.
13
+ **Do NOT claim "you're on 4.0"** unless the version literally starts with `4.` — per ADR-042 the
14
+ number stays `3.9.x-dev` until the 4.0 line is field-verified. The honest framing is: *"these are the
15
+ 4.0-line enhancements, and you already have them — the version stamps to 4.0 once they're proven in
16
+ real use."*
17
+ 2. Read `docs/RELEASE-NOTES-4.0.md` (the curated highlights + its VERSION STATUS banner). Its content is
18
+ the source of truth for what follows — summarize it, don't invent beside it, and carry its honest
19
+ version framing.
20
+
21
+ **Then tell them what the 4.0 line is — the honest headline (adapt to the notes file; do NOT overclaim):**
22
+
23
+ The 4.0 line is where the brain got **honest, legible, fast, and self-measuring** — landing now in the
24
+ 3.9.x releases. The big things:
25
+
26
+ - **The Console is the front door.** One live local page (type `/rvbc`) — your whole RuvNet stack on
27
+ one screen: what's installed, what the AI has actually learned from *your* projects (real memories +
28
+ distilled lessons, drill-down to the verbatim cards), which subscription pays for what, and one-click
29
+ **reversible** fixes for anything stale. New in 4.0: a plain-English explainer on every card, each
30
+ suggestion labelled with its blast radius (*just this project* vs *every project*), safe on/off
31
+ checkboxes that only appear where the undo is proven, and a terminal-first install.
32
+ - **It will not lie about your machine.** Every number is measured live; "we couldn't check" never
33
+ renders as "off"; one project's state can never leak into another's view.
34
+ - **Fast, and it tells you when it's ready.** The console and tips page paint in well under a second;
35
+ on a first scan it shows a countdown and then says *"it's live — take a look at your page,"* so you're
36
+ never staring at nothing.
37
+ - **It measures itself now.** The brain records when it offered help and whether you acted on it, so it
38
+ can *prove* it's improving rather than assert it. **Be honest that this has only just started
39
+ collecting** — the proof accrues as you use it; it is not a claim of "proven better" yet.
40
+ - **It learns across your projects.** A lesson proven in one project can be promoted to your global
41
+ brain and now survives an update (tested against the real updater, not argued).
42
+ - **Runs on your account, cheapest capable model.** The QE suite and routing use your Claude account,
43
+ not an API key, at the least-powerful model that does the job.
44
+
45
+ **Then offer the Console (the point of the whole thing):**
46
+
47
+ End by offering to open it: *"Want me to open the Console so you can see all of this live? Just say the
48
+ word — or type `/rvbc`."* If they say yes, follow `rvbc.md` in this same directory exactly (including the
49
+ warm heads-up about the ~20s scan).
50
+
51
+ **Honesty rules for this command (same as the product):**
52
+ - If `docs/RELEASE-NOTES-4.0.md` is missing, say the notes aren't written yet and summarize from what
53
+ you can verify — never fabricate a highlight.
54
+ - Never claim a metric ("40% better", "95/100") the release has not independently earned. The self-
55
+ measurement is new and still filling; say so plainly.
56
+ - This is the MAJOR story only. If the user wants the point-release detail, point them at the repo's
57
+ release notes / `git log`.
@@ -0,0 +1,160 @@
1
+ {
2
+ "description": "RuvNet Brain lifecycle hooks for Codex. Every command enters through a generation-independent wrapper and adapts the shared Brain hook bodies to Codex contracts.",
3
+ "hooks": {
4
+ "SessionStart": [
5
+ {
6
+ "matcher": "startup|resume|clear|compact",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" session-start",
11
+ "timeout": 5
12
+ }
13
+ ]
14
+ }
15
+ ],
16
+ "UserPromptSubmit": [
17
+ {
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" ground-ruvnet",
22
+ "timeout": 10
23
+ },
24
+ {
25
+ "type": "command",
26
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" unprompted-speech UserPromptSubmit",
27
+ "timeout": 10
28
+ }
29
+ ]
30
+ }
31
+ ],
32
+ "PreToolUse": [
33
+ {
34
+ "matcher": "^(Bash|Edit|Write|exec_command|functions\\.exec_command|functions__exec_command|apply_patch)$",
35
+ "hooks": [
36
+ {
37
+ "type": "command",
38
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" hijack-ruvnet",
39
+ "timeout": 5
40
+ }
41
+ ]
42
+ },
43
+ {
44
+ "matcher": "Agent|spawn_agent",
45
+ "hooks": [
46
+ {
47
+ "type": "command",
48
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" route-dispatch",
49
+ "timeout": 5
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "matcher": "^(Edit|Write|apply_patch)$",
55
+ "hooks": [
56
+ {
57
+ "type": "command",
58
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" ground-before-write",
59
+ "timeout": 5
60
+ },
61
+ {
62
+ "type": "command",
63
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" protect-state",
64
+ "timeout": 5
65
+ },
66
+ {
67
+ "type": "command",
68
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" unprompted-speech PreToolUse-write",
69
+ "timeout": 5
70
+ }
71
+ ]
72
+ },
73
+ {
74
+ "matcher": "^(Bash|exec_command|functions\\.exec_command|functions__exec_command)$",
75
+ "hooks": [
76
+ {
77
+ "type": "command",
78
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" verify-interface",
79
+ "timeout": 5
80
+ },
81
+ {
82
+ "type": "command",
83
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" design-wall",
84
+ "timeout": 5
85
+ },
86
+ {
87
+ "type": "command",
88
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" unprompted-speech PreToolUse-bash",
89
+ "timeout": 5
90
+ }
91
+ ]
92
+ }
93
+ ],
94
+ "PostToolUse": [
95
+ {
96
+ "matcher": "^(?:.*__)?search_ruvnet$",
97
+ "hooks": [
98
+ {
99
+ "type": "command",
100
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" grounding-stamp",
101
+ "timeout": 5
102
+ }
103
+ ]
104
+ },
105
+ {
106
+ "matcher": "^(Bash|Edit|Write|exec_command|functions\\.exec_command|functions__exec_command|apply_patch)$",
107
+ "hooks": [
108
+ {
109
+ "type": "command",
110
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" learn-capture",
111
+ "timeout": 5
112
+ }
113
+ ]
114
+ },
115
+ {
116
+ "matcher": "^(Edit|Write|apply_patch)$",
117
+ "hooks": [
118
+ {
119
+ "type": "command",
120
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" md-stamp",
121
+ "timeout": 5
122
+ }
123
+ ]
124
+ },
125
+ {
126
+ "matcher": "^(Bash|exec_command|functions\\.exec_command|functions__exec_command)$",
127
+ "hooks": [
128
+ {
129
+ "type": "command",
130
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" signal-watch",
131
+ "timeout": 5
132
+ }
133
+ ]
134
+ }
135
+ ],
136
+ "SessionEnd": [
137
+ {
138
+ "matcher": "other",
139
+ "hooks": [
140
+ {
141
+ "type": "command",
142
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" learn-flush",
143
+ "timeout": 3
144
+ }
145
+ ]
146
+ }
147
+ ],
148
+ "Stop": [
149
+ {
150
+ "hooks": [
151
+ {
152
+ "type": "command",
153
+ "command": "node \"$HOME/.cache/ruvnet-brain/codex-hook.mjs\" continuation-gate",
154
+ "timeout": 10
155
+ }
156
+ ]
157
+ }
158
+ ]
159
+ }
160
+ }