ruvnet-brain 4.2.2-dev → 4.3.1-dev
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 +15 -14
- package/bin/install.mjs +11 -2
- package/console/architecture.html +12 -14
- package/console/install-mockup.html +3 -3
- package/kb/zip-extract.mjs +8 -2
- package/package.json +7 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/hooks/codex-hooks.json +1 -1
- package/plugin/hooks/hooks.json +10 -0
- package/plugin/host-adapters/claude.json +11 -0
- package/plugin/host-adapters/codex.json +11 -0
- package/plugin/mcp/managed-cli-interface.mjs +77 -10
- package/plugin/mcp/server.mjs +2 -1
- package/plugin/scripts/anticipate.sh +30 -1
- package/plugin/scripts/capability-claim-evidence.mjs +431 -0
- package/plugin/scripts/capability-inventory-receipt.mjs +225 -0
- package/plugin/scripts/capability-registry.mjs +6 -0
- package/plugin/scripts/capability-routing.mjs +70 -0
- package/plugin/scripts/continuation-gate.mjs +68 -2
- package/plugin/scripts/coverage-integrity.mjs +575 -0
- package/plugin/scripts/ground-ruvnet.sh +2 -2
- package/plugin/scripts/nightly-controller.mjs +19 -2
- package/plugin/scripts/project-progression-contract.mjs +409 -0
- package/plugin/scripts/project-progression-hook.mjs +196 -0
- package/plugin/scripts/project-progression-outbox.mjs +95 -0
- package/plugin/scripts/project-progression-session-start.mjs +183 -0
- package/plugin/scripts/project-progression-store.mjs +248 -0
- package/plugin/scripts/project-store-resolver.mjs +106 -0
- package/plugin/scripts/session-snapshot-hook.mjs +29 -1
- package/plugin/scripts/session-start-core.mjs +22 -18
- package/plugin/scripts/spend-guard.mjs +6 -1
- package/scripts/adr-072-completion.mjs +98 -0
- package/scripts/behavioral-l1-l4.mjs +11 -8
- package/scripts/brain-score.mjs +7 -2
- package/scripts/build-bundle.mjs +82 -6
- package/scripts/card-from-source.mjs +32 -6
- package/scripts/corpus-aggregates.mjs +194 -0
- package/scripts/corpus-candidate.mjs +56 -36
- package/scripts/corpus-reconcile.mjs +295 -28
- package/scripts/corpus-seed-publish.mjs +3 -3
- package/scripts/coverage-integrity.mjs +3 -0
- package/scripts/gist-receipts.mjs +183 -0
- package/scripts/git-hooks/pre-push +21 -0
- package/scripts/host-registry.mjs +118 -0
- package/scripts/independent-review-receipt.mjs +499 -0
- package/scripts/ingest-gists.mjs +34 -5
- package/scripts/ingest-new-repos.mjs +5 -0
- package/scripts/metaharness-gate.mjs +58 -0
- package/scripts/nightly-gists.sh +3 -3
- package/scripts/nightly-wrapper.sh +75 -69
- package/scripts/product-integrity-contract.mjs +156 -0
- package/scripts/public-inventory.mjs +4 -0
- package/scripts/public-verification-aggregate.mjs +342 -0
- package/scripts/public-verification-finalizer.mjs +59 -0
- package/scripts/public-verification-inputs.mjs +473 -0
- package/scripts/public-verification-lane.mjs +244 -0
- package/scripts/publication-receipt.mjs +50 -6
- package/scripts/rebuild-gists-from-receipts.mjs +50 -5
- package/scripts/release-projection.mjs +86 -0
- package/scripts/release-transaction-provider.mjs +43 -102
- package/scripts/release-transaction.mjs +139 -51
- package/scripts/release.mjs +15 -7
- package/scripts/restore-local-ingests.mjs +2 -2
- package/scripts/retrieval-canary.mjs +533 -0
- package/scripts/routing-flywheel.mjs +12 -1
- package/scripts/rvf-generation.mjs +9 -3
- package/scripts/self-update.mjs +19 -44
- package/scripts/source-coverage.mjs +415 -0
- package/scripts/source-scope-receipt.mjs +84 -0
- package/scripts/sync-census.mjs +0 -0
- package/scripts/wired-check.mjs +114 -24
- package/scripts/worktree-integrity.mjs +123 -0
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
* Everything here is READ-ONLY. It observes; it never installs, enables, or writes.
|
|
71
71
|
*/
|
|
72
72
|
import fs from 'node:fs';
|
|
73
|
+
import crypto from 'node:crypto';
|
|
73
74
|
import path from 'node:path';
|
|
74
75
|
import os from 'node:os';
|
|
75
76
|
import { execFileSync } from 'node:child_process';
|
|
@@ -967,6 +968,7 @@ export function auditAll({ project = process.cwd() } = {}) {
|
|
|
967
968
|
// The default is the CALLER'S directory, not this package's. See the note on REPO: taking no
|
|
968
969
|
// argument at all is what made every project-scoped row describe the wrong folder.
|
|
969
970
|
const ctx = { project: path.resolve(project), home: HOME };
|
|
971
|
+
const observedAt = new Date().toISOString();
|
|
970
972
|
return CAPABILITIES.map((c) => {
|
|
971
973
|
let r;
|
|
972
974
|
try { r = c.detect(ctx); }
|
|
@@ -984,6 +986,10 @@ export function auditAll({ project = process.cwd() } = {}) {
|
|
|
984
986
|
turnOn: c.turnOn,
|
|
985
987
|
state,
|
|
986
988
|
evidence,
|
|
989
|
+
// A digest binds proactive routing to this audit invocation's observed bytes. Synthetic test
|
|
990
|
+
// registries do not carry it and remain compatible with the delivery seam.
|
|
991
|
+
evidenceDigest: crypto.createHash('sha256').update(evidence).digest('hex'),
|
|
992
|
+
evidenceObservedAt: observedAt,
|
|
987
993
|
// WHICH project a project-scoped row is about, named rather than assumed. "no memory store
|
|
988
994
|
// exists for this project" is only checkable by a reader who can see which folder was read.
|
|
989
995
|
...(c.scope === SCOPE.PROJECT ? { project: ctx.project } : {}),
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// capability-routing.mjs — evidence-bound routing for proactive recommendations.
|
|
2
|
+
//
|
|
3
|
+
// This is deliberately a small policy layer over goal-match + capability-registry. It does not
|
|
4
|
+
// invent a second capability catalogue: every recommendation must name a row from the live audit,
|
|
5
|
+
// and every row must carry the audit's observation digest. The tool family is a routing preference,
|
|
6
|
+
// not a claim that the tool is installed or healthy.
|
|
7
|
+
import crypto from 'node:crypto';
|
|
8
|
+
|
|
9
|
+
const sha256 = (value) => crypto.createHash('sha256').update(String(value)).digest('hex');
|
|
10
|
+
|
|
11
|
+
// These are the existing RuvNet building blocks for the capability's job. A route is only metadata
|
|
12
|
+
// for the receipt and copy; execution remains the row's verified turnOn command.
|
|
13
|
+
export const TOOL_PREFERENCES = Object.freeze({
|
|
14
|
+
'learning-hooks': ['ruflo'],
|
|
15
|
+
'memory-distillation': ['agentdb', 'ruflo'],
|
|
16
|
+
'workflow-pattern-learning': ['agentdb'],
|
|
17
|
+
'cheap-model-routing': ['agentic-flow'],
|
|
18
|
+
'cross-project-lessons': ['ruflo', 'agentdb'],
|
|
19
|
+
'lessons-in-force': ['ruflo'],
|
|
20
|
+
'harness-evolution': ['sparc', 'ruflo'],
|
|
21
|
+
'write-gates': ['ruflo'],
|
|
22
|
+
'session-capture': ['agentdb', 'ruflo'],
|
|
23
|
+
'mcp-servers': ['ruflo'],
|
|
24
|
+
'nightly-refresh': ['rvf', 'ruflo'],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const hasToolPreference = (key) => Array.isArray(TOOL_PREFERENCES[key]) && TOOL_PREFERENCES[key].length > 0;
|
|
28
|
+
|
|
29
|
+
const evidenceDigest = (row) => row?.evidenceDigest || sha256(row?.evidence || '');
|
|
30
|
+
|
|
31
|
+
export function buildCapabilityRoutingReceipt({ prompt, matches, capabilities, observedAt = new Date().toISOString() } = {}) {
|
|
32
|
+
if (typeof prompt !== 'string' || !prompt.trim() || !Array.isArray(matches) || !Array.isArray(capabilities)) return null;
|
|
33
|
+
const byKey = new Map(capabilities.filter((row) => row && typeof row.key === 'string').map((row) => [row.key, row]));
|
|
34
|
+
const routes = [];
|
|
35
|
+
for (const match of matches) {
|
|
36
|
+
const key = typeof match?.capability === 'string' ? match.capability : match?.capability?.key;
|
|
37
|
+
const row = byKey.get(key);
|
|
38
|
+
const tools = TOOL_PREFERENCES[key];
|
|
39
|
+
// No row, no digest, or no known RuvNet route means UNKNOWN — never a hand-rolled fallback.
|
|
40
|
+
if (!row || !Array.isArray(tools) || !tools.length || typeof row.evidence !== 'string' || !row.evidence.trim()) continue;
|
|
41
|
+
routes.push({ capability: key, tools, state: row.state, evidenceDigest: evidenceDigest(row), confidence: match.confidence });
|
|
42
|
+
}
|
|
43
|
+
if (!routes.length) return null;
|
|
44
|
+
const payload = {
|
|
45
|
+
schemaVersion: 1,
|
|
46
|
+
kind: 'ruvnet-brain-capability-routing',
|
|
47
|
+
observedAt,
|
|
48
|
+
promptDigest: sha256(prompt.trim().toLowerCase()),
|
|
49
|
+
routes,
|
|
50
|
+
verdict: 'PASS',
|
|
51
|
+
};
|
|
52
|
+
return { ...payload, receiptSha256: sha256(JSON.stringify(payload)) };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function validateCapabilityRoutingReceipt(receipt) {
|
|
56
|
+
if (receipt?.schemaVersion !== 1 || receipt?.kind !== 'ruvnet-brain-capability-routing'
|
|
57
|
+
|| !Array.isArray(receipt.routes) || !receipt.routes.length || receipt.verdict !== 'PASS'
|
|
58
|
+
|| !/^[a-f0-9]{64}$/.test(String(receipt.promptDigest || ''))
|
|
59
|
+
|| !/^[a-f0-9]{64}$/.test(String(receipt.receiptSha256 || ''))) {
|
|
60
|
+
throw new Error('capability routing receipt is malformed');
|
|
61
|
+
}
|
|
62
|
+
const { receiptSha256: _digest, ...payload } = receipt;
|
|
63
|
+
if (sha256(JSON.stringify(payload)) !== receipt.receiptSha256) throw new Error('capability routing receipt digest mismatch');
|
|
64
|
+
for (const route of receipt.routes) {
|
|
65
|
+
if (!TOOL_PREFERENCES[route.capability] || !Array.isArray(route.tools)
|
|
66
|
+
|| route.tools.some((tool) => !TOOL_PREFERENCES[route.capability].includes(tool))
|
|
67
|
+
|| !/^[a-f0-9]{64}$/.test(String(route.evidenceDigest || ''))) throw new Error('capability routing route is not evidence-bound');
|
|
68
|
+
}
|
|
69
|
+
return receipt;
|
|
70
|
+
}
|
|
@@ -38,6 +38,11 @@ import fs from 'node:fs';
|
|
|
38
38
|
import path from 'node:path';
|
|
39
39
|
import os from 'node:os';
|
|
40
40
|
import { readStdinBounded } from './hook-input.mjs';
|
|
41
|
+
import {
|
|
42
|
+
auditCapabilityClaims,
|
|
43
|
+
buildCapabilityInventoryReceipt,
|
|
44
|
+
} from './capability-inventory-receipt.mjs';
|
|
45
|
+
import { auditCurrentCapabilityEvidence } from './capability-claim-evidence.mjs';
|
|
41
46
|
|
|
42
47
|
const HOME = os.homedir();
|
|
43
48
|
|
|
@@ -251,6 +256,59 @@ const nowMs = Date.now();
|
|
|
251
256
|
// force. This closes the empty-stdin hole that LOOP-SAFETY 1's `__source` check does not cover.
|
|
252
257
|
if (!hookInput.session_id) process.exit(EXIT_ALLOW);
|
|
253
258
|
|
|
259
|
+
/**
|
|
260
|
+
* FINAL-ANSWER CAPABILITY TRUTH — the Stop boundary is the only place that can inspect the answer
|
|
261
|
+
* the user is about to receive. The concrete failure this closes was an assistant saying
|
|
262
|
+
* "Ruflo ADR Verify is not installed" while `ruflo-adr:adr-verify` was present in that host's own
|
|
263
|
+
* installed skill inventory. A rule in the prompt did not prevent it; a byte-bound inventory does.
|
|
264
|
+
*
|
|
265
|
+
* This first receipt class is intentionally narrow: installed/registered/present claims about
|
|
266
|
+
* RuvNet skills. It does not pretend to prove arbitrary natural-language capability claims. A
|
|
267
|
+
* present source byte disproves an absence claim. A COMPLETE enumeration can disprove a presence
|
|
268
|
+
* claim. An incomplete enumeration can prove neither, so the only allowed verdict is UNKNOWN.
|
|
269
|
+
*/
|
|
270
|
+
function capabilityClaimWork() {
|
|
271
|
+
const message = String(hookInput.last_assistant_message || '');
|
|
272
|
+
if (!message) return [];
|
|
273
|
+
try {
|
|
274
|
+
const receipt = buildCapabilityInventoryReceipt();
|
|
275
|
+
const audit = auditCapabilityClaims(message, receipt);
|
|
276
|
+
const evidenceAudit = auditCurrentCapabilityEvidence(message, { now: new Date(nowMs).toISOString() });
|
|
277
|
+
const evidenceWork = [...evidenceAudit.contradictions, ...evidenceAudit.unresolved].map((finding) => ({
|
|
278
|
+
text: evidenceAudit.contradictions.includes(finding)
|
|
279
|
+
? `RuvNet ${finding.class} claim "${finding.text}" contradicts fresh typed evidence: ${finding.reason || 'claim mismatch'}`
|
|
280
|
+
: `RuvNet ${finding.class} claim "${finding.text}" is UNKNOWN: ${finding.reason}; verify the exact live/source surface before asserting it`,
|
|
281
|
+
done: false,
|
|
282
|
+
at: new Date(nowMs).toISOString(),
|
|
283
|
+
derived: true,
|
|
284
|
+
kind: 'capability-claim-integrity',
|
|
285
|
+
}));
|
|
286
|
+
if (audit.verdict === 'PASS') return evidenceWork;
|
|
287
|
+
if (audit.verdict === 'FAIL') {
|
|
288
|
+
return [...audit.contradictions.map((finding) => ({
|
|
289
|
+
text: finding.matchedRef
|
|
290
|
+
? `RuvNet capability claim "${finding.text}" contradicts the sealed ${audit.host} inventory: ${finding.matchedRef} is present at ${finding.sourcePath}`
|
|
291
|
+
: `RuvNet capability claim "${finding.text}" contradicts the complete sealed ${audit.host} inventory: no matching installed capability exists`,
|
|
292
|
+
done: false,
|
|
293
|
+
at: new Date(nowMs).toISOString(),
|
|
294
|
+
derived: true,
|
|
295
|
+
kind: 'capability-claim-integrity',
|
|
296
|
+
})), ...evidenceWork];
|
|
297
|
+
}
|
|
298
|
+
return [...audit.unresolved.map((finding) => ({
|
|
299
|
+
text: `RuvNet capability claim "${finding.text}" is UNKNOWN because the ${audit.host} inventory is incomplete; verify the live host before asserting absence`,
|
|
300
|
+
done: false,
|
|
301
|
+
at: new Date(nowMs).toISOString(),
|
|
302
|
+
derived: true,
|
|
303
|
+
kind: 'capability-claim-integrity',
|
|
304
|
+
})), ...evidenceWork];
|
|
305
|
+
} catch {
|
|
306
|
+
// The Stop hook remains fail-open on machinery failure. The receipt builder represents
|
|
307
|
+
// ordinary incomplete enumeration as UNKNOWN; reaching this catch means the gate itself broke.
|
|
308
|
+
return [];
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
254
312
|
/**
|
|
255
313
|
* ARTIFACT-DERIVED OPEN WORK — the half that cannot be forgotten.
|
|
256
314
|
*
|
|
@@ -423,6 +481,7 @@ function securityAlertWork() {
|
|
|
423
481
|
}
|
|
424
482
|
|
|
425
483
|
const open = [
|
|
484
|
+
...capabilityClaimWork(),
|
|
426
485
|
...led.items.filter((i) => !i.done),
|
|
427
486
|
...artifactOpenWork(), ...redCiOpenWork(), ...openPrWork(), ...securityAlertWork(),
|
|
428
487
|
];
|
|
@@ -511,11 +570,15 @@ if (!claimCooldown(nowMs, COOLDOWN_MS)) process.exit(EXIT_ALLOW);
|
|
|
511
570
|
*/
|
|
512
571
|
const committed = forceable.filter((i) => !i.derived);
|
|
513
572
|
const observed = forceable.filter((i) => i.derived);
|
|
573
|
+
const capabilityClaims = forceable.filter((i) => i.kind === 'capability-claim-integrity');
|
|
514
574
|
// Every derived item names its own repo in its text; this is for the header, where the ONE repo
|
|
515
575
|
// this tree points at is the honest thing to say.
|
|
516
576
|
const repoLabel = [...OWNED_REPOS][0] || 'this repository';
|
|
517
577
|
|
|
518
|
-
const header =
|
|
578
|
+
const header = capabilityClaims.length
|
|
579
|
+
? ['Your proposed final answer contains a RuvNet capability claim that is contradicted or not provable.',
|
|
580
|
+
'Do NOT deliver it unchanged — continue now and correct the claim from the sealed live-host inventory.']
|
|
581
|
+
: committed.length && observed.length
|
|
519
582
|
? [`You have unfinished work you committed to, and ${repoLabel} has open work of its own.`,
|
|
520
583
|
'Do NOT end the turn — continue now.']
|
|
521
584
|
: committed.length
|
|
@@ -539,7 +602,10 @@ const lines = [
|
|
|
539
602
|
// build, answer the issue, patch the advisory) and the next watcher run stops reporting it —
|
|
540
603
|
// which is the point of deriving it rather than remembering it. Offering --done for one would be
|
|
541
604
|
// offering a way to mark a red build finished without fixing it.
|
|
542
|
-
...(
|
|
605
|
+
...(capabilityClaims.length
|
|
606
|
+
? ['Replace every contradicted claim with the observed capability and source path. Replace every',
|
|
607
|
+
'unresolved absence claim with UNKNOWN until a complete live inventory proves it.']
|
|
608
|
+
: committed.length
|
|
543
609
|
? ['Mark each item done as you complete it: node plugin/scripts/continuation-gate.mjs --done "<exact item text>"']
|
|
544
610
|
: ['These clear by being done, not by being marked: merge or fix the PR, get the build green,',
|
|
545
611
|
'answer the issue, patch the advisory. The next observation stops listing them.']),
|