ruvnet-brain 4.0.8 β 4.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/install.mjs +109 -42
- package/console/app.js +32 -11
- package/console/style.css +5 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/scripts/advocacy-outcomes.mjs +808 -0
- package/plugin/scripts/anticipate.sh +80 -14
- package/plugin/scripts/capability-registry.mjs +994 -0
- package/plugin/scripts/codex-hook-wrapper.mjs +1 -0
- package/plugin/scripts/continuation-gate.mjs +169 -1
- package/plugin/scripts/gates.mjs +146 -0
- package/plugin/scripts/goal-match.mjs +398 -0
- package/plugin/scripts/hijack-ruvnet.sh +69 -1
- package/plugin/scripts/hook-registry.mjs +616 -0
- package/plugin/scripts/hook-shim.mjs +13 -2
- package/plugin/scripts/learn-flush.mjs +37 -2
- package/plugin/scripts/learning-enable.mjs +382 -0
- package/plugin/scripts/lesson-promote.mjs +262 -0
- package/plugin/scripts/lesson-provenance.mjs +43 -0
- package/plugin/scripts/lesson-store.mjs +67 -56
- package/plugin/scripts/memory-doctor.mjs +345 -0
- package/plugin/scripts/nightly-controller.mjs +98 -0
- package/plugin/scripts/project-identity.mjs +89 -0
- package/plugin/scripts/ruflo-bin.mjs +81 -0
- package/plugin/scripts/runtime-preferences.mjs +18 -0
- package/plugin/scripts/session-snapshot-hook.mjs +4 -1
- package/plugin/scripts/session-start-core.mjs +19 -2
- package/plugin/scripts/unprompted-runtime.mjs +22 -7
- package/plugin/scripts/user-settings.mjs +672 -0
- package/plugin/skills/ruvnet-brain/SKILL.md +2 -2
- package/scripts/advocacy-outcomes.mjs +4 -808
- package/scripts/capability-registry.mjs +4 -876
- package/scripts/console-runtime-identity.mjs +74 -0
- package/scripts/corpus-qa.mjs +44 -6
- package/scripts/distill-project.mjs +9 -1
- package/scripts/doc-currency.mjs +45 -4
- package/scripts/gates.mjs +4 -146
- package/scripts/goal-match.mjs +4 -398
- package/scripts/health-repair.mjs +88 -11
- package/scripts/hook-registry.mjs +4 -567
- package/scripts/host-install-matrix.mjs +155 -0
- package/scripts/issue-watch.mjs +108 -0
- package/scripts/learning-enable.mjs +4 -380
- package/scripts/lesson-promote.mjs +4 -262
- package/scripts/memory-doctor.mjs +4 -342
- package/scripts/model-router-catalog.mjs +34 -0
- package/scripts/nightly-controller.mjs +4 -66
- package/scripts/nightly-wrapper.sh +23 -1
- package/scripts/onboarding-console.mjs +34 -12
- package/scripts/proactivity-metrics.mjs +8 -1
- package/scripts/publication-receipt.mjs +10 -1
- package/scripts/qe/ux-suite.mjs +72 -1
- package/scripts/release-abort-stale.mjs +111 -0
- package/scripts/release-convergence-watchdog.mjs +119 -0
- package/scripts/release-transaction-provider.mjs +76 -8
- package/scripts/release-transaction.mjs +55 -17
- package/scripts/rvf-generation.mjs +17 -0
- package/scripts/self-update.mjs +63 -10
- package/scripts/staged-host-verifier.mjs +27 -54
- package/scripts/sync-version.mjs +10 -10
- package/scripts/user-settings.mjs +4 -640
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
|
|
|
@@ -445,7 +445,7 @@ The brain answers **both** kinds of questions. **Name the repo or ask something
|
|
|
445
445
|
|
|
446
446
|
## What it covers
|
|
447
447
|
|
|
448
|
-
|
|
448
|
+
71 of rUv's repos in the [ruvnet](https://github.com/ruvnet) org β the reusable **building blocks** you'd actually compose into a system β each deep-walked into its own canonical RVF segment. The core blocks below also carry symbol indexes and capability cards (the 8 newest repos are findable by name; their capability cards are coming).
|
|
449
449
|
|
|
450
450
|

