ruvnet-brain 4.0.12 → 4.0.24

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 (45) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/plugin/.claude-plugin/plugin.json +2 -2
  4. package/plugin/.codex-plugin/plugin.json +1 -1
  5. package/plugin/scripts/advocacy-outcomes.mjs +808 -0
  6. package/plugin/scripts/anticipate.sh +80 -14
  7. package/plugin/scripts/capability-registry.mjs +994 -0
  8. package/plugin/scripts/codex-hook-wrapper.mjs +1 -0
  9. package/plugin/scripts/continuation-gate.mjs +129 -1
  10. package/plugin/scripts/gates.mjs +146 -0
  11. package/plugin/scripts/goal-match.mjs +398 -0
  12. package/plugin/scripts/hijack-ruvnet.sh +69 -1
  13. package/plugin/scripts/hook-registry.mjs +616 -0
  14. package/plugin/scripts/hook-shim.mjs +13 -2
  15. package/plugin/scripts/learning-enable.mjs +382 -0
  16. package/plugin/scripts/lesson-promote.mjs +262 -0
  17. package/plugin/scripts/lesson-provenance.mjs +43 -0
  18. package/plugin/scripts/lesson-store.mjs +67 -56
  19. package/plugin/scripts/memory-doctor.mjs +345 -0
  20. package/plugin/scripts/nightly-controller.mjs +98 -0
  21. package/plugin/scripts/runtime-preferences.mjs +18 -0
  22. package/plugin/scripts/unprompted-runtime.mjs +22 -7
  23. package/plugin/scripts/user-settings.mjs +672 -0
  24. package/plugin/skills/ruvnet-brain/SKILL.md +2 -2
  25. package/scripts/advocacy-outcomes.mjs +4 -808
  26. package/scripts/capability-registry.mjs +4 -876
  27. package/scripts/corpus-qa.mjs +44 -6
  28. package/scripts/doc-currency.mjs +30 -2
  29. package/scripts/gates.mjs +4 -146
  30. package/scripts/goal-match.mjs +4 -398
  31. package/scripts/hook-registry.mjs +4 -567
  32. package/scripts/issue-watch.mjs +108 -0
  33. package/scripts/learning-enable.mjs +4 -380
  34. package/scripts/lesson-promote.mjs +4 -262
  35. package/scripts/memory-doctor.mjs +4 -345
  36. package/scripts/nightly-controller.mjs +4 -66
  37. package/scripts/nightly-wrapper.sh +23 -1
  38. package/scripts/proactivity-metrics.mjs +8 -1
  39. package/scripts/qe/ux-suite.mjs +72 -1
  40. package/scripts/release-abort-stale.mjs +111 -0
  41. package/scripts/release-convergence-watchdog.mjs +119 -0
  42. package/scripts/release-transaction-provider.mjs +46 -6
  43. package/scripts/release-transaction.mjs +55 -17
  44. package/scripts/self-update.mjs +63 -10
  45. package/scripts/user-settings.mjs +4 -640
@@ -16,7 +16,7 @@
16
16
  import fs from 'node:fs';
17
17
  import os from 'node:os';
18
18
  import path from 'node:path';
19
- import { execFileSync } from 'node:child_process';
19
+ import { execFileSync, spawnSync } from 'node:child_process';
20
20
  import { fileURLToPath } from 'node:url';
21
21
  import { FULL_HINTS, KEEP_DIRS } from './full-hints.mjs';
22
22
  import { withSubmoduleSymlinksDetached } from './git-clone-refresh.mjs';
@@ -228,6 +228,42 @@ if (!APPLY) {
228
228
  }
229
229
 
230
230
  const NOTIFY = (t, m, p) => { try { execFileSync('sh', [path.join(ROOT, 'scripts/notify.sh'), t, m, p || 'default']); } catch { /* alerting never breaks the pipeline */ } };
