ruvnet-brain 4.3.15 → 4.3.16

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 CHANGED
@@ -7,7 +7,7 @@ Created: 2026-06-29 22:36:38 EDT
7
7
 
8
8
  # 🧠 RuvNet Brain
9
9
 
10
- ### 🧠 RuvNet Brain — [![RuvNet Brain version 4.3.15 — updated 2026-07-30 03:24 EDT](https://img.shields.io/badge/version_4.3.15-updated_2026--07--30_03:24_EDT-1E90FF?style=for-the-badge&labelColor=0757BA)](https://github.com/stuinfla/ruvnet-brain/blob/main/plugin/.claude-plugin/plugin.json)
10
+ ### 🧠 RuvNet Brain — [![RuvNet Brain version 4.3.16 — updated 2026-07-30 03:24 EDT](https://img.shields.io/badge/version_4.3.16-updated_2026--07--30_03:24_EDT-1E90FF?style=for-the-badge&labelColor=0757BA)](https://github.com/stuinfla/ruvnet-brain/blob/main/plugin/.claude-plugin/plugin.json)
11
11
 
12
12
  **A portable, source-grounded brain over Reuven Cohen's (rUv's) RuvNet stack — delivered as a Claude Code plugin that makes Claude _use_ the stack instead of fighting it.**
