ruvnet-brain 4.5.8 → 4.5.10

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.
Files changed (42) hide show
  1. package/README.md +1 -1
  2. package/bin/install.mjs +50 -93
  3. package/bin/nightly-refresh.mjs +20 -34
  4. package/kb/download-retry.mjs +97 -0
  5. package/kb/forge-update.mjs +12 -10
  6. package/kb/verify-citation.mjs +10 -13
  7. package/package.json +2 -1
  8. package/plugin/.claude-plugin/plugin.json +1 -1
  9. package/plugin/.codex-plugin/plugin.json +1 -1
  10. package/plugin/scripts/advocacy-catalog.mjs +1 -0
  11. package/plugin/scripts/automatic-update.mjs +125 -0
  12. package/plugin/scripts/completion-claim-evidence.mjs +9 -4
  13. package/plugin/scripts/continuation-gate.mjs +34 -10
  14. package/plugin/scripts/continuation-objective.mjs +45 -10
  15. package/plugin/scripts/continuity-journal.mjs +11 -11
  16. package/plugin/scripts/ground-before-write.sh +27 -11
  17. package/plugin/scripts/ground-ruvnet.sh +3 -0
  18. package/plugin/scripts/grounding-code-projection.mjs +90 -0
  19. package/plugin/scripts/grounding-turn-evidence.mjs +2 -2
  20. package/plugin/scripts/grounding-turn-gate.mjs +8 -14
  21. package/plugin/scripts/grounding-turn-mark.mjs +106 -20
  22. package/plugin/scripts/hijack-ruvnet.sh +4 -4
  23. package/plugin/scripts/hook-input.mjs +1 -1
  24. package/plugin/scripts/host-update.mjs +16 -15
  25. package/plugin/scripts/lesson-presentation.mjs +2 -1
  26. package/plugin/scripts/nightly-scheduler.mjs +34 -7
  27. package/plugin/scripts/project-progression-hook.mjs +22 -10
  28. package/plugin/scripts/project-progression-producer.mjs +11 -1
  29. package/plugin/scripts/project-progression-store.mjs +3 -0
  30. package/plugin/scripts/project-transition-hook.mjs +7 -1
  31. package/plugin/scripts/ruvnet-gate1-pattern.mjs +33 -0
  32. package/plugin/scripts/session-snapshot-hook.mjs +4 -3
  33. package/plugin/scripts/session-start-core.mjs +3 -1
  34. package/plugin/scripts/turn-capture-privacy.mjs +202 -0
  35. package/plugin/scripts/turn-outcome-capture.mjs +28 -13
  36. package/plugin/scripts/user-settings.mjs +23 -1
  37. package/scripts/brain-grade-groundtruth.mjs +1 -0
  38. package/scripts/brain-score.mjs +11 -11
  39. package/scripts/issue-watch.mjs +21 -10
  40. package/scripts/model-weekly-cycle.mjs +45 -9
  41. package/scripts/record-lesson.mjs +13 -7
  42. package/scripts/release-qualification-contract.mjs +40 -1
