ruvnet-brain 3.9.134-dev โ 4.0.2
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/.claude-plugin/marketplace.json +14 -0
- package/README.md +5 -5
- package/bin/install.mjs +382 -36
- package/console/CONTRACT.md +172 -0
- package/console/activity.js +753 -0
- package/console/app.js +4189 -0
- package/console/architecture.html +1221 -0
- package/console/assets/depth-1.webp +0 -0
- package/console/assets/depth-2.webp +0 -0
- package/console/assets/depth-3.webp +0 -0
- package/console/assets/harness-vs-plain.svg +259 -0
- package/console/assets/hero.webp +0 -0
- package/console/assets/memory.webp +0 -0
- package/console/assets/metaharness.svg +247 -0
- package/console/index.html +777 -0
- package/console/install-architecture.html +162 -0
- package/console/install-mockup.html +543 -0
- package/console/style.css +2144 -0
- package/console/tips.css +926 -0
- package/console/tips.html +858 -0
- package/console/tips.js +128 -0
- package/docs/RELEASE-NOTES-4.0.md +88 -0
- package/kb/model-requirements.mjs +37 -6
- package/kb/zip-extract.mjs +53 -14
- package/keys/ruvnet-brain-signing.pub.pem +3 -0
- package/package.json +14 -22
- package/plugin/.claude-plugin/marketplace.json +14 -0
- package/plugin/.claude-plugin/plugin.json +22 -0
- package/plugin/.codex-plugin/plugin.json +21 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/commands/brain-console.md +16 -0
- package/plugin/commands/configure.md +33 -0
- package/plugin/commands/rvbc.md +79 -0
- package/plugin/commands/rvcb.md +16 -0
- package/plugin/commands/whats-new.md +57 -0
- package/plugin/hooks/codex-hooks.json +160 -0
- package/plugin/hooks/hook-contracts.json +77 -0
- package/plugin/hooks/hooks.json +202 -0
- package/plugin/mcp/managed-cli-interface.mjs +47 -4
- package/plugin/mcp/server.mjs +56 -6
- package/plugin/scripts/anticipate.sh +534 -0
- package/plugin/scripts/codex-hook-adapter.mjs +96 -0
- package/plugin/scripts/continuation-gate.mjs +267 -0
- package/plugin/scripts/design-wall.sh +137 -0
- package/plugin/scripts/detach.mjs +182 -0
- package/plugin/scripts/first-session-worker.mjs +38 -0
- package/plugin/scripts/gate-receipt.sh +35 -0
- package/plugin/scripts/ground-before-write.sh +199 -0
- package/plugin/scripts/ground-ruvnet.sh +517 -0
- package/plugin/scripts/grounding-stamp.sh +113 -0
- package/plugin/scripts/grounding-substance.mjs +595 -0
- package/plugin/scripts/hijack-ruvnet.sh +81 -0
- package/plugin/scripts/hook-input.mjs +558 -0
- package/plugin/scripts/hook-shim-bash.mjs +55 -0
- package/plugin/scripts/hook-shim.mjs +303 -0
- package/plugin/scripts/host-update.mjs +58 -0
- package/plugin/scripts/kling-preflight.sh +146 -0
- package/plugin/scripts/learn-capture.sh +173 -0
- package/plugin/scripts/learn-flush.mjs +155 -0
- package/plugin/scripts/lesson-hooks.sh +213 -0
- package/plugin/scripts/md-stamp.mjs +219 -0
- package/plugin/scripts/protect-brain-state.sh +84 -0
- package/plugin/scripts/route-dispatch.sh +147 -0
- package/plugin/scripts/routing-outcome-capture.mjs +89 -0
- package/plugin/scripts/runtime-preferences.mjs +269 -0
- package/plugin/scripts/session-start-core.mjs +477 -0
- package/plugin/scripts/session-start.sh +13 -0
- package/plugin/scripts/signal-watch.mjs +193 -0
- package/plugin/scripts/unprompted-runtime.mjs +377 -0
- package/plugin/scripts/update-apply.mjs +419 -0
- package/plugin/scripts/verify-interface.sh +53 -0
- package/plugin/scripts/version-bump-gate.sh +112 -0
- package/plugin/skills/brain-build/SKILL.md +123 -0
- package/plugin/skills/brain-console/SKILL.md +22 -0
- package/plugin/skills/brain-prompt/SKILL.md +83 -0
- package/plugin/skills/brain-score/SKILL.md +101 -0
- package/plugin/skills/release-proof/SKILL.md +81 -0
- package/plugin/skills/release-proof/agents/openai.yaml +4 -0
- package/plugin/skills/release-proof/references/receipt-contract.md +38 -0
- package/plugin/skills/release-proof/scripts/release-proof.mjs +210 -0
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +121 -0
- package/plugin/skills/ruvnet-brain/SKILL.md +234 -0
- package/plugin/skills/rvbc/SKILL.md +23 -0
- package/plugin/skills/savings/SKILL.md +46 -0
- package/plugin/skills/whats-new/SKILL.md +22 -0
- package/scripts/adr-backfill.mjs +107 -0
- package/scripts/advocacy-outcomes.mjs +808 -0
- package/scripts/agentdb-context.mjs +216 -0
- package/scripts/agentdb-fleet-doctor.mjs +101 -0
- package/scripts/ascii-drift.mjs +236 -0
- package/scripts/behavioral-l1-l4.mjs +210 -0
- package/scripts/brain-capability-check.mjs +72 -0
- package/scripts/brain-grade-groundtruth.mjs +100 -0
- package/scripts/brain-latency-50.mjs +227 -0
- package/scripts/brain-novice-50.mjs +189 -0
- package/scripts/brain-stamp.mjs +94 -0
- package/scripts/brain-state.mjs +212 -0
- package/scripts/build-bundle.mjs +522 -0
- package/scripts/build-concepts.mjs +132 -0
- package/scripts/build-l2.mjs +71 -0
- package/scripts/build-primer.mjs +73 -0
- package/scripts/build-symbols.mjs +68 -0
- package/scripts/calibrate-router.mjs +97 -0
- package/scripts/capability-audit.mjs +321 -0
- package/scripts/capability-registry.mjs +876 -0
- package/scripts/check-indexation.mjs +108 -0
- package/scripts/check-legibility.mjs +189 -0
- package/scripts/ci/build-fixture-kb.mjs +67 -0
- package/scripts/ci/learning-replay-codex-adapter.mjs +62 -0
- package/scripts/ci/learning-replay-recorder.mjs +59 -0
- package/scripts/ci/mutate-hook-timeout.mjs +70 -0
- package/scripts/ci/stranger-fixture-stage.mjs +17 -0
- package/scripts/ci/stranger-scenario.mjs +228 -0
- package/scripts/ci/stranger-timeout.mjs +25 -0
- package/scripts/ci-verdict.mjs +29 -0
- package/scripts/claims-verify.mjs +710 -0
- package/scripts/clear-claude-tmp.sh +31 -0
- package/scripts/console-engine.mjs +434 -0
- package/scripts/console-engine.test.mjs +125 -0
- package/scripts/corpus-qa.mjs +250 -0
- package/scripts/correction-detect-embed.mjs +346 -0
- package/scripts/correction-detect-measure.mjs +270 -0
- package/scripts/correction-detect.mjs +686 -0
- package/scripts/count-chunks.mjs +54 -0
- package/scripts/described-questions.json +30 -0
- package/scripts/design-grade.mjs +58 -0
- package/scripts/dev-plugin-link.sh +105 -0
- package/scripts/distill-project.mjs +200 -0
- package/scripts/doc-currency.mjs +801 -0
- package/scripts/eval-brain.mjs +244 -0
- package/scripts/fix-metaharness-memretrieve.mjs +121 -0
- package/scripts/full-hints.mjs +87 -0
- package/scripts/gate.sh +39 -0
- package/scripts/gates.mjs +146 -0
- package/scripts/gen-console-images.mjs +54 -0
- package/scripts/gen-images.mjs +47 -0
- package/scripts/git-clone-refresh.mjs +52 -0
- package/scripts/git-hooks/pre-push +126 -0
- package/scripts/goal-match.mjs +398 -0
- package/scripts/goldie-research.mjs +223 -0
- package/scripts/goldie-weekly.sh +67 -0
- package/scripts/health-repair.mjs +250 -0
- package/scripts/helix-scenario-questions.json +10 -0
- package/scripts/ingest-gists.mjs +230 -0
- package/scripts/ingest-meeting.mjs +115 -0
- package/scripts/ingest-repo.mjs +79 -0
- package/scripts/install-npx-witness.sh +49 -0
- package/scripts/issue-fix.mjs +639 -0
- package/scripts/issue-watch.mjs +276 -0
- package/scripts/issue4-close-note.md +31 -0
- package/scripts/key-canary.mjs +91 -0
- package/scripts/latency-to-surface.mjs +233 -0
- package/scripts/learning-enable.mjs +380 -0
- package/scripts/learning-replay.mjs +1570 -0
- package/scripts/learnings.mjs +62 -0
- package/scripts/lesson-gate.mjs +680 -0
- package/scripts/lesson-lifecycle.mjs +449 -0
- package/scripts/lesson-promote.mjs +262 -0
- package/scripts/lesson-ratify.mjs +98 -0
- package/scripts/lesson-seed.mjs +252 -0
- package/scripts/lesson-store.mjs +447 -0
- package/scripts/loop-checkpoint.mjs +86 -0
- package/scripts/memdb-health.sh +14 -0
- package/scripts/memory-doctor.mjs +271 -0
- package/scripts/model-catalog.mjs +79 -0
- package/scripts/nightly-controller.mjs +66 -0
- package/scripts/nightly-gists.sh +72 -0
- package/scripts/nightly-wrapper.sh +180 -0
- package/scripts/notify.sh +12 -0
- package/scripts/npx-witness.sh +56 -0
- package/scripts/onboarding-console.mjs +2749 -0
- package/scripts/private-fence.mjs +69 -0
- package/scripts/proactivity-metrics.mjs +118 -0
- package/scripts/proof-questions.json +56 -0
- package/scripts/prove.mjs +95 -0
- package/scripts/proxy/claude-proxied.sh +57 -0
- package/scripts/proxy/proxy-revert.sh +59 -0
- package/scripts/proxy/proxy-up.sh +60 -0
- package/scripts/proxy/proxy-verify.mjs +142 -0
- package/scripts/published-surface-probe.mjs +241 -0
- package/scripts/qe/card-lane-gate.mjs +162 -0
- package/scripts/qe/session-start-gate.mjs +229 -0
- package/scripts/qe/ux-suite.mjs +323 -0
- package/scripts/reconcile-project.mjs +0 -0
- package/scripts/record-lesson.mjs +113 -0
- package/scripts/refresh-model-catalog.mjs +99 -0
- package/scripts/release-proof.mjs +9 -0
- package/scripts/release-vector.mjs +281 -0
- package/scripts/release.mjs +395 -0
- package/scripts/remedy-registry.mjs +247 -0
- package/scripts/rerank-cap-eval.mjs +265 -0
- package/scripts/rerank-cap-warm-ab.mjs +129 -0
- package/scripts/route-cheap.mjs +20 -15
- package/scripts/router-utilization.mjs +182 -0
- package/scripts/routing-flywheel.mjs +596 -0
- package/scripts/rvf-generation.mjs +104 -0
- package/scripts/rvf-index-audit.mjs +138 -0
- package/scripts/self-update.mjs +508 -0
- package/scripts/selfcheck.mjs +7 -1
- package/scripts/sign-bundle.mjs +69 -0
- package/scripts/signal-watch.mjs +171 -0
- package/scripts/stack-sync.mjs +469 -0
- package/scripts/stamp-existing-rvf-generations.mjs +53 -0
- package/scripts/stamp-sweep.mjs +144 -0
- package/scripts/status-honesty.mjs +102 -0
- package/scripts/sync-version.mjs +217 -0
- package/scripts/token-report.mjs +102 -0
- package/scripts/top100-benchmark.mjs +479 -0
- package/scripts/top100-corpus.mjs +112 -0
- package/scripts/top100-semantic-assertions.mjs +449 -0
- package/scripts/update-apply.mjs +9 -0
- package/scripts/upgrade-notice.mjs +14 -0
- package/scripts/verify-bundle.mjs +51 -0
- package/scripts/verify-channels.mjs +184 -0
- package/scripts/verify-model-catalog.mjs +104 -0
- package/scripts/verify-nightly-close-issue4.sh +31 -0
- package/scripts/version.mjs +40 -0
- package/scripts/wired-check.mjs +864 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The one host-neutral SessionStart authority. A Windows incidentโGit Bash consumed the hook's
|
|
3
|
+
// latency budget before the shell body reached its first instructionโmotivated moving every host
|
|
4
|
+
// onto this dependency-free core. It runs from both the frozen plugin and a Stable Spine generation;
|
|
5
|
+
// session-start.sh remains only a compatibility launcher for callers that still invoke that path.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const read = (file, fallback = '') => {
|
|
14
|
+
try { return fs.readFileSync(file, 'utf8'); } catch { return fallback; }
|
|
15
|
+
};
|
|
16
|
+
const json = (file, fallback = null) => {
|
|
17
|
+
try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return fallback; }
|
|
18
|
+
};
|
|
19
|
+
const exists = (file) => {
|
|
20
|
+
try { return fs.existsSync(file); } catch { return false; }
|
|
21
|
+
};
|
|
22
|
+
const mkdir = (dir) => {
|
|
23
|
+
try { fs.mkdirSync(dir, { recursive: true }); return true; } catch { return false; }
|
|
24
|
+
};
|
|
25
|
+
const write = (file, value) => {
|
|
26
|
+
try { mkdir(path.dirname(file)); fs.writeFileSync(file, String(value)); return true; } catch { return false; }
|
|
27
|
+
};
|
|
28
|
+
const mtimeMs = (file) => {
|
|
29
|
+
try { return fs.statSync(file).mtimeMs; } catch { return 0; }
|
|
30
|
+
};
|
|
31
|
+
const firstVersion = (text) => String(text).split(/\r?\n/).find((line) =>
|
|
32
|
+
/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/.test(line.trim()))?.trim() || '';
|
|
33
|
+
const commandExists = (name, env, platform) => {
|
|
34
|
+
const extensions = platform === 'win32'
|
|
35
|
+
? String(env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';')
|
|
36
|
+
: [''];
|
|
37
|
+
for (const dir of String(env.PATH || '').split(path.delimiter).filter(Boolean)) {
|
|
38
|
+
for (const extension of extensions) {
|
|
39
|
+
if (exists(path.join(dir, `${name}${extension}`))) return true;
|
|
40
|
+
if (platform === 'win32' && exists(path.join(dir, `${name}${extension.toLowerCase()}`))) return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const runNode = (file, args = [], options = {}) => {
|
|
47
|
+
if (!exists(file)) return null;
|
|
48
|
+
try {
|
|
49
|
+
return spawnSync(process.execPath, [file, ...args], {
|
|
50
|
+
cwd: options.cwd,
|
|
51
|
+
env: options.env,
|
|
52
|
+
encoding: 'utf8',
|
|
53
|
+
stdio: options.stdio || 'ignore',
|
|
54
|
+
timeout: options.timeout,
|
|
55
|
+
windowsHide: true,
|
|
56
|
+
});
|
|
57
|
+
} catch { return null; }
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const dispatchDetached = (hookDir, ttl, log, command, args = [], env = process.env) =>
|
|
61
|
+
runNode(path.join(hookDir, 'detach.mjs'), [
|
|
62
|
+
String(ttl), log, command, ...args,
|
|
63
|
+
], { env, stdio: 'ignore', timeout: 2000 })?.status === 0;
|
|
64
|
+
|
|
65
|
+
const surfaceIssues = (stateDir, emit, now) => {
|
|
66
|
+
const status = json(path.join(stateDir, 'open-issues.json'));
|
|
67
|
+
if (!status?.at || now - new Date(status.at).getTime() > 6 * 3600_000) return;
|
|
68
|
+
const open = Array.isArray(status.issues) ? status.issues : [];
|
|
69
|
+
if (!open.length) return;
|
|
70
|
+
const breaches = open.filter((issue) => issue.breach).sort((a, b) => b.ageHours - a.ageHours);
|
|
71
|
+
const selected = breaches.length ? breaches : [...open].sort((a, b) => b.ageHours - a.ageHours);
|
|
72
|
+
const top = selected.slice(0, 4).map((issue) =>
|
|
73
|
+
breaches.length
|
|
74
|
+
? `#${issue.number} (${issue.ageHours}h) ${String(issue.title || '').slice(0, 64)}`
|
|
75
|
+
: `#${issue.number} (${issue.ageHours}h)`).join(' ยท ');
|
|
76
|
+
const more = selected.length > 4 ? ` ยท +${selected.length - 4} more` : '';
|
|
77
|
+
if (breaches.length) {
|
|
78
|
+
emit('[RuvNet Brain โ OPEN ISSUES need attention (surface this to the maintainer, once, near the top)]');
|
|
79
|
+
emit(`${breaches.length} open issue(s) past SLA on ${status.repo}: ${top}${more}`);
|
|
80
|
+
emit('These are real user-filed bugs sitting past the response SLA. Mention them plainly so they do not stack unseen; offer to fix them (gh issue list --state open for detail).');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
emit("[RuvNet Brain โ open issues on the maintainer's repo (mention once, calmly, near the top)]");
|
|
84
|
+
emit(`${open.length} open issue(s) on ${status.repo}, none past SLA: ${top}${more}`);
|
|
85
|
+
emit('Within SLA, but the maintainer should know they exist. One line is enough; offer to look.');
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const surfaceSignals = ({ env, cwd, stateDir, hookDir, emit, now }) => {
|
|
89
|
+
const signalDir = env.RUVNET_SIGNAL_DIR || path.join(stateDir, 'external-signals');
|
|
90
|
+
const pending = path.join(signalDir, 'pending.jsonl');
|
|
91
|
+
const statusFile = path.join(signalDir, 'ci-status.json');
|
|
92
|
+
const surfacedFile = path.join(signalDir, 'surfaced.json');
|
|
93
|
+
if (!exists(pending)) return;
|
|
94
|
+
|
|
95
|
+
const poller = path.join(env.CLAUDE_PROJECT_DIR || cwd, 'scripts', 'signal-watch.mjs');
|
|
96
|
+
if (exists(poller) && now - mtimeMs(statusFile) > 600_000) {
|
|
97
|
+
runNode(poller, [], { cwd, env, stdio: 'ignore', timeout: 3500 });
|
|
98
|
+
}
|
|
99
|
+
const status = json(statusFile);
|
|
100
|
+
if (!status || typeof status !== 'object') return;
|
|
101
|
+
const onDisk = json(surfacedFile, {});
|
|
102
|
+
const surfaced = {
|
|
103
|
+
debts: onDisk?.debts && typeof onDisk.debts === 'object' ? onDisk.debts : {},
|
|
104
|
+
redRepo: onDisk?.redRepo && typeof onDisk.redRepo === 'object' ? onDisk.redRepo : {},
|
|
105
|
+
};
|
|
106
|
+
let changed = false;
|
|
107
|
+
const entries = Object.entries(status).sort((a, b) =>
|
|
108
|
+
new Date(a[1]?.checkedAt || 0) - new Date(b[1]?.checkedAt || 0));
|
|
109
|
+
for (const [key, debt] of entries) {
|
|
110
|
+
const shortSha = String(debt?.ref || '').slice(0, 7);
|
|
111
|
+
if (debt?.state === 'resolved' && debt.conclusion !== 'success') {
|
|
112
|
+
if (surfaced.debts[key] === 'red') continue;
|
|
113
|
+
emit(`[RuvNet Brain โ EXTERNAL SIGNAL: CI is RED for ${debt.repo}@${shortSha} โ surface this to the user now, near the top, with ZERO prompting]`);
|
|
114
|
+
emit(`Workflow ${debt.workflowName || 'ci'} concluded ${debt.conclusion} on ${debt.repo}@${shortSha}. Say it plainly and offer to look (gh run list --repo ${debt.repo} --commit ${debt.ref}).`);
|
|
115
|
+
surfaced.debts[key] = 'red';
|
|
116
|
+
surfaced.redRepo[debt.repo] = key;
|
|
117
|
+
changed = true;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (debt?.state === 'resolved' && debt.conclusion === 'success') {
|
|
121
|
+
if (surfaced.redRepo[debt.repo]) {
|
|
122
|
+
emit(`[RuvNet Brain โ external signal: CI is GREEN again for ${debt.repo}@${shortSha} โ one line, then move on]`);
|
|
123
|
+
delete surfaced.redRepo[debt.repo];
|
|
124
|
+
changed = true;
|
|
125
|
+
}
|
|
126
|
+
if (surfaced.debts[key] !== 'green') { surfaced.debts[key] = 'green'; changed = true; }
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (debt?.state === 'unverifiable' && surfaced.debts[key] !== 'unverifiable') {
|
|
130
|
+
emit(`[RuvNet Brain โ external signal: CI status could not be checked for ${debt.repo}@${shortSha}: ${debt.reason || 'unknown reason'}]`);
|
|
131
|
+
surfaced.debts[key] = 'unverifiable';
|
|
132
|
+
changed = true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (changed) write(surfacedFile, JSON.stringify(surfaced, null, 2));
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const brainState = (env, home) => {
|
|
139
|
+
const stateDir = env.RUVNET_BRAIN_STATE_DIR || path.join(home, '.config', 'ruvnet-brain');
|
|
140
|
+
const file = path.join(stateDir, 'brain-off');
|
|
141
|
+
let off = env.RUVNET_BRAIN_OFF === '1';
|
|
142
|
+
if (!off) {
|
|
143
|
+
try { fs.statSync(file); off = true; }
|
|
144
|
+
catch (error) { off = !(error && (error.code === 'ENOENT' || error.code === 'ENOTDIR')); }
|
|
145
|
+
}
|
|
146
|
+
let since = '';
|
|
147
|
+
if (off) {
|
|
148
|
+
const record = json(file);
|
|
149
|
+
if (typeof record?.since === 'string') since = record.since.slice(0, 10);
|
|
150
|
+
if (!since && mtimeMs(file)) since = new Date(mtimeMs(file)).toISOString().slice(0, 10);
|
|
151
|
+
}
|
|
152
|
+
return { off, since, stateDir, file };
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const health = (home, off) => {
|
|
156
|
+
const kb = path.join(home, '.cache', 'ruvnet-brain', 'kb');
|
|
157
|
+
let rvf = false;
|
|
158
|
+
try { rvf = fs.readdirSync(kb).some((name) => name.endsWith('.rvf') && exists(path.join(kb, name))); }
|
|
159
|
+
catch { /* absent */ }
|
|
160
|
+
const absentByChoice = off && (!exists(kb) || !rvf);
|
|
161
|
+
if (absentByChoice) return { problem: '', absentByChoice };
|
|
162
|
+
if (!exists(kb)) return { problem: `the brain cache directory is MISSING (${kb}) โ reinstall: npx github:stuinfla/ruvnet-brain`, absentByChoice };
|
|
163
|
+
if (!rvf) return { problem: `NO vector stores (.rvf) found in ${kb} โ the brain is empty; reinstall: npx github:stuinfla/ruvnet-brain --force`, absentByChoice };
|
|
164
|
+
if (!exists(path.join(kb, 'node_modules', '@xenova', 'transformers', 'package.json'))) {
|
|
165
|
+
return { problem: `reader dependencies are MISSING (node_modules gone) โ every search WILL fail. Fix: cd ${kb} && npm i`, absentByChoice };
|
|
166
|
+
}
|
|
167
|
+
const last = json(path.join(home, '.cache', 'ruvnet-brain', 'health.json'));
|
|
168
|
+
if (last?.status === 'down') {
|
|
169
|
+
const detail = `"error": ${JSON.stringify(String(last.error || 'unknown error'))}`.slice(0, 180);
|
|
170
|
+
return { problem: `the last real search FAILED across all repos (${detail}). Fix: cd ${kb} && npm i, then run one search to clear the alarm`, absentByChoice };
|
|
171
|
+
}
|
|
172
|
+
return { problem: '', absentByChoice };
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const announceVersion = ({ running, off, stateDir, consoleInvoke, emit }) => {
|
|
176
|
+
if (off || !running) return;
|
|
177
|
+
const announced = path.join(stateDir, '.last-announced-version');
|
|
178
|
+
const milestoneFile = path.join(stateDir, '.last-major-milestone');
|
|
179
|
+
const [base] = running.split('-');
|
|
180
|
+
const parts = base.split('.').map(Number);
|
|
181
|
+
let milestone = '';
|
|
182
|
+
if (parts.every(Number.isFinite)) {
|
|
183
|
+
const [major = 0, minor = 0, patch = 0] = parts;
|
|
184
|
+
if (major >= 4) milestone = '4.x';
|
|
185
|
+
else if (major * 1_000_000 + minor * 1000 + patch >= 3_009_071) milestone = '4.0-line';
|
|
186
|
+
}
|
|
187
|
+
let milestoneShown = false;
|
|
188
|
+
if (milestone && read(milestoneFile).trim() !== milestone) {
|
|
189
|
+
milestoneShown = true;
|
|
190
|
+
emit(`[RuvNet Brain โ MAJOR-LINE welcome (${milestone}), show ONCE near the top of your first response]`);
|
|
191
|
+
emit("They upgraded INTO the big release and will NOT have seen the web explainer. Warmly, a few sentences, adapt โ don't recite:");
|
|
192
|
+
emit(milestone === '4.x'
|
|
193
|
+
? ` 1. They're on RuvNet-Brain ${running}, a major version โ the release where the brain got honest, legible, fast and self-measuring.`
|
|
194
|
+
: ` 1. The 4.0-LINE enhancements have landed (they're on v${running}). Honestly: the number stays 3.9.x until the work is field-verified (ADR-042), so 'the 4.0-line upgrades are here', NOT 'you're on 4.0'.`);
|
|
195
|
+
emit(` 2. Two or three things they can DO now: the Console on ${consoleInvoke} (their whole stack, one live page); every number measured from their machine, never projected; it learns across their projects.`);
|
|
196
|
+
emit(` 3. OFFER to open the Console now โ on yes, invoke ${consoleInvoke}. Point at the native what's-new workflow for the full highlights.`);
|
|
197
|
+
emit("Never claim 'proven better' or 'fully proactive' โ the self-measurement is new and still filling. Once only.");
|
|
198
|
+
write(milestoneFile, `${milestone}\n`);
|
|
199
|
+
}
|
|
200
|
+
if (read(announced).trim() === running) return;
|
|
201
|
+
const whatsNew = running.startsWith('3.2.') || running.startsWith('3.1.')
|
|
202
|
+
? `opens with the Console โ ${consoleInvoke} shows your whole RuvNet stack on one live page: what's installed, what your AI has actually learned from YOUR projects, and one-click reversible fixes. Every number is measured from your machine, never projected.`
|
|
203
|
+
: running.startsWith('3.0.') ? 'ships a visual configurator โ /ruvnet-brain:configure mirrors your machine\'s RuvNet setup in plain English and turns things on safely with one click. Read-only until you say so; nothing leaves your machine.'
|
|
204
|
+
: running.startsWith('2.4.') ? 'routes every task to the cheapest model that can do the job, aware of YOUR subscriptions specifically โ it detects what it can prove, asks what it can\'t, and learns from every override.'
|
|
205
|
+
: running.startsWith('2.3.') ? 'can no longer break silently: a failed search now rings three independent alarms (phone push, red banner at session start, nightly canary), each tested by deliberately breaking the brain. Failed searches say WHY, instead of pretending nothing matched.'
|
|
206
|
+
: /^2\.2\.[012]/.test(running) ? 'ships a safety watchdog that alerts you the instant a background tool starts running up API costs or a scheduled job starts failing silently. (Agentic QE testing still bills your Anthropic key โ cost-optimized, and opt-in.)'
|
|
207
|
+
: '';
|
|
208
|
+
if (whatsNew && !milestoneShown) {
|
|
209
|
+
emit(`[RuvNet Brain โ new in v${running}]`);
|
|
210
|
+
emit(`Near the top of your first response, share ONE upbeat line, in your words, on what this update gives them (say it once): RuvNet Brain v${running} ${whatsNew}`);
|
|
211
|
+
}
|
|
212
|
+
write(announced, `${running}\n`);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const stableSpine = ({ env, hookDir, stateDir, home, pluginVersion, emit, now }) => {
|
|
216
|
+
const activeFile = path.join(stateDir, 'active.json');
|
|
217
|
+
const stamp = path.join(stateDir, '.last-update-check');
|
|
218
|
+
let seedDispatched = false;
|
|
219
|
+
if (!exists(activeFile)) {
|
|
220
|
+
const seedStamp = path.join(stateDir, '.seed-attempted');
|
|
221
|
+
const last = Number(read(seedStamp).trim()) || 0;
|
|
222
|
+
const canSeed = exists(path.join(env.CLAUDE_PLUGIN_ROOT || '', 'scripts'))
|
|
223
|
+
|| exists(path.join(home, '.claude', 'plugins', 'cache', 'ruvnet-brain'));
|
|
224
|
+
if (canSeed && now / 1000 - last > 300) {
|
|
225
|
+
write(seedStamp, `${Math.floor(now / 1000)}\n`);
|
|
226
|
+
seedDispatched = dispatchDetached(hookDir, 120, path.join(stateDir, '.seed.log'),
|
|
227
|
+
'node', [
|
|
228
|
+
path.join(hookDir, 'first-session-worker.mjs'),
|
|
229
|
+
path.join(hookDir, 'update-apply.mjs'),
|
|
230
|
+
path.join(hookDir, 'host-update.mjs'),
|
|
231
|
+
path.join(stateDir, '.last-version-check.log'),
|
|
232
|
+
], env);
|
|
233
|
+
if (seedDispatched) write(stamp, `${Math.floor(now / 1000)}\n`);
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
const active = json(activeFile);
|
|
237
|
+
if (active?.shellChanged && pluginVersion && active.version && active.version !== pluginVersion) {
|
|
238
|
+
emit(`[RuvNet Brain โ v${active.version} changed boot-level declarations (the rare case); this session booted v${pluginVersion}'s]`);
|
|
239
|
+
const host = env.RUVNET_HOOK_HOST || 'claude';
|
|
240
|
+
const convergence = json(path.join(stateDir, 'host-convergence.json'));
|
|
241
|
+
const ready = convergence?.desiredVersion === active.version
|
|
242
|
+
&& convergence?.hosts?.[host]?.state === 'ready'
|
|
243
|
+
&& convergence.hosts[host].version === active.version;
|
|
244
|
+
if (!ready) {
|
|
245
|
+
emit(`Tell the user ONE line: "๐ง RuvNet Brain v${active.version} runtime is live, but this host's exact boot snapshot is not yet verified โ do not restart for this update yet; automatic host repair will retry."`);
|
|
246
|
+
} else if (host === 'codex') {
|
|
247
|
+
emit(`Tell the user ONE line: "๐ง RuvNet Brain v${active.version} is already installed and verified for Codex; restart Codex to load its boot-level declarations, then run /hooks and trust only ruvnet-brain@ruvnet-brain if Codex shows the changed definitions as pending. Runtime behavior already updated live."`);
|
|
248
|
+
} else {
|
|
249
|
+
emit(`Tell the user ONE line: "๐ง RuvNet Brain v${active.version} is already installed and verified for Claude Code; one restart picks up its boot-level declarations (\`claude --continue\` keeps this conversation). Runtime behavior already updated live."`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return { seedDispatched, stamp };
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const heartbeat = ({ env, hookDir, stateDir, home, running, seedDispatched, stamp, emit, now }) => {
|
|
257
|
+
const last = Number(read(stamp).trim()) || 0;
|
|
258
|
+
const epoch = Math.floor(now / 1000);
|
|
259
|
+
if (seedDispatched || epoch <= 0 || epoch - last <= 900) return;
|
|
260
|
+
write(stamp, `${epoch}\n`);
|
|
261
|
+
const pref = read(path.join(stateDir, '.auto-update-pref')).trim();
|
|
262
|
+
const kbDir = path.join(home, '.cache', 'ruvnet-brain', 'kb');
|
|
263
|
+
if (pref === 'yes' && exists(path.join(kbDir, 'forge-update.mjs'))) {
|
|
264
|
+
const kbLog = path.join(stateDir, '.last-kb-check.log');
|
|
265
|
+
if (/\bBEHIND\b/.test(read(kbLog))) {
|
|
266
|
+
emit('[RuvNet Brain โ a newer knowledge bundle is available. It is signed (Ed25519) and the updater verifies that signature before extracting anything. We do NOT auto-apply it: applying replaces executable tool files, which is your call. To update: cd ~/.cache/ruvnet-brain/kb && node forge-update.mjs --apply]');
|
|
267
|
+
}
|
|
268
|
+
dispatchDetached(hookDir, 60, kbLog, process.execPath,
|
|
269
|
+
[path.join(kbDir, 'forge-update.mjs'), '--check'], env);
|
|
270
|
+
}
|
|
271
|
+
const versionLog = path.join(stateDir, '.last-version-check.log');
|
|
272
|
+
const remote = firstVersion(read(versionLog));
|
|
273
|
+
dispatchDetached(hookDir, 10, versionLog, process.execPath,
|
|
274
|
+
[path.join(hookDir, 'host-update.mjs'), '--check'], env);
|
|
275
|
+
if (!running || !remote || remote === running) return;
|
|
276
|
+
if (pref === 'yes' && exists(path.join(hookDir, 'host-update.mjs'))) {
|
|
277
|
+
dispatchDetached(hookDir, 600, path.join(stateDir, '.last-auto-update.log'),
|
|
278
|
+
process.execPath, [path.join(hookDir, 'host-update.mjs')], env);
|
|
279
|
+
emit(`[RuvNet Brain โ v${remote} is downloading and will AUTO-APPLY via the Stable Spine (ADR-023); this session picks up the new behavior live]`);
|
|
280
|
+
emit('Tell the user ONE short line, near the top of your first response:');
|
|
281
|
+
emit(` "๐ง RuvNet Brain v${remote} is installing in the background for every detected host. Runtime behavior goes live automatically; if boot-level declarations changed, I'll ask for a restart only after that host's exact new snapshot is verified."`);
|
|
282
|
+
emit("Don't repeat this notice later in the same session.");
|
|
283
|
+
emit('');
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
emit('[RuvNet Brain โ update available, auto-update not enabled]');
|
|
287
|
+
emit('Tell the user this PLAINLY, near the top of your first response:');
|
|
288
|
+
emit(` "๐ง RuvNet Brain found v${remote} (you're on v${running}). Run: npx ruvnet-brain@latest --update"`);
|
|
289
|
+
emit(" (Or say the word and I'll turn on auto-update so this never comes up again.)\"");
|
|
290
|
+
emit("Don't repeat this notice later in the same session.");
|
|
291
|
+
emit('');
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const meter = ({ env, cwd, stateDir, output }) => {
|
|
295
|
+
if (env.RUVNET_BRAIN_METER === '0') return;
|
|
296
|
+
const ledgerDir = env.XDG_CACHE_HOME
|
|
297
|
+
? path.join(env.XDG_CACHE_HOME, 'ruvnet-brain')
|
|
298
|
+
: stateDir;
|
|
299
|
+
const entry = {
|
|
300
|
+
ts: new Date().toISOString(),
|
|
301
|
+
source: 'hook',
|
|
302
|
+
class: 'session-start',
|
|
303
|
+
bytes: Buffer.byteLength(output, 'utf8'),
|
|
304
|
+
cwd,
|
|
305
|
+
};
|
|
306
|
+
try {
|
|
307
|
+
mkdir(ledgerDir);
|
|
308
|
+
fs.appendFileSync(path.join(ledgerDir, 'token-ledger.jsonl'), `${JSON.stringify(entry)}\n`);
|
|
309
|
+
} catch { /* metering never blocks */ }
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
export async function runSessionStart({
|
|
313
|
+
env = process.env,
|
|
314
|
+
cwd = process.cwd(),
|
|
315
|
+
stdout = process.stdout,
|
|
316
|
+
stderr = process.stderr,
|
|
317
|
+
platform = process.platform,
|
|
318
|
+
} = {}) {
|
|
319
|
+
const lines = [];
|
|
320
|
+
const emit = (line = '') => lines.push(String(line));
|
|
321
|
+
const home = env.HOME || env.USERPROFILE || os.homedir();
|
|
322
|
+
const stateDir = env.RUVNET_BRAIN_HOME || path.join(home, '.cache', 'ruvnet-brain');
|
|
323
|
+
const hookDir = path.dirname(fileURLToPath(import.meta.url));
|
|
324
|
+
const now = Date.now();
|
|
325
|
+
const consoleInvoke = env.RUVNET_HOOK_HOST === 'codex' ? '$ruvnet-brain:rvbc' : '/rvbc';
|
|
326
|
+
const brain = brainState(env, home);
|
|
327
|
+
const pluginRoot = env.CLAUDE_PLUGIN_ROOT || path.resolve(hookDir, '..');
|
|
328
|
+
const manifest = json(path.join(pluginRoot, '.claude-plugin', 'plugin.json'), {});
|
|
329
|
+
const running = typeof manifest?.version === 'string' ? manifest.version : '';
|
|
330
|
+
const updated = typeof manifest?.updated === 'string' ? manifest.updated : '';
|
|
331
|
+
const trace = (stage) => {
|
|
332
|
+
if (env.RUVNET_SESSION_TRACE === '1') {
|
|
333
|
+
stderr.write(`SESSION_TRACE ${Date.now() / 1000} ${stage}\n`);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
trace('body-start');
|
|
339
|
+
const settingsFile = env.RUVNET_SETTINGS_FILE
|
|
340
|
+
|| path.join(home, '.config', 'ruvnet-brain', 'settings.json');
|
|
341
|
+
if (/"newProjectDefaults"\s*:\s*true(?:\s*[,}]|$)/.test(read(settingsFile))) {
|
|
342
|
+
runNode(path.join(hookDir, 'runtime-preferences.mjs'), ['--seed-project'], {
|
|
343
|
+
cwd, env, stdio: 'ignore', timeout: 2000,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const nightly = path.join(env.CLAUDE_PROJECT_DIR || cwd, '.ruvnet-brain', 'nightly-failure.json');
|
|
348
|
+
if (exists(nightly)) {
|
|
349
|
+
emit('[RuvNet Brain โ NIGHTLY FAILED, self-heal retry also failed, this is now yours to fix]');
|
|
350
|
+
emit('Before anything else this session: the ruvnet-brain nightly publish failed twice last run (immediate');
|
|
351
|
+
emit(`attempt + a 3-minute retry) and could not self-heal. Read ${nightly} for the exact tag/error,`);
|
|
352
|
+
emit('then logs/nightly.log in that repo for full context. Diagnose and fix it now โ do not wait to be');
|
|
353
|
+
emit('asked, this is a standing directive. Once genuinely fixed and verified (a real release tag advances),');
|
|
354
|
+
emit('delete the marker file.');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const state = health(home, brain.off);
|
|
358
|
+
if (state.problem) {
|
|
359
|
+
emit('๐จ [RuvNet Brain โ HEALTH ALARM: THE BRAIN IS DOWN โ SURFACE THIS FIRST] ๐จ');
|
|
360
|
+
emit(`Health check failed: ${state.problem}`);
|
|
361
|
+
emit('Open your VERY FIRST line with a loud plain alert naming that reason and that fix, and offer to run');
|
|
362
|
+
emit('it. Never answer rUv-stack questions from memory while it is down. Verify after: npx ruvnet-brain --doctor');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const consoleOffered = path.join(stateDir, '.console-offered');
|
|
366
|
+
if (!brain.off && !exists(consoleOffered)) {
|
|
367
|
+
write(consoleOffered, '');
|
|
368
|
+
emit('[RuvNet Brain โ FIRST LOAD: offer the Console once]');
|
|
369
|
+
emit(`Offer ONCE: "Want to see your whole RuvNet stack on one page?" โ installed parts, learned project knowledge and reversible fixes, read-only until clicked; later it's ${consoleInvoke}. On yes invoke ${consoleInvoke}; on no, don't re-offer.`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
surfaceIssues(stateDir, emit, now);
|
|
373
|
+
surfaceSignals({ env, cwd, stateDir, hookDir, emit, now });
|
|
374
|
+
|
|
375
|
+
const routerProfile = path.join(home, '.claude', 'model-router', 'profile.json');
|
|
376
|
+
const routerNudge = path.join(stateDir, '.router-profile-nudged');
|
|
377
|
+
if (!brain.off && !exists(routerProfile) && !exists(routerNudge)) {
|
|
378
|
+
write(routerNudge, '');
|
|
379
|
+
emit('[RuvNet Brain โ MetaHarness routing is available but not set up for THIS user yet]');
|
|
380
|
+
emit('Offer ONCE: route each task to the cheapest capable model, using THEIR subscriptions ($0) before billed models. Set it up?');
|
|
381
|
+
emit('On yes ask only (1) Claude sub, Pro or Max? (2) Codex CLI on a ChatGPT plan? then:');
|
|
382
|
+
const routerSetup = path.join(home, '.claude', 'model-router', 'bin', 'model-router-setup.mjs');
|
|
383
|
+
emit(exists(routerSetup)
|
|
384
|
+
? ` in ${path.dirname(routerSetup)}: node model-router-setup.mjs --detect-only; set profile.json's subscription fields from their answers (basis 'user-attested <date>'); node model-router-status.mjs and RELAY its 'Recommended path' block โ SEEING their zero-cost options IS the deliverable of yes.`
|
|
385
|
+
: " the router isn't installed here โ run: npx github:stuinfla/ruvnet-brain (it sets it up, asks these questions and shows the path)");
|
|
386
|
+
emit('On no or silence: drop it, never re-offer.');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
announceVersion({ running, off: brain.off, stateDir, consoleInvoke, emit });
|
|
390
|
+
const autoPref = path.join(stateDir, '.auto-update-pref');
|
|
391
|
+
if (!exists(autoPref)) {
|
|
392
|
+
emit('[RuvNet Brain โ one-time setup question]');
|
|
393
|
+
emit('Ask ONCE: should the brain auto-update in the background? (recommended; reversible). Then run ONE via Bash:');
|
|
394
|
+
emit(` mkdir -p '${stateDir}' && echo yes > '${autoPref}' (or echo no)`);
|
|
395
|
+
emit('No answer: ask next session, never twice.');
|
|
396
|
+
}
|
|
397
|
+
const spine = stableSpine({ env, hookDir, stateDir, home, pluginVersion: running, emit, now });
|
|
398
|
+
heartbeat({ env, hookDir, stateDir, home, running, ...spine, emit, now });
|
|
399
|
+
|
|
400
|
+
const star = path.join(stateDir, '.star-ask-shown');
|
|
401
|
+
if (!brain.off && exists(path.join(stateDir, '.grounded-once')) && !exists(star) && write(star, '')) {
|
|
402
|
+
emit('[RuvNet Brain โ one-time note. This will NEVER be shown again on this machine.]');
|
|
403
|
+
emit('At a natural CLOSING moment (after the work, never as an interruption), relay this line once and move on โ never repeated, never embellished, in this or any future session:');
|
|
404
|
+
emit(' "Finding this useful? Star github.com/stuinfla/ruvnet-brain or leave feedback โ it keeps the nightly updates coming."');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const bannerVersion = running || 'unknown';
|
|
408
|
+
if (pluginRoot.startsWith(path.join(home, '.claude', 'plugins') + path.sep)) {
|
|
409
|
+
if (running) write(path.join(stateDir, '.running-version'), `${running}\n`);
|
|
410
|
+
} else if (running) {
|
|
411
|
+
write(path.join(stateDir, '.dev-version'), `${running}\n`);
|
|
412
|
+
}
|
|
413
|
+
const source = json(path.join(home, '.cache', 'ruvnet-brain', 'kb', 'SOURCE.json'), {});
|
|
414
|
+
const kbVersion = typeof source?.releaseTag === 'string' ? source.releaseTag : '';
|
|
415
|
+
|
|
416
|
+
const grounding = json(path.join(stateDir, 'install-state.json'));
|
|
417
|
+
const asciiDrift = path.join(env.CLAUDE_PROJECT_DIR || cwd, 'scripts', 'ascii-drift.mjs');
|
|
418
|
+
if (!brain.off && exists(asciiDrift)) {
|
|
419
|
+
const result = runNode(asciiDrift, ['--quiet'], {
|
|
420
|
+
cwd, env, stdio: 'pipe', timeout: 1000,
|
|
421
|
+
});
|
|
422
|
+
const text = String(result?.stdout || '').replace(/\r\n/g, '\n').replace(/\n$/, '');
|
|
423
|
+
if (text) for (const line of text.split('\n')) emit(line);
|
|
424
|
+
}
|
|
425
|
+
if (grounding?.grounding && grounding.grounding !== 'proven') {
|
|
426
|
+
const when = grounding.at ? new Date(grounding.at).toISOString().slice(0, 16).replace('T', ' ') : 'an earlier run';
|
|
427
|
+
emit('[RuvNet Brain โ grounding not yet PROVEN on this machine (mention once, calmly, near the top)]');
|
|
428
|
+
emit(`The last check (${when} (${grounding.reason || 'no reason recorded'})) could not verify a real, resolvable citation โ often just a first-run model download or an offline machine, not necessarily a broken install. Say so once, plainly: the next real search_ruvnet confirms or clears it automatically, and \`npx ruvnet-brain --doctor\` shows the current verdict any time.`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (brain.off) {
|
|
432
|
+
const absent = state.absentByChoice ? '; no knowledge bundle on this machine โ disabled by choice, not broken' : '';
|
|
433
|
+
emit(`[RuvNet Brain โ brain OFF by your setting${brain.since ? ` (since ${brain.since})` : ''}${absent}. Do not mention it unless the user asks.]`);
|
|
434
|
+
} else {
|
|
435
|
+
if (state.problem) {
|
|
436
|
+
emit(`[RuvNet Brain v${bannerVersion} โ active this session, RETRIEVAL DOWN]`);
|
|
437
|
+
emit(`The plugin and its hooks are running, but the brain itself is broken (see the alarm above). Do not claim grounding works. If you mention it at all: "๐ง RuvNet Brain active (v${bannerVersion}) โ but its search is down right now."`);
|
|
438
|
+
} else {
|
|
439
|
+
emit(`[RuvNet Brain v${bannerVersion} โ active this session${updated ? ` ยท updated ${updated}` : ''}${kbVersion ? ` ยท knowledge bundle ${kbVersion}` : ''}]`);
|
|
440
|
+
emit('USER-LEVEL: one brain (~/.cache/ruvnet-brain/kb) shared by every project and window here โ nothing to reinstall per project. search_ruvnet and the grounding hooks are live now.');
|
|
441
|
+
emit(`Open your FIRST response with ONE short, warm confirmation in your own words (2-3 lines, then move on; never repeat it this session). It must say "๐ง RuvNet Brain active (v${bannerVersion}${kbVersion ? `, brain ${kbVersion}` : ''})" โ that version, in parentheses, always โ and convey: it grounds rUv's stack (RVF, Ruflo, AgentDB, SPARC, agentic-flowโฆ) in his real source rather than guessing; npx github:stuinfla/ruvnet-brain --doctor checks it; ${consoleInvoke} opens a visual settings page.`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const claudeJson = read(path.join(home, '.claude.json'));
|
|
445
|
+
if (commandExists('ruflo', env, platform) || /"ruflo"/.test(claudeJson)
|
|
446
|
+
|| exists(path.join(cwd, '.claude-flow')) || exists(path.join(cwd, '.swarm'))) {
|
|
447
|
+
emit('[RuvNet Brain โ token intelligence + QE, mention once]');
|
|
448
|
+
emit('Right after the confidence line, ONE line, never repeated this session: token-smart routing, a QE test fleet and a 0-100 repo scorecard are available โ say "do this cheaper", "QE this", "score my harness", "score this repo", /brain-build or /brain-prompt. Scoring is free; the self-improvement loop and cheap-model routing need an OPENROUTER_API_KEY.');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const playbook = `${hookDir}${path.sep}..${path.sep}skills${path.sep}ruvnet-brain${path.sep}PLAYBOOK.md`;
|
|
452
|
+
emit('[RuvNet Brain โ standing build playbook for this session (referenced by later turns as THE PLAYBOOK)]');
|
|
453
|
+
emit(`Full text: ${playbook} โ read it before your first build response this session. Condensed:`);
|
|
454
|
+
emit('Every build/change request: take the wheel. FIRST, silently โ read the files this touches in THEIR repo; search_ruvnet what the feature technically DOES; check project memory.');
|
|
455
|
+
emit('โ NO SILENT SUBSTITUTION (#1 trust-killer): never hand-roll, or aim a generic Task subagent at, work a RuvNet tool owns โ QE=agentic-qe, swarms=ruflo, routing=agentic-flow, vectors=RuVector, memory=AgentDB, red/blue=@metaharness/redblue. Use the real one; if absent offer the exact install; if unusable say so out loud, every time. Never give your own code its name.');
|
|
456
|
+
emit('Beats A-D are in that file, in full. In short: A RESPOND in one voice (hear them; THE ATTACK as one lettered plan over their real files; why it holds; what you checked; "Build it now?") ยท B ON A YES EXECUTE END-TO-END (SPARC with a QA gate per phase, DDD, ADRs, PARALLEL Ruflo swarm work, AgentDB persistence, frontend-design + real image generation, a PROVEN result scored to >=98, ONE ask for a missing API key) ยท C TAKE OVER what you do well ยท D keep them oriented. RUN THE PROCESS.');
|
|
457
|
+
}
|
|
458
|
+
} catch (error) {
|
|
459
|
+
if (env.RUVNET_SESSION_TRACE === '1') stderr.write(`SESSION_TRACE native-fail-open ${error?.message || error}\n`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const output = lines.length ? `${lines.join('\n')}\n` : '';
|
|
463
|
+
meter({ env, cwd, stateDir, output });
|
|
464
|
+
stdout.write(output);
|
|
465
|
+
trace('body-finished');
|
|
466
|
+
return { ok: true, outputBytes: Buffer.byteLength(output, 'utf8'), platform };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const direct = process.argv[1]
|
|
470
|
+
&& path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
|
|
471
|
+
if (direct) {
|
|
472
|
+
runSessionStart().catch((error) => {
|
|
473
|
+
if (process.env.RUVNET_SESSION_TRACE === '1') {
|
|
474
|
+
process.stderr.write(`SESSION_TRACE native-fail-open ${error?.message || error}\n`);
|
|
475
|
+
}
|
|
476
|
+
}).finally(() => { process.exitCode = 0; });
|
|
477
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# POSIX compatibility launcher. SessionStart has one behavioral authority on every host:
|
|
3
|
+
# session-start-core.mjs. Fail open because SessionStart is advisory and must never block a session.
|
|
4
|
+
set +e
|
|
5
|
+
HOOK_DIR="${0%/*}"
|
|
6
|
+
if [ "$HOOK_DIR" = "$0" ]; then
|
|
7
|
+
case "$0" in
|
|
8
|
+
*\\*) HOOK_DIR="${0%\\*}" ;;
|
|
9
|
+
*) HOOK_DIR="." ;;
|
|
10
|
+
esac
|
|
11
|
+
fi
|
|
12
|
+
node "$HOOK_DIR/session-start-core.mjs" || true
|
|
13
|
+
exit 0
|