ruvnet-brain 4.7.21 → 4.7.22
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 +2 -2
- package/bin/install.mjs +126 -9
- package/console/activity.js +122 -38
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/scripts/agentdb-recall-process.mjs +115 -5
- package/plugin/scripts/agentdb-recall-reader.mjs +44 -2
- package/plugin/scripts/agentdb-recall.mjs +34 -12
- package/plugin/scripts/completion-claim-evidence.mjs +185 -11
- package/plugin/scripts/continuation-gate.mjs +52 -7
- package/plugin/scripts/lesson-gate.mjs +7 -39
- package/plugin/scripts/lesson-scope.mjs +51 -0
- package/plugin/scripts/lesson-store.mjs +1 -1
- package/plugin/scripts/ruflo-bin.mjs +14 -6
- package/plugin/scripts/ruflo-memory-hooks.mjs +157 -19
- package/plugin/scripts/session-snapshot-hook.mjs +1 -1
- package/plugin/scripts/session-start-core.mjs +2 -1
- package/plugin/scripts/session-start-health.mjs +3 -2
- package/scripts/codex-boot-surface-proof.mjs +208 -0
- package/scripts/onboarding-console.mjs +76 -15
- package/scripts/recall-latency-probe.mjs +48 -0
- package/scripts/release-qualification-contract.mjs +18 -0
- package/scripts/wired-check.mjs +2 -0
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Created: 2026-06-29 22:36:38 EDT
|
|
|
9
9
|
|
|
10
10
|
### Discover the stack. Read the source. Build with confidence.
|
|
11
11
|
|
|
12
|
-
[](plugin/.claude-plugin/plugin.json)
|
|
13
13
|
|
|
14
14
|
**A source-grounded companion for Claude Code and Codex that helps you find and use Reuven Cohen's (rUv's) RuvNet ecosystem.**
|
|
15
15
|
|
|
@@ -255,7 +255,7 @@ uses the protected release workflow; a consumer update downloads an available ge
|
|
|
255
255
|
does not rebuild upstream knowledge. Check `--doctor`, `SOURCE.json` and refresh receipts
|
|
256
256
|
before treating an installation as current.
|
|
257
257
|
|
|
258
|
-
The **4.6.0 published-corpus baseline** is **
|
|
258
|
+
The **4.6.0 published-corpus baseline** is **206 public stores · 171,869 public source chunks**.
|
|
259
259
|
Those are bundle census values, not a claim that every upstream repository is fresh today.
|
|
260
260
|
Later corpus publication may advance the knowledge generation independently of code changes.
|
|
261
261
|
|
package/bin/install.mjs
CHANGED
|
@@ -85,6 +85,7 @@ import {
|
|
|
85
85
|
import { shellDiff as pluginShellDiff } from '../plugin/scripts/host-shell-boundary.mjs';
|
|
86
86
|
import { codexPluginContentChanged, codexTrustChanges, codexUsesContentTrust, CODEX_TRUST_ACTION } from '../scripts/codex-hook-trust.mjs';
|
|
87
87
|
import { probeFreshCodexDeclarations } from '../scripts/codex-fresh-host-proof.mjs';
|
|
88
|
+
import { proveCodexBootSurface, parseChangedPaths } from '../scripts/codex-boot-surface-proof.mjs';
|
|
88
89
|
import { refreshSharedCodexAppServer } from '../scripts/codex-hook-trust-reconcile.mjs';
|
|
89
90
|
import { recordCodexOvernightNotice } from '../plugin/scripts/codex-overnight-notice.mjs';
|
|
90
91
|
import { repairSecurityGuidance } from '../scripts/security-guidance-codex-compat.mjs';
|
|
@@ -3344,8 +3345,15 @@ async function doctorRun({ json }) {
|
|
|
3344
3345
|
const binary = codexAdministrativeProbeBinary();
|
|
3345
3346
|
const fresh = await probeFreshCodexDeclarations({ binary, codexHome: codexHomeDir(), cwd: process.cwd(),
|
|
3346
3347
|
releasedPluginRoot: path.join(REPO_ROOT, 'plugin'), expectedVersion: PACKAGE_VERSION });
|
|
3347
|
-
|
|
3348
|
-
|
|
3348
|
+
// Issue #391: a receipt that names the hook shim, MCP server or skills/ as changed is proven path by path
|
|
3349
|
+
// (scripts/codex-boot-surface-proof.mjs). Doctor only READS: the receipt on disk is never rewritten here.
|
|
3350
|
+
const changedPaths = parseChangedPaths(codex.sessionSafetyReason);
|
|
3351
|
+
const needsBootProof = changedPaths.some((file) => !['hooks/codex-hooks.json', 'hooks/hooks.json'].includes(file));
|
|
3352
|
+
const bootSurface = needsBootProof ? await proveCodexBootSurfaceHere(changedPaths, fresh) : null;
|
|
3353
|
+
hostConvergence = reconcileFreshCodexDeclarations(recorded, fresh, PACKAGE_VERSION, bootSurface);
|
|
3354
|
+
if (bootSurface?.ok) info(`Codex boot-surface-ready: ${bootSurface.proven.map((p) => p.path).join(', ')} proven for NEW sessions (real shim run, real MCP initialize + tools/list). Existing open windows remain unproven; a host relaunch was not observed.`);
|
|
3355
|
+
else if (bootSurface) warn(`Codex boot surface remains unproven: ${bootSurface.reason}`);
|
|
3356
|
+
else if (fresh.ok) info('Codex fresh-declarations-ready: current trusted hook declarations only; hook bodies and MCP execution were not tested. Existing open windows remain unproven.');
|
|
3349
3357
|
else warn(`Codex fresh declarations remain unproven: ${fresh.reason}`);
|
|
3350
3358
|
}
|
|
3351
3359
|
if (hostConvergence.healthy) {
|
|
@@ -4241,9 +4249,15 @@ export function syncHostsAfterUpdate(cacheDir = resolvedKbDir(), {
|
|
|
4241
4249
|
*/
|
|
4242
4250
|
export async function convergeHostsAfterUpdate(cacheDir = resolvedKbDir(), { refreshHost = refreshCodexSharedHost,
|
|
4243
4251
|
unattended = process.env.RUVNET_NIGHTLY === '1', recordNotice = recordCodexOvernightNotice,
|
|
4244
|
-
noticeHome = () => footprintRoots().brainHome, ...options } = {}) {
|
|
4245
|
-
const
|
|
4252
|
+
noticeHome = () => footprintRoots().brainHome, proveBootSurface = proveCodexBootSurfaceDefault, ...options } = {}) {
|
|
4253
|
+
const brainHome = options.brainHome || process.env.RUVNET_BRAIN_HOME || path.join(os.homedir(), '.cache', 'ruvnet-brain');
|
|
4254
|
+
const receiptPath = path.join(brainHome, 'host-convergence.json');
|
|
4255
|
+
const readReceipt = () => { try { return JSON.parse(fs.readFileSync(receiptPath, 'utf8')); } catch { return null; } };
|
|
4256
|
+
const prior = readReceipt();
|
|
4257
|
+
let convergence = syncHostsAfterUpdate(cacheDir, options);
|
|
4246
4258
|
if (!convergence.results?.codex) return convergence;
|
|
4259
|
+
convergence = await recordCodexBootSurface(convergence, { prior, receiptPath, readReceipt, proveBootSurface,
|
|
4260
|
+
hostLockPath: options.hostLockPath || path.join(brainHome, 'host-convergence.lock') });
|
|
4247
4261
|
// Owner decision (4.7.21 review r2): the unattended nightly never reloads the shared Codex app-server; a reload
|
|
4248
4262
|
// pauses Brain hooks in sessions already open until someone re-reviews them in /hooks. It leaves a notice the next
|
|
4249
4263
|
// SessionStart shows once instead. An interactive --update still reloads.
|
|
@@ -4260,6 +4274,57 @@ export async function convergeHostsAfterUpdate(cacheDir = resolvedKbDir(), { ref
|
|
|
4260
4274
|
return { ...convergence, hostRefresh };
|
|
4261
4275
|
}
|
|
4262
4276
|
|
|
4277
|
+
/**
|
|
4278
|
+
* Issue #391: when Codex still carries a boot-level restart request (a fresh one from this sync, or the previous
|
|
4279
|
+
* receipt's that a repeat sync would otherwise silently drop), prove each changed path and write a NEW receipt:
|
|
4280
|
+
* restartScope 'open-sessions' only when every path is proven, otherwise the request stays 'unproven' with the reason.
|
|
4281
|
+
* A historical receipt is never edited in place; the new one is the atomic replacement the sync itself writes.
|
|
4282
|
+
*/
|
|
4283
|
+
async function recordCodexBootSurface(convergence, { prior, receiptPath, readReceipt, proveBootSurface, hostLockPath }) {
|
|
4284
|
+
if (!convergence.ok && convergence.restartOnly !== true) return convergence;
|
|
4285
|
+
const current = readReceipt();
|
|
4286
|
+
const codex = current?.hosts?.codex;
|
|
4287
|
+
if (!current || current.desiredVersion !== PACKAGE_VERSION || codex?.state !== 'ready') return convergence;
|
|
4288
|
+
const priorCodex = prior?.desiredVersion === PACKAGE_VERSION ? prior.hosts?.codex : null;
|
|
4289
|
+
const fresh = codex.restartRequired === true && codex.restartScope === 'unproven';
|
|
4290
|
+
const carried = codex.restartRequired !== true && priorCodex?.state === 'ready' && priorCodex.restartRequired === true
|
|
4291
|
+
&& priorCodex.restartScope === 'unproven' && convergence.results.codex.action === 'unchanged';
|
|
4292
|
+
if (!fresh && !carried) return convergence;
|
|
4293
|
+
const reason = fresh ? codex.sessionSafetyReason : priorCodex.sessionSafetyReason;
|
|
4294
|
+
const changedPaths = parseChangedPaths(reason);
|
|
4295
|
+
let proof;
|
|
4296
|
+
try { proof = await proveBootSurface({ changedPaths }); }
|
|
4297
|
+
catch (error) { proof = { ok: false, state: 'boot-surface-unproven', changed: changedPaths, proven: [], unproven: [], reason: error?.message || String(error),
|
|
4298
|
+
existingWindows: 'unproven', hostRelaunch: 'not-observed' }; }
|
|
4299
|
+
const ready = proof?.ok === true && proof.state === 'boot-surface-ready';
|
|
4300
|
+
const next = { ...current, verifiedAt: new Date().toISOString(), hosts: { ...current.hosts, codex: { ...codex,
|
|
4301
|
+
restartRequired: true, sessionSafety: 'restart-required', sessionSafetyReason: reason,
|
|
4302
|
+
restartScope: ready ? 'open-sessions' : 'unproven',
|
|
4303
|
+
bootSurface: { state: proof?.state || 'boot-surface-unproven', changed: proof?.changed || changedPaths,
|
|
4304
|
+
proven: (proof?.proven || []).map((p) => p.path), unproven: proof?.unproven || [], reason: proof?.reason || null,
|
|
4305
|
+
existingWindows: 'unproven', hostRelaunch: 'not-observed' } } } };
|
|
4306
|
+
// The receipt is the sync's own file, so this read-modify-write takes the same lock the sync holds (the proof
|
|
4307
|
+
// above can be slow, so the lock is held only for the write). A receipt that changed while we proved, or a lock
|
|
4308
|
+
// we cannot take, means a newer sync owns it: record nothing and leave the prior, still-truthful receipt.
|
|
4309
|
+
const writeLock = acquireHostConvergenceLock(hostLockPath);
|
|
4310
|
+
if (!writeLock.acquired) return { ...convergence, bootSurface: proof, bootSurfaceNotRecorded: `host synchronization lock unavailable: ${writeLock.error}` };
|
|
4311
|
+
try {
|
|
4312
|
+
const latest = readReceipt();
|
|
4313
|
+
if (latest?.verifiedAt !== current.verifiedAt || latest?.desiredVersion !== current.desiredVersion) {
|
|
4314
|
+
return { ...convergence, bootSurface: proof, bootSurfaceNotRecorded: 'host-convergence receipt changed while the boot surface was proven' };
|
|
4315
|
+
}
|
|
4316
|
+
const tmp = `${receiptPath}.tmp-${process.pid}`;
|
|
4317
|
+
fs.writeFileSync(tmp, JSON.stringify(next, null, ' ')); // two-space indent, same bytes as before
|
|
4318
|
+
fs.renameSync(tmp, receiptPath);
|
|
4319
|
+
} finally { writeLock.release(); }
|
|
4320
|
+
const classified = classifyHostConvergence(next);
|
|
4321
|
+
if (classified.healthy) classified.state = 'boot-surface-ready';
|
|
4322
|
+
const { restartOnly, ...rest } = convergence;
|
|
4323
|
+
return { ...rest, ok: classified.healthy === true, convergence: classified, bootSurface: proof,
|
|
4324
|
+
...(!classified.healthy && hostRestartIsOnlyGap(next) ? { restartOnly: true } : {}),
|
|
4325
|
+
error: classified.healthy ? null : `host convergence is ${classified.state}: ${classified.action}` };
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4263
4328
|
/**
|
|
4264
4329
|
* True when a host restart is the ONLY gap in a convergence receipt: every restart-flagged host is ready at the
|
|
4265
4330
|
* expected version, and with those hosts read as "already-open windows only" everything else (other hosts, the
|
|
@@ -4327,7 +4392,7 @@ export function hostSynchronizationFailureMessage(convergence, active, expectedV
|
|
|
4327
4392
|
}
|
|
4328
4393
|
|
|
4329
4394
|
/** Doctor projection only. Fresh hook metadata cannot clear an executable or MCP boot gap. */
|
|
4330
|
-
export function reconcileFreshCodexDeclarations(recorded, proof, expectedVersion = PACKAGE_VERSION) {
|
|
4395
|
+
export function reconcileFreshCodexDeclarations(recorded, proof, expectedVersion = PACKAGE_VERSION, bootSurface = null) {
|
|
4331
4396
|
const original = classifyHostConvergence(recorded, expectedVersion);
|
|
4332
4397
|
const codex = recorded?.hosts?.codex;
|
|
4333
4398
|
if (!proof?.ok || proof.state !== 'fresh-declarations-ready' || proof.expectedVersion !== expectedVersion
|
|
@@ -4338,15 +4403,44 @@ export function reconcileFreshCodexDeclarations(recorded, proof, expectedVersion
|
|
|
4338
4403
|
const prefix = 'boot-level declarations changed: ';
|
|
4339
4404
|
const reason = codex.sessionSafetyReason;
|
|
4340
4405
|
const changed = typeof reason === 'string' && reason.startsWith(prefix) ? reason.slice(prefix.length).split(', ') : [];
|
|
4341
|
-
|
|
4342
|
-
|
|
4406
|
+
const hookFilesOnly = changed.length > 0 && changed.every((file) => ['hooks/codex-hooks.json', 'hooks/hooks.json'].includes(file));
|
|
4407
|
+
// #391: every changed path proven by the boot-surface proof (same list as the receipt names) clears the unproven
|
|
4408
|
+
// scope. The proof's own existingWindows:'unproven' and hostRelaunch:'not-observed' stay on the result.
|
|
4409
|
+
const bootProven = !hookFilesOnly && bootSurface?.ok === true && bootSurface.state === 'boot-surface-ready'
|
|
4410
|
+
&& bootSurface.changed?.length === changed.length && changed.every((file) => bootSurface.changed.includes(file));
|
|
4411
|
+
if (!hookFilesOnly && !bootProven) {
|
|
4412
|
+
return { ...original, freshDeclarations: proof, ...(bootSurface ? { bootSurface } : {}),
|
|
4413
|
+
notice: bootSurface?.reason ? `Fresh Codex hook declarations are ready; boot surface unproven — ${bootSurface.reason}. Existing open windows remain unproven.`
|
|
4414
|
+
: 'Fresh Codex hook declarations are ready; executable/MCP boot readiness and existing open windows remain unproven.' };
|
|
4343
4415
|
}
|
|
4344
4416
|
const projected = classifyHostConvergence({ ...recorded, hosts: { ...recorded.hosts,
|
|
4345
4417
|
codex: { ...codex, restartScope: 'open-sessions' } } }, expectedVersion);
|
|
4418
|
+
if (bootProven) {
|
|
4419
|
+
return { ...projected, ...(projected.healthy ? { state: 'boot-surface-ready' } : {}), freshDeclarations: proof, bootSurface,
|
|
4420
|
+
notice: 'Fresh Codex boot surface is proven for new sessions (hook shim run, MCP initialize + tools/list); existing open windows remain unproven and a host relaunch was not observed.' };
|
|
4421
|
+
}
|
|
4346
4422
|
return { ...projected, ...(projected.healthy ? { state: 'fresh-declarations-ready' } : {}), freshDeclarations: proof,
|
|
4347
4423
|
notice: 'Fresh Codex hook declarations are ready; hook bodies and MCP execution were not tested. Existing open windows remain unproven.' };
|
|
4348
4424
|
}
|
|
4349
4425
|
|
|
4426
|
+
/** Prove the changed Codex boot paths against the plugin tree Codex loads. Read-only; `declarations` is a fresh-declarations probe. */
|
|
4427
|
+
async function proveCodexBootSurfaceHere(changedPaths, declarations) {
|
|
4428
|
+
const codexHome = codexHomeDir();
|
|
4429
|
+
let installedPluginRoot = declarations?.pluginRoot || null;
|
|
4430
|
+
if (!installedPluginRoot) { try { installedPluginRoot = codexInstalledPluginRoot({ codexHome }); } catch { installedPluginRoot = null; } }
|
|
4431
|
+
return await proveCodexBootSurface({ changedPaths, declarations, installedPluginRoot,
|
|
4432
|
+
releasedPluginRoot: path.join(REPO_ROOT, 'plugin') });
|
|
4433
|
+
}
|
|
4434
|
+
|
|
4435
|
+
async function proveCodexBootSurfaceDefault({ changedPaths }) {
|
|
4436
|
+
let declarations;
|
|
4437
|
+
try {
|
|
4438
|
+
declarations = await probeFreshCodexDeclarations({ binary: codexAdministrativeProbeBinary(), codexHome: codexHomeDir(), cwd: process.cwd(),
|
|
4439
|
+
releasedPluginRoot: path.join(REPO_ROOT, 'plugin'), expectedVersion: PACKAGE_VERSION });
|
|
4440
|
+
} catch (error) { declarations = { ok: false, reason: error?.message || String(error) }; }
|
|
4441
|
+
return await proveCodexBootSurfaceHere(changedPaths, declarations);
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4350
4444
|
const HOST_LABELS = { claude: 'Claude Code', codex: 'Codex' };
|
|
4351
4445
|
/** The one accurate line for converged hosts whose already-open windows booted the old declarations. */
|
|
4352
4446
|
export function openSessionsNotice(hosts, version) {
|
|
@@ -6931,6 +7025,7 @@ the installer reports that boot-level declarations changed.
|
|
|
6931
7025
|
let installedTag = null;
|
|
6932
7026
|
let latestTag = null;
|
|
6933
7027
|
let resolvedRelease = null; // reused below so the release is resolved at most once
|
|
7028
|
+
let namedTag = null, namedMove = false, namedDowngrade = false; // --version / --pin over an existing install (#504)
|
|
6934
7029
|
if (alreadyInstalled && !FLAG_FORCE) {
|
|
6935
7030
|
installedTag = installedBrainVersion(cacheDir); // 'unknown' when SOURCE.json has no releaseTag
|
|
6936
7031
|
try {
|
|
@@ -6973,10 +7068,28 @@ the installer reports that boot-level declarations changed.
|
|
|
6973
7068
|
ahead = Boolean(a && b && cmpTag(a, b) > 0);
|
|
6974
7069
|
staleSkip = Boolean(b && (a === null || (a !== b && !ahead)));
|
|
6975
7070
|
}
|
|
7071
|
+
// A NAMED release (--version / --pin) runs no lookup, so latestTag stays null and the block below
|
|
7072
|
+
// used to print "the release lookup failed" for a lookup that never happened, installing nothing
|
|
7073
|
+
// (#504). The operator named the target: same tag = truthful skip; different or unknown = take the
|
|
7074
|
+
// same signature-verified download path. Older than installed is allowed (explicit request), loudly.
|
|
7075
|
+
const named = resolvedRelease && (resolvedRelease.source === 'pinned' || resolvedRelease.source === 'forced')
|
|
7076
|
+
? (resolvedRelease.tag_name || resolvedRelease.tag) : null;
|
|
7077
|
+
if (named) {
|
|
7078
|
+
const nrm = (v) => (v == null || v === 'unknown' ? null : String(v).replace(/^v/, ''));
|
|
7079
|
+
namedTag = named;
|
|
7080
|
+
if (isCorpusReleaseTag(named)) namedMove = installedCorpusTag(cacheDir) !== named;
|
|
7081
|
+
else {
|
|
7082
|
+
namedMove = nrm(installedTag) !== nrm(named);
|
|
7083
|
+
namedDowngrade = Boolean(nrm(installedTag) && cmpTag(nrm(installedTag), nrm(named)) > 0);
|
|
7084
|
+
}
|
|
7085
|
+
}
|
|
6976
7086
|
}
|
|
6977
7087
|
|
|
6978
|
-
if (alreadyInstalled && !FLAG_FORCE && !staleSkip) {
|
|
6979
|
-
if (
|
|
7088
|
+
if (alreadyInstalled && !FLAG_FORCE && !staleSkip && !namedMove) {
|
|
7089
|
+
if (namedTag) {
|
|
7090
|
+
step('Brain already at the named release — skipping the download', `installed ${installedTag} matches ${namedTag}`);
|
|
7091
|
+
ok(`found ${namedTag} at ${cacheDir} — nothing to download`);
|
|
7092
|
+
} else if (latestTag && ahead) {
|
|
6980
7093
|
// Never silently imply equality when the user is AHEAD — that would be a small lie, and it is
|
|
6981
7094
|
// the one that hides a downgrade. Skipping is right; misdescribing why is not.
|
|
6982
7095
|
step('Brain already current — skipping the download', `installed ${installedTag} is NEWER than the latest release (${latestTag})`);
|
|
@@ -6991,6 +7104,10 @@ the installer reports that boot-level declarations changed.
|
|
|
6991
7104
|
info(`When you have a connection: ${c.bold('npx ruvnet-brain --update')} ${c.dim('(or --force to refetch now)')}`);
|
|
6992
7105
|
}
|
|
6993
7106
|
} else {
|
|
7107
|
+
if (namedMove) {
|
|
7108
|
+
if (namedDowngrade) warn(`${namedTag} is OLDER than the installed ${installedTag} — downgrading because you named it explicitly.`);
|
|
7109
|
+
step('Moving to the named release', `installed ${installedTag || 'unknown'} → named ${namedTag}`);
|
|
7110
|
+
}
|
|
6994
7111
|
if (staleSkip) {
|
|
6995
7112
|
step('Brain is out of date — fetching the current release', `installed ${installedTag || 'unknown'} → latest ${latestTag}`);
|
|
6996
7113
|
info(`${c.dim('(the old installer skipped this whenever any brain was present, which is why stale installs never moved)')}`);
|
package/console/activity.js
CHANGED
|
@@ -10,6 +10,81 @@
|
|
|
10
10
|
(function () {
|
|
11
11
|
'use strict';
|
|
12
12
|
|
|
13
|
+
/* #499: every figure this card shows, as display text. A value that could not be read (null /
|
|
14
|
+
undefined / non-numeric) is the word "Unknown" — never 0, which would claim the store is empty.
|
|
15
|
+
Pure and published on the global before the DOM guard below so a test can load this file without a
|
|
16
|
+
page. nMem/nLes/nMachine are numeric (unknown -> 0) for the animation maths only; never display them. */
|
|
17
|
+
function activityFigures(d) {
|
|
18
|
+
function known(v) { return typeof v === 'number' && isFinite(v); }
|
|
19
|
+
function show(v) { return known(v) ? Number(v).toLocaleString('en-US') : 'Unknown'; }
|
|
20
|
+
function num(v) { return known(v) ? v : 0; }
|
|
21
|
+
d = d || {};
|
|
22
|
+
var t = d.totals || {};
|
|
23
|
+
var ag = known(t.agentdbLessons) ? t.agentdbLessons : t.lessons;
|
|
24
|
+
var ap = d.approvedLessons && d.approvedLessons.available === true ? d.approvedLessons.count : null;
|
|
25
|
+
var mach = d.machine && d.machine.totalMemories;
|
|
26
|
+
var apx = d.approvedLessons && d.approvedLessons.available === true ? d.approvedLessons.applicable : null;
|
|
27
|
+
return {
|
|
28
|
+
memories: show(t.memories), agentdbLessons: show(ag), lessons: show(ag),
|
|
29
|
+
approvedLessons: show(ap), approvedMachine: show(ap), approvedApplicable: show(apx), machine: show(mach),
|
|
30
|
+
observations: show(t.observations), patterns: show(t.patterns), episodes: show(t.episodes),
|
|
31
|
+
approvedAvailable: known(ap), agentdbKnown: known(ag),
|
|
32
|
+
nMem: num(t.memories), nLes: num(ag), nMachine: num(mach)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
(typeof globalThis !== 'undefined' ? globalThis : this).activityFigures = activityFigures;
|
|
36
|
+
|
|
37
|
+
function escText(v) { return String(v == null ? '' : v).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); }
|
|
38
|
+
|
|
39
|
+
/* #499: the approved lessons that apply to this project, with the approval / scope / enforcement the owner
|
|
40
|
+
set — rendered from the server's own bounded list. Pure; an unreadable lesson store says Unknown. */
|
|
41
|
+
function approvedLessonsHTML(d) {
|
|
42
|
+
var a = d && d.approvedLessons;
|
|
43
|
+
if (!a || a.available !== true) return '<p class="ab-fallback">Approved lessons: Unknown (the lesson store could not be read).</p>';
|
|
44
|
+
var items = Array.isArray(a.items) ? a.items : [];
|
|
45
|
+
var F = activityFigures(d);
|
|
46
|
+
var head = '<div class="ab-dl-title"><span class="ab-g">✦</span>' + F.approvedApplicable + ' approved lessons apply to this project · ' + F.approvedMachine + ' approved machine-wide</div>';
|
|
47
|
+
if (!items.length) return head + '<p class="ab-fallback">None of your approved lessons are scoped to this project.</p>';
|
|
48
|
+
return head + items.map(function (l) {
|
|
49
|
+
return '<div class="ab-lrow"><b>' + escText(l.id) + '</b> ' + escText(l.title) +
|
|
50
|
+
' <span class="ab-lmeta">scope: ' + escText(l.scope) + ' · enforcement: ' + escText(l.enforcement == null ? 'unknown' : l.enforcement) +
|
|
51
|
+
' · ' + (l.ratified ? 'ratified by you' : escText(l.status || 'not ratified')) + '</span></div>';
|
|
52
|
+
}).join('') + (a.itemsTruncated ? '<p class="ab-fallback">…showing the first ' + items.length + ' of ' + F.approvedApplicable + '.</p>' : '');
|
|
53
|
+
}
|
|
54
|
+
(typeof globalThis !== 'undefined' ? globalThis : this).activityApprovedHTML = approvedLessonsHTML;
|
|
55
|
+
|
|
56
|
+
/* #500: which machine-wide row is "you". By STORE IDENTITY (the server's projectId against each fleet entry's
|
|
57
|
+
id), never by folder name — two unrelated repos can both be called "app". No identity on either side means
|
|
58
|
+
no match (the row is then synthesised from this project's own totals), not a guess from the name. */
|
|
59
|
+
function activityUniverse(d) {
|
|
60
|
+
d = d || {};
|
|
61
|
+
var machine = d.machine || {};
|
|
62
|
+
var projects = (Array.isArray(machine.projects) ? machine.projects.slice() : [])
|
|
63
|
+
.sort(function (a, b) { return (b.memories || 0) - (a.memories || 0); });
|
|
64
|
+
var isMe = function (p) { return !!d.projectId && p.id === d.projectId; };
|
|
65
|
+
var found = projects.filter(isMe)[0] || null;
|
|
66
|
+
var others = projects.filter(function (p) { return !isMe(p); });
|
|
67
|
+
return { projects: projects, me: found, others: others.slice(0, 12), more: Math.max(0, others.length - 12) };
|
|
68
|
+
}
|
|
69
|
+
(typeof globalThis !== 'undefined' ? globalThis : this).activityUniverse = activityUniverse;
|
|
70
|
+
|
|
71
|
+
/* #499: the rolling narration, from each pipeline stage's OWN measurement. Distillation turns observations
|
|
72
|
+
into patterns and episodes; it does not write lessons — lessons are ratified by the owner. */
|
|
73
|
+
function activityNarration(d) {
|
|
74
|
+
var F = activityFigures(d);
|
|
75
|
+
var lines = [
|
|
76
|
+
'Your AI captures everything as it works — ' + F.memories + ' memories in this project so far.',
|
|
77
|
+
'Learning pipeline in this project: ' + F.observations + ' workflow observations, ' + F.patterns + ' structural patterns, ' + F.episodes + ' episodes.',
|
|
78
|
+
'Observations are distilled in the background (session end or compaction, when learning is on) into patterns and episodes — not into lessons.',
|
|
79
|
+
F.approvedApplicable + ' approved lessons apply here (' + F.approvedMachine + ' machine-wide). You ratify lessons; nothing is auto-approved.'
|
|
80
|
+
];
|
|
81
|
+
if (F.agentdbKnown) lines.push(F.agentdbLessons + ' AgentDB lesson records in this project.');
|
|
82
|
+
else lines.push('AgentDB lesson records: Unknown.');
|
|
83
|
+
lines.push('Click any number above to see exactly what it knows.');
|
|
84
|
+
return lines;
|
|
85
|
+
}
|
|
86
|
+
(typeof globalThis !== 'undefined' ? globalThis : this).activityNarration = activityNarration;
|
|
87
|
+
|
|
13
88
|
var CARD = document.getElementById('card-activity');
|
|
14
89
|
var BODY = document.getElementById('body-activity');
|
|
15
90
|
if (!CARD || !BODY) return;
|
|
@@ -148,6 +223,8 @@
|
|
|
148
223
|
'#card-activity .ab-glyph.ab-pulse{animation:ab-pulse-once 1.1s ease-out 1;display:inline-block}',
|
|
149
224
|
'#card-activity .ab-caret{display:inline-block;width:7px;color:var(--amber-text)}',
|
|
150
225
|
/* graceful degrade */
|
|
226
|
+
'#card-activity .ab-lrow{font-size:13px;color:var(--ink-2);margin:6px 2px;line-height:1.4}',
|
|
227
|
+
'#card-activity .ab-lmeta{display:block;font-size:12px;color:var(--muted)}',
|
|
151
228
|
'#card-activity .ab-fallback{margin-top:16px;font-size:13.5px;color:var(--ink-2);font-style:italic}',
|
|
152
229
|
'@media (prefers-reduced-motion:reduce){',
|
|
153
230
|
' #card-activity .ab-glyph.ab-pulse{animation:none}',
|
|
@@ -194,6 +271,14 @@
|
|
|
194
271
|
setChips('<span class="chip tone-grey">unavailable</span>');
|
|
195
272
|
}
|
|
196
273
|
|
|
274
|
+
/* #499: no readable project store, but the approved-lesson count is a separate, readable fact. */
|
|
275
|
+
function degradeWithApprovals(d) {
|
|
276
|
+
var F = activityFigures(d);
|
|
277
|
+
BODY.innerHTML = '<p class="ab-fallback">This project\'s memory store is not readable right now (memories: ' + F.memories + '). ' +
|
|
278
|
+
'<b>' + F.approvedMachine + '</b> approved lessons are in force on this machine' + (F.approvedApplicable !== 'Unknown' ? ' (<b>' + F.approvedApplicable + '</b> apply to this project)' : '') + '.</p>' + approvedLessonsHTML(d);
|
|
279
|
+
setChips('<span class="chip tone-grey">memories unknown</span><span class="chip tone-green">' + F.approvedMachine + ' approved lessons (machine-wide)</span>');
|
|
280
|
+
}
|
|
281
|
+
|
|
197
282
|
/* ---------------- key → plain-English sentence mapper ---------------- */
|
|
198
283
|
var TOOL_LABEL = { memory: 'AgentDB', autocapture: 'Autocapture', hooks: 'Hooks intel', metaharness: 'MetaHarness' };
|
|
199
284
|
var LESSON_TITLES = {
|
|
@@ -267,6 +352,7 @@
|
|
|
267
352
|
fetch('/api/activity', { headers: { Accept: 'application/json' } })
|
|
268
353
|
.then(function (r) { if (!r.ok) throw new Error('http ' + r.status); return r.json(); })
|
|
269
354
|
.then(function (d) {
|
|
355
|
+
if (d && d.hasStore === false && d.approvedLessons && d.approvedLessons.available === true) { degradeWithApprovals(d); return; }
|
|
270
356
|
if (!d || d.hasStore === false || !d.totals) { degrade(); return; }
|
|
271
357
|
render(d);
|
|
272
358
|
})
|
|
@@ -282,8 +368,9 @@
|
|
|
282
368
|
var projects = (Array.isArray(machine.projects) ? machine.projects.slice() : [])
|
|
283
369
|
.sort(function (a, b) { return (b.memories || 0) - (a.memories || 0); });
|
|
284
370
|
var projectName = d.project || 'this project';
|
|
285
|
-
var
|
|
286
|
-
var
|
|
371
|
+
var F = activityFigures(d);
|
|
372
|
+
var nMem = F.nMem, nLes = F.nLes;
|
|
373
|
+
var nProj = projects.length;
|
|
287
374
|
|
|
288
375
|
var LESSONS = lessonsRaw.map(function (l) {
|
|
289
376
|
return { key: l.key, learned: l.learned, recalled: l.access_count || 0, segs: parseExcerpt(l.excerpt) };
|
|
@@ -293,8 +380,10 @@
|
|
|
293
380
|
|
|
294
381
|
/* summary chips */
|
|
295
382
|
setChips(
|
|
296
|
-
'<span class="chip tone-amber">' +
|
|
297
|
-
'<span class="chip tone-green">' +
|
|
383
|
+
'<span class="chip tone-amber">' + F.memories + ' memories</span>' +
|
|
384
|
+
'<span class="chip tone-green">' + F.approvedApplicable + ' approved lessons · this project</span>' +
|
|
385
|
+
'<span class="chip tone-grey">' + F.approvedMachine + ' approved machine-wide</span>' +
|
|
386
|
+
'<span class="chip tone-grey">' + F.agentdbLessons + ' AgentDB lesson records</span>'
|
|
298
387
|
);
|
|
299
388
|
|
|
300
389
|
/* ---------------- panel skeleton ---------------- */
|
|
@@ -302,16 +391,18 @@
|
|
|
302
391
|
'<div class="ab-stats">' +
|
|
303
392
|
'<div class="ab-stat ab-door" id="ab-stat-memories" role="button" tabindex="0" aria-expanded="false" aria-controls="ab-doors">' +
|
|
304
393
|
'<span class="ab-hint"></span><span class="ab-tchev">▸</span>' +
|
|
305
|
-
'<div class="ab-n">' +
|
|
394
|
+
'<div class="ab-n">' + F.memories + '</div><div class="ab-l">memories · this project</div></div>' +
|
|
306
395
|
'<div class="ab-stat ab-door" id="ab-stat-lessons" role="button" tabindex="0" aria-expanded="false" aria-controls="ab-doors">' +
|
|
307
396
|
'<span class="ab-hint"></span><span class="ab-tchev">▸</span>' +
|
|
308
|
-
'<div class="ab-n">' +
|
|
397
|
+
'<div class="ab-n">' + F.approvedApplicable + '</div><div class="ab-l">approved lessons · apply to this project</div>' +
|
|
398
|
+
'<div class="ab-l">' + F.approvedMachine + ' approved machine-wide</div>' +
|
|
399
|
+
'<div class="ab-l">' + F.agentdbLessons + ' AgentDB lesson records · this project</div></div>' +
|
|
309
400
|
'<div class="ab-stat ab-door" id="ab-stat-projects" role="button" tabindex="0" aria-expanded="false" aria-controls="ab-doors">' +
|
|
310
401
|
'<span class="ab-hint"></span><span class="ab-tchev">▸</span>' +
|
|
311
402
|
'<div class="ab-n">' + fmt(nProj) + '</div><div class="ab-l">projects machine-wide</div></div>' +
|
|
312
403
|
'<div class="ab-stat ab-door" id="ab-stat-machine" role="button" tabindex="0" aria-expanded="false" aria-controls="ab-doors">' +
|
|
313
404
|
'<span class="ab-hint"></span><span class="ab-tchev">▸</span>' +
|
|
314
|
-
'<div class="ab-n">' +
|
|
405
|
+
'<div class="ab-n">' + F.machine + '</div><div class="ab-l">memories machine-wide</div></div>' +
|
|
315
406
|
'</div>' +
|
|
316
407
|
'<div class="ab-doors" id="ab-doors">' +
|
|
317
408
|
'<div class="ab-doors-clip"><div class="ab-door-box">' +
|
|
@@ -324,17 +415,17 @@
|
|
|
324
415
|
'<div class="ab-door-view" id="ab-view-universe"></div>' +
|
|
325
416
|
'</div></div>' +
|
|
326
417
|
'</div>' +
|
|
327
|
-
'<div class="ab-flow" id="ab-flow" aria-label="' +
|
|
418
|
+
'<div class="ab-flow" id="ab-flow" aria-label="' + F.memories + ' captured memories, of which ' + F.lessons + ' are kept lesson records">' +
|
|
328
419
|
'<canvas id="ab-flowCanvas"></canvas>' +
|
|
329
|
-
'<div class="ab-flow-hit ab-left" id="ab-flowHitLeft" role="button" tabindex="0" aria-label="see the ' +
|
|
330
|
-
'<div class="ab-flow-hit ab-right" id="ab-flowHitRight" role="button" tabindex="0" aria-label="open the ' +
|
|
331
|
-
'<div class="ab-flow-label ab-left"><span class="mono">' +
|
|
332
|
-
'<div class="ab-flow-label ab-gate">
|
|
333
|
-
'<div class="ab-flow-label ab-right"><span class="mono">' +
|
|
420
|
+
'<div class="ab-flow-hit ab-left" id="ab-flowHitLeft" role="button" tabindex="0" aria-label="see the ' + F.memories + ' memories by kind" title="see the ' + F.memories + ' memories by kind"></div>' +
|
|
421
|
+
'<div class="ab-flow-hit ab-right" id="ab-flowHitRight" role="button" tabindex="0" aria-label="open the ' + F.lessons + ' lesson records" title="open the ' + F.lessons + ' lesson records"></div>' +
|
|
422
|
+
'<div class="ab-flow-label ab-left"><span class="mono">' + F.memories + '</span> captured</div>' +
|
|
423
|
+
'<div class="ab-flow-label ab-gate">curated</div>' +
|
|
424
|
+
'<div class="ab-flow-label ab-right"><span class="mono">' + F.lessons + '</span> lesson records — the keepers</div>' +
|
|
334
425
|
'<div class="ab-flow-narr"><span class="ab-fn-text" id="ab-fnText"></span><span class="ab-fn-steps" id="ab-fnSteps" aria-hidden="true"></span></div>' +
|
|
335
426
|
'</div>' +
|
|
336
|
-
'<div class="ab-vocab"><b>memories</b> = everything your AI captures automatically · <b>
|
|
337
|
-
(nLes ? '' : '<div class="ab-empty-note">
|
|
427
|
+
'<div class="ab-vocab"><b>memories</b> = everything your AI captures automatically · <b>lesson records</b> = the ones kept in the AgentDB lessons namespace (task → what failed → what works) · <b>approved lessons</b> = the ones you ratified. Background distillation makes patterns and episodes, not lessons.</div>' +
|
|
428
|
+
((nLes || !F.agentdbKnown) ? '' : '<div class="ab-empty-note">No lesson records in this project\'s AgentDB yet. Lessons are ratified by you; background distillation (session end or compaction, when learning is on) produces patterns and episodes, not lesson records. <code>record-lesson</code> keeps one now. Approved lessons are counted separately above.</div>') +
|
|
338
429
|
'<div class="ab-roster" id="ab-roster">' +
|
|
339
430
|
'<span class="ab-who">written by</span>' +
|
|
340
431
|
'<span class="ab-rchip" data-tool="memory">AgentDB<span class="ab-cs">via ruflo memory</span><span class="ab-k">✦</span></span>' +
|
|
@@ -404,11 +495,13 @@
|
|
|
404
495
|
});
|
|
405
496
|
|
|
406
497
|
/* ---------------- doors ---------------- */
|
|
407
|
-
document.getElementById('ab-view-lessons').innerHTML = LESSONS.length
|
|
498
|
+
document.getElementById('ab-view-lessons').innerHTML = approvedLessonsHTML(d) + (LESSONS.length
|
|
408
499
|
? LESSONS.map(function (L) {
|
|
409
500
|
return '<div class="ab-dl-title"><span class="ab-g">✦</span>' + esc(lessonTitle(L.key)) + '</div>' + lessonCardHTML(L);
|
|
410
501
|
}).join('')
|
|
411
|
-
:
|
|
502
|
+
: (F.agentdbKnown
|
|
503
|
+
? '<p class="ab-fallback">No lesson records in this project\'s AgentDB yet. Lessons are ratified by you; background distillation produces patterns and episodes, not lesson records. <code>record-lesson</code> keeps one now.</p>'
|
|
504
|
+
: '<p class="ab-fallback">AgentDB lesson records: Unknown (the store could not be read).</p>'));
|
|
412
505
|
|
|
413
506
|
(function () {
|
|
414
507
|
var GLOSS = {
|
|
@@ -418,7 +511,7 @@
|
|
|
418
511
|
'adr-patterns': 'architecture decision patterns',
|
|
419
512
|
'adr-edges': 'links between decisions',
|
|
420
513
|
trajectories: 'reasoning paths kept for replay',
|
|
421
|
-
lessons: '
|
|
514
|
+
lessons: 'lesson records — the keepers',
|
|
422
515
|
'metaharness-audit': 'MetaHarness audit runs',
|
|
423
516
|
feedback: 'edit verifications, scored',
|
|
424
517
|
pattern: 'learned edit patterns',
|
|
@@ -436,7 +529,7 @@
|
|
|
436
529
|
'<span class="ab-kc">' + fmt(b.n) + '</span>' +
|
|
437
530
|
'<span class="ab-kg">' + esc(GLOSS[b.namespace] || '') + '</span></div>';
|
|
438
531
|
}).join('') +
|
|
439
|
-
'<div class="ab-door-foot">' + breakdown.length + ' namespaces · <span class="mono">' +
|
|
532
|
+
'<div class="ab-door-foot">' + breakdown.length + ' namespaces · <span class="mono">' + F.memories + '</span> rows — live from <span class="mono">.swarm/memory.db</span> · bars log-scaled</div>';
|
|
440
533
|
})();
|
|
441
534
|
|
|
442
535
|
(function () {
|
|
@@ -444,9 +537,12 @@
|
|
|
444
537
|
document.getElementById('ab-view-universe').innerHTML = '<p class="ab-fallback">no machine-wide data available</p>';
|
|
445
538
|
return;
|
|
446
539
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
var
|
|
540
|
+
/* #500: "you are here" is the project whose STORE IDENTITY matches, never whose folder name does —
|
|
541
|
+
two unrelated repos can share a name. No identity (older payload) means no match, not a name guess. */
|
|
542
|
+
var U = activityUniverse(d);
|
|
543
|
+
var others = U.others;
|
|
544
|
+
var meRow = U.me || { name: projectName, memories: nMem };
|
|
545
|
+
var more = U.more;
|
|
450
546
|
var top = Math.log10(Math.max(projects[0].memories || 1, 10));
|
|
451
547
|
var base = Math.min(2.7, top - 0.5);
|
|
452
548
|
var wU = function (n) { return Math.round(100 * Math.max(0.10, (Math.log10(Math.max(n, 1)) - base) / (top - base))); };
|
|
@@ -459,12 +555,12 @@
|
|
|
459
555
|
others.map(function (p) { return rowHTML(p.name, p.memories, false); }).join('') +
|
|
460
556
|
rowHTML(meRow.name, meRow.memories, true) +
|
|
461
557
|
(more ? '<div class="ab-umore">…and ' + more + ' more projects</div>' : '') +
|
|
462
|
-
'<div class="ab-door-foot"><span class="mono">' +
|
|
558
|
+
'<div class="ab-door-foot"><span class="mono">' + F.machine + '</span> memories machine-wide — every one persists across sessions and reboots</div>';
|
|
463
559
|
})();
|
|
464
560
|
|
|
465
561
|
var DOOR_META = {
|
|
466
|
-
lessons: [
|
|
467
|
-
memories: [
|
|
562
|
+
lessons: [F.lessons + ' lesson records', 'kept in this project\'s AgentDB lessons namespace — every card is a real memory'],
|
|
563
|
+
memories: [F.memories + ' memories in this project', 'by namespace — what your AI writes down as you work'],
|
|
468
564
|
universe: [fmt(nProj) + ' projects machine-wide', "everywhere this machine's AI remembers"]
|
|
469
565
|
};
|
|
470
566
|
var VIEW_OF = { 'ab-stat-memories': 'memories', 'ab-stat-lessons': 'lessons', 'ab-stat-projects': 'universe', 'ab-stat-machine': 'universe' };
|
|
@@ -721,19 +817,7 @@
|
|
|
721
817
|
|
|
722
818
|
/* ---------------- rolling narration — the takeaway in plain English ---------------- */
|
|
723
819
|
(function () {
|
|
724
|
-
var LINES =
|
|
725
|
-
'Your AI captures everything as it works — ' + fmt(nMem) + ' memories in this project so far.',
|
|
726
|
-
"Most of that stays raw. Only what's proven survives distillation.",
|
|
727
|
-
fmt(nLes) + ' lessons have earned permanence — the task, what failed, what actually works.',
|
|
728
|
-
"These survive compaction, restarts, and reboots. Your AI doesn't start over tomorrow.",
|
|
729
|
-
'Click any number above to see exactly what it knows.'
|
|
730
|
-
] : [
|
|
731
|
-
'Your AI captures everything as it works — ' + fmt(nMem) + ' memories in this project so far.',
|
|
732
|
-
"Most of that stays raw. Only what's proven survives distillation.",
|
|
733
|
-
'0 lessons distilled yet. Distillation is manual for now; there is no auto-distill.',
|
|
734
|
-
'Capture one any time: run record-lesson in this project.',
|
|
735
|
-
'Click any number above to see exactly what it knows.'
|
|
736
|
-
];
|
|
820
|
+
var LINES = activityNarration(d);
|
|
737
821
|
var txt = document.getElementById('ab-fnText');
|
|
738
822
|
var steps = document.getElementById('ab-fnSteps');
|
|
739
823
|
var idx = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvnet-brain",
|
|
3
|
-
"version": "4.7.
|
|
3
|
+
"version": "4.7.22",
|
|
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
3
|
"description": "RuvNet brain transplant for Claude Code — grounds every RuvNet decision in real source across 77 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 a UserPromptSubmit retrieve-and-inject grounding hook and a PreToolUse write gate that refuses ungrounded rUv-product code until search_ruvnet has been consulted (ADR-0012 / ADR-067).",
|
|
4
|
-
"version": "4.7.
|
|
4
|
+
"version": "4.7.22",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Stuart Kerr"
|
|
7
7
|
},
|