ruvnet-brain 4.7.5 → 4.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/scripts/agentdb-recall-reader.mjs +3 -2
- package/plugin/scripts/ruflo-bin.mjs +15 -1
- package/plugin/scripts/ruflo-memory-hooks.mjs +7 -4
- package/scripts/release-qualification-contract.mjs +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Created: 2026-06-29 22:36:38 EDT
|
|
|
9
9
|
|
|
10
10
|
### Discover the stack. Read the source. Build with confidence.
|
|
11
11
|
|
|
12
|
-
[](plugin/.claude-plugin/plugin.json)
|
|
13
13
|
|
|
14
14
|
**A source-grounded companion for Claude Code and Codex that helps you find and use Reuven Cohen's (rUv's) RuvNet ecosystem.**
|
|
15
15
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvnet-brain",
|
|
3
|
-
"version": "4.7.
|
|
3
|
+
"version": "4.7.6",
|
|
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.6",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Stuart Kerr"
|
|
7
7
|
},
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
* so a stalled CLI is retired at the recall deadline. Output contract unchanged: {ok, value}.
|
|
4
4
|
*/
|
|
5
5
|
import { execFile } from 'node:child_process';
|
|
6
|
-
import { resolveRuflo } from './ruflo-bin.mjs';
|
|
6
|
+
import { resolveRuflo, rufloInvocationOrPlain } from './ruflo-bin.mjs';
|
|
7
7
|
|
|
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
12
|
(error, stdout) => resolve(error ? null : String(stdout || '')));
|
|
12
13
|
});
|
|
13
14
|
|
|
@@ -68,6 +68,16 @@ function defaultNodeCandidates(env) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
const nativeNodeCache = new Map();
|
|
71
|
+
const PROBE_TIMEOUT_MS = 2000;
|
|
72
|
+
const PROBE_BUDGET_MS = 3000;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* rufloInvocation for callers that must never throw: an unrecognised shim keeps the plain `bin args` they
|
|
76
|
+
* always ran, so routing a call site through node selection can only add the correct interpreter.
|
|
77
|
+
*/
|
|
78
|
+
export function rufloInvocationOrPlain(binary, args, opts) {
|
|
79
|
+
try { return rufloInvocation(binary, args, opts); } catch { return { executable: binary, args }; }
|
|
80
|
+
}
|
|
71
81
|
|
|
72
82
|
/**
|
|
73
83
|
* The node that can load ruflo's native better-sqlite3, or null when the caller's own PATH node
|
|
@@ -90,8 +100,12 @@ export function nativeNodeFor(binary, { env = process.env, candidates } = {}) {
|
|
|
90
100
|
|
|
91
101
|
const probe = `new (require(${JSON.stringify(addon)}))(':memory:').close()`;
|
|
92
102
|
let chosen = null;
|
|
103
|
+
// Bounded: this runs inside hooks that have their own deadline (SessionStart is stopped 1.5 s early), so a
|
|
104
|
+
// wedged candidate must cost seconds at most and then leave the invocation exactly as it was.
|
|
105
|
+
const started = Date.now();
|
|
93
106
|
for (let i = 0; i < list.length; i += 1) {
|
|
94
|
-
|
|
107
|
+
if (Date.now() - started > PROBE_BUDGET_MS) break;
|
|
108
|
+
const r = spawnSync(list[i], ['-e', probe], { stdio: 'ignore', timeout: PROBE_TIMEOUT_MS, windowsHide: true });
|
|
95
109
|
if (r.status === 0) { chosen = i === 0 ? null : { node: list[i], entry }; break; }
|
|
96
110
|
}
|
|
97
111
|
nativeNodeCache.set(cacheKey, chosen);
|
|
@@ -18,7 +18,7 @@ import crypto from 'node:crypto';
|
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import { execFile, execFileSync, spawnSync } from 'node:child_process';
|
|
20
20
|
import os from 'node:os';
|
|
21
|
-
import { resolveRuflo, rufloRunDir, rufloScratchRoot } from './ruflo-bin.mjs';
|
|
21
|
+
import { resolveRuflo, rufloInvocationOrPlain, rufloRunDir, rufloScratchRoot } from './ruflo-bin.mjs';
|
|
22
22
|
import { resolveProjectStore } from './project-store-resolver.mjs';
|
|
23
23
|
import { checkpointNamespaces, newestOfFamily } from './checkpoint-keys.mjs';
|
|
24
24
|
|
|
@@ -57,7 +57,8 @@ export function rufloMemory(args, { db, env = process.env, bin = resolveRuflo({
|
|
|
57
57
|
return new Promise((resolve) => {
|
|
58
58
|
if (!bin) return resolve({ ok: false, stdout: '', error: 'global Ruflo unavailable' });
|
|
59
59
|
const spawnAt = rufloSpawnOptions(db, env);
|
|
60
|
-
|
|
60
|
+
const inv = rufloInvocationOrPlain(bin, ['memory', ...args, '--path', db], { env: spawnAt.env });
|
|
61
|
+
execFile(inv.executable, inv.args, { cwd: spawnAt.cwd, env: spawnAt.env, timeout, killSignal: 'SIGKILL', encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 },
|
|
61
62
|
// The ledger gets a fixed reason, never execFile's message (it embeds the whole argv: value, db path).
|
|
62
63
|
(error, stdout) => { spawnAt.cleanup(); resolve({ ok: !error, stdout: String(stdout || ''), error: error ? (error.killed ? 'timed out' : `ruflo memory ${args[0]} exited ${error.code ?? 'abnormally'}`) : null }); });
|
|
63
64
|
});
|
|
@@ -194,11 +195,13 @@ export function storeExact({ db, namespace, key, value, env = process.env, bin =
|
|
|
194
195
|
const spawnAt = rufloSpawnOptions(db, env);
|
|
195
196
|
const options = { encoding: 'utf8', timeout: timeoutMs, killSignal: 'SIGKILL', maxBuffer: 8 * 1024 * 1024, cwd: spawnAt.cwd, env: spawnAt.env };
|
|
196
197
|
try {
|
|
197
|
-
const
|
|
198
|
+
const storeInv = rufloInvocationOrPlain(bin, ['memory', 'store', '--key', key, '--value', value, '--namespace', namespace, '--path', db], { env: spawnAt.env });
|
|
199
|
+
const stored = run(storeInv.executable, storeInv.args, options);
|
|
198
200
|
if (stored.error || stored.status !== 0) {
|
|
199
201
|
return { stored: false, verified: false, error: String(stored.stderr || stored.stdout || stored.error?.message || 'store failed').trim().slice(0, 200) };
|
|
200
202
|
}
|
|
201
|
-
const
|
|
203
|
+
const readInv = rufloInvocationOrPlain(bin, ['memory', 'retrieve', '--key', key, '--namespace', namespace, '--value-only', '--path', db], { env: spawnAt.env });
|
|
204
|
+
const read = run(readInv.executable, readInv.args, options);
|
|
202
205
|
return { stored: true, verified: !read.error && read.status === 0 && String(read.stdout || '').trim() === value, key };
|
|
203
206
|
} finally { spawnAt.cleanup(); }
|
|
204
207
|
}
|
|
@@ -222,7 +222,7 @@ export const RELEASE_REQUIREMENTS = Object.freeze({
|
|
|
222
222
|
},
|
|
223
223
|
{
|
|
224
224
|
"id": "ruflo-runs-under-a-node-that-loads-its-binding",
|
|
225
|
-
"reason": "Hooks inherit whatever node is first on their PATH; ruflo
|
|
225
|
+
"reason": "Hooks inherit whatever node is first on their PATH; every direct ruflo launch (turn and learning capture, the SessionEnd/PreCompact checkpoint writer, the SessionStart memory reader and the recall reader) runs ruflo under the first node that can load its own better-sqlite3 within a bounded probe (a healthy machine is untouched, and no candidate means no change), so a native memory write is not refused for a node-ABI mismatch",
|
|
226
226
|
"files": [],
|
|
227
227
|
"platformFiles": {
|
|
228
228
|
"linux": ["tests/unit/ruflo-native-node.test.mjs"],
|