@@ -36,16 +36,50 @@ function writeOwned(dir, token, file, value) { const committed = transaction(dir
36
36
  function release(dir, token) { transaction(dir, () => { if (json(dir, ownerFile, 4096)?.token === token) fs.unlinkSync(path.join(dir, ownerFile)); }); }
37
37
  export function runCycleStage({ script, args, timeoutMs, env, spawnHost = spawn }) {
38
38
  return new Promise((resolve, reject) => {
39
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 900000) throw new Error('Invalid weekly stage deadline');
40
+ const deadline = performance.now() + timeoutMs, retirementMs = Math.min(1000, timeoutMs / 10);
41
+ const workDeadline = deadline - retirementMs;
39
42
  const child = spawnHost(process.execPath, [path.join(DIR, script), ...args], { env, shell: false, stdio: ['ignore', 'pipe', 'pipe'] });
40
- let stdout = '', timedOut = false, killTimer;
41
- const timer = setTimeout(() => { timedOut = true; child.kill('SIGTERM'); killTimer = setTimeout(() => child.kill('SIGKILL'), 1000); }, timeoutMs);
42
- child.stderr.on('data', () => {}); child.stdout.on('data', (d) => { stdout += d; if (stdout.length > 262144) { timedOut = true; child.kill('SIGKILL'); } });
43
- child.once('error', () => { clearTimeout(timer); clearTimeout(killTimer); reject(new Error('Weekly cycle child host unavailable')); });
44
- child.once('exit', (code) => { clearTimeout(timer); clearTimeout(killTimer);
45
- if (timedOut) return reject(new Error('Weekly cycle child exceeded bounded deadline or output limit'));
46
- let result; try { result = JSON.parse(stdout.trim().split('\n').at(-1)); } catch { return reject(new Error('Weekly cycle child receipt missing')); }
47
- resolve({ code, result });
43
+ let stdout = '', outputBytes = 0, failure, settled = false, exitObserved = false;
44
+ let timer, killTimer, retirementTimer;
45
+ const kill = (signal) => { try { child.kill(signal); } catch { /* owned child retirement remains unverified */ } };
46
+ const finish = (error, value) => {
47
+ if (settled) return; settled = true;
48
+ clearTimeout(timer); clearTimeout(killTimer); clearTimeout(retirementTimer);
49
+ // Drop only our child's referenced handles, including pipes held open after its exit.
50
+ // Signals/exit cannot prove detached descendants retired.
51
+ for (const stream of [child.stdin, child.stdout, child.stderr]) { try { stream?.destroy?.(); } catch { /* best effort */ } }
52
+ try { child.unref?.(); } catch { /* best effort */ }
53
+ if (error) {
54
+ error.stageCleanup = { ownedChildPid: child.pid ?? null, childExitObserved: exitObserved,
55
+ ownedChildRetirement: exitObserved ? 'exit-observed' : 'unverified', descendantRetirement: 'unproven' };
56
+ reject(error);
57
+ } else resolve(value);
58
+ };
59
+ const fail = (reason) => {
60
+ if (settled || failure) return; failure = new Error(reason);
61
+ // Reserve retirement inside the caller's budget; do not wait indefinitely for exit/close.
62
+ const remaining = Math.max(0, Math.min(retirementMs, deadline - performance.now()));
63
+ killTimer = setTimeout(() => kill('SIGKILL'), remaining / 2);
64
+ retirementTimer = setTimeout(() => finish(failure), remaining);
65
+ kill('SIGTERM');
66
+ };
67
+ const consume = (chunk, capture) => {
68
+ if (settled || failure) return;
69
+ if (performance.now() >= workDeadline) return fail('Weekly cycle child exceeded bounded deadline or output limit');
70
+ outputBytes += Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(String(chunk));
71
+ if (outputBytes > 262144) return fail('Weekly cycle child exceeded bounded deadline or output limit');
72
+ if (capture) stdout += chunk;
73
+ };
74
+ child.stderr.on('data', (chunk) => consume(chunk, false)); child.stdout.on('data', (chunk) => consume(chunk, true));
75
+ child.on('error', () => fail('Weekly cycle child host unavailable'));
76
+ child.once('exit', (code) => {
77
+ exitObserved = true; if (failure) return finish(failure); if (settled) return;
78
+ if (performance.now() >= workDeadline) { fail('Weekly cycle child exceeded bounded deadline or output limit'); return finish(failure); }
79
+ let result; try { result = JSON.parse(stdout.trim().split('\n').at(-1)); } catch { return finish(new Error('Weekly cycle child receipt missing')); }
80
+ finish(null, { code, result });
48
81
  });
82
+ timer = setTimeout(() => fail('Weekly cycle child exceeded bounded deadline or output limit'), Math.max(0, workDeadline - performance.now()));
49
83
  });
50
84
  }
51
85
  const INVENTORY_URL = 'https://openrouter.ai/api/v1/models';
@@ -172,7 +206,9 @@ export async function runWeeklyCycle({ routerDir = path.join(os.homedir(), '.cla
172
206
  delete state.pendingSemanticReceipt; delete state.pendingSemanticReleaseIds;
173
207
  state.lastQualification = qualified.result; save(STATE_FILE, state); record.status = 'complete';
174
208
  save('weekly-cycle-last-attempt.json', record); return record;
175
- } catch (error) { record.status = 'failed'; record.releaseStatus = 'unknown-or-pending'; record.reason = error.message.slice(0, 240); try { save('weekly-cycle-last-attempt.json', record); } catch { /* superseded cannot write */ } return record;
209
+ } catch (error) { record.status = 'failed'; record.releaseStatus = 'unknown-or-pending'; record.reason = error.message.slice(0, 240);
210
+ if (error.stageCleanup) record.stageCleanup = error.stageCleanup;
211
+ try { save('weekly-cycle-last-attempt.json', record); } catch { /* superseded cannot write */ } return record;
176
212
  } finally { release(routerDir, token); }
177
213
  }
178
214
  if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
@@ -19,7 +19,8 @@
19
19
  import { execFileSync } from 'node:child_process';
20
20
  import fs from 'node:fs';
21
21
  import path from 'node:path';
22
- import { resolveRuflo, RUFLO_MISSING } from '../plugin/scripts/ruflo-bin.mjs';
22
+ import { randomUUID } from 'node:crypto';
23
+ import { resolveRuflo, rufloInvocation, RUFLO_MISSING } from '../plugin/scripts/ruflo-bin.mjs';
23
24
 
24
25
  const arg = (name, def = '') => {
25
26
  const i = process.argv.indexOf(`--${name}`);
@@ -60,21 +61,26 @@ const value = [
60
61
  worked ? `WORKED: ${worked}` : null,
61
62
  critique ? `CRITIQUE: ${critique}` : null,
62
63
  `OUTCOME: ${outcome}`,
64
+ `RECORDING: ${randomUUID()}`, // binds the read-back to THIS canonical lesson write
63
65
  ].filter(Boolean).join(' ');
64
66
 
65
67
  // Every `ruflo` invocation auto-starts a project background daemon unless this is set (verified
66
68
  // live: ~/.npm-global/lib/node_modules/ruflo/node_modules/@claude-flow/cli/dist/src/services/
67
69
  // daemon-autostart.js:85) — recording a lesson has no business leaving one running.
68
- const ruflo = (args) =>
69
- execFileSync(RUFLO, args, { cwd: dir, encoding: 'utf8', timeout: 60000,
70
- shell: process.platform === 'win32', env: { ...process.env, RUFLO_DAEMON_AUTOSTART: '0' } });
70
+ const ruflo = (args) => {
71
+ const invocation = rufloInvocation(RUFLO, args);
72
+ return execFileSync(invocation.executable, invocation.args, {
73
+ cwd: dir, encoding: 'utf8', timeout: 60000, shell: false,
74
+ env: { ...process.env, RUFLO_DAEMON_AUTOSTART: '0' },
75
+ });
76
+ };
71
77
 
72
78
  console.log(`\nRecording lesson into ${path.basename(dir)}/.swarm/memory.db (namespace: ${ns})`);
73
79
  console.log(` key: ${key}`);
74
80
 
75
81
  // 1a. STORE (native, signal namespace) — L1 content + L2 embedding
76
82
  try {
77
- ruflo(['memory', 'store', '-k', key, '-n', ns, '--value', value]);
83
+ ruflo(['memory', 'store', '-k', key, '-n', ns, '--value', value, '--path', db]);
78
84
  } catch (e) {
79
85
  console.error(' store FAILED:', String(e.stdout || e.message).split('\n')[0]);
80
86
  process.exit(1);
@@ -91,7 +97,7 @@ try {
91
97
  let stored = false;
92
98
  try {
93
99
  const back = ruflo(['memory', 'retrieve', '-k', key, '-n', ns, '--value-only', '--path', db]);
94
- stored = String(back).includes(value);
100
+ stored = String(back) === value;
95
101
  } catch (e) {
96
102
  console.error(' round-trip FAILED:', String(e.stdout || e.message).split('\n')[0]);
97
103
  }
@@ -133,6 +139,6 @@ console.log(
133
139
  // DERIVED, not asserted (F15): the closing line reports exactly what was verified, never more. The
134
140
  // old line claimed "captured, refined, and recall-verified" even when distill failed and recall
135
141
  // didn't return the key — asserted prose over an honest exit code.
136
- const parts = ['captured', distillOk ? 'refined' : 'NOT refined (distill failed)', recalled ? 'recall-verified' : 'recall NOT verified'];
142
+ const parts = [stored ? 'captured' : 'capture NOT verified', distillOk ? 'refined' : 'NOT refined (distill failed)', recalled ? 'recall-verified' : 'recall NOT verified'];
137
143
  console.log(`\nDone. Lesson is ${parts.join(', ')}.\n`);
138
144
  process.exit(stored ? 0 : 1);
@@ -1,6 +1,43 @@
1
1
  // Reviewed stabilization release boundaries. Legacy tests remain developer diagnostics.
2
2
  export const RELEASE_REQUIREMENTS = Object.freeze({
3
3
  "source": [
4
+ {
5
+ "id": "installer-release-argument-safety",
6
+ "reason": "Incomplete named releases refuse before network or settings writes; valid named releases and installer help retain their contracts",
7
+ "files": ["tests/unit/install-release-fallback.test.mjs"]
8
+ },
9
+ {
10
+ "id": "honest-currency-lesson-and-metrics-evidence",
11
+ "reason": "Foreign KB roots remain isolated; panel currency derives from measured completion rather than checkout time; exact lesson readback binds this shell-free write; public metrics never fabricate performance or a competing product score",
12
+ "files": ["tests/unit/forge-currency-selected-root.test.mjs", "tests/unit/brain-score-producer.test.mjs", "tests/unit/brain-grade-groundtruth-timestamp.test.mjs", "tests/unit/record-lesson.test.mjs", "tests/unit/lesson-presentation-budget.test.mjs", "tests/unit/metrics-truth.test.mjs", "tests/unit/issue-watch-retry.test.mjs"]
13
+ },
14
+ {
15
+ "id": "automatic-update-source-and-transport",
16
+ "reason": "Captured owner policy never falls back on read failures; installed-only automation avoids npx; transient GET and body failures retry without weakening trust; POSIX executable aliases retain interpreter identity",
17
+ "files": ["tests/unit/automatic-update.test.mjs", "tests/unit/download-retry.test.mjs"],
18
+ "platformFiles": {
19
+ "linux": ["tests/unit/automatic-update-node-alias.test.mjs"],
20
+ "macos": ["tests/unit/automatic-update-node-alias.test.mjs"]
21
+ }
22
+ },
23
+ {
24
+ "id": "customer-scope-and-session-ownership",
25
+ "reason": "Captured assistant commitments retain their actual session owner and honest noncompleted states; scoped grounding retains strict conservative evidence under contention; legacy guidance never suppresses a tool call",
26
+ "files": ["tests/unit/continuation-commitment-ownership.test.mjs"],
27
+ "platformFiles": {
28
+ "linux": ["tests/unit/grounding-scope.test.mjs", "tests/unit/hijack-no-defer.test.mjs"],
29
+ "macos": ["tests/unit/grounding-scope.test.mjs", "tests/unit/hijack-no-defer.test.mjs"]
30
+ }
31
+ },
32
+ {
33
+ "id": "bounded-advocacy-and-write-grounding",
34
+ "reason": "Explicit document meaning requests retain corroboration; literal searches stay silent; POSIX complete-write exemptions preserve executable and managed-store grounding",
35
+ "files": ["tests/unit/advocacy-route.test.mjs", "tests/unit/advocacy-catalog.test.mjs", "tests/unit/card-lane.test.mjs"],
36
+ "platformFiles": {
37
+ "linux": ["tests/unit/advocacy-route-budget.test.mjs", "tests/unit/ground-before-write.test.mjs", "tests/unit/grounding-code-projection.test.mjs"],
38
+ "macos": ["tests/unit/advocacy-route-budget.test.mjs", "tests/unit/ground-before-write.test.mjs", "tests/unit/grounding-code-projection.test.mjs"]
39
+ }
40
+ },
4
41
  {
5
42
  "id": "owned-startup-execution-evidence",
6
43
  "reason": "Opt-in native execution and private stage diagnostics bind released source and preserve unknown convergence and incomplete cleanup boundaries",
@@ -136,9 +173,11 @@ export const RELEASE_REQUIREMENTS = Object.freeze({
136
173
  },
137
174
  {
138
175
  "id": "canonical-memory-privacy",
139
- "reason": "Canonical project isolation, persisted consent and truthful capture status",
176
+ "reason": "Canonical project isolation, persisted consent, excluded-resource privacy before recording, immutable delivery refusal and truthful terminal failure status",
140
177
  "files": [
141
178
  "tests/unit/turn-outcome-capture.test.mjs",
179
+ "tests/unit/turn-capture-content-privacy.test.mjs",
180
+ "tests/integration/project-progression-hook.test.mjs",
142
181
  "tests/unit/turn-journal-platform.test.mjs",
143
182
  "tests/unit/project-store-resolver.test.mjs"
144
183
  ]