|
|
451
451
|
|
|
@@ -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** β 64 public stores Β· 138,140 public source chunks (71 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
|
@@ -24,7 +24,10 @@ import {
|
|
|
24
24
|
requiredEmbedderModels,
|
|
25
25
|
missingEmbedderModels,
|
|
26
26
|
} from '../kb/model-requirements.mjs';
|
|
27
|
-
import {
|
|
27
|
+
import { applyManagedCatalogUpdate } from '../scripts/model-router-catalog.mjs';
|
|
28
|
+
import {
|
|
29
|
+
CONSOLE_RUNTIME_SURFACE, CONSOLE_RUNTIME_IDENTITY_FILE, consoleRuntimeDigest,
|
|
30
|
+
} from '../scripts/console-runtime-identity.mjs';
|
|
28
31
|
|
|
29
32
|
// SEC-0010 #6 β the Ed25519 PUBLIC key is EMBEDDED here (not a separate file) so the installer's
|
|
30
33
|
// trust root travels with the installer code itself: an attacker who swaps the downloaded bundle
|
|
@@ -613,22 +616,17 @@ export function beginConsoleRuntimeTransaction(cacheDir, sourceRoot = REPO_ROOT)
|
|
|
613
616
|
fs.rmSync(prior, { recursive: true, force: true });
|
|
614
617
|
fs.mkdirSync(staged, { recursive: true, mode: 0o700 });
|
|
615
618
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
['bin/install.mjs', 'bin/install.mjs'],
|
|
623
|
-
['package.json', 'package.json'],
|
|
624
|
-
];
|
|
625
|
-
for (const [from, to] of required) {
|
|
626
|
-
const source = path.join(sourceRoot, from);
|
|
619
|
+
// The copy list IS the generation's digest input (scripts/console-runtime-identity.mjs). Adding a
|
|
620
|
+
// file the runtime needs adds it to the runtime's identity in the same edit, so an asset can never
|
|
621
|
+
// again travel separately from the executable that reads it (#76) and a candidate can never again
|
|
622
|
+
// be indistinguishable from the one it replaces (#79).
|
|
623
|
+
for (const relative of CONSOLE_RUNTIME_SURFACE) {
|
|
624
|
+
const source = path.join(sourceRoot, relative);
|
|
627
625
|
if (!fs.existsSync(source)) {
|
|
628
626
|
fs.rmSync(staged, { recursive: true, force: true });
|
|
629
|
-
throw new Error(`console runtime is incomplete: missing ${
|
|
627
|
+
throw new Error(`console runtime is incomplete: missing ${relative}`);
|
|
630
628
|
}
|
|
631
|
-
const target = path.join(staged,
|
|
629
|
+
const target = path.join(staged, relative);
|
|
632
630
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
633
631
|
fs.cpSync(source, target, { recursive: true, force: true, preserveTimestamps: true });
|
|
634
632
|
}
|
|
@@ -661,15 +659,28 @@ export function beginConsoleRuntimeTransaction(cacheDir, sourceRoot = REPO_ROOT)
|
|
|
661
659
|
throw new Error(`console runtime failed syntax verification: ${path.relative(staged, syntaxTarget)}`);
|
|
662
660
|
}
|
|
663
661
|
}
|
|
662
|
+
// #76: prove the installed What's New boundary from the staged bytes, not from a file listing. The
|
|
663
|
+
// executable exits nonzero when its version manifest or curated notes are absent, so a runtime that
|
|
664
|
+
// would have to tell the user "the notes are unavailable" never activates in the first place.
|
|
665
|
+
const stagedWhatsNew = path.join(staged, 'plugin', 'scripts', 'whats-new.mjs');
|
|
666
|
+
const whatsNew = spawnSync(process.execPath, [stagedWhatsNew], { encoding: 'utf8' });
|
|
667
|
+
if (whatsNew.error || whatsNew.status !== 0) {
|
|
668
|
+
const detail = (whatsNew.stderr || whatsNew.error?.message || `exit ${whatsNew.status}`).trim();
|
|
669
|
+
fs.rmSync(staged, { recursive: true, force: true });
|
|
670
|
+
throw new Error(`console runtime cannot report its own release notes: ${detail}`);
|
|
671
|
+
}
|
|
664
672
|
const identity = {
|
|
665
673
|
product: 'ruvnet-brain-console-runtime',
|
|
666
674
|
schema: 1,
|
|
667
675
|
apiContract: 1,
|
|
668
676
|
runtimeVersion: packageManifest.version,
|
|
669
677
|
entrypoint: 'scripts/onboarding-console.mjs',
|
|
670
|
-
|
|
678
|
+
// The WHOLE runtime, not just its entrypoint (#79). The launcher compares this against a live
|
|
679
|
+
// server's self-reported identity, so it has to change whenever anything the server executes or
|
|
680
|
+
// serves changes β otherwise a stale process passes as current.
|
|
681
|
+
sourceSha256: consoleRuntimeDigest(staged),
|
|
671
682
|
};
|
|
672
|
-
fs.writeFileSync(path.join(staged,
|
|
683
|
+
fs.writeFileSync(path.join(staged, CONSOLE_RUNTIME_IDENTITY_FILE), `${JSON.stringify(identity, null, 2)}\n`, { mode: 0o600 });
|
|
673
684
|
|
|
674
685
|
let state = 'staged';
|
|
675
686
|
const entry = path.join(runtime, identity.entrypoint);
|
|
@@ -2230,6 +2241,37 @@ const xmlEscape = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').
|
|
|
2230
2241
|
const cronExample = (kbDir) =>
|
|
2231
2242
|
`47 3 * * * cd ${kbDir} && ${process.execPath} forge-update.mjs --apply >> ${kbDir}/update.log 2>&1`;
|
|
2232
2243
|
|
|
2244
|
+
/**
|
|
2245
|
+
* kb/forge-update.mjs exit 11 = "the download completed but the bundle on disk did NOT change"
|
|
2246
|
+
* (its `EXIT_NOT_LANDED`). Duplicated as a literal on purpose: the updater lives in the KB BUNDLE,
|
|
2247
|
+
* which versions and ships independently of this installer, so there is no import to share. The
|
|
2248
|
+
* two are pinned together by tests/unit/update-not-landed-exit.test.mjs, which reads the constant
|
|
2249
|
+
* out of kb/forge-update.mjs and fails if they ever drift.
|
|
2250
|
+
*/
|
|
2251
|
+
export const UPDATE_NOT_LANDED = 11;
|
|
2252
|
+
|
|
2253
|
+
/**
|
|
2254
|
+
* What `--update` does with the KB updater's exit code (issue #106).
|
|
2255
|
+
*
|
|
2256
|
+
* `--update` used to convert an honest failure into a reported success. The updater detected the
|
|
2257
|
+
* problem itself and said so β "UPDATE MISMATCH: SOURCE.json on disk is IDENTICAL to before the
|
|
2258
|
+
* update β¦ REFUSING to report success" β and then the fresh-install FALLBACK below ran, succeeded
|
|
2259
|
+
* at re-installing the very same bytes, and its exit 0 became the exit code of the whole command.
|
|
2260
|
+
* A user's scheduled job read that as success while the corpus had not moved.
|
|
2261
|
+
*
|
|
2262
|
+
* The fallback exists for ONE thing: an old bundle whose canonical manifest URL 404s, where a fresh
|
|
2263
|
+
* install genuinely rescues the user. "Nothing landed" is not that case β it is a TRUE verdict
|
|
2264
|
+
* about an intact KB, and re-downloading the same bundle cannot change it. So that verdict is
|
|
2265
|
+
* terminal and keeps its own exit code; every other failure keeps today's fallback behaviour.
|
|
2266
|
+
*/
|
|
2267
|
+
export function classifyUpdaterExit(status, { fallbackAllowed = true } = {}) {
|
|
2268
|
+
if (status === 0) return { verdict: 'updated', fallback: false, exitCode: 0 };
|
|
2269
|
+
if (status === UPDATE_NOT_LANDED) {
|
|
2270
|
+
return { verdict: 'not-landed', fallback: false, exitCode: UPDATE_NOT_LANDED };
|
|
2271
|
+
}
|
|
2272
|
+
return { verdict: 'failed', fallback: fallbackAllowed, exitCode: status || 1 };
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2233
2275
|
function missingUpdaterHelp(kbDir) {
|
|
2234
2276
|
console.error(`\n${c.red('β can\'t update:')} ${c.bold('forge-update.mjs')} is missing from ${kbDir}.`);
|
|
2235
2277
|
console.error(` Either no brain is installed there, or the bundle predates the self-updater.`);
|
|
@@ -2366,12 +2408,29 @@ function runUpdate() {
|
|
|
2366
2408
|
// real user, Jan Lafko, hit). NEVER leave the user stranded at a 404: re-run THIS installer as a
|
|
2367
2409
|
// fresh install, which pulls the latest Release DIRECTLY (releases/latest) and never touches the
|
|
2368
2410
|
// manifest β so --update always succeeds and self-heals the stale SOURCE.json in one shot.
|
|
2369
|
-
|
|
2411
|
+
//
|
|
2412
|
+
// Scoped again 2026-08-04 (issue #106): "nothing landed" is excluded, because for THAT verdict the
|
|
2413
|
+
// fallback re-installed identical bytes and its exit 0 became the command's exit code, turning the
|
|
2414
|
+
// updater's own correct refusal into a reported success.
|
|
2415
|
+
const outcome = classifyUpdaterExit(updateStatus, {
|
|
2416
|
+
fallbackAllowed: !process.env.RUVNET_BRAIN_NO_UPDATE_FALLBACK,
|
|
2417
|
+
});
|
|
2418
|
+
if (outcome.verdict === 'not-landed') {
|
|
2419
|
+
console.error(`\n ${c.red('β the knowledge bundle did not change.')} The updater downloaded a bundle and refused to`);
|
|
2420
|
+
console.error(` call it an update because the copy on disk is identical to the one it replaced.`);
|
|
2421
|
+
console.error(` Nothing is broken and nothing was lost β but this run is ${c.bold('not')} a success, so it exits`);
|
|
2422
|
+
console.error(` ${c.bold(String(UPDATE_NOT_LANDED))} rather than 0 and a scheduled job will see the failure (issue #106).`);
|
|
2423
|
+
console.error(` If you believe a newer build exists, check: ${c.bold('node forge-update.mjs --check')} in ${kbDir}`);
|
|
2424
|
+
process.exit(outcome.exitCode);
|
|
2425
|
+
}
|
|
2426
|
+
if (outcome.fallback) {
|
|
2370
2427
|
warn("\nthe bundle's own updater couldn't complete β falling back to a fresh install of the latest Release (this always works)β¦\n");
|
|
2371
2428
|
const self = fileURLToPath(import.meta.url);
|
|
2372
2429
|
const fr = spawnSync(process.execPath, [self, '--force'], { stdio: 'inherit',
|
|
2373
2430
|
env: { ...process.env, RUVNET_BRAIN_NO_UPDATE_FALLBACK: '1' } });
|
|
2374
2431
|
updateStatus = fr.error ? 1 : (fr.status === null ? 1 : fr.status);
|
|
2432
|
+
} else {
|
|
2433
|
+
updateStatus = outcome.exitCode;
|
|
2375
2434
|
}
|
|
2376
2435
|
}
|
|
2377
2436
|
if (updateStatus === 0) {
|
|
@@ -2382,6 +2441,25 @@ function runUpdate() {
|
|
|
2382
2441
|
updateStatus = 1;
|
|
2383
2442
|
}
|
|
2384
2443
|
}
|
|
2444
|
+
// Issue #87: a normal lifecycle update must also carry MANAGED MODEL additions to an existing
|
|
2445
|
+
// user. This call used to live only in offerRouterProfile() β the fresh-install path β so someone
|
|
2446
|
+
// who already had ~/.claude/model-router/catalog.json (i.e. every existing user, the only ones who
|
|
2447
|
+
// can be behind) never received a newly shipped managed candidate no matter how often they
|
|
2448
|
+
// updated. Additive and non-interactive; their overrides win, metered rows are never auto-enabled,
|
|
2449
|
+
// and a failure here is reported but never fails an otherwise-good update.
|
|
2450
|
+
if (updateStatus === 0) {
|
|
2451
|
+
try {
|
|
2452
|
+
const managed = applyManagedCatalogUpdate({
|
|
2453
|
+
routerDir: path.join(os.homedir(), '.claude', 'model-router'),
|
|
2454
|
+
packageRoot: REPO_ROOT,
|
|
2455
|
+
});
|
|
2456
|
+
if (managed.action === 'merged' && managed.added.length) {
|
|
2457
|
+
ok(`model router: added ${managed.added.length} managed model(s) β ${managed.added.join(', ')} (your catalog edits were preserved)`);
|
|
2458
|
+
}
|
|
2459
|
+
} catch (e) {
|
|
2460
|
+
warn(`managed model additions were not merged (${e.message}); your catalog was left unchanged`);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2385
2463
|
// `--update --auto` = update now AND enroll in Evergreen, so this is the LAST time it's ever run by
|
|
2386
2464
|
// hand. Only enroll if the update itself succeeded β never promise "you're set forever" on a failed
|
|
2387
2465
|
// update. enableNightly() prints its own real verification (plist path + launchctl result).
|
|
@@ -2975,31 +3053,20 @@ export async function offerRouterProfile() {
|
|
|
2975
3053
|
);
|
|
2976
3054
|
|
|
2977
3055
|
fs.mkdirSync(path.join(routerDir, 'bin'), { recursive: true });
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
if (merged !== existing) {
|
|
2993
|
-
fs.copyFileSync(d, `${d}.pre-managed-merge`);
|
|
2994
|
-
const tmp = `${d}.tmp-${process.pid}`;
|
|
2995
|
-
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, { mode: 0o600 });
|
|
2996
|
-
fs.renameSync(tmp, d);
|
|
2997
|
-
ok(`merged ${merged.candidates.length - existing.candidates.length} managed subscription model(s); your catalog overrides were preserved`);
|
|
2998
|
-
}
|
|
2999
|
-
} catch (error) {
|
|
3000
|
-
warn(`managed model additions were not merged (${error.message}); your existing catalog was left unchanged`);
|
|
3001
|
-
}
|
|
3002
|
-
}
|
|
3056
|
+
const policySrc = path.join(pkgRoot, 'config', 'model-router', 'policy.default.mjs');
|
|
3057
|
+
const policyDst = path.join(routerDir, 'policy.default.mjs');
|
|
3058
|
+
if (fs.existsSync(policySrc) && !fs.existsSync(policyDst)) {
|
|
3059
|
+
fs.copyFileSync(policySrc, policyDst);
|
|
3060
|
+
ok('installed policy.default.mjs (edit freely β goldie keeps prices fresh where scheduled)');
|
|
3061
|
+
}
|
|
3062
|
+
// ONE implementation of the managed-catalog step, shared with runUpdate() (issue #87). It used to
|
|
3063
|
+
// live here only, which is exactly why existing users never received managed additions.
|
|
3064
|
+
try {
|
|
3065
|
+
const managed = applyManagedCatalogUpdate({ routerDir, packageRoot: pkgRoot });
|
|
3066
|
+
if (managed.action === 'created') ok('installed catalog.json (edit freely β goldie keeps prices fresh where scheduled)');
|
|
3067
|
+
if (managed.action === 'merged') ok(`merged ${managed.added.length} managed subscription model(s); your catalog overrides were preserved`);
|
|
3068
|
+
} catch (error) {
|
|
3069
|
+
warn(`managed model additions were not merged (${error.message}); your existing catalog was left unchanged`);
|
|
3003
3070
|
}
|
|
3004
3071
|
let copied = 0;
|
|
3005
3072
|
// dispatch-receipt + metaharness-receipts added 2026-07-13: without the LOGGER, subagent routing is
|
package/console/app.js
CHANGED
|
@@ -2313,6 +2313,25 @@ function renderDistribution(u) {
|
|
|
2313
2313
|
el('p', { class: 'fineprint' }, u.note));
|
|
2314
2314
|
}
|
|
2315
2315
|
|
|
2316
|
+
// One provider's key chip. THREE states, never two: found, not found, and NOT CHECKED. The third
|
|
2317
|
+
// exists because an instrument that could not run has found nothing β it has not found "no key"
|
|
2318
|
+
// (issue #86). Pure and total, so the not-checked branch cannot be reached by accident.
|
|
2319
|
+
function providerKeyChip(id, keys, keysVerified, names) {
|
|
2320
|
+
const name = names[id] || id;
|
|
2321
|
+
if (!keysVerified) {
|
|
2322
|
+
return el('span', {
|
|
2323
|
+
class: 'plan-key unknown',
|
|
2324
|
+
title: `Not checked: Brain could not load its provider catalog, so it cannot tell whether an API key for ${name} is set on this machine`,
|
|
2325
|
+
}, el('span', { class: 'plan-key-mark', 'aria-hidden': 'true' }, '?'), ` ${name}`);
|
|
2326
|
+
}
|
|
2327
|
+
const found = !!keys[id];
|
|
2328
|
+
return el('span', {
|
|
2329
|
+
class: `plan-key ${found ? 'yes' : 'no'}`,
|
|
2330
|
+
title: found ? `An API key for ${name} is set on this machine`
|
|
2331
|
+
: `No API key for ${name} found on this machine`,
|
|
2332
|
+
}, el('span', { class: 'plan-key-mark', 'aria-hidden': 'true' }, found ? 'β' : 'β'), ` ${name}`);
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2316
2335
|
// Plan block (issue #24, applying sparkling's #21 redesign) β three genuinely different questions
|
|
2317
2336
|
// used to render as one flat row of identical "chips": which subscription this runs on ("house" β a
|
|
2318
2337
|
// word nobody outside the source knows), whether cheap-task routing is on, and which OTHER API keys
|
|
@@ -2339,19 +2358,21 @@ function renderProviders(sv) {
|
|
|
2339
2358
|
|
|
2340
2359
|
// BOX 1 β YOUR PLAN. The one choice here that changes cost: which subscription MetaHarness treats
|
|
2341
2360
|
// as $0. Footer checklist: which OTHER providers have a real API key on this machine β honest now.
|
|
2361
|
+
//
|
|
2362
|
+
// Issue #86: `keys` is only a MEASUREMENT while the verified provider catalog actually loaded. When
|
|
2363
|
+
// that asset was missing from the packaged runtime, the server fell back to native detections and
|
|
2364
|
+
// said so in providerCatalog β and this checklist ignored it, printing a confident "β β¦ No API key
|
|
2365
|
+
// for Gemini found on this machine" straight off `keys`. That turned an internal packaging failure
|
|
2366
|
+
// into a stated fact about the user's credentials. Not-checked is now its own third state.
|
|
2367
|
+
const catalogHealth = re.providerCatalog || null;
|
|
2368
|
+
const keysVerified = !catalogHealth
|
|
2369
|
+
|| (catalogHealth.keysVerified !== false && catalogHealth.status !== 'degraded');
|
|
2342
2370
|
const others = ['anthropic', 'openai', 'google', 'xai'].filter((id) => id !== house.provider);
|
|
2343
2371
|
const checklist = el('div', { class: 'plan-keys' },
|
|
2344
|
-
el('span', { class: 'plan-keys-lab' }, 'Other keys found:'),
|
|
2345
|
-
...others.map((id) =>
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
class: `plan-key ${ok ? 'yes' : 'no'}`,
|
|
2349
|
-
title: ok ? `An API key for ${KEY_NAME[id]} is set on this machine`
|
|
2350
|
-
: `No API key for ${KEY_NAME[id]} found on this machine`,
|
|
2351
|
-
},
|
|
2352
|
-
el('span', { class: 'plan-key-mark', 'aria-hidden': 'true' }, ok ? 'β' : 'β'),
|
|
2353
|
-
` ${KEY_NAME[id]}`);
|
|
2354
|
-
}));
|
|
2372
|
+
el('span', { class: 'plan-keys-lab' }, keysVerified ? 'Other keys found:' : 'Other keys:'),
|
|
2373
|
+
...others.map((id) => providerKeyChip(id, keys, keysVerified, KEY_NAME)),
|
|
2374
|
+
...(keysVerified ? [] : [el('span', { class: 'plan-keys-note' },
|
|
2375
|
+
`Not checked β Brain could not load its provider catalog${catalogHealth && catalogHealth.detail ? ` (${catalogHealth.detail})` : ''}.`)]));
|
|
2355
2376
|
const planBox = el('div', { class: 'plan-box' },
|
|
2356
2377
|
el('span', { class: 'plan-label' }, 'Your plan', infoBtn('Your plan and OpenRouter', PROVIDERS_INFO)),
|
|
2357
2378
|
head('is-house', houseName, action('Change', 'provider')),
|
package/console/style.css
CHANGED
|
@@ -1236,6 +1236,11 @@ details.sub .sub-body > *:first-child { margin-top: 4px; }
|
|
|
1236
1236
|
.plan-key-mark { font-weight: 700; }
|
|
1237
1237
|
.plan-key.yes .plan-key-mark { color: var(--green-text); }
|
|
1238
1238
|
.plan-key.no, .plan-key.no .plan-key-mark { color: var(--faint); }
|
|
1239
|
+
/* Not checked (issue #86): visually distinct from "no key found" so a degraded catalog never reads
|
|
1240
|
+
as a finding about the user's machine. */
|
|
1241
|
+
.plan-key.unknown { color: var(--muted); }
|
|
1242
|
+
.plan-key.unknown .plan-key-mark { color: var(--amber); }
|
|
1243
|
+
.plan-keys-note { flex-basis: 100%; color: var(--muted); font-size: 11.5px; line-height: 1.5; }
|
|
1239
1244
|
|
|
1240
1245
|
/* WP2b β first-run empty state: confident, designed, not an apology */
|
|
1241
1246
|
.mh-empty {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvnet-brain",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.24",
|
|
4
4
|
"description": "One-command installer for RuvNet Brain β a portable, source-grounded brain over rUv's RuvNet building blocks, delivered as a Claude Code plugin so Claude uses the stack instead of fighting it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvnet-brain",
|
|
3
|
-
"description": "RuvNet brain transplant for Claude Code β grounds every RuvNet decision in real source across
|
|
4
|
-
"version": "4.0.
|
|
3
|
+
"description": "RuvNet brain transplant for Claude Code β grounds every RuvNet decision in real source across 71 rUv repositories, prefers Ruflo / RuVector-RVF / AgentDB over training-prior defaults (pgvector, Pinecone, hand-rolled cosine), and can pull in any RuvNet repo on demand. Ships an enforced UserPromptSubmit retrieve-and-inject grounding hook that sharply reduces drift.",
|
|
4
|
+
"version": "4.0.24",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Stuart Kerr"
|
|
7
7
|
},
|