231
+
232
+ // ---- child steps that can explain themselves -------------------------------------------------
233
+ // With stdio:'inherit' a failing child's Error carries NO output: e.stdout and e.stderr are both
234
+ // null and e.message is only "Command failed: <argv>" (verified 2026-08-06). That empty reason is
235
+ // what propagated into the [FATAL] summary, which is in turn what nightly-wrapper.sh samples for
236
+ // the escalation — so six identical nightly failures escalated saying nothing, while the log had
237
+ // the answer the whole time.
238
+ //
239
+ // Capture, then RE-EMIT verbatim so the log is byte-identical to what stdio:'inherit' produced,
240
+ // and keep a tail for the failure record.
241
+ // captureStdout=false (clone/fetch/reset/symbols): stdout stays inherited so those steps stream
242
+ // live and survive a kill; only stderr is buffered (small: stacks and warnings).
243
+ // captureStdout=true (every step that carries a corpus-QA verdict — [refresh] and [qa]): BOTH
244
+ // streams, because corpus-qa prints its verdict table and every '↳ <reason>' line via
245
+ // console.log — i.e. on STDOUT. Capturing stderr alone would have captured nothing for the
246
+ // exact failure this exists to explain. Measured output is ~34 lines/step, so it costs nothing.
247
+ const STEP_TAIL_LINES = 14;
248
+ function runStep(label, file, args, opts = {}, { captureStdout = false } = {}) {
249
+ const r = spawnSync(file, args, {
250
+ ...opts,
251
+ stdio: ['inherit', captureStdout ? 'pipe' : 'inherit', 'pipe'],
252
+ maxBuffer: 64 * 1024 * 1024,
253
+ });
254
+ if (r.stdout?.length) process.stdout.write(r.stdout);
255
+ if (r.stderr?.length) process.stderr.write(r.stderr);
256
+ if (r.error) { r.error.step = label; throw r.error; }
257
+ if (r.status !== 0) {
258
+ const tail = Buffer.concat([r.stdout || Buffer.alloc(0), r.stderr || Buffer.alloc(0)]).toString()
259
+ .split('\n').map((l) => l.trim()).filter(Boolean).slice(-STEP_TAIL_LINES).join(' | ');
260
+ const err = new Error(`${label} exited ${r.status}${tail ? ` — ${tail}` : ' — (child produced no output)'}`);
261
+ err.step = label;
262
+ err.status = r.status;
263
+ err.output = tail;
264
+ throw err;
265
+ }
266
+ }
231
267
  const failures = []; // per-repo build failures collected here; ANY failure aborts before publish (see below)
