ruvnet-brain 4.0.6 → 4.0.8

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 (47) hide show
  1. package/README.md +3 -3
  2. package/bin/install.mjs +118 -9
  3. package/config/model-router/catalog.template.json +17 -2
  4. package/console/app.js +32 -14
  5. package/console/architecture.html +1 -1
  6. package/data/model-catalog.json +54 -0
  7. package/package.json +2 -1
  8. package/plugin/.claude-plugin/plugin.json +1 -1
  9. package/plugin/.codex-plugin/plugin.json +1 -1
  10. package/plugin/hooks/codex-hooks.json +17 -17
  11. package/plugin/hooks/hooks.json +34 -1
  12. package/plugin/scripts/codex-hook-wrapper.mjs +72 -1
  13. package/plugin/scripts/ground-ruvnet.sh +2 -1
  14. package/plugin/scripts/hook-shim.mjs +13 -6
  15. package/plugin/scripts/lesson-provenance.mjs +21 -0
  16. package/plugin/scripts/lesson-store.mjs +33 -3
  17. package/plugin/scripts/route-dispatch.sh +23 -49
  18. package/plugin/scripts/session-snapshot-contract.mjs +100 -0
  19. package/plugin/scripts/session-snapshot-hook.mjs +34 -0
  20. package/plugin/scripts/session-start-core.mjs +25 -7
  21. package/plugin/scripts/swarm-slot-recycler.mjs +89 -0
  22. package/plugin/skills/release-proof/SKILL.md +4 -4
  23. package/plugin/skills/release-proof/scripts/release-proof.mjs +12 -5
  24. package/plugin/skills/ruvnet-brain/PLAYBOOK.md +19 -4
  25. package/scripts/candidate-host-evidence.mjs +49 -0
  26. package/scripts/capability-registry.mjs +3 -3
  27. package/scripts/ci/learning-replay-codex-adapter.mjs +13 -1
  28. package/scripts/learning-replay-contract.mjs +14 -4
  29. package/scripts/learning-replay-execution.mjs +4 -3
  30. package/scripts/learning-replay-fixture.mjs +23 -2
  31. package/scripts/lesson-lifecycle.mjs +2 -1
  32. package/scripts/lesson-ratify.mjs +6 -3
  33. package/scripts/lesson-seed.mjs +21 -16
  34. package/scripts/model-router-catalog.mjs +16 -0
  35. package/scripts/model-router-engine.mjs +4 -1
  36. package/scripts/onboarding-console.mjs +52 -14
  37. package/scripts/provider-availability.mjs +12 -0
  38. package/scripts/publication-receipt.mjs +126 -34
  39. package/scripts/release-authority.mjs +14 -2
  40. package/scripts/release-evidence-aggregate.mjs +55 -0
  41. package/scripts/release-payload.mjs +100 -0
  42. package/scripts/release-transaction-provider.mjs +413 -0
  43. package/scripts/release-transaction.mjs +404 -0
  44. package/scripts/release.mjs +97 -280
  45. package/scripts/selfcheck.mjs +20 -5
  46. package/scripts/session-snapshot-contract.mjs +1 -0
  47. package/scripts/staged-host-verifier.mjs +103 -0
