ruvnet-brain 4.0.12 → 4.0.28
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 +5 -5
- 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 +129 -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/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/runtime-preferences.mjs +18 -0
- package/plugin/scripts/session-start-core.mjs +3 -3
- 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/corpus-qa.mjs +44 -6
- package/scripts/doc-currency.mjs +30 -2
- package/scripts/gates.mjs +4 -146
- package/scripts/goal-match.mjs +4 -398
- package/scripts/hook-registry.mjs +4 -567
- 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 -345
- package/scripts/nightly-controller.mjs +4 -66
- package/scripts/nightly-wrapper.sh +23 -1
- package/scripts/proactivity-metrics.mjs +8 -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 +61 -7
- package/scripts/release-transaction.mjs +63 -17
- package/scripts/self-update.mjs +63 -10
- package/scripts/user-settings.mjs +4 -640
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// nightly-controller.mjs — a thin adapter around the installer's one scheduler implementation.
|
|
2
|
+
//
|
|
3
|
+
// It does not write a plist, call launchctl, or invent platform behavior. Both the installer and the
|
|
4
|
+
// console reach the same `bin/install.mjs --enable-nightly/--disable-nightly` door; this adapter only
|
|
5
|
+
// supplies structured status and captures its exit result for the console.
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
|
|
13
|
+
// ROOT is the tree that holds `bin/install.mjs`, and it is resolved by an EXACT layout test rather
|
|
14
|
+
// than by `..` — because `..` means two different things since this file moved into the payload
|
|
15
|
+
// (ADR-065). From `<root>/scripts/` it was the root; from `<root>/plugin/scripts/` it is
|
|
16
|
+
// `<root>/plugin`, and `<root>/plugin/bin/install.mjs` does not exist. Caught live by
|
|
17
|
+
// console-apply-timings.test.mjs, which drove a real /api/apply through the console and got
|
|
18
|
+
// `Error: Cannot find module '<root>/plugin/bin/install.mjs'` back inside a 200 response — a remedy
|
|
19
|
+
// that reported failure honestly, but failed for a packaging reason nobody would have guessed.
|
|
20
|
+
//
|
|
21
|
+
// The test is exact, not a heuristic: this file's directory IS `<candidate>/plugin/scripts` if and
|
|
22
|
+
// only if `<candidate>` is a non-flattened root. A flattened install (the Spine's versions/<gen>/,
|
|
23
|
+
// the plugin cache's <ver>/) has no `plugin/` level, so `../..` is some unrelated parent and the
|
|
24
|
+
// answer falls back to `..` — where `bin/` also does not exist, and applyNightlyChoice() then
|
|
25
|
+
// reports that honestly instead of silently spawning nothing. nightlyStatus() only reads a plist and
|
|
26
|
+
// needs no installer at all, so status stays correct in every layout.
|
|
27
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const ROOT = path.resolve(HERE, '..', '..', 'plugin', 'scripts') === HERE
|
|
29
|
+
? path.resolve(HERE, '..', '..')
|
|
30
|
+
: path.resolve(HERE, '..');
|
|
31
|
+
const INSTALLER = path.join(ROOT, 'bin', 'install.mjs');
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* THE NIGHTLY JOB'S NAME, stated once for everything that has to recognise it.
|
|
35
|
+
*
|
|
36
|
+
* Exported because issue #113 was two files disagreeing about this exact string with nothing to
|
|
37
|
+
* notice: bin/install.mjs writes a LaunchAgent labelled `com.ruvnet.brain-update`, while
|
|
38
|
+
* capability-registry.mjs looked for launchd jobs matching `/(nightly|refresh)/` — so the console
|
|
39
|
+
* reported "not installed" about the very job the installer had loaded, scheduled and run. A label
|
|
40
|
+
* is an interface between the writer and everyone who looks for it; spelling it out per reader is
|
|
41
|
+
* how the two drift apart silently.
|
|
42
|
+
*
|
|
43
|
+
* bin/install.mjs still holds its own literal (installer-owned code, changed under its own review);
|
|
44
|
+
* tests/unit/nightly-job-identity.test.mjs asserts the two are the same string, so a drift is a red
|
|
45
|
+
* test rather than a capability that quietly disappears from the console.
|
|
46
|
+
*/
|
|
47
|
+
export const NIGHTLY_LABEL = 'com.ruvnet.brain-update';
|
|
48
|
+
|
|
49
|
+
export function nightlyArtifact({ env = process.env, platform = process.platform } = {}) {
|
|
50
|
+
const home = env.HOME || os.homedir();
|
|
51
|
+
return {
|
|
52
|
+
supported: platform === 'darwin',
|
|
53
|
+
platform,
|
|
54
|
+
path: path.join(home, 'Library', 'LaunchAgents', `${NIGHTLY_LABEL}.plist`),
|
|
55
|
+
label: NIGHTLY_LABEL,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function nightlyStatus(options = {}) {
|
|
60
|
+
const artifact = nightlyArtifact(options);
|
|
61
|
+
if (!artifact.supported) {
|
|
62
|
+
return { state: 'unsupported', evidence: `No reversible scheduler adapter is implemented for ${artifact.platform}.`, artifact };
|
|
63
|
+
}
|
|
64
|
+
const present = fs.existsSync(artifact.path);
|
|
65
|
+
return {
|
|
66
|
+
state: present ? 'on' : 'off',
|
|
67
|
+
evidence: present ? `LaunchAgent plist exists at ${artifact.path}` : `No LaunchAgent plist at ${artifact.path}`,
|
|
68
|
+
artifact,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function applyNightlyChoice(enabled, options = {}) {
|
|
73
|
+
if (typeof enabled !== 'boolean') return { ok: false, log: 'nightly must be true or false' };
|
|
74
|
+
const env = options.env || process.env;
|
|
75
|
+
const before = nightlyStatus({ ...options, env });
|
|
76
|
+
if (!before.artifact.supported) return { ok: false, state: before, log: before.evidence };
|
|
77
|
+
const run = spawnSync(process.execPath, [
|
|
78
|
+
options.installer || INSTALLER,
|
|
79
|
+
enabled ? '--enable-nightly' : '--disable-nightly',
|
|
80
|
+
], {
|
|
81
|
+
env: { ...env, RUVNET_BRAIN_IMPORT_ONLY: '0' },
|
|
82
|
+
cwd: options.cwd || ROOT,
|
|
83
|
+
encoding: 'utf8',
|
|
84
|
+
shell: false,
|
|
85
|
+
timeout: options.timeout || 30_000,
|
|
86
|
+
});
|
|
87
|
+
const after = nightlyStatus({ ...options, env });
|
|
88
|
+
const desired = enabled ? 'on' : 'off';
|
|
89
|
+
const ok = !run.error && run.status === 0 && after.state === desired;
|
|
90
|
+
return {
|
|
91
|
+
ok,
|
|
92
|
+
before,
|
|
93
|
+
after,
|
|
94
|
+
log: ok
|
|
95
|
+
? `Nightly refresh is ${desired}; verified from ${after.artifact.path}.`
|
|
96
|
+
: `Nightly refresh did not reach ${desired}: ${run.error?.message || run.stderr?.trim() || run.stdout?.trim() || `exit ${run.status}`}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -66,6 +66,18 @@ function validSettings(raw = {}) {
|
|
|
66
66
|
newProjectDefaults: source.newProjectDefaults === true,
|
|
67
67
|
advocacy: Number.isInteger(source.advocacy) && source.advocacy >= 1 && source.advocacy <= 5
|
|
68
68
|
? source.advocacy : 3,
|
|
69
|
+
// ADR-063 / issue #103. Unknown keys are DROPPED by this function, so a setting absent here is
|
|
70
|
+
// silently unreadable no matter what the Console writes — which is how the first cut of the
|
|
71
|
+
// managed-memory boundary read `advise` even when the file said `block`.
|
|
72
|
+
//
|
|
73
|
+
// NOTE THE DUPLICATION, deliberately not "fixed" here: scripts/user-settings.mjs SETTINGS_SCHEMA
|
|
74
|
+
// is the authority for these keys and this is a second enumeration of it. Deriving one from the
|
|
75
|
+
// other is the obvious move and it is the WRONG one — this file ships inside plugin/, that one
|
|
76
|
+
// does not, so the import would resolve in the checkout and throw ERR_MODULE_NOT_FOUND on a real
|
|
77
|
+
// install. Same trap as kb/forge-update.mjs reaching for ../scripts. The honest fix is a drift
|
|
78
|
+
// test that reads both files, not a bridge that breaks installs.
|
|
79
|
+
managedMemoryBoundary: ['advise', 'read-only', 'block'].includes(source.managedMemoryBoundary)
|
|
80
|
+
? source.managedMemoryBoundary : 'advise',
|
|
69
81
|
};
|
|
70
82
|
}
|
|
71
83
|
|
|
@@ -263,6 +275,12 @@ export function seedProjectDefaults(options = {}) {
|
|
|
263
275
|
|
|
264
276
|
if (process.argv.includes('--learning-scope')) {
|
|
265
277
|
process.stdout.write(`${loadRuntimePreferences().values.learningScope}\n`);
|
|
278
|
+
} else if (process.argv.includes('--managed-memory-boundary')) {
|
|
279
|
+
// ADR-063 / issue #103. hijack-ruvnet.sh is POSIX sh and must not parse JSON, so it asks here —
|
|
280
|
+
// the same idiom learn-capture.sh already uses for --learning-scope. Falls back to the shipped
|
|
281
|
+
// default rather than erroring: a hook that cannot read a preference must never refuse a command
|
|
282
|
+
// because of it, so an unreadable settings file degrades to `advise`, which blocks nothing.
|
|
283
|
+
process.stdout.write(`${loadRuntimePreferences().values.managedMemoryBoundary || 'advise'}\n`);
|
|
266
284
|
} else if (process.argv.includes('--seed-project')) {
|
|
267
285
|
const result = seedProjectDefaults();
|
|
268
286
|
if (!result.ok) process.exitCode = 1;
|
|
@@ -492,14 +492,14 @@ export async function runSessionStart({
|
|
|
492
492
|
}
|
|
493
493
|
let confidenceInstruction;
|
|
494
494
|
if (readiness.state === 'ready') {
|
|
495
|
-
emit('USER-LEVEL: one brain (~/.cache/ruvnet-brain/kb) shared by every project and window here — nothing to reinstall per project. search_ruvnet is ready and live; the grounding hooks are active.');
|
|
495
|
+
emit('USER-LEVEL: one brain ON DISK (~/.cache/ruvnet-brain/kb) shared by every project and window here — nothing to reinstall per project (each window still runs its own worker process, which now exits when idle). search_ruvnet is ready and live; the grounding hooks are active.');
|
|
496
496
|
confidenceInstruction = `Open your FIRST response with ONE short, warm confirmation in your own words (2-3 lines, then move on; never repeat it this session). It must say "🧠 RuvNet Brain active (v${bannerVersion})" — ONE version, in parentheses, always; never a second number, never a bundle tag beside it — and convey: it grounds rUv's stack (RVF, Ruflo, AgentDB, SPARC, agentic-flow…) in his real source rather than guessing; npx github:stuinfla/ruvnet-brain --doctor checks it; ${consoleInvoke} opens a visual settings page.`;
|
|
497
497
|
} else if (readiness.state === 'degraded') {
|
|
498
498
|
const receipt = readiness.receipt || {};
|
|
499
|
-
emit(`USER-LEVEL: one brain (~/.cache/ruvnet-brain/kb) shared by every project and window here — nothing to reinstall per project. search_ruvnet is registered but degraded (${receipt.phase || 'startup'}: ${receipt.error || 'readiness failed'}); the grounding hooks remain active.`);
|
|
499
|
+
emit(`USER-LEVEL: one brain ON DISK (~/.cache/ruvnet-brain/kb) shared by every project and window here — nothing to reinstall per project (each window still runs its own worker process, which now exits when idle). search_ruvnet is registered but degraded (${receipt.phase || 'startup'}: ${receipt.error || 'readiness failed'}); the grounding hooks remain active.`);
|
|
500
500
|
confidenceInstruction = `Open your FIRST response with ONE short line: "🧠 RuvNet Brain active (v${bannerVersion}) — search is degraded right now." Do not claim source grounding until a real search succeeds. npx github:stuinfla/ruvnet-brain --doctor shows the current verdict; ${consoleInvoke} opens the Console.`;
|
|
501
501
|
} else {
|
|
502
|
-
emit('USER-LEVEL: one brain (~/.cache/ruvnet-brain/kb) shared by every project and window here — nothing to reinstall per project. search_ruvnet is registered; live readiness is not yet proven. The grounding hooks are active.');
|
|
502
|
+
emit('USER-LEVEL: one brain ON DISK (~/.cache/ruvnet-brain/kb) shared by every project and window here — nothing to reinstall per project (each window still runs its own worker process, which now exits when idle). search_ruvnet is registered; live readiness is not yet proven. The grounding hooks are active.');
|
|
503
503
|
confidenceInstruction = `Open your FIRST response with ONE short line: "🧠 RuvNet Brain active (v${bannerVersion}) — search is registered and will prove readiness on first use." Do not claim source grounding until a real search returns a citation. npx github:stuinfla/ruvnet-brain --doctor shows the current verdict; ${consoleInvoke} opens the Console.`;
|
|
504
504
|
}
|
|
505
505
|
emit(confidenceInstruction);
|
|
@@ -77,12 +77,25 @@ import { spawnSync } from 'node:child_process';
|
|
|
77
77
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
78
78
|
import { readStdinBounded } from './hook-input.mjs';
|
|
79
79
|
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
80
|
+
// WHERE THIS FILE'S SIBLINGS LIVE. Resolved from THIS file's own location so it is correct under the
|
|
81
|
+
// Stable Spine (an immutable versions/<gen> tree) and in a dev checkout alike — hook-shim.mjs has
|
|
82
|
+
// already chosen the tree by the time it dispatches this body.
|
|
83
|
+
//
|
|
84
|
+
// THIS USED TO ALSO COMPUTE `CODE_ROOT = SCRIPTS_DIR/../..`, and that line was WRONG in exactly the
|
|
85
|
+
// way anticipate.sh's was (PR #114): it assumed `scripts/` is a sibling of `plugin/`, which holds
|
|
86
|
+
// ONLY in a git checkout. Both shipped layouts flatten the `plugin/` level —
|
|
87
|
+
//
|
|
88
|
+
// ~/.cache/ruvnet-brain/versions/<gen>/scripts/… → ../.. = …/versions
|
|
89
|
+
// ~/.claude/plugins/cache/…/<ver>/scripts/… → ../.. = …/ruvnet-brain
|
|
90
|
+
// <src>/plugin/scripts/… → ../.. = <src> ← only this one
|
|
91
|
+
//
|
|
92
|
+
// — so `$CODE_ROOT/scripts/user-settings.mjs` pointed at nothing on every real install. Both consumers
|
|
93
|
+
// (the 1–5 advocacy dial and the DismissalLedger, wired at SETTINGS_MODULE/ADVOCACY_MODULE below)
|
|
94
|
+
// `catch` an import failure and fall back to defaults, so the dial was silently UNREAD for every user
|
|
95
|
+
// while the code read as if it were honoured. Same defect class, same fix: the modules are SIBLINGS of
|
|
96
|
+
// this file inside the payload, so name them from SCRIPTS_DIR and never reach outside it.
|
|
83
97
|
const SELF = fileURLToPath(import.meta.url);
|
|
84
|
-
const SCRIPTS_DIR = path.dirname(SELF); //
|
|
85
|
-
const CODE_ROOT = path.resolve(SCRIPTS_DIR, '..', '..'); // <codeRoot>
|
|
98
|
+
const SCRIPTS_DIR = path.dirname(SELF); // the payload's scripts/ dir, whatever its parent
|
|
86
99
|
|
|
87
100
|
// The CC event name the shim forwarded. No event → nothing to run; stay silent.
|
|
88
101
|
const EVENT = process.argv[2] || '';
|
|
@@ -235,8 +248,10 @@ if (!candidates.length) silent();
|
|
|
235
248
|
// RUVNET_SETTINGS_FILE); suppression comes from advocacy-outcomes.shouldStillOffer()/record(). Both
|
|
236
249
|
// are loaded only if an advocacy/promotion candidate is actually present, so a lesson- or alarm-only
|
|
237
250
|
// event pays nothing for them.
|
|
238
|
-
|
|
239
|
-
|
|
251
|
+
// SIBLINGS, not `$CODE_ROOT/scripts/…` — see the SCRIPTS_DIR note at the top of this file for the
|
|
252
|
+
// measured layouts that made the old path resolve to nothing on every shipped install.
|
|
253
|
+
const SETTINGS_MODULE = process.env.RUVNET_USER_SETTINGS_MODULE || path.join(SCRIPTS_DIR, 'user-settings.mjs');
|
|
254
|
+
const ADVOCACY_MODULE = process.env.RUVNET_ADVOCACY_OUTCOMES_MODULE || path.join(SCRIPTS_DIR, 'advocacy-outcomes.mjs');
|
|
240
255
|
|
|
241
256
|
// THE 1–5 DIAL (ADR-052). This runtime is the SINGLE enforcement chokepoint (DDD-0004): the level maps
|
|
242
257
|
// to exactly what each channel is allowed to deliver, and no producer decides. LEVEL_POLICY is the one
|