232
268
  for (const p of todo) {
233
269
  const dir = clonePath(p.name);
@@ -235,15 +271,15 @@ for (const p of todo) {
235
271
  if (!fs.existsSync(path.join(dir, '.git'))) {
236
272
  console.log(`[clone] ${p.name}`);
237
273
  fs.mkdirSync(CLONE_DIR, { recursive: true });
238
- execFileSync('git', ['clone', '--depth', '1', `https://github.com/${p.owner || p.org || 'ruvnet'}/${p.repo || p.name}`, dir], { stdio: 'inherit' });
274
+ runStep(`[clone] ${p.name}`, 'git', ['clone', '--depth', '1', `https://github.com/${p.owner || p.org || 'ruvnet'}/${p.repo || p.name}`, dir]);
239
275
  } else {
240
276
  // Some cached clones deduplicate large submodules with symlinks to another clone. Git refuses
241
277
  // even a fetch when a gitlink path is a symlink ("expected submodule path ... not to be a
242
278
  // symbolic link"). Detach only those declared submodule symlinks for fetch/reset, then restore
243
279
  // them in finally so a network/reset failure cannot strand the cache in a half-repaired state.
244
280
  withSubmoduleSymlinksDetached(dir, () => {
245
- execFileSync('git', ['-C', dir, 'fetch', '--depth', '1', 'origin'], { stdio: 'inherit' });
246
- execFileSync('git', ['-C', dir, 'reset', '--hard', 'origin/HEAD'], { stdio: 'inherit' });
281
+ runStep(`[fetch] ${p.name}`, 'git', ['-C', dir, 'fetch', '--depth', '1', 'origin']);
282
+ runStep(`[reset] ${p.name}`, 'git', ['-C', dir, 'reset', '--hard', 'origin/HEAD']);
247
283
  });
248
284
  }
249
285
  const env = { ...process.env, KB_MODEL_CACHE: MODEL_CACHE };
@@ -255,26 +291,43 @@ for (const p of todo) {
255
291
  if (full) buildArgs.push('--full', full);
256
292
  if (keep) buildArgs.push('--keep', keep);
257
293
  console.log(`[refresh] ${kb}`);
258
- execFileSync(NODE, buildArgs, { cwd: path.join(ROOT, 'kb'), env, stdio: 'inherit' });
294
+ // captureStdout: forge-refresh runs corpus-qa against its CANDIDATE dir and refuses to promote
295
+ // on a FAIL — so this step, not the [qa] step below, is where the six 2026-08-03..08-06 nightly
296
+ // failures actually died, and the verdict table naming the offending chunk is on ITS stdout.
297
+ // Steps produce ~34 lines at most (largest measured [refresh] section in logs/nightly.log), so
298
+ // buffering costs nothing; the tradeoff is that the log flushes at step end rather than live.
299
+ runStep(`[refresh] ${kb}`, NODE, buildArgs, { cwd: path.join(ROOT, 'kb'), env }, { captureStdout: true });
259
300
  console.log(`[symbols] ${kb}`);
260
- execFileSync(NODE, ['scripts/build-symbols.mjs', '--name', kb], { cwd: ROOT, env, stdio: 'inherit' });
301
+ runStep(`[symbols] ${kb}`, NODE, ['scripts/build-symbols.mjs', '--name', kb], { cwd: ROOT, env });
261
302
  // Corpus QA gate (scripts/corpus-qa.mjs): structural (passages>0, full-bodies>0 where
262
303
  // FULL_HINTS demands them, vectors==passages, embed.json present) + deterministic
263
304
  // self-retrieval round trip on every canonical store. Non-zero exit throws -> failures[] ->
264
305
  // the run aborts before stamp/bundle/publish. This is the machine version of the
265
306
  // 2026-07-10 hand-verification: a store that embeds wrong or reads wrong cannot ship.
266
307
  console.log(`[qa] ${kb}`);
267
- execFileSync(NODE, ['scripts/corpus-qa.mjs', '--store', kb], { cwd: ROOT, env, stdio: 'inherit' });
268
- } catch (e) { console.error(`[FAIL] ${p.name}: ${e.message}`); failures.push({ name: p.name, error: e.message }); }
308
+ // captureStdout: corpus-qa's verdict table and its '↳ <reason>' lines go to stdout.
309
+ runStep(`[qa] ${kb}`, NODE, ['scripts/corpus-qa.mjs', '--store', kb], { cwd: ROOT, env }, { captureStdout: true });
310
+ } catch (e) {
311
+ console.error(`[FAIL] ${p.name}: ${e.message}`);
312
+ failures.push({ name: p.name, step: e.step || '(unknown step)', error: e.message, reason: e.output || '' });
313
+ }
269
314
  }
270
315
 
271
316
  // A per-repo build failure used to be logged and swallowed before the run re-stamped and re-bundled
272
317
  // partial data. Fail loud: if ANY repo failed, abort before stamp/bundle. This rebuild process has no
273
318
  // publication authority; a later protected release may consume only a fully prepared candidate.
274
319
  if (failures.length) {
275
- NOTIFY('🔴 Nightly brain rebuild ABORTED', `${failures.length} repo build(s) failed — no candidate prepared. See logs/nightly.log`, 'urgent');
320
+ // Carry the REASON, not just the count. The wrapper samples this block's tail for the push
321
+ // escalation, so whatever is not printed here is not in the alert either.
322
+ const first = failures[0];
323
+ NOTIFY('🔴 Nightly brain rebuild ABORTED',
324
+ `${failures.length} repo build(s) failed — no candidate prepared. First: ${first.name} ${first.step} — ${first.reason || first.error}`,
325
+ 'urgent');
276
326
  console.error(`\n[FATAL] ${failures.length} repo build(s) failed this run — aborting before stamp/bundle. No candidate prepared:`);
277
- for (const f of failures) console.error(` - ${f.name}: ${f.error}`);
327
+ for (const f of failures) {
328
+ console.error(` - ${f.name} ${f.step}: ${f.error}`);
329
+ if (f.reason) console.error(` reason: ${f.reason}`);
330
+ }
278
331
  console.error('Fix the failing repo(s) and re-run.');
279
332
  process.exit(1);
280
333
  }