13
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvnet-brain",
3
- "version": "4.3.15",
3
+ "version": "4.3.16",
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 an enforced UserPromptSubmit retrieve-and-inject grounding hook that sharply reduces drift.",
4
- "version": "4.3.15",
4
+ "version": "4.3.16",
5
5
  "author": {
6
6
  "name": "Stuart Kerr"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvnet-brain",
3
- "version": "4.3.15",
3
+ "version": "4.3.16",
4
4
  "description": "Source-grounded RuvNet knowledge, lifecycle enforcement, and learning for Codex.",
5
5
  "author": {
6
6
  "name": "Stuart Kerr"
@@ -106,6 +106,11 @@ verification → at most one revision. Both hosts design the ADR, DDD and intend
106
106
  Agentic-QE then generates and executes the measurable test artifacts. A host merely completing is
107
107
  not an accepted outcome.
108
108
 
109
+ When the duel returns `learningPersistenceRequest`, pass that request to Ruflo's structured
110
+ `memory_store` tool and verify the exact returned key. The coordinator never starts a second Ruflo
111
+ memory driver itself; without an MCP-aware caller it leaves `learningPersisted: false` and prints
112
+ the pending request so the completed duel is not mistaken for a persisted learning event.
113
+
109
114
  Subscription-only is a billing boundary: verify with `claude auth status --json` and
110
115
  `codex login status`, strip provider API-key variables from both child processes, and never fall back
111
116
  to an API when a subscription is absent or capacity-limited. Subscription use consumes plan
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { createHash } from 'node:crypto';
4
- import { spawn, spawnSync } from 'node:child_process';
4
+ import { spawn } from 'node:child_process';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { probeSubscriptionHosts, subscriptionOnlyEnv } from './subscription-hosts.mjs';
7
7
 
@@ -67,15 +67,34 @@ function parseHostValue(host, stdout) {
67
67
  }
68
68
  }
69
69
 
70
- function spawnHost(binary, args, options) {
70
+ function spawnHost(binary, args, options, input = '') {
71
71
  return new Promise((resolve) => {
72
72
  const child = spawn(binary, args, options);
73
73
  let stdout = '';
74
74
  let stderr = '';
75
+ let inputError = null;
76
+ let settled = false;
77
+ const finish = (result) => {
78
+ if (settled) return;
79
+ settled = true;
80
+ resolve(result);
81
+ };
75
82
  child.stdout.on('data', (chunk) => { stdout += chunk; });
76
83
  child.stderr.on('data', (chunk) => { stderr += chunk; });
77
- child.on('error', (error) => resolve({ status: null, stdout, stderr: error.message }));
78
- child.on('close', (status) => resolve({ status, stdout, stderr }));
84
+ child.stdin.on('error', (error) => {
85
+ inputError = error;
86
+ stderr += `${stderr ? '\n' : ''}${error.message}`;
87
+ try { child.kill(); } catch { /* child may already be gone */ }
88
+ });
89
+ child.on('error', (error) => finish({ status: null, stdout, stderr: error.message, error }));
90
+ try {
91
+ child.stdin.end(input);
92
+ } catch (error) {
93
+ inputError = error;
94
+ stderr += `${stderr ? '\n' : ''}${error.message}`;
95
+ try { child.kill(); } catch { /* child may already be gone */ }
96
+ }
97
+ child.on('close', (status) => finish({ status, stdout, stderr, error: inputError }));
79
98
  });
80
99
  }
81
100
 
@@ -87,20 +106,21 @@ export async function runSubscriptionHost(host, stage, payload, { cwd = process.
87
106
  binary: 'claude',
88
107
  args: [
89
108
  '-p', '--output-format', 'json', '--permission-mode', 'plan',
90
- '--tools', 'Read,Grep,Glob', '--no-session-persistence', '--effort', 'high', prompt,
109
+ '--tools', 'Read,Grep,Glob', '--no-session-persistence', '--effort', 'high',
91
110
  ],
92
111
  }
93
112
  : {
94
113
  binary: 'codex',
95
114
  args: [
96
115
  'exec', '--ephemeral', '--sandbox', 'read-only', '--color', 'never', '--json',
97
- '-m', 'gpt-5.6-sol', '-c', 'model_reasoning_effort="high"', prompt,
116
+ '-m', 'gpt-5.6-sol', '-c', 'model_reasoning_effort="high"',
98
117
  ],
99
118
  };
100
- const result = await spawnHost(command.binary, command.args, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });
119
+ const result = await spawnHost(command.binary, command.args, { cwd, env, stdio: ['pipe', 'pipe', 'pipe'] }, prompt);
101
120
  if (result.status !== 0) {
102
121
  return {
103
122
  ok: false,
123
+ error: result.stderr || `host exited without a status (${result.status})`,
104
124
  reason: /limit|quota|capacity|usage/i.test(result.stderr)
105
125
  ? 'capacity-limited'
106
126
  : 'host-failed',
@@ -109,11 +129,7 @@ export async function runSubscriptionHost(host, stage, payload, { cwd = process.
109
129
  return { ok: true, value: parseHostValue(host, result.stdout) };
110
130
  }
111
131
 
112
- export async function persistDeliberationReceipt(receipt, {
113
- cwd = process.cwd(),
114
- now = Date.now,
115
- run = spawnSync,
116
- } = {}) {
132
+ export function deliberationMemoryStoreRequest(receipt, { now = Date.now } = {}) {
117
133
  const recordedAt = now();
118
134
  const value = {
119
135
  protocol: receipt.protocol,
@@ -125,21 +141,28 @@ export async function persistDeliberationReceipt(receipt, {
125
141
  recordedAt: new Date(recordedAt).toISOString(),
126
142
  };
127
143
  const key = `dual-deliberation-${recordedAt}-${receipt.taskHash.slice(0, 12)}`;
128
- const result = run('ruflo', [
129
- 'memory', 'store',
130
- '-k', key,
131
- '--value', JSON.stringify(value),
132
- '--namespace', 'ruvnet-brain',
133
- '--path', '.swarm/memory.db',
134
- '--no-upsert',
135
- '--scan-content',
136
- ], {
137
- cwd,
138
- encoding: 'utf8',
139
- env: subscriptionOnlyEnv(),
140
- timeout: 15_000,
141
- });
142
- return result?.status === 0;
144
+ return {
145
+ tool: 'memory_store',
146
+ name: 'memory_store',
147
+ arguments: { key, value: JSON.stringify(value), namespace: 'ruvnet-brain' },
148
+ };
149
+ }
150
+
151
+ function persistenceProof(proof, key) {
152
+ return proof === true || (proof?.stored === true && proof?.verified === true && proof?.key === key);
153
+ }
154
+
155
+ export async function persistDeliberationReceipt(receipt, {
156
+ now = Date.now,
157
+ memoryStore,
158
+ } = {}) {
159
+ const request = deliberationMemoryStoreRequest(receipt, { now });
160
+ if (typeof memoryStore !== 'function') return request;
161
+ try {
162
+ return persistenceProof(await memoryStore(request), request.arguments.key);
163
+ } catch {
164
+ return false;
165
+ }
143
166
  }
144
167
 
145
168
  function missingHosts(probes) {
@@ -164,7 +187,6 @@ export async function deliberate(task, options = {}) {
164
187
  runSubscriptionHost(host, stage, payload, { cwd: options.cwd })
165
188
  ));
166
189
  const cwd = options.cwd ?? process.cwd();
167
- const persist = options.persist ?? ((receipt) => persistDeliberationReceipt(receipt, { cwd }));
168
190
  const eligibleHosts = HOSTS.filter((host) => probes[hostKey(host)]?.eligible);
169
191
 
170
192
  if (eligibleHosts.length === 0) {
@@ -247,7 +269,18 @@ export async function deliberate(task, options = {}) {
247
269
  roles,
248
270
  accepted,
249
271
  };
250
- const learningPersisted = accepted ? Boolean(await persist(receipt)) : false;
272
+ const learningPersistenceRequest = accepted
273
+ ? deliberationMemoryStoreRequest(receipt, { now: options.now ?? Date.now })
274
+ : undefined;
275
+ let learningPersisted = false;
276
+ if (accepted && typeof options.persist === 'function') {
277
+ try {
278
+ const persisted = await options.persist(learningPersistenceRequest, receipt);
279
+ learningPersisted = persistenceProof(persisted, learningPersistenceRequest.arguments.key);
280
+ } catch {
281
+ learningPersisted = false;
282
+ }
283
+ }
251
284
  return {
252
285
  status: accepted ? 'accepted' : 'unresolved',
253
286
  dual: true,
@@ -256,6 +289,7 @@ export async function deliberate(task, options = {}) {
256
289
  verification: verification.ok ? verification.value : undefined,
257
290
  verifiedOutcome: accepted,
258
291
  learningPersisted,
292
+ ...(learningPersistenceRequest ? { learningPersistenceRequest } : {}),
259
293
  };
260
294
  }
261
295