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
package/bin/install.mjs
CHANGED
|
@@ -84,6 +84,7 @@ const FLAG_DEMO = argv.includes('--demo'); // guided, real (non-fabricated) walk
|
|
|
84
84
|
const FLAG_FEEDBACK = argv.includes('--feedback'); // prefill a GitHub Discussion (version + health, nothing private) and open it
|
|
85
85
|
// ── freshness flags — invoke/schedule the SELF-UPDATER the bundle already ships (kb/forge-update.mjs) ──
|
|
86
86
|
const FLAG_UPDATE = argv.includes('--update'); // one-shot: pull the latest Release bundle into the installed brain now
|
|
87
|
+
const FLAG_HOST_SYNC_ONLY = argv.includes('--host-sync-only'); // internal: repair host shells even when KB updater is absent
|
|
87
88
|
const FLAG_AUTO = argv.includes('--auto'); // with --update: also enroll in Evergreen auto-update, so it's never run by hand again
|
|
88
89
|
const FLAG_ENABLE_NIGHTLY = argv.includes('--enable-nightly'); // schedule that update nightly (macOS LaunchAgent)
|
|
89
90
|
const FLAG_DISABLE_NIGHTLY = argv.includes('--disable-nightly'); // remove the nightly schedule
|
|
@@ -94,6 +95,7 @@ const FLAG_ENABLE_SPEND_GUARD = argv.includes('--enable-spend-guard');
|
|
|
94
95
|
const FLAG_DISABLE_SPEND_GUARD = argv.includes('--disable-spend-guard'); // the missing undo
|
|
95
96
|
const FLAG_UNINSTALL = argv.includes('--uninstall'); // reverse everything, in one command
|
|
96
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
|
|
97
99
|
// ── onboarding-experience flags (all optional; every offer is safe to decline) ──
|
|
98
100
|
const FLAG_YES = argv.includes('--yes') || argv.includes('-y'); // accept every optional offer non-interactively
|
|
99
101
|
const FLAG_PLAN = argv.includes('--plan') || argv.includes('--dry-run'); // show the interactive checklist, then exit — install NOTHING
|
|
@@ -597,6 +599,50 @@ function installReader(cacheDir) {
|
|
|
597
599
|
ok('reader installed');
|
|
598
600
|
}
|
|
599
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 installConsoleRuntime(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
|
+
if (fs.existsSync(runtime)) fs.renameSync(runtime, prior);
|
|
635
|
+
try {
|
|
636
|
+
fs.renameSync(staged, runtime);
|
|
637
|
+
fs.rmSync(prior, { recursive: true, force: true });
|
|
638
|
+
} catch (error) {
|
|
639
|
+
if (fs.existsSync(prior) && !fs.existsSync(runtime)) fs.renameSync(prior, runtime);
|
|
640
|
+
fs.rmSync(staged, { recursive: true, force: true });
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
643
|
+
return path.join(runtime, 'scripts', 'onboarding-console.mjs');
|
|
644
|
+
}
|
|
645
|
+
|
|
600
646
|
// ── plugin presence: the ONLY reliable proof the slash commands will exist ───────────────────────
|
|
601
647
|
// Reported by a user on 3.4.21-dev whose install was otherwise healthy: `/rvbc` returned
|
|
602
648
|
// "Unknown command: /rvbc. Did you mean /rvf?". search_ruvnet worked, the KB was current — the
|
|
@@ -620,7 +666,40 @@ function pluginCommandsDir() {
|
|
|
620
666
|
}
|
|
621
667
|
|
|
622
668
|
// ── step: wire the Claude Code plugin ────────────────────────────────────────────────────────────
|
|
623
|
-
function
|
|
669
|
+
export function claudePluginStatus({ home = os.homedir() } = {}) {
|
|
670
|
+
const registry = path.join(home, '.claude', 'plugins', 'installed_plugins.json');
|
|
671
|
+
let sawUserRecord = false;
|
|
672
|
+
try {
|
|
673
|
+
const doc = JSON.parse(fs.readFileSync(registry, 'utf8'));
|
|
674
|
+
const rows = doc?.plugins?.['ruvnet-brain@ruvnet-brain'];
|
|
675
|
+
const row = Array.isArray(rows) ? rows.find((candidate) => candidate?.scope === 'user') : null;
|
|
676
|
+
if (!row?.installPath) return { managed: false, installed: false, version: null };
|
|
677
|
+
sawUserRecord = true;
|
|
678
|
+
const realHome = fs.realpathSync(home);
|
|
679
|
+
const cacheRoot = path.join(realHome, '.claude', 'plugins', 'cache', 'ruvnet-brain', 'ruvnet-brain');
|
|
680
|
+
const realInstall = fs.realpathSync(row.installPath);
|
|
681
|
+
if (realInstall !== cacheRoot && !realInstall.startsWith(`${cacheRoot}${path.sep}`)) {
|
|
682
|
+
return { managed: true, installed: false, version: null, error: 'user plugin installPath escapes the managed cache' };
|
|
683
|
+
}
|
|
684
|
+
const manifestPath = path.join(realInstall, '.claude-plugin', 'plugin.json');
|
|
685
|
+
const commandPath = path.join(realInstall, 'commands', 'rvbc.md');
|
|
686
|
+
if (!fs.lstatSync(manifestPath).isFile() || fs.lstatSync(manifestPath).isSymbolicLink()
|
|
687
|
+
|| !fs.lstatSync(commandPath).isFile() || fs.lstatSync(commandPath).isSymbolicLink()) {
|
|
688
|
+
return { managed: true, installed: false, version: null, error: 'managed payload files are not regular files' };
|
|
689
|
+
}
|
|
690
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
691
|
+
return {
|
|
692
|
+
managed: true,
|
|
693
|
+
installed: true,
|
|
694
|
+
version: manifest?.version || row.version || null,
|
|
695
|
+
installPath: realInstall,
|
|
696
|
+
};
|
|
697
|
+
} catch {
|
|
698
|
+
return { managed: sawUserRecord, installed: false, version: null };
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function wirePlugin({ expectedVersion = PACKAGE_VERSION, requireManaged = false } = {}) {
|
|
624
703
|
step(
|
|
625
704
|
'Wiring the Claude Code plugin',
|
|
626
705
|
'this registers search_ruvnet + the grounding hook so Claude uses the brain automatically',
|
|
@@ -636,37 +715,54 @@ function wirePlugin() {
|
|
|
636
715
|
info(` ${c.bold(manualMarketplace)}`);
|
|
637
716
|
info(` ${c.bold(manualInstall)}`);
|
|
638
717
|
info(`Then reopen Claude Code once. ${c.dim('Or simply open Claude Code and ask: “finish setting up the RuvNet Brain.”')}`);
|
|
639
|
-
return { wired: false, manualMarketplace, manualInstall };
|
|
718
|
+
return { host: false, wired: false, manualMarketplace, manualInstall };
|
|
640
719
|
}
|
|
641
720
|
|
|
642
|
-
const
|
|
721
|
+
const before = claudePluginStatus();
|
|
722
|
+
if (requireManaged && !before.managed) return { host: false, wired: false, action: 'unmanaged' };
|
|
723
|
+
const addedMarket = before.managed
|
|
724
|
+
? tryRun('claude', ['plugin', 'marketplace', 'update', 'ruvnet-brain'])
|
|
725
|
+
: tryRun('claude', ['plugin', 'marketplace', 'add', 'stuinfla/ruvnet-brain']);
|
|
643
726
|
// Deliberately NOT reassuring here. This used to say "it may already be added — that's fine",
|
|
644
727
|
// which is a GUESS about someone else's machine, and when it was wrong the user finished the
|
|
645
728
|
// install with a working search_ruvnet, no slash commands, and a message telling them all was
|
|
646
729
|
// well. The real state is checked below; nothing is declared fine until it has been looked at.
|
|
647
730
|
if (!addedMarket) info(`marketplace add didn't report success — checking what actually landed…`);
|
|
648
731
|
|
|
649
|
-
tryRun('claude',
|
|
732
|
+
tryRun('claude', before.installed
|
|
733
|
+
? ['plugin', 'update', 'ruvnet-brain@ruvnet-brain', '--scope', 'user']
|
|
734
|
+
: ['plugin', 'install', 'ruvnet-brain@ruvnet-brain', '--scope', 'user']);
|
|
650
735
|
|
|
651
736
|
// NEVER take "installed" on faith — same discipline verifyInstall() applies to the KB. An exit
|
|
652
737
|
// code says the command ran, not that the plugin is usable; the commands either exist on disk or
|
|
653
738
|
// they do not. This is the difference between `/rvbc` working and "Unknown command: /rvbc".
|
|
654
|
-
const
|
|
655
|
-
if (
|
|
656
|
-
ok(
|
|
739
|
+
const installed = claudePluginStatus();
|
|
740
|
+
if (installed.installed && (!expectedVersion || installed.version === expectedVersion)) {
|
|
741
|
+
ok(`plugin installed at user scope (global, alongside Ruflo / RuVector) — exact version ${installed.version}`);
|
|
657
742
|
info(` commands available after a restart: ${c.bold('/rvbc')}, ${c.bold('/ruvnet-brain:configure')}`);
|
|
658
|
-
return { wired: true, manualMarketplace, manualInstall };
|
|
743
|
+
return { host: true, wired: true, version: installed.version, manualMarketplace, manualInstall };
|
|
659
744
|
}
|
|
660
745
|
|
|
661
746
|
// The honest failure. The brain still WORKS — this is the difference between a broken install and
|
|
662
747
|
// a partial one, and the user is told exactly which they have instead of being congratulated.
|
|
663
|
-
|
|
748
|
+
const mismatch = installed.installed && expectedVersion && installed.version !== expectedVersion;
|
|
749
|
+
warn(mismatch
|
|
750
|
+
? `Claude installed ${installed.version || 'an unknown version'}, not required ${expectedVersion}; refusing to call this host converged.`
|
|
751
|
+
: `the plugin did NOT land — so slash commands like ${c.bold('/rvbc')} will not exist yet.`);
|
|
664
752
|
info(`${c.green('Your brain still works')}: search_ruvnet is wired and Claude will ground answers with it.`);
|
|
665
753
|
info(`Only the plugin extras (slash commands, the Console, the grounding hook) are missing.`);
|
|
666
754
|
info(`Run these two yourself to finish:`);
|
|
667
755
|
info(` ${c.bold(manualMarketplace)}`);
|
|
668
756
|
info(` ${c.bold(manualInstall)}`);
|
|
669
|
-
return {
|
|
757
|
+
return {
|
|
758
|
+
host: true,
|
|
759
|
+
wired: false,
|
|
760
|
+
action: mismatch ? 'verification-failed' : 'install-failed',
|
|
761
|
+
expectedVersion,
|
|
762
|
+
version: installed.version,
|
|
763
|
+
manualMarketplace,
|
|
764
|
+
manualInstall,
|
|
765
|
+
};
|
|
670
766
|
}
|
|
671
767
|
|
|
672
768
|
// ── step: wire the Codex host (issue #42, Henrik Pettersen) ──────────────────────────────────────
|
|
@@ -725,6 +821,37 @@ export function mergeCodexConfig(existing, serverPath) {
|
|
|
725
821
|
return { text: `${text}${sep}${block}\n`, action: 'added' };
|
|
726
822
|
}
|
|
727
823
|
|
|
824
|
+
export function removeCodexManagedBlock(existing) {
|
|
825
|
+
const text = typeof existing === 'string' ? existing : '';
|
|
826
|
+
const start = text.indexOf(CODEX_BLOCK_START);
|
|
827
|
+
const end = text.indexOf(CODEX_BLOCK_END, start === -1 ? 0 : start);
|
|
828
|
+
if (start === -1 || end === -1 || end < start) return { text, action: 'absent' };
|
|
829
|
+
const blockEnd = end + CODEX_BLOCK_END.length;
|
|
830
|
+
let before = text.slice(0, start);
|
|
831
|
+
let after = text.slice(blockEnd);
|
|
832
|
+
// mergeCodexConfig inserts one separator newline before the block and the block carries one
|
|
833
|
+
// trailing newline. Remove exactly those two installer-owned seam bytes when present.
|
|
834
|
+
if (before.endsWith('\n\n')) before = before.slice(0, -1);
|
|
835
|
+
if (after.startsWith('\n')) after = after.slice(1);
|
|
836
|
+
return { text: `${before}${after}`, action: 'removed' };
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function removeCodexWiring() {
|
|
840
|
+
const configPath = codexConfigPath();
|
|
841
|
+
let before = '';
|
|
842
|
+
try { before = fs.readFileSync(configPath, 'utf8'); } catch { return 'absent'; }
|
|
843
|
+
const result = removeCodexManagedBlock(before);
|
|
844
|
+
if (result.action !== 'removed') return result.action;
|
|
845
|
+
try {
|
|
846
|
+
const backup = `${configPath}.bak-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
847
|
+
fs.copyFileSync(configPath, backup);
|
|
848
|
+
atomicReplace(configPath, (tmp) => fs.writeFileSync(tmp, result.text));
|
|
849
|
+
return 'removed';
|
|
850
|
+
} catch {
|
|
851
|
+
return 'failed';
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
728
855
|
// What the doctor asserts from disk — never from the fact that we once ran. "Wired" means our entry
|
|
729
856
|
// is in the config AND the server.mjs it points at is really there, because a registration pointing
|
|
730
857
|
// at a deleted file is worse than no registration: Codex fails at spawn time with nothing to read.
|
|
@@ -774,6 +901,7 @@ export function wireCodexHost({
|
|
|
774
901
|
serverDir = codexServerDir(),
|
|
775
902
|
source = path.join(__dirname, '..', 'plugin', 'mcp', 'server.mjs'),
|
|
776
903
|
hookWrapperSource = path.join(__dirname, '..', 'plugin', 'scripts', 'codex-hook-wrapper.mjs'),
|
|
904
|
+
runtimePreferencesSource = path.join(__dirname, '..', 'plugin', 'scripts', 'runtime-preferences.mjs'),
|
|
777
905
|
hookWrapperPath = codexHookWrapperPath(codexDir),
|
|
778
906
|
announce = true,
|
|
779
907
|
} = {}) {
|
|
@@ -794,14 +922,21 @@ export function wireCodexHost({
|
|
|
794
922
|
if (announce) warn('MCP structured-interface module missing from this bundle — Codex left untouched (non-fatal)');
|
|
795
923
|
return { host: true, action: 'no-source' };
|
|
796
924
|
}
|
|
925
|
+
if (!fs.existsSync(runtimePreferencesSource)) {
|
|
926
|
+
if (announce) warn('MCP runtime-preferences module missing from this bundle — Codex left untouched (non-fatal)');
|
|
927
|
+
return { host: true, action: 'no-source' };
|
|
928
|
+
}
|
|
797
929
|
const serverPath = path.join(serverDir, 'server.mjs');
|
|
798
930
|
const managedCliPath = path.join(serverDir, 'managed-cli-interface.mjs');
|
|
931
|
+
const runtimePreferencesPath = path.join(path.dirname(serverDir), 'scripts', 'runtime-preferences.mjs');
|
|
799
932
|
fs.mkdirSync(serverDir, { recursive: true });
|
|
800
933
|
// Write-beside-then-rename, both here and for the config below (issue #43): an interrupted plain
|
|
801
934
|
// copy leaves a TORN server.mjs at the exact path a prior install's config already points at, so
|
|
802
935
|
// Codex spawns half a file. rename() over the target is atomic; a failure leaves the old bytes.
|
|
803
936
|
// Copy the dependency first. If the later server swap fails, the previously registered server
|
|
804
937
|
// remains byte-intact and continues to import a backward-compatible module at the same path.
|
|
938
|
+
fs.mkdirSync(path.dirname(runtimePreferencesPath), { recursive: true });
|
|
939
|
+
atomicReplace(runtimePreferencesPath, (tmp) => fs.copyFileSync(runtimePreferencesSource, tmp));
|
|
805
940
|
atomicReplace(managedCliPath, (tmp) => fs.copyFileSync(managedCliSource, tmp));
|
|
806
941
|
atomicReplace(serverPath, (tmp) => fs.copyFileSync(source, tmp));
|
|
807
942
|
if (fs.existsSync(hookWrapperSource)) {
|
|
@@ -817,7 +952,7 @@ export function wireCodexHost({
|
|
|
817
952
|
ok('Codex already declares ruvnet-brain in your own config — left exactly as you wrote it');
|
|
818
953
|
info(` to hand it to us instead, delete that ${c.bold('[mcp_servers.ruvnet-brain]')} block and re-run this installer`);
|
|
819
954
|
}
|
|
820
|
-
return { host: true, action, serverPath, managedCliPath, hookWrapperPath };
|
|
955
|
+
return { host: true, action, serverPath, managedCliPath, runtimePreferencesPath, hookWrapperPath };
|
|
821
956
|
}
|
|
822
957
|
if (text !== before) {
|
|
823
958
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
@@ -828,13 +963,39 @@ export function wireCodexHost({
|
|
|
828
963
|
info(` server: ${serverPath} ${c.dim('(persistent copy — the npx dir vanishes)')}`);
|
|
829
964
|
info(` ${c.dim('only our marked block is written; every other section is byte-preserved')}`);
|
|
830
965
|
}
|
|
831
|
-
return { host: true, action, serverPath, managedCliPath, hookWrapperPath, changed: text !== before };
|
|
966
|
+
return { host: true, action, serverPath, managedCliPath, runtimePreferencesPath, hookWrapperPath, changed: text !== before };
|
|
832
967
|
}
|
|
833
968
|
|
|
834
969
|
const CODEX_PLUGIN_ID = 'ruvnet-brain@ruvnet-brain';
|
|
835
970
|
const CODEX_MARKETPLACE = 'ruvnet-brain';
|
|
836
971
|
const CODEX_MARKETPLACE_SOURCE = 'stuinfla/ruvnet-brain';
|
|
837
972
|
|
|
973
|
+
function prepareCodexMarketplace() {
|
|
974
|
+
const target = path.join(
|
|
975
|
+
process.env.RUVNET_BRAIN_HOME || path.join(os.homedir(), '.cache', 'ruvnet-brain'),
|
|
976
|
+
'codex-marketplace',
|
|
977
|
+
);
|
|
978
|
+
const staged = `${target}.tmp-${process.pid}`;
|
|
979
|
+
fs.rmSync(staged, { recursive: true, force: true });
|
|
980
|
+
fs.mkdirSync(path.join(staged, '.claude-plugin'), { recursive: true });
|
|
981
|
+
fs.copyFileSync(
|
|
982
|
+
path.join(REPO_ROOT, '.claude-plugin', 'marketplace.json'),
|
|
983
|
+
path.join(staged, '.claude-plugin', 'marketplace.json'),
|
|
984
|
+
);
|
|
985
|
+
fs.cpSync(path.join(REPO_ROOT, 'plugin'), path.join(staged, 'plugin'), { recursive: true });
|
|
986
|
+
const prior = `${target}.prior-${process.pid}`;
|
|
987
|
+
fs.rmSync(prior, { recursive: true, force: true });
|
|
988
|
+
if (fs.existsSync(target)) fs.renameSync(target, prior);
|
|
989
|
+
try {
|
|
990
|
+
fs.renameSync(staged, target);
|
|
991
|
+
fs.rmSync(prior, { recursive: true, force: true });
|
|
992
|
+
} catch (error) {
|
|
993
|
+
if (fs.existsSync(prior) && !fs.existsSync(target)) fs.renameSync(prior, target);
|
|
994
|
+
throw error;
|
|
995
|
+
}
|
|
996
|
+
return target;
|
|
997
|
+
}
|
|
998
|
+
|
|
838
999
|
// Codex 0.145 returns `{ marketplaces: [...] }`; early preview builds returned the array itself.
|
|
839
1000
|
// Keep both shapes so an installer update never turns a harmless host-version difference into a
|
|
840
1001
|
// repeated "marketplace already exists" failure.
|
|
@@ -904,6 +1065,7 @@ export function wireCodexPlugin({
|
|
|
904
1065
|
return { host: true, action: 'unchanged', ...before };
|
|
905
1066
|
}
|
|
906
1067
|
|
|
1068
|
+
const localMarketplace = runJson === runCodexJson ? prepareCodexMarketplace() : null;
|
|
907
1069
|
const markets = runJson(['plugin', 'marketplace', 'list', '--json'], options);
|
|
908
1070
|
if (!markets.ok) {
|
|
909
1071
|
if (announce) warn(`Codex marketplace check failed — ${markets.error}`);
|
|
@@ -911,9 +1073,15 @@ export function wireCodexPlugin({
|
|
|
911
1073
|
}
|
|
912
1074
|
const marketRows = codexMarketplaceRows(markets.value);
|
|
913
1075
|
const known = marketRows.some((market) => market?.name === CODEX_MARKETPLACE);
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1076
|
+
let marketAction;
|
|
1077
|
+
if (localMarketplace) {
|
|
1078
|
+
if (known) runJson(['plugin', 'marketplace', 'remove', CODEX_MARKETPLACE, '--json'], options);
|
|
1079
|
+
marketAction = runJson(['plugin', 'marketplace', 'add', localMarketplace, '--json'], options);
|
|
1080
|
+
} else {
|
|
1081
|
+
marketAction = known
|
|
1082
|
+
? runJson(['plugin', 'marketplace', 'upgrade', CODEX_MARKETPLACE, '--json'], options)
|
|
1083
|
+
: runJson(['plugin', 'marketplace', 'add', marketplaceSource, '--json'], options);
|
|
1084
|
+
}
|
|
917
1085
|
if (!marketAction.ok) {
|
|
918
1086
|
if (announce) warn(`Codex marketplace ${known ? 'upgrade' : 'add'} failed — ${marketAction.error}`);
|
|
919
1087
|
return { host: true, action: 'marketplace-failed', error: marketAction.error };
|
|
@@ -924,9 +1092,9 @@ export function wireCodexPlugin({
|
|
|
924
1092
|
return { host: true, action: 'plugin-failed', error: added.error };
|
|
925
1093
|
}
|
|
926
1094
|
const after = codexPluginStatus({ ...options, runJson });
|
|
927
|
-
if (!after.installed || !after.enabled) {
|
|
1095
|
+
if (!after.installed || !after.enabled || (expectedVersion && after.version !== expectedVersion)) {
|
|
928
1096
|
if (announce) warn('Codex accepted the install command but the Brain plugin is not installed and enabled.');
|
|
929
|
-
return { host: true, action: 'verification-failed', ...after };
|
|
1097
|
+
return { host: true, action: 'verification-failed', expectedVersion, ...after };
|
|
930
1098
|
}
|
|
931
1099
|
if (announce) {
|
|
932
1100
|
ok(`Codex Brain plugin installed and enabled (${after.version || 'version unknown'}).`);
|
|
@@ -1434,9 +1602,22 @@ async function doctor() {
|
|
|
1434
1602
|
: info('git not found — that\'s fine, this installer never needs it');
|
|
1435
1603
|
cleanupStrayRuvectorDb(); // issue #39: sweep a leftover empty scaffold from before this fix, if one's here
|
|
1436
1604
|
const env = detectEnvironment();
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1605
|
+
let rufloOperational = null;
|
|
1606
|
+
if (env.ruflo) {
|
|
1607
|
+
rufloOperational = probeRufloOperationalHealth();
|
|
1608
|
+
if (rufloOperational.healthy) {
|
|
1609
|
+
ok(rufloOperational.directMode
|
|
1610
|
+
? 'Ruflo direct mode ready — daemon/swarm is stopped by design; AgentDB remains CLI-backed'
|
|
1611
|
+
: 'Ruflo operational — runtime, memory, and learning signals agree');
|
|
1612
|
+
} else {
|
|
1613
|
+
warn('Ruflo CLI is present, but operational learning is DEGRADED — availability is not enforcement.');
|
|
1614
|
+
if (rufloOperational.stopped) warn(' live status reports STOPPED / swarm not running');
|
|
1615
|
+
if (rufloOperational.memoryContradiction) warn(` status reports no memory while status memory reports ${rufloOperational.memoryEntries} entries`);
|
|
1616
|
+
if (rufloOperational.zeroLearning) warn(' learning dashboard is all zero: patterns=0, routes=0, commands=0');
|
|
1617
|
+
}
|
|
1618
|
+
} else {
|
|
1619
|
+
warn('Ruflo not found — answers still work. To build: npm install -g claude-flow@alpha (or /plugin add ruvnet/claude-flow)');
|
|
1620
|
+
}
|
|
1440
1621
|
env.ruvector
|
|
1441
1622
|
? ok('RuVector present — vector CLI / MCP available')
|
|
1442
1623
|
: warn(`RuVector not found — answers still work. To add: ${buildRuvectorMcpAddCommand(env).say}`);
|
|
@@ -1569,9 +1750,12 @@ async function doctor() {
|
|
|
1569
1750
|
&& !codexLifecycleGuidance(codexLifecycle).healthy
|
|
1570
1751
|
&& !codexLifecycleGuidance(codexLifecycle).intentional,
|
|
1571
1752
|
);
|
|
1753
|
+
const codexWiringFailed = Boolean(cx.host && !cx.wired);
|
|
1572
1754
|
const failed = (hookResult ? hookResult.exitCode !== 0 : !allGreen)
|
|
1573
1755
|
|| groundingUnprovenPersisted
|
|
1574
|
-
|| codexLifecycleFailed
|
|
1756
|
+
|| codexLifecycleFailed
|
|
1757
|
+
|| codexWiringFailed
|
|
1758
|
+
|| Boolean(rufloOperational && !rufloOperational.healthy);
|
|
1575
1759
|
if (failed && !hookResult && !groundingUnprovenPersisted) {
|
|
1576
1760
|
console.log(` ${c.red('✗ FAILING')} — the warnings above are real. Re-run ${c.bold('npx ruvnet-brain')} to repair.`);
|
|
1577
1761
|
}
|
|
@@ -1834,6 +2018,49 @@ function missingUpdaterHelp(kbDir) {
|
|
|
1834
2018
|
console.error(` — the current bundle ships forge-update.mjs; then this command will work.`);
|
|
1835
2019
|
}
|
|
1836
2020
|
|
|
2021
|
+
function syncHostsAfterUpdate() {
|
|
2022
|
+
const results = {};
|
|
2023
|
+
results.claude = wirePlugin({ expectedVersion: PACKAGE_VERSION, requireManaged: true });
|
|
2024
|
+
if (results.claude.host && !results.claude.wired) return { ok: false, results };
|
|
2025
|
+
try {
|
|
2026
|
+
results.codexHost = wireCodexHost();
|
|
2027
|
+
if (results.codexHost.host) {
|
|
2028
|
+
results.codex = wireCodexPlugin({ expectedVersion: PACKAGE_VERSION });
|
|
2029
|
+
if (!['unchanged', 'installed', 'updated', 'disabled'].includes(results.codex.action)) {
|
|
2030
|
+
return { ok: false, results };
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
} catch (error) {
|
|
2034
|
+
results.codex = { action: 'failed', error: error?.message || String(error) };
|
|
2035
|
+
return { ok: false, results };
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
const apply = path.join(__dirname, '..', 'plugin', 'scripts', 'update-apply.mjs');
|
|
2039
|
+
if (!fs.existsSync(apply)) return { ok: false, results, error: 'Stable Spine updater missing from package' };
|
|
2040
|
+
const applied = spawnSync(process.execPath, [apply, '--auto', '--expected-version', PACKAGE_VERSION], { stdio: 'inherit', env: process.env });
|
|
2041
|
+
const okApplied = !applied.error && applied.status === 0;
|
|
2042
|
+
if (okApplied) {
|
|
2043
|
+
const home = process.env.RUVNET_BRAIN_HOME || path.join(os.homedir(), '.cache', 'ruvnet-brain');
|
|
2044
|
+
const receiptPath = path.join(home, 'host-convergence.json');
|
|
2045
|
+
try {
|
|
2046
|
+
fs.mkdirSync(home, { recursive: true });
|
|
2047
|
+
const tmp = `${receiptPath}.tmp-${process.pid}`;
|
|
2048
|
+
fs.writeFileSync(tmp, JSON.stringify({
|
|
2049
|
+
desiredVersion: PACKAGE_VERSION,
|
|
2050
|
+
verifiedAt: new Date().toISOString(),
|
|
2051
|
+
hosts: {
|
|
2052
|
+
claude: { state: results.claude.host ? 'ready' : 'absent', version: results.claude.version || null },
|
|
2053
|
+
codex: { state: results.codex?.action === 'disabled' ? 'disabled' : (results.codexHost?.host ? 'ready' : 'absent'), version: results.codex?.version || null },
|
|
2054
|
+
},
|
|
2055
|
+
}, null, 2));
|
|
2056
|
+
fs.renameSync(tmp, receiptPath);
|
|
2057
|
+
} catch (error) {
|
|
2058
|
+
return { ok: false, results, applyStatus: applied.status, error: `host convergence receipt failed: ${error.message}` };
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
return { ok: okApplied, results, applyStatus: applied.status };
|
|
2062
|
+
}
|
|
2063
|
+
|
|
1837
2064
|
function runUpdate() {
|
|
1838
2065
|
printBanner('update');
|
|
1839
2066
|
const kbDir = resolvedKbDir();
|
|
@@ -1848,10 +2075,14 @@ function runUpdate() {
|
|
|
1848
2075
|
// private KB stores a surprise fresh PUBLIC install is exactly the store-stripping hazard the
|
|
1849
2076
|
// project docs warn about. The fallback's own comment scopes it to an updater that EXISTS but
|
|
1850
2077
|
// is broken — this branch enforces that scope.)
|
|
1851
|
-
if (!fs.existsSync(path.join(kbDir, 'forge-update.mjs'))) {
|
|
2078
|
+
if (!fs.existsSync(path.join(kbDir, 'forge-update.mjs')) && !FLAG_HOST_SYNC_ONLY) {
|
|
1852
2079
|
missingUpdaterHelp(kbDir);
|
|
1853
2080
|
process.exit(1);
|
|
1854
2081
|
}
|
|
2082
|
+
if (FLAG_HOST_SYNC_ONLY && !fs.existsSync(path.join(kbDir, 'forge-update.mjs'))) {
|
|
2083
|
+
warn('KB updater is absent — continuing with host-shell repair only; the knowledge bundle remains unchanged.');
|
|
2084
|
+
updateStatus = 0;
|
|
2085
|
+
} else {
|
|
1855
2086
|
info(c.dim("running the bundle's own self-updater (backs up first, re-verifies, never half-applies)…\n"));
|
|
1856
2087
|
// Relative filename + matching cwd — same launch convention as smokeQuery(); stdio:'inherit'
|
|
1857
2088
|
// streams the updater's narration live and unedited.
|
|
@@ -1869,6 +2100,15 @@ function runUpdate() {
|
|
|
1869
2100
|
env: { ...process.env, RUVNET_BRAIN_NO_UPDATE_FALLBACK: '1' } });
|
|
1870
2101
|
updateStatus = fr.error ? 1 : (fr.status === null ? 1 : fr.status);
|
|
1871
2102
|
}
|
|
2103
|
+
}
|
|
2104
|
+
if (updateStatus === 0) {
|
|
2105
|
+
info(c.dim('\nsynchronizing every detected host to this exact published version…\n'));
|
|
2106
|
+
const convergence = syncHostsAfterUpdate();
|
|
2107
|
+
if (!convergence.ok) {
|
|
2108
|
+
warn(`host synchronization is incomplete — runtime stays on the prior verified generation${convergence.error ? ` (${convergence.error})` : ''}`);
|
|
2109
|
+
updateStatus = 1;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
1872
2112
|
// `--update --auto` = update now AND enroll in Evergreen, so this is the LAST time it's ever run by
|
|
1873
2113
|
// hand. Only enroll if the update itself succeeded — never promise "you're set forever" on a failed
|
|
1874
2114
|
// update. enableNightly() prints its own real verification (plist path + launchctl result).
|
|
@@ -2185,6 +2425,27 @@ export function machineFootprint() {
|
|
|
2185
2425
|
items.push({ label: 'The search_ruvnet MCP server registration', path: claudeJson, undo: 'claude mcp remove ruvnet-brain --scope user' });
|
|
2186
2426
|
}
|
|
2187
2427
|
} catch { /* unreadable — do not claim it */ }
|
|
2428
|
+
try {
|
|
2429
|
+
const codex = codexStatus();
|
|
2430
|
+
if (codex.host && codex.serverPath) {
|
|
2431
|
+
items.push({
|
|
2432
|
+
label: 'The Codex search_ruvnet MCP registration',
|
|
2433
|
+
path: codexConfigPath(),
|
|
2434
|
+
undo: 'npx ruvnet-brain --uninstall (removes only the managed block)',
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
add('Codex lifecycle hook wrapper', codexHookWrapperPath(),
|
|
2438
|
+
'npx ruvnet-brain --uninstall');
|
|
2439
|
+
add('Codex MCP server files', codexServerDir(), 'npx ruvnet-brain --uninstall');
|
|
2440
|
+
const codexPlugin = codexPluginStatus();
|
|
2441
|
+
if (codexPlugin.installed) {
|
|
2442
|
+
items.push({
|
|
2443
|
+
label: 'Codex plugin',
|
|
2444
|
+
path: codexHomeDir(),
|
|
2445
|
+
undo: 'codex plugin remove ruvnet-brain@ruvnet-brain && codex plugin marketplace remove ruvnet-brain',
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
} catch { /* Codex absent or unavailable — do not claim it */ }
|
|
2188
2449
|
// GAP FIX: recordNotified() (scripts/upgrade-notice.mjs, invoked from main() and runDemo() below)
|
|
2189
2450
|
// writes this file the first time a "what's new" notice is shown — a real disk artifact this
|
|
2190
2451
|
// installer's own code path creates, that was never listed here and never removed on --uninstall.
|
|
@@ -2215,6 +2476,18 @@ function printFootprint({ heading = 'What this put on your machine' } = {}) {
|
|
|
2215
2476
|
return items;
|
|
2216
2477
|
}
|
|
2217
2478
|
|
|
2479
|
+
function showWhatsNew() {
|
|
2480
|
+
const notes = path.join(REPO_ROOT, 'docs', 'RELEASE-NOTES-4.0.md');
|
|
2481
|
+
if (!fs.existsSync(notes)) {
|
|
2482
|
+
console.error('RuvNet Brain release notes are missing from this artifact.');
|
|
2483
|
+
process.exitCode = 1;
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
const text = fs.readFileSync(notes, 'utf8');
|
|
2487
|
+
process.stdout.write(text);
|
|
2488
|
+
if (!text.endsWith('\n')) process.stdout.write('\n');
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2218
2491
|
/**
|
|
2219
2492
|
* Surgically remove ONLY our block from CLAUDE.md, leaving every other line exactly as it was.
|
|
2220
2493
|
* Backed up and written atomically, same as when it was added — taking something away is at least
|
|
@@ -2276,7 +2549,9 @@ function uninstallAll() {
|
|
|
2276
2549
|
'Model-router files', 'Status-bar version script', 'Status-bar preference', 'Usage-counts preference',
|
|
2277
2550
|
// Two gaps closed here: the statusLine KEY is now removable in place (we know exactly what we
|
|
2278
2551
|
// wrote — see removeSettingsStatusLine()), and the upgrade-notice tracker is a file we fully own.
|
|
2279
|
-
'The statusLine entry in settings.json', 'Upgrade-notice state (release-notice tracking)'
|
|
2552
|
+
'The statusLine entry in settings.json', 'Upgrade-notice state (release-notice tracking)',
|
|
2553
|
+
'The Codex search_ruvnet MCP registration', 'Codex lifecycle hook wrapper',
|
|
2554
|
+
'Codex MCP server files']);
|
|
2280
2555
|
const willRemove = before.filter((it) => AUTO.has(it.label));
|
|
2281
2556
|
const manual = before.filter((it) => !AUTO.has(it.label));
|
|
2282
2557
|
|
|
@@ -2302,6 +2577,8 @@ function uninstallAll() {
|
|
|
2302
2577
|
// customized — removeSettingsStatusLine() checks the live command before touching anything).
|
|
2303
2578
|
const statusLine = removeSettingsStatusLine();
|
|
2304
2579
|
if (statusLine === 'removed') ok('removed the statusLine entry from ~/.claude/settings.json (your other settings untouched, backup saved)');
|
|
2580
|
+
const codexWiring = removeCodexWiring();
|
|
2581
|
+
if (codexWiring === 'removed') ok('removed the managed RuvNet Brain block from Codex config (your other config untouched, backup saved)');
|
|
2305
2582
|
|
|
2306
2583
|
// NEVER rm -rf A PATH WE HAVE NOT PROVEN IS OURS. resolvedKbDir() honours $RUVNET_BRAIN_KB, which
|
|
2307
2584
|
// the docs encourage for custom install locations — so `RUVNET_BRAIN_KB=$HOME npx ruvnet-brain
|
|
@@ -2340,6 +2617,8 @@ function uninstallAll() {
|
|
|
2340
2617
|
['upgrade-notice state', upgradeNoticeStatePath()],
|
|
2341
2618
|
// ADR-054 §5: a reinstall must never inherit an invisible OFF. See brainOffSentinelPath().
|
|
2342
2619
|
['brain on/off switch', brainOffSentinelPath()],
|
|
2620
|
+
['Codex lifecycle hook wrapper', codexHookWrapperPath()],
|
|
2621
|
+
['Codex MCP server files', codexServerDir()],
|
|
2343
2622
|
]) {
|
|
2344
2623
|
if (!fs.existsSync(target)) continue;
|
|
2345
2624
|
try { fs.rmSync(target, { recursive: true, force: true }); ok(`removed the ${label}`); }
|
|
@@ -2714,9 +2993,9 @@ async function printPlanAndConfirm() {
|
|
|
2714
2993
|
{ name: 'Download the knowledge base',
|
|
2715
2994
|
cost: short(resolveCacheDir().cacheDir),
|
|
2716
2995
|
what: 'Real rUv source, on your disk, so answers cite files instead of guessing.' },
|
|
2717
|
-
{ name: 'Register
|
|
2718
|
-
cost: `
|
|
2719
|
-
what: 'Gives Claude
|
|
2996
|
+
{ name: 'Register host plugins + MCP server',
|
|
2997
|
+
cost: `managed entries in ${short(path.join(H, '.claude'))} and, when detected, ${short(path.join(H, '.codex'))}`,
|
|
2998
|
+
what: 'Gives Claude Code and Codex the same search_ruvnet tool and native Brain workflows.' },
|
|
2720
2999
|
];
|
|
2721
3000
|
// The OPTIONAL steps — each asked ONCE here, stored in PLAN_CHOICES, and consumed by its offer
|
|
2722
3001
|
// later (no second prompt). `def` is the pre-check: nightly + spend are recommended (default yes),
|
|
@@ -2850,6 +3129,42 @@ function detectEnvironment() {
|
|
|
2850
3129
|
return env;
|
|
2851
3130
|
}
|
|
2852
3131
|
|
|
3132
|
+
export function classifyRufloOperationalHealth({ status = '', memory = '', metrics = '' } = {}) {
|
|
3133
|
+
const stopped = /\b(?:RuFlo V3 \[STOPPED\]|Swarm not running|MCP Server\s*\\n.*Not running)/i.test(status);
|
|
3134
|
+
const statusSaysNoMemory = /Backend\s*[|:]?\s*none|Entries\s*[|:]?\s*0\b/i.test(status);
|
|
3135
|
+
const memoryEntries = Number((memory.match(/Total Entries\s*[|:]?\s*([\d,]+)/i)?.[1] || '0').replace(/,/g, ''));
|
|
3136
|
+
const zeroLearning = /Total Patterns\s*[|:]?\s*0\b/i.test(metrics)
|
|
3137
|
+
&& /Total Routes\s*[|:]?\s*0\b/i.test(metrics)
|
|
3138
|
+
&& /Total Executed\s*[|:]?\s*0\b/i.test(metrics);
|
|
3139
|
+
const memoryContradiction = statusSaysNoMemory && memoryEntries > 0;
|
|
3140
|
+
// Brain 4 is explicitly zero-daemon: `ruflo memory store/search` open the per-project
|
|
3141
|
+
// AgentDB directly. A stopped orchestration daemon therefore makes daemon-owned status
|
|
3142
|
+
// and metrics non-authoritative; treating that intentional state as degraded made Doctor
|
|
3143
|
+
// exit 1 immediately after proving source-grounded retrieval worked. Keep the strict
|
|
3144
|
+
// contradiction/learning checks when a daemon is actually running.
|
|
3145
|
+
const directMode = stopped;
|
|
3146
|
+
return {
|
|
3147
|
+
healthy: directMode || (!memoryContradiction && !zeroLearning),
|
|
3148
|
+
directMode,
|
|
3149
|
+
stopped,
|
|
3150
|
+
zeroLearning,
|
|
3151
|
+
memoryContradiction,
|
|
3152
|
+
memoryEntries,
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
function probeRufloOperationalHealth() {
|
|
3157
|
+
const run = (args) => {
|
|
3158
|
+
const result = spawnSync('ruflo', args, { cwd: process.cwd(), encoding: 'utf8', timeout: 10_000 });
|
|
3159
|
+
return `${result.stdout || ''}\n${result.stderr || ''}`;
|
|
3160
|
+
};
|
|
3161
|
+
return classifyRufloOperationalHealth({
|
|
3162
|
+
status: run(['status']),
|
|
3163
|
+
memory: run(['status', 'memory']),
|
|
3164
|
+
metrics: run(['hooks', 'metrics', '--v3-dashboard']),
|
|
3165
|
+
});
|
|
3166
|
+
}
|
|
3167
|
+
|
|
2853
3168
|
// ── issue #39: the ruvector MCP server's own native VectorDb defaults ITS storage to
|
|
2854
3169
|
// "./ruvector.db" relative to whatever cwd it happens to be launched in — and Claude Code
|
|
2855
3170
|
// always launches an MCP server with cwd = the current project, so every consumer project got
|
|
@@ -3295,7 +3610,7 @@ function installedRepoCount(cacheDir) {
|
|
|
3295
3610
|
}
|
|
3296
3611
|
}
|
|
3297
3612
|
|
|
3298
|
-
function success({ cacheDir, isCustom, plugin, env, nightly }) {
|
|
3613
|
+
function success({ cacheDir, isCustom, plugin, codexHost, codexPlugin, env, nightly }) {
|
|
3299
3614
|
const line = '─'.repeat(64);
|
|
3300
3615
|
console.log(`\n${c.green(line)}`);
|
|
3301
3616
|
console.log(`${c.green(c.bold(' RuvNet Brain is installed.'))}`);
|
|
@@ -3306,6 +3621,13 @@ function success({ cacheDir, isCustom, plugin, env, nightly }) {
|
|
|
3306
3621
|
console.log(
|
|
3307
3622
|
` • the Claude Code plugin ${plugin.wired ? c.green('wired at user scope') : c.yellow('(finish the 2 commands above)')} — search_ruvnet + grounding hook`,
|
|
3308
3623
|
);
|
|
3624
|
+
if (codexHost?.host) {
|
|
3625
|
+
const hostReady = ['added', 'rewritten', 'user-owned'].includes(codexHost.action);
|
|
3626
|
+
const pluginReady = Boolean(codexPlugin?.installed && codexPlugin?.enabled);
|
|
3627
|
+
console.log(
|
|
3628
|
+
` • the Codex host ${hostReady && pluginReady ? c.green('wired at user scope') : c.yellow('needs attention')} — search_ruvnet + native Brain skills`,
|
|
3629
|
+
);
|
|
3630
|
+
}
|
|
3309
3631
|
if (env) {
|
|
3310
3632
|
const t = (okv) => (okv ? c.green('✓ present') : c.yellow('not added (optional)'));
|
|
3311
3633
|
console.log(` • rUv build toolkit — Ruflo: ${t(env.ruflo)} · RuVector: ${t(env.ruvector)} ${c.dim('answers work without them')}`);
|
|
@@ -3316,7 +3638,8 @@ function success({ cacheDir, isCustom, plugin, env, nightly }) {
|
|
|
3316
3638
|
console.log(` ${c.bold(`export RUVNET_BRAIN_KB="${cacheDir}"`)}`);
|
|
3317
3639
|
}
|
|
3318
3640
|
// ── where + how it runs — the confidence answers, stated up front ──
|
|
3319
|
-
|
|
3641
|
+
const hostLabel = codexHost?.host ? 'Claude Code and Codex' : 'Claude Code';
|
|
3642
|
+
console.log(`\n ${c.bold('Where it runs:')} ${c.bold(`everywhere you use ${hostLabel}`)} — CLI and supported editor/desktop surfaces.`);
|
|
3320
3643
|
console.log(` It's ${c.bold('user-level (global)')}: open ANY repo or folder and it's already there. Nothing per project,`);
|
|
3321
3644
|
console.log(` nothing to copy in, nothing to git-ignore. ${c.dim('(Runs locally — it is not active in the claude.ai web app.)')}`);
|
|
3322
3645
|
console.log(`\n ${c.bold('How it runs:')} ${c.bold('automatically')} — you never call or configure anything. Ask normally; on rUv-stack work it`);
|
|
@@ -3325,12 +3648,12 @@ function success({ cacheDir, isCustom, plugin, env, nightly }) {
|
|
|
3325
3648
|
console.log(` Release regardless (that part isn't cached). For the bleeding-edge installer too, use ${c.bold('npx github:stuinfla/ruvnet-brain')}.`);
|
|
3326
3649
|
|
|
3327
3650
|
// ── one important expectation: the hook activates on the NEXT session ──
|
|
3328
|
-
console.log(`\n ${c.yellow(c.bold('One thing to know:'))}
|
|
3329
|
-
console.log(` ${c.dim(
|
|
3651
|
+
console.log(`\n ${c.yellow(c.bold('One thing to know:'))} lifecycle hooks turn on in your ${c.bold('next')} host session.`);
|
|
3652
|
+
console.log(` ${c.dim(`If ${hostLabel} is open right now, quit and reopen it so the new plugin snapshot loads.`)}`);
|
|
3330
3653
|
|
|
3331
3654
|
// ── what to do now ──
|
|
3332
3655
|
console.log(`\n ${c.bold('What to do now:')}`);
|
|
3333
|
-
console.log(` ${c.cyan('1.')} Open
|
|
3656
|
+
console.log(` ${c.cyan('1.')} Open ${hostLabel} in ${c.bold('any')} project (your own, or a fresh repo — nothing to copy in).`);
|
|
3334
3657
|
console.log(` ${c.cyan('2.')} Just ask, normally. Try: ${c.bold('"Set up vector search here the way rUv would."')}`);
|
|
3335
3658
|
console.log(` ${c.cyan('3.')} Watch what changes (below).`);
|
|
3336
3659
|
|
|
@@ -3404,6 +3727,7 @@ Usage:
|
|
|
3404
3727
|
npx ruvnet-brain --disable-nightly Remove the nightly schedule (safe to run any time)
|
|
3405
3728
|
npx ruvnet-brain --what-changed Show exactly what RuvNet Brain has put on this machine,
|
|
3406
3729
|
with the undo command for each piece
|
|
3730
|
+
npx ruvnet-brain --whats-new Show the curated v3 → v4 capabilities and honest limits
|
|
3407
3731
|
npx ruvnet-brain --uninstall Remove all of it (bundle, LaunchAgents, and our CLAUDE.md
|
|
3408
3732
|
block only — your own CLAUDE.md content is preserved and backed up)
|
|
3409
3733
|
npx ruvnet-brain --enable-spend-guard Install the hourly runaway-agent spend alarm (alert-only)
|
|
@@ -3443,7 +3767,17 @@ It is safe to re-run at any time. After installing, restart Claude Code so the g
|
|
|
3443
3767
|
|
|
3444
3768
|
// ── main ─────────────────────────────────────────────────────────────────────────────────────────
|
|
3445
3769
|
(async () => {
|
|
3446
|
-
|
|
3770
|
+
// Importing an executable module for its exported policy helpers must never install software.
|
|
3771
|
+
// The old contract required every caller to remember RUVNET_BRAIN_IMPORT_ONLY=1; one missed flag
|
|
3772
|
+
// in a unit test ran a real install against the maintainer's HOME. Direct argv identity is the
|
|
3773
|
+
// authority boundary. The old IMPORT_ONLY flag may still be present in a long-lived test worker;
|
|
3774
|
+
// it must never suppress a genuinely direct CLI execution.
|
|
3775
|
+
const canonical = (value) => {
|
|
3776
|
+
try { return fs.realpathSync(value); } catch { return path.resolve(value); }
|
|
3777
|
+
};
|
|
3778
|
+
const invokedDirectly = Boolean(process.argv[1])
|
|
3779
|
+
&& canonical(process.argv[1]) === canonical(fileURLToPath(import.meta.url));
|
|
3780
|
+
if (!invokedDirectly) return;
|
|
3447
3781
|
if (FLAG_HELP) return showHelp();
|
|
3448
3782
|
// `process.exitCode`, not `return` — doctor()'s verdict is the whole point of running it in a
|
|
3449
3783
|
// script. A bare `return await doctor()` discarded the number, which is how "! Needs attention"
|
|
@@ -3462,9 +3796,10 @@ It is safe to re-run at any time. After installing, restart Claude Code so the g
|
|
|
3462
3796
|
if (FLAG_DISABLE_SPEND_GUARD) { disableSpendGuard(); return; }
|
|
3463
3797
|
if (FLAG_UNINSTALL) { uninstallAll(); return; }
|
|
3464
3798
|
if (FLAG_WHAT_CHANGED) { printBanner('what RuvNet Brain put on this machine'); printFootprint(); return; }
|
|
3799
|
+
if (FLAG_WHATS_NEW) { showWhatsNew(); return; }
|
|
3465
3800
|
|
|
3466
3801
|
printBanner('installer');
|
|
3467
|
-
console.log(c.dim("I'll set up the brain
|
|
3802
|
+
console.log(c.dim("I'll set up the brain for Claude Code and Codex, explaining each step as I go.\n"));
|
|
3468
3803
|
|
|
3469
3804
|
// ── environment guard: fail early and CLEARLY on unsupported Node, not cryptically mid-install ──
|
|
3470
3805
|
// Node itself can't be silently auto-upgraded from inside a script it's currently running as —
|
|
@@ -3607,13 +3942,24 @@ It is safe to re-run at any time. After installing, restart Claude Code so the g
|
|
|
3607
3942
|
}
|
|
3608
3943
|
|
|
3609
3944
|
installReader(cacheDir);
|
|
3945
|
+
try {
|
|
3946
|
+
const consoleEntry = installConsoleRuntime(cacheDir);
|
|
3947
|
+
ok(`Brain Console installed at ${consoleEntry}`);
|
|
3948
|
+
} catch (error) {
|
|
3949
|
+
die(
|
|
3950
|
+
`the Brain Console runtime could not be installed (${error.message})`,
|
|
3951
|
+
`The knowledge base is present, but /rvbc would be broken. Re-run the installer from a complete package.`,
|
|
3952
|
+
);
|
|
3953
|
+
}
|
|
3610
3954
|
const plugin = wirePlugin();
|
|
3611
3955
|
// Codex hosts got nothing before this (issue #42): shipped, never registered. Non-fatal like every
|
|
3612
3956
|
// other wiring step — a second host we cannot reach must never break the one we can.
|
|
3957
|
+
let codexHost = { host: false, action: 'no-host' };
|
|
3958
|
+
let codexPlugin = null;
|
|
3613
3959
|
try {
|
|
3614
|
-
|
|
3960
|
+
codexHost = wireCodexHost();
|
|
3615
3961
|
if (codexHost.host) {
|
|
3616
|
-
|
|
3962
|
+
codexPlugin = wireCodexPlugin();
|
|
3617
3963
|
if (codexPlugin.available) printCodexLifecycle(await codexLifecycleStatus());
|
|
3618
3964
|
}
|
|
3619
3965
|
} catch (e) {
|
|
@@ -3657,7 +4003,7 @@ It is safe to re-run at any time. After installing, restart Claude Code so the g
|
|
|
3657
4003
|
// Same rule: an optional offer can never break a finished install.
|
|
3658
4004
|
try { await offerStatusline(); } catch { /* non-fatal — a status-bar nicety must never break the install */ }
|
|
3659
4005
|
|
|
3660
|
-
success({ cacheDir, isCustom, plugin, env, nightly });
|
|
4006
|
+
success({ cacheDir, isCustom, plugin, codexHost, codexPlugin, env, nightly });
|
|
3661
4007
|
|
|
3662
4008
|
// Close every install by stating, in one place, exactly what is now on their machine and how to
|
|
3663
4009
|
// take each piece back off. Someone reading this should never have to file a bug report to find
|