ruvnet-brain 4.0.1 → 4.0.4
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 +1 -0
- package/README.md +4 -4
- package/bin/install.mjs +303 -24
- 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/keys/ruvnet-brain-signing.pub.pem +3 -0
- package/package.json +8 -22
- package/plugin/.claude-plugin/marketplace.json +1 -0
- package/plugin/.claude-plugin/plugin.json +2 -3
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/commands/brain-console.md +2 -2
- package/plugin/commands/configure.md +3 -2
- package/plugin/commands/rvbc.md +4 -3
- package/plugin/commands/rvcb.md +2 -2
- package/plugin/commands/whats-new.md +6 -6
- package/plugin/docs/RELEASE-NOTES-4.0.md +88 -0
- package/plugin/hooks/hooks.json +1 -2
- package/plugin/mcp/managed-cli-interface.mjs +47 -4
- package/plugin/mcp/server.mjs +90 -32
- package/plugin/scripts/detach.mjs +14 -0
- package/plugin/scripts/first-session-worker.mjs +38 -0
- package/plugin/scripts/ground-ruvnet.sh +16 -6
- package/plugin/scripts/hook-shim.mjs +34 -29
- package/plugin/scripts/learn-capture.sh +22 -3
- package/plugin/scripts/learn-flush.mjs +21 -4
- package/plugin/scripts/runtime-preferences.mjs +269 -0
- package/plugin/scripts/session-start-core.mjs +503 -0
- package/plugin/scripts/session-start.sh +3 -858
- package/plugin/scripts/whats-new.mjs +42 -0
- package/plugin/skills/brain-console/SKILL.md +4 -2
- package/plugin/skills/release-proof/SKILL.md +98 -0
- package/plugin/skills/release-proof/agents/openai.yaml +4 -0
- package/plugin/skills/release-proof/references/receipt-contract.md +44 -0
- package/plugin/skills/release-proof/scripts/release-proof.mjs +286 -0
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +5 -1
- package/plugin/skills/ruvnet-brain/SKILL.md +22 -7
- package/plugin/skills/rvbc/SKILL.md +9 -6
- package/plugin/skills/whats-new/SKILL.md +4 -4
- 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 +531 -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/fix-workstream.mjs +291 -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 +237 -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 +558 -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 +326 -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 +172 -0
- package/scripts/notify.sh +12 -0
- package/scripts/npx-witness.sh +56 -0
- package/scripts/onboarding-console.mjs +2922 -0
- package/scripts/private-fence.mjs +69 -0
- package/scripts/proactivity-metrics.mjs +118 -0
- package/scripts/proof-questions.json +56 -0
- package/scripts/protected-release-invocation.mjs +76 -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/publication-receipt.mjs +307 -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-authority.mjs +93 -0
- package/scripts/release-proof.mjs +9 -0
- package/scripts/release-vector.mjs +281 -0
- package/scripts/release.mjs +439 -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 +296 -0
- package/scripts/selfcheck.mjs +7 -1
- package/scripts/sign-bundle.mjs +69 -0
- package/scripts/signal-watch.mjs +171 -0
- package/scripts/stabilization-receipt.mjs +108 -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 +867 -0
- package/plugin/scripts/finalize-token-meter.mjs +0 -25
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# 🧠 RuvNet Brain
|
|
6
6
|
|
|
7
|
-
### 🧠 RuvNet Brain — [](https://github.com/stuinfla/ruvnet-brain/blob/main/plugin/.claude-plugin/plugin.json)
|
|
8
8
|
|
|
9
9
|
**A portable, source-grounded brain over Reuven Cohen's (rUv's) RuvNet stack — delivered as a Claude Code plugin that makes Claude _use_ the stack instead of fighting it.**
|
|
10
10
|
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
[](https://isovision.ai/ruvnet-brain/)
|
|
31
31
|
[](LICENSE)
|
|
32
32
|
[](#testing--proof)
|
|
33
|
-
[](#testing--proof)
|
|
34
34
|
|
|
35
35
|
> **One Brain generation everywhere.** npm, the GitHub tag/release, bundle manifests, source metadata, and checksum-bound RVF generations must share the same product version. Headline claims are regenerated and checked by the claims ledger (`scripts/claims-verify.mjs`); other numbers below are hand-stamped and dated:
|
|
36
36
|
> - **`plugin`** (badge above) — the Claude Code plugin itself: SKILL.md, the grounding hooks, the MCP server. Read live from [`plugin/.claude-plugin/plugin.json`](plugin/.claude-plugin/plugin.json). Updates often — this is where behavior fixes land.
|
|
@@ -485,7 +485,7 @@ node plugin/test/run-tests.mjs # full plugin QA over real JSO
|
|
|
485
485
|
| **L4 "orchestrate"** | **downgraded — measures speech, not obedience** | L4 asserts the hook's own injected prose contains required words (`must: ['take the wheel','SPARC','swarm',…]`). That proves **the brain spoke**. It cannot fail when the advice is read and ignored — which is the failure this product exists to prevent. Counterfactual replay against a brain-off control (ADR-058 §D4) is what will earn this row back |
|
|
486
486
|
| **Plugin QA** | **60 / 60** | manifests, hook firing, MCP `initialize`/`tools/list`, capability battery |
|
|
487
487
|
| **Clean-room install** | **3 / 3** | download the published bundle fresh → unzip → query → grounded, cited answers |
|
|
488
|
-
| **Unit tests** | **2,327 passing, 169 todo** ·
|
|
488
|
+
| **Unit tests** | **2,327 passing, 169 todo** · 34% of ALL source covered | `npm run test:cov` regenerates both — the coverage floor fails CI if it slips (`claims:verify` re-derives the %, it is not a hand-typed badge). 34% is the honest number over every shipped file; the previous "75%" measured a hand-picked 8-file subset |
|
|
489
489
|
| **Grounding proof** | `npx ruvnet-brain --doctor` | asks a real question, then checks the cited path really exists in the on-disk store; a citation that doesn't resolve is reported as **NOT grounded** |
|
|
490
490
|
| **Held-out eval** | **grounded 100/100** · routed 63/80 | `npm run eval` — 120 frozen, hash-pinned questions across 5 strata, never used for tuning, graded on ground truth, never by a model |
|
|
491
491
|
|
|
@@ -522,7 +522,7 @@ node forge-ask-all.mjs --dir . --q "How does RuVector implement HNSW vector sear
|
|
|
522
522
|
|
|
523
523
|
This project versions in the open (see the live badge up top for the exact plugin version; the downloadable knowledge bundle is a separate track) — we don't claim “done,” “complete,” or “zero hallucinations.” Where it stands:
|
|
524
524
|
|
|
525
|
-
- ✅ **The grounding brain is real and proven** —
|
|
525
|
+
- ✅ **The grounding brain is real and proven** — 63 public stores · 153,369 public source chunks (70 built stores incl. private), dual embeddings, cross-encoder rerank, plugin (MCP tool + enforcement hook + skill), all re-runnable.
|
|
526
526
|
- ✅ **Code-level depth** — the code-rich repos are indexed to full function bodies; “how is it implemented?” returns the implementation. Verified in the shipped bundle (clean-room 3/3).
|
|
527
527
|
- ✅ **Routing holds** — named 47/48, described 26/28, scenario 7/8; behavioral L1–L3 all pass (**L4 downgraded — it measures that the brain spoke, not that anything listened**); private stores fenced out of the public bundle (zero-leak verified).
|
|
528
528
|
- ⚠️ **Two routing residuals** (above) — surfaced, not hidden.
|
package/bin/install.mjs
CHANGED
|
@@ -95,6 +95,7 @@ const FLAG_ENABLE_SPEND_GUARD = argv.includes('--enable-spend-guard');
|
|
|
95
95
|
const FLAG_DISABLE_SPEND_GUARD = argv.includes('--disable-spend-guard'); // the missing undo
|
|
96
96
|
const FLAG_UNINSTALL = argv.includes('--uninstall'); // reverse everything, in one command
|
|
97
97
|
const FLAG_WHAT_CHANGED = argv.includes('--what-changed'); // show our footprint on this machine
|
|
98
|
+
const FLAG_WHATS_NEW = argv.includes('--whats-new'); // show curated major-release highlights
|
|
98
99
|
// ── onboarding-experience flags (all optional; every offer is safe to decline) ──
|
|
99
100
|
const FLAG_YES = argv.includes('--yes') || argv.includes('-y'); // accept every optional offer non-interactively
|
|
100
101
|
const FLAG_PLAN = argv.includes('--plan') || argv.includes('--dry-run'); // show the interactive checklist, then exit — install NOTHING
|
|
@@ -598,6 +599,137 @@ function installReader(cacheDir) {
|
|
|
598
599
|
ok('reader installed');
|
|
599
600
|
}
|
|
600
601
|
|
|
602
|
+
// The Console must survive the temporary npm/npx extraction directory. Before 4.0.2 its skills
|
|
603
|
+
// searched a developer checkout, so `/rvbc` worked for maintainers and failed for clean users even
|
|
604
|
+
// though installation was green. Persist the exact runtime shipped by the installer underneath the
|
|
605
|
+
// installed KB: Node then resolves optional reader dependencies from `<cacheDir>/node_modules`, and
|
|
606
|
+
// both Claude Code and Codex get one stable path with no source clone or second package install.
|
|
607
|
+
export function beginConsoleRuntimeTransaction(cacheDir, sourceRoot = REPO_ROOT) {
|
|
608
|
+
const runtime = path.join(cacheDir, '.console-runtime');
|
|
609
|
+
const staged = `${runtime}.tmp-${process.pid}`;
|
|
610
|
+
const prior = `${runtime}.prior-${process.pid}`;
|
|
611
|
+
fs.rmSync(staged, { recursive: true, force: true });
|
|
612
|
+
fs.rmSync(prior, { recursive: true, force: true });
|
|
613
|
+
fs.mkdirSync(staged, { recursive: true, mode: 0o700 });
|
|
614
|
+
|
|
615
|
+
const required = [
|
|
616
|
+
['console', 'console'],
|
|
617
|
+
['scripts', 'scripts'],
|
|
618
|
+
['plugin/scripts', 'plugin/scripts'],
|
|
619
|
+
['kb/brain-profile.mjs', 'kb/brain-profile.mjs'],
|
|
620
|
+
['bin/install.mjs', 'bin/install.mjs'],
|
|
621
|
+
['package.json', 'package.json'],
|
|
622
|
+
];
|
|
623
|
+
for (const [from, to] of required) {
|
|
624
|
+
const source = path.join(sourceRoot, from);
|
|
625
|
+
if (!fs.existsSync(source)) {
|
|
626
|
+
fs.rmSync(staged, { recursive: true, force: true });
|
|
627
|
+
throw new Error(`console runtime is incomplete: missing ${from}`);
|
|
628
|
+
}
|
|
629
|
+
const target = path.join(staged, to);
|
|
630
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
631
|
+
fs.cpSync(source, target, { recursive: true, force: true, preserveTimestamps: true });
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
let packageManifest;
|
|
635
|
+
try {
|
|
636
|
+
packageManifest = JSON.parse(fs.readFileSync(path.join(staged, 'package.json'), 'utf8'));
|
|
637
|
+
} catch (error) {
|
|
638
|
+
fs.rmSync(staged, { recursive: true, force: true });
|
|
639
|
+
throw new Error(`console runtime identity is invalid: ${error.message}`);
|
|
640
|
+
}
|
|
641
|
+
if (!packageManifest.version || packageManifest.version !== PACKAGE_VERSION) {
|
|
642
|
+
fs.rmSync(staged, { recursive: true, force: true });
|
|
643
|
+
throw new Error(`console runtime version ${packageManifest.version || '(missing)'} does not match candidate ${PACKAGE_VERSION}`);
|
|
644
|
+
}
|
|
645
|
+
const stagedEntry = path.join(staged, 'scripts', 'onboarding-console.mjs');
|
|
646
|
+
for (const syntaxTarget of [stagedEntry, path.join(staged, 'bin', 'install.mjs')]) {
|
|
647
|
+
const checked = spawnSync(process.execPath, ['--check', syntaxTarget], { encoding: 'utf8' });
|
|
648
|
+
if (checked.error || checked.status !== 0) {
|
|
649
|
+
fs.rmSync(staged, { recursive: true, force: true });
|
|
650
|
+
throw new Error(`console runtime failed syntax verification: ${path.relative(staged, syntaxTarget)}`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const identity = {
|
|
654
|
+
product: 'ruvnet-brain-console-runtime',
|
|
655
|
+
schema: 1,
|
|
656
|
+
apiContract: 1,
|
|
657
|
+
runtimeVersion: packageManifest.version,
|
|
658
|
+
entrypoint: 'scripts/onboarding-console.mjs',
|
|
659
|
+
sourceSha256: crypto.createHash('sha256').update(fs.readFileSync(stagedEntry)).digest('hex'),
|
|
660
|
+
};
|
|
661
|
+
fs.writeFileSync(path.join(staged, 'runtime-identity.json'), `${JSON.stringify(identity, null, 2)}\n`, { mode: 0o600 });
|
|
662
|
+
|
|
663
|
+
let state = 'staged';
|
|
664
|
+
const entry = path.join(runtime, identity.entrypoint);
|
|
665
|
+
return {
|
|
666
|
+
identity,
|
|
667
|
+
entry,
|
|
668
|
+
activate() {
|
|
669
|
+
if (state !== 'staged') throw new Error(`console runtime transaction cannot activate from ${state}`);
|
|
670
|
+
if (fs.existsSync(runtime)) fs.renameSync(runtime, prior);
|
|
671
|
+
try {
|
|
672
|
+
fs.renameSync(staged, runtime);
|
|
673
|
+
state = 'active';
|
|
674
|
+
return entry;
|
|
675
|
+
} catch (error) {
|
|
676
|
+
if (fs.existsSync(prior) && !fs.existsSync(runtime)) fs.renameSync(prior, runtime);
|
|
677
|
+
fs.rmSync(staged, { recursive: true, force: true });
|
|
678
|
+
state = 'rolled-back';
|
|
679
|
+
throw error;
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
commit() {
|
|
683
|
+
if (state !== 'active') throw new Error(`console runtime transaction cannot commit from ${state}`);
|
|
684
|
+
fs.rmSync(prior, { recursive: true, force: true });
|
|
685
|
+
state = 'committed';
|
|
686
|
+
return entry;
|
|
687
|
+
},
|
|
688
|
+
rollback() {
|
|
689
|
+
if (state === 'staged') fs.rmSync(staged, { recursive: true, force: true });
|
|
690
|
+
if (state === 'active') {
|
|
691
|
+
fs.rmSync(runtime, { recursive: true, force: true });
|
|
692
|
+
if (fs.existsSync(prior)) fs.renameSync(prior, runtime);
|
|
693
|
+
}
|
|
694
|
+
fs.rmSync(staged, { recursive: true, force: true });
|
|
695
|
+
fs.rmSync(prior, { recursive: true, force: true });
|
|
696
|
+
state = 'rolled-back';
|
|
697
|
+
},
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
export function installConsoleRuntime(cacheDir, sourceRoot = REPO_ROOT) {
|
|
702
|
+
const transaction = beginConsoleRuntimeTransaction(cacheDir, sourceRoot);
|
|
703
|
+
try {
|
|
704
|
+
transaction.activate();
|
|
705
|
+
return transaction.commit();
|
|
706
|
+
} catch (error) {
|
|
707
|
+
transaction.rollback();
|
|
708
|
+
throw error;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
export function consoleRestartState(identity, {
|
|
713
|
+
receiptDir = path.join(process.env.RUVNET_BRAIN_HOME || path.join(os.homedir(), '.cache', 'ruvnet-brain'), 'console-instances'),
|
|
714
|
+
} = {}) {
|
|
715
|
+
let receipts = [];
|
|
716
|
+
try {
|
|
717
|
+
receipts = fs.readdirSync(receiptDir)
|
|
718
|
+
.filter((name) => name.endsWith('.json'))
|
|
719
|
+
.map((name) => {
|
|
720
|
+
try { return JSON.parse(fs.readFileSync(path.join(receiptDir, name), 'utf8')); }
|
|
721
|
+
catch { return null; }
|
|
722
|
+
})
|
|
723
|
+
.filter((receipt) => receipt?.product === 'ruvnet-brain-console' && receipt.schema === 1);
|
|
724
|
+
} catch { /* no running Console receipts is the ordinary ready state */ }
|
|
725
|
+
const staleInstances = receipts.filter((receipt) => receipt.sourceSha256 !== identity.sourceSha256).length;
|
|
726
|
+
return {
|
|
727
|
+
state: staleInstances > 0 ? 'pending-console-restart' : 'ready',
|
|
728
|
+
instanceReceipts: receipts.length,
|
|
729
|
+
staleInstances,
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
601
733
|
// ── plugin presence: the ONLY reliable proof the slash commands will exist ───────────────────────
|
|
602
734
|
// Reported by a user on 3.4.21-dev whose install was otherwise healthy: `/rvbc` returned
|
|
603
735
|
// "Unknown command: /rvbc. Did you mean /rvf?". search_ruvnet worked, the KB was current — the
|
|
@@ -753,6 +885,7 @@ function codexManagedBlock(serverPath) {
|
|
|
753
885
|
'[mcp_servers.ruvnet-brain]',
|
|
754
886
|
'command = "node"',
|
|
755
887
|
`args = [${JSON.stringify(serverPath)}]`,
|
|
888
|
+
'startup_timeout_sec = 30',
|
|
756
889
|
CODEX_BLOCK_END,
|
|
757
890
|
].join('\n');
|
|
758
891
|
}
|
|
@@ -810,7 +943,11 @@ function removeCodexWiring() {
|
|
|
810
943
|
// What the doctor asserts from disk — never from the fact that we once ran. "Wired" means our entry
|
|
811
944
|
// is in the config AND the server.mjs it points at is really there, because a registration pointing
|
|
812
945
|
// at a deleted file is worse than no registration: Codex fails at spawn time with nothing to read.
|
|
813
|
-
export function codexStatus({
|
|
946
|
+
export function codexStatus({
|
|
947
|
+
configPath = codexConfigPath(),
|
|
948
|
+
codexDir = codexHomeDir(),
|
|
949
|
+
brainHome = path.join(path.dirname(codexDir), '.cache', 'ruvnet-brain'),
|
|
950
|
+
} = {}) {
|
|
814
951
|
let host = false;
|
|
815
952
|
try { host = fs.existsSync(codexDir); } catch { /* unreadable — treat as absent */ }
|
|
816
953
|
if (!host) return { host: false, wired: false, serverPath: null, serverExists: false };
|
|
@@ -821,7 +958,63 @@ export function codexStatus({ configPath = codexConfigPath(), codexDir = codexHo
|
|
|
821
958
|
try { serverPath = m ? JSON.parse(m[1]) : null; } catch { /* malformed entry — treat as absent */ }
|
|
822
959
|
let serverExists = false;
|
|
823
960
|
try { serverExists = serverPath ? fs.existsSync(serverPath) : false; } catch { /* unreadable */ }
|
|
824
|
-
|
|
961
|
+
const section = m ? text.slice(m.index).split(/\n[ \t]*\[/, 1)[0] : '';
|
|
962
|
+
const timeoutMatch = /(?:^|\n)[ \t]*startup_timeout_sec\s*=\s*(\d+)/.exec(section);
|
|
963
|
+
const startupTimeoutSec = timeoutMatch ? Number(timeoutMatch[1]) : null;
|
|
964
|
+
const readinessReceipt = (() => {
|
|
965
|
+
try { return JSON.parse(fs.readFileSync(path.join(brainHome, 'mcp-readiness.json'), 'utf8')); }
|
|
966
|
+
catch { return null; }
|
|
967
|
+
})();
|
|
968
|
+
let live = false;
|
|
969
|
+
if (readinessReceipt?.state === 'ready'
|
|
970
|
+
&& Number.isInteger(readinessReceipt.pid)
|
|
971
|
+
&& Number.isInteger(readinessReceipt.workerPid)) {
|
|
972
|
+
try {
|
|
973
|
+
process.kill(readinessReceipt.pid, 0);
|
|
974
|
+
process.kill(readinessReceipt.workerPid, 0);
|
|
975
|
+
live = true;
|
|
976
|
+
} catch { /* stale process or worker receipt */ }
|
|
977
|
+
}
|
|
978
|
+
const readiness = live ? 'ready' : readinessReceipt?.state === 'degraded' ? 'degraded' : 'registered';
|
|
979
|
+
return {
|
|
980
|
+
host: true,
|
|
981
|
+
wired: Boolean(serverPath) && serverExists,
|
|
982
|
+
serverPath,
|
|
983
|
+
serverExists,
|
|
984
|
+
startupTimeoutSec,
|
|
985
|
+
readiness,
|
|
986
|
+
live,
|
|
987
|
+
readinessReceipt,
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
export function codexMcpGuidance(status) {
|
|
992
|
+
if (!status?.wired) {
|
|
993
|
+
return { healthy: false, blocking: true, summary: 'Codex MCP is not registered to a usable server.', detail: null };
|
|
994
|
+
}
|
|
995
|
+
if (status.live && status.readiness === 'ready') {
|
|
996
|
+
return {
|
|
997
|
+
healthy: true,
|
|
998
|
+
blocking: false,
|
|
999
|
+
summary: `Codex MCP ready and live; discovery uses a ${status.startupTimeoutSec ?? 'configured'}s startup deadline.`,
|
|
1000
|
+
detail: 'A running MCP shell completed worker initialize and warmup.',
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
if (status.readiness === 'degraded') {
|
|
1004
|
+
const receipt = status.readinessReceipt || {};
|
|
1005
|
+
return {
|
|
1006
|
+
healthy: false,
|
|
1007
|
+
blocking: true,
|
|
1008
|
+
summary: 'Codex MCP is registered but degraded.',
|
|
1009
|
+
detail: `${receipt.phase || 'startup'}: ${receipt.error || 'worker readiness failed'}`,
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
return {
|
|
1013
|
+
healthy: false,
|
|
1014
|
+
blocking: false,
|
|
1015
|
+
summary: `Codex MCP is registered with a ${status.startupTimeoutSec ?? 'missing'}s startup deadline; live readiness is not yet proven.`,
|
|
1016
|
+
detail: 'The first real search establishes worker readiness without removing search_ruvnet from discovery.',
|
|
1017
|
+
};
|
|
825
1018
|
}
|
|
826
1019
|
|
|
827
1020
|
// Atomic file replacement: produce the new bytes BESIDE the target, then rename() over it. Against
|
|
@@ -856,6 +1049,7 @@ export function wireCodexHost({
|
|
|
856
1049
|
serverDir = codexServerDir(),
|
|
857
1050
|
source = path.join(__dirname, '..', 'plugin', 'mcp', 'server.mjs'),
|
|
858
1051
|
hookWrapperSource = path.join(__dirname, '..', 'plugin', 'scripts', 'codex-hook-wrapper.mjs'),
|
|
1052
|
+
runtimePreferencesSource = path.join(__dirname, '..', 'plugin', 'scripts', 'runtime-preferences.mjs'),
|
|
859
1053
|
hookWrapperPath = codexHookWrapperPath(codexDir),
|
|
860
1054
|
announce = true,
|
|
861
1055
|
} = {}) {
|
|
@@ -876,14 +1070,21 @@ export function wireCodexHost({
|
|
|
876
1070
|
if (announce) warn('MCP structured-interface module missing from this bundle — Codex left untouched (non-fatal)');
|
|
877
1071
|
return { host: true, action: 'no-source' };
|
|
878
1072
|
}
|
|
1073
|
+
if (!fs.existsSync(runtimePreferencesSource)) {
|
|
1074
|
+
if (announce) warn('MCP runtime-preferences module missing from this bundle — Codex left untouched (non-fatal)');
|
|
1075
|
+
return { host: true, action: 'no-source' };
|
|
1076
|
+
}
|
|
879
1077
|
const serverPath = path.join(serverDir, 'server.mjs');
|
|
880
1078
|
const managedCliPath = path.join(serverDir, 'managed-cli-interface.mjs');
|
|
1079
|
+
const runtimePreferencesPath = path.join(path.dirname(serverDir), 'scripts', 'runtime-preferences.mjs');
|
|
881
1080
|
fs.mkdirSync(serverDir, { recursive: true });
|
|
882
1081
|
// Write-beside-then-rename, both here and for the config below (issue #43): an interrupted plain
|
|
883
1082
|
// copy leaves a TORN server.mjs at the exact path a prior install's config already points at, so
|
|
884
1083
|
// Codex spawns half a file. rename() over the target is atomic; a failure leaves the old bytes.
|
|
885
1084
|
// Copy the dependency first. If the later server swap fails, the previously registered server
|
|
886
1085
|
// remains byte-intact and continues to import a backward-compatible module at the same path.
|
|
1086
|
+
fs.mkdirSync(path.dirname(runtimePreferencesPath), { recursive: true });
|
|
1087
|
+
atomicReplace(runtimePreferencesPath, (tmp) => fs.copyFileSync(runtimePreferencesSource, tmp));
|
|
887
1088
|
atomicReplace(managedCliPath, (tmp) => fs.copyFileSync(managedCliSource, tmp));
|
|
888
1089
|
atomicReplace(serverPath, (tmp) => fs.copyFileSync(source, tmp));
|
|
889
1090
|
if (fs.existsSync(hookWrapperSource)) {
|
|
@@ -899,7 +1100,7 @@ export function wireCodexHost({
|
|
|
899
1100
|
ok('Codex already declares ruvnet-brain in your own config — left exactly as you wrote it');
|
|
900
1101
|
info(` to hand it to us instead, delete that ${c.bold('[mcp_servers.ruvnet-brain]')} block and re-run this installer`);
|
|
901
1102
|
}
|
|
902
|
-
return { host: true, action, serverPath, managedCliPath, hookWrapperPath };
|
|
1103
|
+
return { host: true, action, serverPath, managedCliPath, runtimePreferencesPath, hookWrapperPath };
|
|
903
1104
|
}
|
|
904
1105
|
if (text !== before) {
|
|
905
1106
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
@@ -910,7 +1111,7 @@ export function wireCodexHost({
|
|
|
910
1111
|
info(` server: ${serverPath} ${c.dim('(persistent copy — the npx dir vanishes)')}`);
|
|
911
1112
|
info(` ${c.dim('only our marked block is written; every other section is byte-preserved')}`);
|
|
912
1113
|
}
|
|
913
|
-
return { host: true, action, serverPath, managedCliPath, hookWrapperPath, changed: text !== before };
|
|
1114
|
+
return { host: true, action, serverPath, managedCliPath, runtimePreferencesPath, hookWrapperPath, changed: text !== before };
|
|
914
1115
|
}
|
|
915
1116
|
|
|
916
1117
|
const CODEX_PLUGIN_ID = 'ruvnet-brain@ruvnet-brain';
|
|
@@ -1553,7 +1754,9 @@ async function doctor() {
|
|
|
1553
1754
|
if (env.ruflo) {
|
|
1554
1755
|
rufloOperational = probeRufloOperationalHealth();
|
|
1555
1756
|
if (rufloOperational.healthy) {
|
|
1556
|
-
ok(
|
|
1757
|
+
ok(rufloOperational.directMode
|
|
1758
|
+
? 'Ruflo direct mode ready — daemon/swarm is stopped by design; AgentDB remains CLI-backed'
|
|
1759
|
+
: 'Ruflo operational — runtime, memory, and learning signals agree');
|
|
1557
1760
|
} else {
|
|
1558
1761
|
warn('Ruflo CLI is present, but operational learning is DEGRADED — availability is not enforcement.');
|
|
1559
1762
|
if (rufloOperational.stopped) warn(' live status reports STOPPED / swarm not running');
|
|
@@ -1623,11 +1826,14 @@ async function doctor() {
|
|
|
1623
1826
|
// from disk (our entry present AND the server.mjs it names really there), never asserted from the
|
|
1624
1827
|
// fact that an install once ran.
|
|
1625
1828
|
const cx = codexStatus();
|
|
1829
|
+
let codexMcp = null;
|
|
1626
1830
|
let codexLifecycle = null;
|
|
1627
1831
|
if (!cx.host) {
|
|
1628
1832
|
console.log(` ${c.dim('Codex: no host detected (no ~/.codex) — nothing to wire.')}`);
|
|
1629
1833
|
} else if (cx.wired) {
|
|
1630
|
-
|
|
1834
|
+
codexMcp = codexMcpGuidance(cx);
|
|
1835
|
+
console.log(` ${codexMcp.healthy ? c.green('✓') : codexMcp.blocking ? c.yellow('!') : c.dim('○')} ${codexMcp.summary}`);
|
|
1836
|
+
if (codexMcp.detail) console.log(` ${codexMcp.detail}`);
|
|
1631
1837
|
codexLifecycle = await codexLifecycleStatus();
|
|
1632
1838
|
printCodexLifecycle(codexLifecycle);
|
|
1633
1839
|
} else {
|
|
@@ -1696,10 +1902,12 @@ async function doctor() {
|
|
|
1696
1902
|
&& !codexLifecycleGuidance(codexLifecycle).intentional,
|
|
1697
1903
|
);
|
|
1698
1904
|
const codexWiringFailed = Boolean(cx.host && !cx.wired);
|
|
1905
|
+
const codexReadinessFailed = Boolean(codexMcp?.blocking);
|
|
1699
1906
|
const failed = (hookResult ? hookResult.exitCode !== 0 : !allGreen)
|
|
1700
1907
|
|| groundingUnprovenPersisted
|
|
1701
1908
|
|| codexLifecycleFailed
|
|
1702
1909
|
|| codexWiringFailed
|
|
1910
|
+
|| codexReadinessFailed
|
|
1703
1911
|
|| Boolean(rufloOperational && !rufloOperational.healthy);
|
|
1704
1912
|
if (failed && !hookResult && !groundingUnprovenPersisted) {
|
|
1705
1913
|
console.log(` ${c.red('✗ FAILING')} — the warnings above are real. Re-run ${c.bold('npx ruvnet-brain')} to repair.`);
|
|
@@ -1963,32 +2171,61 @@ function missingUpdaterHelp(kbDir) {
|
|
|
1963
2171
|
console.error(` — the current bundle ships forge-update.mjs; then this command will work.`);
|
|
1964
2172
|
}
|
|
1965
2173
|
|
|
1966
|
-
function syncHostsAfterUpdate() {
|
|
2174
|
+
export function syncHostsAfterUpdate(cacheDir = resolvedKbDir(), {
|
|
2175
|
+
sourceRoot = REPO_ROOT,
|
|
2176
|
+
brainHome = process.env.RUVNET_BRAIN_HOME || path.join(os.homedir(), '.cache', 'ruvnet-brain'),
|
|
2177
|
+
consoleReceiptDir = path.join(brainHome, 'console-instances'),
|
|
2178
|
+
wireClaude = wirePlugin,
|
|
2179
|
+
wireCodexHost: detectCodexHost = wireCodexHost,
|
|
2180
|
+
wireCodexPlugin: installCodexPlugin = wireCodexPlugin,
|
|
2181
|
+
runStableSpine = (apply) => spawnSync(
|
|
2182
|
+
process.execPath,
|
|
2183
|
+
[apply, '--auto', '--expected-version', PACKAGE_VERSION],
|
|
2184
|
+
{ stdio: 'inherit', env: process.env },
|
|
2185
|
+
),
|
|
2186
|
+
} = {}) {
|
|
1967
2187
|
const results = {};
|
|
1968
|
-
|
|
1969
|
-
if (results.claude.host && !results.claude.wired) return { ok: false, results };
|
|
2188
|
+
let runtimeTransaction;
|
|
1970
2189
|
try {
|
|
1971
|
-
|
|
2190
|
+
runtimeTransaction = beginConsoleRuntimeTransaction(cacheDir, sourceRoot);
|
|
2191
|
+
} catch (error) {
|
|
2192
|
+
return { ok: false, results, error: `Console runtime staging failed: ${error.message}` };
|
|
2193
|
+
}
|
|
2194
|
+
const fail = (detail = {}) => {
|
|
2195
|
+
runtimeTransaction.rollback();
|
|
2196
|
+
return { ok: false, results, ...detail };
|
|
2197
|
+
};
|
|
2198
|
+
|
|
2199
|
+
try {
|
|
2200
|
+
results.claude = wireClaude === wirePlugin
|
|
2201
|
+
? wirePlugin({ expectedVersion: PACKAGE_VERSION, requireManaged: true })
|
|
2202
|
+
: wireClaude({ expectedVersion: PACKAGE_VERSION, requireManaged: true });
|
|
2203
|
+
if (results.claude.host && !results.claude.wired) return fail();
|
|
2204
|
+
results.codexHost = detectCodexHost();
|
|
1972
2205
|
if (results.codexHost.host) {
|
|
1973
|
-
results.codex =
|
|
2206
|
+
results.codex = installCodexPlugin({ expectedVersion: PACKAGE_VERSION });
|
|
1974
2207
|
if (!['unchanged', 'installed', 'updated', 'disabled'].includes(results.codex.action)) {
|
|
1975
|
-
return
|
|
2208
|
+
return fail();
|
|
1976
2209
|
}
|
|
1977
2210
|
}
|
|
1978
2211
|
} catch (error) {
|
|
1979
2212
|
results.codex = { action: 'failed', error: error?.message || String(error) };
|
|
1980
|
-
return
|
|
2213
|
+
return fail();
|
|
1981
2214
|
}
|
|
1982
2215
|
|
|
1983
|
-
const apply = path.join(
|
|
1984
|
-
if (!fs.existsSync(apply)) return {
|
|
1985
|
-
const applied =
|
|
2216
|
+
const apply = path.join(sourceRoot, 'plugin', 'scripts', 'update-apply.mjs');
|
|
2217
|
+
if (!fs.existsSync(apply)) return fail({ error: 'Stable Spine updater missing from package' });
|
|
2218
|
+
const applied = runStableSpine(apply);
|
|
1986
2219
|
const okApplied = !applied.error && applied.status === 0;
|
|
1987
2220
|
if (okApplied) {
|
|
1988
|
-
const
|
|
1989
|
-
const receiptPath = path.join(home, 'host-convergence.json');
|
|
2221
|
+
const receiptPath = path.join(brainHome, 'host-convergence.json');
|
|
1990
2222
|
try {
|
|
1991
|
-
|
|
2223
|
+
runtimeTransaction.activate();
|
|
2224
|
+
results.consoleRuntime = {
|
|
2225
|
+
...runtimeTransaction.identity,
|
|
2226
|
+
...consoleRestartState(runtimeTransaction.identity, { receiptDir: consoleReceiptDir }),
|
|
2227
|
+
};
|
|
2228
|
+
fs.mkdirSync(brainHome, { recursive: true });
|
|
1992
2229
|
const tmp = `${receiptPath}.tmp-${process.pid}`;
|
|
1993
2230
|
fs.writeFileSync(tmp, JSON.stringify({
|
|
1994
2231
|
desiredVersion: PACKAGE_VERSION,
|
|
@@ -1997,13 +2234,16 @@ function syncHostsAfterUpdate() {
|
|
|
1997
2234
|
claude: { state: results.claude.host ? 'ready' : 'absent', version: results.claude.version || null },
|
|
1998
2235
|
codex: { state: results.codex?.action === 'disabled' ? 'disabled' : (results.codexHost?.host ? 'ready' : 'absent'), version: results.codex?.version || null },
|
|
1999
2236
|
},
|
|
2237
|
+
consoleRuntime: results.consoleRuntime,
|
|
2000
2238
|
}, null, 2));
|
|
2001
2239
|
fs.renameSync(tmp, receiptPath);
|
|
2240
|
+
runtimeTransaction.commit();
|
|
2002
2241
|
} catch (error) {
|
|
2003
|
-
return {
|
|
2242
|
+
return fail({ applyStatus: applied.status, error: `Console runtime convergence failed: ${error.message}` });
|
|
2004
2243
|
}
|
|
2005
2244
|
}
|
|
2006
|
-
return {
|
|
2245
|
+
if (!okApplied) return fail({ applyStatus: applied.status, error: applied.error?.message || 'Stable Spine activation failed' });
|
|
2246
|
+
return { ok: true, results, applyStatus: applied.status };
|
|
2007
2247
|
}
|
|
2008
2248
|
|
|
2009
2249
|
function runUpdate() {
|
|
@@ -2048,7 +2288,7 @@ function runUpdate() {
|
|
|
2048
2288
|
}
|
|
2049
2289
|
if (updateStatus === 0) {
|
|
2050
2290
|
info(c.dim('\nsynchronizing every detected host to this exact published version…\n'));
|
|
2051
|
-
const convergence = syncHostsAfterUpdate();
|
|
2291
|
+
const convergence = syncHostsAfterUpdate(kbDir);
|
|
2052
2292
|
if (!convergence.ok) {
|
|
2053
2293
|
warn(`host synchronization is incomplete — runtime stays on the prior verified generation${convergence.error ? ` (${convergence.error})` : ''}`);
|
|
2054
2294
|
updateStatus = 1;
|
|
@@ -2421,6 +2661,17 @@ function printFootprint({ heading = 'What this put on your machine' } = {}) {
|
|
|
2421
2661
|
return items;
|
|
2422
2662
|
}
|
|
2423
2663
|
|
|
2664
|
+
function showWhatsNew() {
|
|
2665
|
+
const executable = path.join(REPO_ROOT, 'plugin', 'scripts', 'whats-new.mjs');
|
|
2666
|
+
const result = spawnSync(process.execPath, [executable], { stdio: 'inherit' });
|
|
2667
|
+
if (result.error) {
|
|
2668
|
+
console.error(`RuvNet Brain What's New failed: ${result.error.message}`);
|
|
2669
|
+
process.exitCode = 1;
|
|
2670
|
+
return;
|
|
2671
|
+
}
|
|
2672
|
+
process.exitCode = result.status === 0 ? 0 : 1;
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2424
2675
|
/**
|
|
2425
2676
|
* Surgically remove ONLY our block from CLAUDE.md, leaving every other line exactly as it was.
|
|
2426
2677
|
* Backed up and written atomically, same as when it was added — taking something away is at least
|
|
@@ -3070,8 +3321,15 @@ export function classifyRufloOperationalHealth({ status = '', memory = '', metri
|
|
|
3070
3321
|
&& /Total Routes\s*[|:]?\s*0\b/i.test(metrics)
|
|
3071
3322
|
&& /Total Executed\s*[|:]?\s*0\b/i.test(metrics);
|
|
3072
3323
|
const memoryContradiction = statusSaysNoMemory && memoryEntries > 0;
|
|
3324
|
+
// Brain 4 is explicitly zero-daemon: `ruflo memory store/search` open the per-project
|
|
3325
|
+
// AgentDB directly. A stopped orchestration daemon therefore makes daemon-owned status
|
|
3326
|
+
// and metrics non-authoritative; treating that intentional state as degraded made Doctor
|
|
3327
|
+
// exit 1 immediately after proving source-grounded retrieval worked. Keep the strict
|
|
3328
|
+
// contradiction/learning checks when a daemon is actually running.
|
|
3329
|
+
const directMode = stopped;
|
|
3073
3330
|
return {
|
|
3074
|
-
healthy:
|
|
3331
|
+
healthy: directMode || (!memoryContradiction && !zeroLearning),
|
|
3332
|
+
directMode,
|
|
3075
3333
|
stopped,
|
|
3076
3334
|
zeroLearning,
|
|
3077
3335
|
memoryContradiction,
|
|
@@ -3653,6 +3911,7 @@ Usage:
|
|
|
3653
3911
|
npx ruvnet-brain --disable-nightly Remove the nightly schedule (safe to run any time)
|
|
3654
3912
|
npx ruvnet-brain --what-changed Show exactly what RuvNet Brain has put on this machine,
|
|
3655
3913
|
with the undo command for each piece
|
|
3914
|
+
npx ruvnet-brain --whats-new Show the curated v3 → v4 capabilities and honest limits
|
|
3656
3915
|
npx ruvnet-brain --uninstall Remove all of it (bundle, LaunchAgents, and our CLAUDE.md
|
|
3657
3916
|
block only — your own CLAUDE.md content is preserved and backed up)
|
|
3658
3917
|
npx ruvnet-brain --enable-spend-guard Install the hourly runaway-agent spend alarm (alert-only)
|
|
@@ -3692,7 +3951,17 @@ It is safe to re-run at any time. After installing, restart Claude Code so the g
|
|
|
3692
3951
|
|
|
3693
3952
|
// ── main ─────────────────────────────────────────────────────────────────────────────────────────
|
|
3694
3953
|
(async () => {
|
|
3695
|
-
|
|
3954
|
+
// Importing an executable module for its exported policy helpers must never install software.
|
|
3955
|
+
// The old contract required every caller to remember RUVNET_BRAIN_IMPORT_ONLY=1; one missed flag
|
|
3956
|
+
// in a unit test ran a real install against the maintainer's HOME. Direct argv identity is the
|
|
3957
|
+
// authority boundary. The old IMPORT_ONLY flag may still be present in a long-lived test worker;
|
|
3958
|
+
// it must never suppress a genuinely direct CLI execution.
|
|
3959
|
+
const canonical = (value) => {
|
|
3960
|
+
try { return fs.realpathSync(value); } catch { return path.resolve(value); }
|
|
3961
|
+
};
|
|
3962
|
+
const invokedDirectly = Boolean(process.argv[1])
|
|
3963
|
+
&& canonical(process.argv[1]) === canonical(fileURLToPath(import.meta.url));
|
|
3964
|
+
if (!invokedDirectly) return;
|
|
3696
3965
|
if (FLAG_HELP) return showHelp();
|
|
3697
3966
|
// `process.exitCode`, not `return` — doctor()'s verdict is the whole point of running it in a
|
|
3698
3967
|
// script. A bare `return await doctor()` discarded the number, which is how "! Needs attention"
|
|
@@ -3711,6 +3980,7 @@ It is safe to re-run at any time. After installing, restart Claude Code so the g
|
|
|
3711
3980
|
if (FLAG_DISABLE_SPEND_GUARD) { disableSpendGuard(); return; }
|
|
3712
3981
|
if (FLAG_UNINSTALL) { uninstallAll(); return; }
|
|
3713
3982
|
if (FLAG_WHAT_CHANGED) { printBanner('what RuvNet Brain put on this machine'); printFootprint(); return; }
|
|
3983
|
+
if (FLAG_WHATS_NEW) { showWhatsNew(); return; }
|
|
3714
3984
|
|
|
3715
3985
|
printBanner('installer');
|
|
3716
3986
|
console.log(c.dim("I'll set up the brain for Claude Code and Codex, explaining each step as I go.\n"));
|
|
@@ -3856,6 +4126,15 @@ It is safe to re-run at any time. After installing, restart Claude Code so the g
|
|
|
3856
4126
|
}
|
|
3857
4127
|
|
|
3858
4128
|
installReader(cacheDir);
|
|
4129
|
+
try {
|
|
4130
|
+
const consoleEntry = installConsoleRuntime(cacheDir);
|
|
4131
|
+
ok(`Brain Console installed at ${consoleEntry}`);
|
|
4132
|
+
} catch (error) {
|
|
4133
|
+
die(
|
|
4134
|
+
`the Brain Console runtime could not be installed (${error.message})`,
|
|
4135
|
+
`The knowledge base is present, but /rvbc would be broken. Re-run the installer from a complete package.`,
|
|
4136
|
+
);
|
|
4137
|
+
}
|
|
3859
4138
|
const plugin = wirePlugin();
|
|
3860
4139
|
// Codex hosts got nothing before this (issue #42): shipped, never registered. Non-fatal like every
|
|
3861
4140
|
// other wiring step — a second host we cannot reach must never break the one we can.
|