ruvnet-brain 4.0.7 → 4.0.12
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/bin/install.mjs +115 -44
- package/console/app.js +32 -11
- package/console/style.css +5 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/hooks/codex-hooks.json +17 -17
- package/plugin/scripts/codex-hook-wrapper.mjs +72 -1
- package/plugin/scripts/continuation-gate.mjs +41 -1
- package/plugin/scripts/learn-flush.mjs +37 -2
- package/plugin/scripts/project-identity.mjs +89 -0
- package/plugin/scripts/ruflo-bin.mjs +81 -0
- package/plugin/scripts/session-snapshot-hook.mjs +4 -1
- package/plugin/scripts/session-start-core.mjs +19 -2
- package/plugin/skills/release-proof/scripts/release-proof.mjs +12 -5
- package/scripts/candidate-host-evidence.mjs +49 -0
- package/scripts/ci/learning-replay-codex-adapter.mjs +13 -1
- package/scripts/console-runtime-identity.mjs +74 -0
- package/scripts/distill-project.mjs +9 -1
- package/scripts/doc-currency.mjs +15 -2
- package/scripts/health-repair.mjs +88 -11
- package/scripts/host-install-matrix.mjs +155 -0
- package/scripts/learning-replay-contract.mjs +14 -4
- package/scripts/learning-replay-execution.mjs +4 -3
- package/scripts/learning-replay-fixture.mjs +23 -2
- package/scripts/memory-doctor.mjs +26 -23
- package/scripts/model-router-catalog.mjs +34 -0
- package/scripts/onboarding-console.mjs +34 -12
- package/scripts/publication-receipt.mjs +135 -34
- package/scripts/release-evidence-aggregate.mjs +55 -0
- package/scripts/release-payload.mjs +100 -0
- package/scripts/release-transaction-provider.mjs +260 -55
- package/scripts/release-transaction.mjs +295 -105
- package/scripts/release.mjs +41 -12
- package/scripts/rvf-generation.mjs +17 -0
- package/scripts/selfcheck.mjs +20 -5
- package/scripts/staged-host-verifier.mjs +27 -57
- package/scripts/sync-version.mjs +10 -10
package/scripts/selfcheck.mjs
CHANGED
|
@@ -159,17 +159,31 @@ export function resolveInstalledSurface({ home = os.homedir(), repo = null } = {
|
|
|
159
159
|
for (const v of fs.readdirSync(cache)) {
|
|
160
160
|
const root = path.join(cache, v);
|
|
161
161
|
if (fs.existsSync(path.join(root, 'hooks', 'hooks.json'))) {
|
|
162
|
-
candidates.push({ root, source: `installed:${v}`, mtime: fs.statSync(path.join(root, 'hooks', 'hooks.json')).mtimeMs });
|
|
162
|
+
candidates.push({ root, source: `installed:${v}`, hooksFile: path.join(root, 'hooks', 'hooks.json'), mtime: fs.statSync(path.join(root, 'hooks', 'hooks.json')).mtimeMs });
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
} catch { /* no packed install on this machine */ }
|
|
166
166
|
candidates.sort((a, b) => b.mtime - a.mtime); // newest generation wins; several can coexist
|
|
167
167
|
const clone = path.join(home, '.claude', 'plugins', 'marketplaces', 'ruvnet-brain', 'plugin');
|
|
168
168
|
if (fs.existsSync(path.join(clone, 'hooks', 'hooks.json'))) {
|
|
169
|
-
candidates.push({ root: clone, source: 'marketplace-clone', mtime: 0 });
|
|
169
|
+
candidates.push({ root: clone, source: 'marketplace-clone', hooksFile: path.join(clone, 'hooks', 'hooks.json'), mtime: 0 });
|
|
170
170
|
}
|
|
171
|
+
// Codex installs the same immutable payload under its own cache. A Codex-only machine has no
|
|
172
|
+
// Claude marketplace clone, so treating that layout as "no plugin" made the real installed
|
|
173
|
+
// Codex hooks impossible to self-check (the release host matrix caught this). Keep the same
|
|
174
|
+
// newest-generation rule and read the Codex hook registry from the installed payload itself.
|
|
175
|
+
const codexCache = path.join(home, '.codex', 'plugins', 'cache', 'ruvnet-brain', 'ruvnet-brain');
|
|
176
|
+
try {
|
|
177
|
+
for (const v of fs.readdirSync(codexCache)) {
|
|
178
|
+
const root = path.join(codexCache, v);
|
|
179
|
+
if (fs.existsSync(path.join(root, 'hooks', 'codex-hooks.json'))) {
|
|
180
|
+
candidates.push({ root, source: `codex-installed:${v}`, hooksFile: path.join(root, 'hooks', 'codex-hooks.json'), mtime: fs.statSync(path.join(root, 'hooks', 'codex-hooks.json')).mtimeMs, codex: true });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch { /* no Codex plugin cache on this machine */ }
|
|
171
184
|
if (repo && fs.existsSync(path.join(repo, 'plugin', 'hooks', 'hooks.json'))) {
|
|
172
|
-
|
|
185
|
+
const root = path.join(repo, 'plugin');
|
|
186
|
+
candidates.push({ root, source: 'checkout', hooksFile: path.join(root, 'hooks', 'hooks.json'), mtime: 0 });
|
|
173
187
|
}
|
|
174
188
|
const chosen = candidates[0];
|
|
175
189
|
if (!chosen) return { ok: false, reason: 'no installed ruvnet-brain plugin payload found on this machine' };
|
|
@@ -177,8 +191,9 @@ export function resolveInstalledSurface({ home = os.homedir(), repo = null } = {
|
|
|
177
191
|
ok: true,
|
|
178
192
|
root: chosen.root,
|
|
179
193
|
source: chosen.source,
|
|
180
|
-
hooksFile:
|
|
194
|
+
hooksFile: chosen.hooksFile,
|
|
181
195
|
shimFile: path.join(chosen.root, 'scripts', 'hook-shim.mjs'),
|
|
196
|
+
codex: Boolean(chosen.codex),
|
|
182
197
|
alternates: candidates.slice(1).map((c) => c.source),
|
|
183
198
|
};
|
|
184
199
|
}
|
|
@@ -480,7 +495,7 @@ export async function runBattery({ home = os.homedir(), repo = null, cwd = os.tm
|
|
|
480
495
|
const shimId = reg.shimIdIn(r.command);
|
|
481
496
|
const entry = shimId ? table[shimId] : null;
|
|
482
497
|
const contract = entry ? null : contracts.find((c) => reg.contractMatches(c, { layer: 'plugin', event: r.event, matcher: r.matcher, command: r.command }));
|
|
483
|
-
const mode = entry?.mode ?? contract?.mode ?? null;
|
|
498
|
+
const mode = entry?.mode ?? contract?.mode ?? (s.codex ? 'advisory' : null);
|
|
484
499
|
const handler = entry?.file ?? reg.basenamesIn(r.command).filter((b) => b !== 'hook-shim.mjs').pop() ?? null;
|
|
485
500
|
const rec = { ...r, handler, shimId, mode };
|
|
486
501
|
|
|
@@ -3,6 +3,7 @@ import crypto from 'node:crypto';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import { runHostMatrix } from './host-install-matrix.mjs';
|
|
6
7
|
|
|
7
8
|
const sha256 = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
8
9
|
const locate = (name) => {
|
|
@@ -18,15 +19,9 @@ const run = (name, args, options) => {
|
|
|
18
19
|
return result;
|
|
19
20
|
};
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const pendingReview = result.status === 1
|
|
25
|
-
&& /Codex installed the Brain, but \d+ lifecycle hooks await review/i.test(output)
|
|
26
|
-
&& /Grounding PROVEN/i.test(output)
|
|
27
|
-
&& !/reader MISSING|search_ruvnet MISSING|host convergence incomplete|receipt is invalid/i.test(output);
|
|
28
|
-
return { accepted: pendingReview, status: pendingReview ? 'PENDING_REVIEW' : 'FAIL', output };
|
|
29
|
-
}
|
|
22
|
+
// The doctor verdict has ONE rule, in host-install-matrix.mjs. This name is kept because
|
|
23
|
+
// tests/unit/staged-host-verifier.test.mjs pins it, but it is now an alias, not a second copy.
|
|
24
|
+
export { classifyDoctor as classifyDoctorResult } from './host-install-matrix.mjs';
|
|
30
25
|
|
|
31
26
|
const preparePackage = ({ packagePath, bundlePath }) => {
|
|
32
27
|
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'ruvnet-staged-host-'));
|
|
@@ -43,61 +38,36 @@ const preparePackage = ({ packagePath, bundlePath }) => {
|
|
|
43
38
|
return { temp, packageRoot };
|
|
44
39
|
};
|
|
45
40
|
|
|
46
|
-
const fixturePath = (mode, temp) => {
|
|
47
|
-
const bin = path.join(temp, `bin-${mode}`);
|
|
48
|
-
fs.mkdirSync(bin);
|
|
49
|
-
const hosts = mode === 'claude' ? ['claude'] : mode === 'codex' ? ['codex'] : ['claude', 'codex'];
|
|
50
|
-
const desired = ['node', 'npm', ...hosts];
|
|
51
|
-
for (const name of desired) {
|
|
52
|
-
const target = locate(name);
|
|
53
|
-
if (!target) throw new Error(`${name} CLI unavailable for ${mode} host fixture`);
|
|
54
|
-
fs.symlinkSync(target, path.join(bin, name));
|
|
55
|
-
}
|
|
56
|
-
return `${bin}:/usr/bin:/bin`;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
41
|
export function stagedHostVerifier({ assets, identity }) {
|
|
60
42
|
return {
|
|
61
43
|
async verify({ source, assets: observedAssets = assets }) {
|
|
62
44
|
const prepared = preparePackage(observedAssets);
|
|
63
|
-
const results = {};
|
|
64
45
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
run(process.execPath, [installer, '--local', '--yes', '--force', '--no-nightly-prompt',
|
|
82
|
-
'--no-telemetry', '--no-stack', '--no-enhance', '--no-statusline', '--no-selfcheck'], {
|
|
83
|
-
cwd: prepared.packageRoot, env, timeout: 1_200_000,
|
|
84
|
-
});
|
|
85
|
-
const doctor = spawnSync(process.execPath, [installer, '--doctor'], {
|
|
86
|
-
cwd: prepared.packageRoot, env, timeout: 180_000,
|
|
87
|
-
});
|
|
88
|
-
const classified = classifyDoctorResult(doctor);
|
|
89
|
-
if (!classified.accepted) {
|
|
90
|
-
throw new Error(`doctor failed for ${mode}: ${classified.output.slice(-5000) || doctor.error?.message}`);
|
|
91
|
-
}
|
|
92
|
-
results[mode] = {
|
|
93
|
-
status: classified.status,
|
|
94
|
-
doctorExit: doctor.status,
|
|
95
|
-
version: identity.version,
|
|
96
|
-
};
|
|
46
|
+
// The loop, the mode names, the env and the doctor verdict all live in
|
|
47
|
+
// scripts/host-install-matrix.mjs, shared with the published-side check in
|
|
48
|
+
// publication-receipt.mjs. This file used to carry its own copy, which had drifted to
|
|
49
|
+
// different mode names (claude/codex/dual vs claudeOnly/codexOnly/dual) and a different
|
|
50
|
+
// install shape than the one that runs after publication — so the two halves of a release
|
|
51
|
+
// were judging different things and could not be compared. Only the STAGED-vs-PUBLISHED
|
|
52
|
+
// difference is real, and it is now a named variant rather than a second implementation.
|
|
53
|
+
const matrix = runHostMatrix({
|
|
54
|
+
packageRoot: prepared.packageRoot,
|
|
55
|
+
version: identity.version,
|
|
56
|
+
variant: 'staged',
|
|
57
|
+
locate,
|
|
58
|
+
temp: prepared.temp,
|
|
59
|
+
});
|
|
60
|
+
if (matrix.verdict !== 'PASS') {
|
|
61
|
+
return { verdict: 'FAIL', source, error: matrix.error, fixtures: matrix.fixtures };
|
|
97
62
|
}
|
|
98
|
-
return {
|
|
63
|
+
return {
|
|
64
|
+
verdict: 'PASS',
|
|
65
|
+
source,
|
|
66
|
+
artifactSha256: sha256(observedAssets.packagePath),
|
|
67
|
+
fixtures: matrix.fixtures,
|
|
68
|
+
};
|
|
99
69
|
} catch (error) {
|
|
100
|
-
return { verdict: 'FAIL', error: error.message, fixtures:
|
|
70
|
+
return { verdict: 'FAIL', source, error: error.message, fixtures: {} };
|
|
101
71
|
} finally {
|
|
102
72
|
fs.rmSync(prepared.temp, { recursive: true, force: true });
|
|
103
73
|
}
|
package/scripts/sync-version.mjs
CHANGED
|
@@ -113,16 +113,16 @@ for (const rel of ['kb/RVF-GENERATIONS.json']) {
|
|
|
113
113
|
|
|
114
114
|
if (CHECK && fs.existsSync(path.join(ROOT, 'kb/RVF-GENERATIONS.json'))) {
|
|
115
115
|
const kbDir = path.join(ROOT, 'kb');
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
//
|
|
120
|
-
// the
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
});
|
|
116
|
+
// Validate the committed ledger's version fields ONLY. The previous form scanned the working
|
|
117
|
+
// directory for `*.big.rvf` and byte-compared every ledger row against whatever this machine
|
|
118
|
+
// happened to have — but those binaries are gitignored, so the verdict described the machine, not
|
|
119
|
+
// the commit. It was unsatisfiable in practice (ledger: 72 stores; a real checkout: 71, because
|
|
120
|
+
// the nightly rebuilds RVFs and regenerates the ledger as one step) and it blocked every push,
|
|
121
|
+
// tags included, while pointing at a remedy that cannot clear it. Byte verification now happens
|
|
122
|
+
// where the bytes are genuinely present and genuinely shipped: scripts/build-bundle.mjs:204-214.
|
|
123
|
+
// This is the same rule release.mjs:100 already applies — a verdict is only about the committed
|
|
124
|
+
// candidate.
|
|
125
|
+
const { failures } = verifyRvfGenerations(kbDir, { verifyBytes: false });
|
|
126
126
|
for (const failure of failures) {
|
|
127
127
|
console.error(`[version] RVF GENERATION DRIFT: ${failure}`);
|
|
128
128
|
drift++;
|