ruvnet-brain 4.7.12 → 4.7.14

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
@@ -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.12-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.14-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,393 public source chunks**.
258
+ The **4.6.0 published-corpus baseline** is **203 public stores · 164,402 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvnet-brain",
3
- "version": "4.7.12",
3
+ "version": "4.7.14",
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.12",
4
+ "version": "4.7.14",
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.12",
3
+ "version": "4.7.14",
4
4
  "description": "Source-grounded RuvNet knowledge, lifecycle enforcement, and learning for Codex.",
5
5
  "author": {
6
6
  "name": "Stuart Kerr"
@@ -67,6 +67,27 @@ function defaultNodeCandidates(env) {
67
67
  return [...new Set(out)].filter((c) => { try { return fs.statSync(c).isFile(); } catch { return false; } });
68
68
  }
69
69
 
70
+ // Where ruflo's native better-sqlite3 lives depends on the ruflo release. Measured 2026-10-17 with npm -g: ruflo
71
+ // 3.56.10 hoisted nothing (copies nested under @claude-flow/cli, @claude-flow/memory and agentdb), so probing only
72
+ // <ruflo>/node_modules/better-sqlite3 found nothing and node selection silently never ran; ruflo 3.57.0 hoists it.
73
+ // We probe the hoisted copy first, so a machine that has one behaves exactly as before, then the nested layouts.
74
+ // Every candidate is a fixed path under ruflo's own realpath. Copies can differ in version (3.57.0: hoisted 12.11.1,
75
+ // agentdb's nested 11.10.0), so the probed copy is a proxy for the one the writer loads, not proof of it.
76
+ const ADDON_DIRS = [
77
+ ['node_modules', 'better-sqlite3'],
78
+ ['node_modules', '@claude-flow', 'cli', 'node_modules', 'better-sqlite3'],
79
+ ['node_modules', '@claude-flow', 'memory', 'node_modules', 'better-sqlite3'],
80
+ ['node_modules', 'agentdb', 'node_modules', 'better-sqlite3'],
81
+ ];
82
+
83
+ function findAddon(packageRoot) {
84
+ for (const parts of ADDON_DIRS) {
85
+ const dir = path.join(packageRoot, ...parts);
86
+ try { if (fs.statSync(dir).isDirectory()) return dir; } catch { /* try the next layout */ }
87
+ }
88
+ return null;
89
+ }
90
+
70
91
  const nativeNodeCache = new Map();
71
92
  const PROBE_TIMEOUT_MS = 2000;
72
93
  const PROBE_BUDGET_MS = 3000;
@@ -91,8 +112,8 @@ export function rufloInvocationOrPlain(binary, args, opts) {
91
112
  export function nativeNodeFor(binary, { env = process.env, candidates } = {}) {
92
113
  let entry;
93
114
  try { entry = fs.realpathSync(binary); } catch { return null; }
94
- const addon = path.join(path.dirname(path.dirname(entry)), 'node_modules', 'better-sqlite3');
95
- try { if (!fs.statSync(addon).isDirectory()) return null; } catch { return null; }
115
+ const addon = findAddon(path.dirname(path.dirname(entry)));
116
+ if (!addon) return null;
96
117
 
97
118
  const list = candidates ?? defaultNodeCandidates(env);
98
119
  const cacheKey = `${entry}\0${list.join('\0')}`;
@@ -315,10 +315,28 @@ function runUpdater({ kbDir, env, resultFile }) {
315
315
  });
316
316
  }
317
317
 
318
- async function fetchRelease({ apiBase, repo, tag }) {
319
- const response = await fetch(candidateManifestUrl({ apiBase, repo, tag }), { headers: { accept: 'application/vnd.github+json' } });
320
- if (!response.ok) throw new Error(`release ${tag} returned HTTP ${response.status}`);
321
- return response.json();
318
+ // An anonymous GitHub API read from a shared runner address is sometimes refused (403/429) or fails (5xx)
319
+ // for a moment; one such blip must not fail the night. The same call is retried after a short wait.
320
+ const TRANSIENT_HTTP = new Set([403, 429, 500, 502, 503, 504]);
321
+ async function fetchJson(url, what, { retryDelayMs = 15_000, maxAttempts = 4 } = {}) {
322
+ for (let attempt = 1; ; attempt += 1) {
323
+ let status = null;
324
+ try {
325
+ const response = await fetch(url, { headers: { accept: 'application/vnd.github+json' } });
326
+ if (response.ok) return await response.json();
327
+ status = response.status;
328
+ if (!TRANSIENT_HTTP.has(status)) throw new Error(`${what} returned HTTP ${status}`);
329
+ } catch (error) {
330
+ if (status !== null && !TRANSIENT_HTTP.has(status)) throw error;
331
+ if (status === null && attempt >= maxAttempts) throw error;
332
+ }
333
+ if (attempt >= maxAttempts) throw new Error(`${what} returned HTTP ${status}`);
334
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs * attempt));
335
+ }
336
+ }
337
+
338
+ async function fetchRelease({ apiBase, repo, tag, retryDelayMs }) {
339
+ return fetchJson(candidateManifestUrl({ apiBase, repo, tag }), `release ${tag}`, { retryDelayMs });
322
340
  }