@@ -0,0 +1,103 @@
1
+ import { execFileSync, spawnSync } from 'node:child_process';
2
+ import crypto from 'node:crypto';
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+
7
+ const sha256 = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
8
+ const locate = (name) => {
9
+ try { return execFileSync('which', [name], { encoding: 'utf8' }).trim(); } catch { return null; }
10
+ };
11
+
12
+ const run = (name, args, options) => {
13
+ const result = spawnSync(name, args, { encoding: 'utf8', ...options });
14
+ if (result.error || result.status !== 0) {
15
+ const detail = String(result.stderr || result.stdout || result.error?.message);
16
+ throw new Error(`${path.basename(name)} ${args.join(' ')} failed: ${detail.slice(-5000)}`);
17
+ }
18
+ return result;
19
+ };
20
+
21
+ export function classifyDoctorResult(result) {
22
+ const output = `${result.stdout || ''}\n${result.stderr || ''}`;
23
+ if (!result.error && result.status === 0) return { accepted: true, status: 'PASS', output };
24
+ return { accepted: false, status: 'FAIL', output };
25
+ }
26
+
27
+ const preparePackage = ({ packagePath, bundlePath }) => {
28
+ const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'ruvnet-staged-host-'));
29
+ run('tar', ['-xzf', packagePath, '-C', temp]);
30
+ const packageRoot = path.join(temp, 'package');
31
+ const bundleRoot = path.join(packageRoot, 'dist', 'ruvnet-brain');
32
+ fs.mkdirSync(bundleRoot, { recursive: true });
33
+ run('unzip', ['-q', bundlePath, '-d', bundleRoot]);
34
+ const nested = path.join(bundleRoot, 'ruvnet-brain');
35
+ if (fs.existsSync(nested)) {
36
+ for (const name of fs.readdirSync(nested)) fs.renameSync(path.join(nested, name), path.join(bundleRoot, name));
37
+ fs.rmdirSync(nested);
38
+ }
39
+ return { temp, packageRoot };
40
+ };
41
+
42
+ const fixturePath = (mode, temp) => {
43
+ const bin = path.join(temp, `bin-${mode}`);
44
+ fs.mkdirSync(bin);
45
+ const hosts = mode === 'claude' ? ['claude'] : mode === 'codex' ? ['codex'] : ['claude', 'codex'];
46
+ const desired = ['node', 'npm', ...hosts];
47
+ for (const name of desired) {
48
+ const target = locate(name);
49
+ if (!target) throw new Error(`${name} CLI unavailable for ${mode} host fixture`);
50
+ fs.symlinkSync(target, path.join(bin, name));
51
+ }
52
+ return `${bin}:/usr/bin:/bin`;
53
+ };
54
+
55
+ export function stagedHostVerifier({ assets, identity }) {
56
+ return {
57
+ async verify({ source, assets: observedAssets = assets }) {
58
+ const prepared = preparePackage(observedAssets);
59
+ const results = {};
60
+ try {
61
+ for (const mode of ['claude', 'codex', 'dual']) {
62
+ const home = path.join(prepared.temp, `home-${mode}`);
63
+ const brainHome = path.join(home, '.cache', 'ruvnet-brain');
64
+ fs.mkdirSync(path.join(home, '.claude'), { recursive: true });
65
+ if (mode !== 'claude') fs.mkdirSync(path.join(home, '.codex'), { recursive: true });
66
+ const env = {
67
+ ...process.env,
68
+ HOME: home,
69
+ CODEX_HOME: path.join(home, '.codex'),
70
+ RUVNET_BRAIN_HOME: brainHome,
71
+ RUVNET_BRAIN_KB: path.join(brainHome, 'kb'),
72
+ RUVNET_CLAUDE_MARKETPLACE_SOURCE: prepared.packageRoot,
73
+ RUVNET_CODEX_HOOK_TRUST_MODE: 'bypass',
74
+ CI: 'true',
75
+ PATH: fixturePath(mode, prepared.temp),
76
+ };
77
+ const installer = path.join(prepared.packageRoot, 'bin', 'install.mjs');
78
+ run(process.execPath, [installer, '--local', '--yes', '--force', '--no-nightly-prompt',
79
+ '--no-telemetry', '--no-stack', '--no-enhance', '--no-statusline', '--no-selfcheck'], {
80
+ cwd: prepared.packageRoot, env, timeout: 1_200_000,
81
+ });
82
+ const doctor = spawnSync(process.execPath, [installer, '--doctor', '--hooks'], {
83
+ cwd: prepared.packageRoot, env, timeout: 180_000,
84
+ });
85
+ const classified = classifyDoctorResult(doctor);
86
+ if (!classified.accepted) {
87
+ throw new Error(`doctor failed for ${mode}: ${classified.output.slice(-5000) || doctor.error?.message}`);
88
+ }
89
+ results[mode] = {
90
+ status: classified.status,
91
+ doctorExit: doctor.status,
92
+ version: identity.version,
93
+ };
94
+ }
95
+ return { verdict: 'PASS', source, artifactSha256: sha256(observedAssets.packagePath), fixtures: results };
96
+ } catch (error) {
97
+ return { verdict: 'FAIL', error: error.message, fixtures: results };
98
+ } finally {
99
+ fs.rmSync(prepared.temp, { recursive: true, force: true });
100
+ }
101
+ },
102
+ };
103
+ }