323
341
 
324
342
  /**
@@ -340,10 +358,8 @@ export const CANARY_CASES = Object.freeze({
340
358
  'older-runtime': Object.freeze({ runtime: 'N-1', door: 'installer', overlay: false }),
341
359
  });
342
360
 
343
- async function fetchReleaseList({ apiBase, repo }) {
344
- const response = await fetch(`${apiBase}/repos/${repo}/releases?per_page=100`, { headers: { accept: 'application/vnd.github+json' } });
345
- if (!response.ok) throw new Error(`release list returned HTTP ${response.status}`);
346
- return response.json();
361
+ async function fetchReleaseList({ apiBase, repo, retryDelayMs }) {
362
+ return fetchJson(`${apiBase}/repos/${repo}/releases?per_page=100`, 'release list', { retryDelayMs });
347
363
  }
348
364
 
349
365
  /** The footprint of a customer HOME after one update (plugin/scripts/brain-footprint.mjs, never restated). */
@@ -372,7 +388,7 @@ async function runCase({ name, spec, repo, tag, approvedVersion, release, work,
372
388
  retryDelayMs, maxAttempts, log, writerRoot, approvedInstaller, footprintUpdates = false }) {
373
389
  fs.mkdirSync(home, { recursive: true });
374
390
  let runtime = approvedVersion;
375
- if (spec.runtime !== 'approved') runtime = resolveRuntime(spec.runtime, { candidateTag: `v${approvedVersion}`, releases: await fetchReleaseList({ apiBase, repo }) });
391
+ if (spec.runtime !== 'approved') runtime = resolveRuntime(spec.runtime, { candidateTag: `v${approvedVersion}`, releases: await fetchReleaseList({ apiBase, repo, retryDelayMs }) });
376
392
  const installed = await install({ approvedVersion: runtime, work, home, log });
377
393
  const { kbDir } = installed;
378
394
  home = installed.home || home;
@@ -465,7 +481,7 @@ export async function runCanary({
465
481
  throw new Error(`--cases must start with clean and name only: ${Object.keys(CANARY_CASES).join(', ')}`);
466
482
  }
467
483
  const started = now();
468
- const release = await fetchRelease({ apiBase, repo, tag });
484
+ const release = await fetchRelease({ apiBase, repo, tag, retryDelayMs });
469
485
  // The approved package's installer — what `npx ruvnet-brain@latest` runs — fetched once, on demand.
470
486
  let installerPath = null;
471
487
  const resolveInstaller = approvedInstaller || (async () => {