ruvnet-brain 4.0.90-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 +31 -7
- package/bin/install.mjs +153 -22
- 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/decision-outcomes.mjs +0 -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 +23 -9
- package/scripts/build-bundle.mjs +94 -8
- package/scripts/card-from-source.mjs +140 -0
- package/scripts/corpus-aggregates.mjs +194 -0
- package/scripts/corpus-candidate.mjs +314 -0
- package/scripts/corpus-reconcile.mjs +540 -0
- package/scripts/corpus-seed-publish.mjs +110 -0
- 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 +127 -0
- package/scripts/metaharness-gate.mjs +58 -0
- package/scripts/nightly-gists.sh +3 -3
- package/scripts/nightly-wrapper.sh +105 -64
- 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 +291 -0
- package/scripts/release-projection.mjs +86 -0
- package/scripts/release-transaction-provider.mjs +43 -102
- package/scripts/release-transaction.mjs +165 -49
- package/scripts/release.mjs +158 -3
- 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 +50 -0
- 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
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
|
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
[](https://isovision.ai/ruvnet-brain/)
|
|
31
31
|
[](LICENSE)
|
|
32
32
|
[](#testing--proof)
|
|
33
|
-
[](#testing--proof)
|
|
34
34
|
|
|
35
35
|
> **One Brain generation everywhere.** npm, the GitHub tag/release, bundle manifests, source metadata, and checksum-bound RVF generations must share the same product version. Headline claims are regenerated and checked by the claims ledger (`scripts/claims-verify.mjs`); other numbers below are hand-stamped and dated:
|
|
36
36
|
> - **`plugin`** (badge above) โ the Claude Code plugin itself: SKILL.md, the grounding hooks, the MCP server. Read live from [`plugin/.claude-plugin/plugin.json`](plugin/.claude-plugin/plugin.json). Updates often โ this is where behavior fixes land.
|
|
37
37
|
> - **`installer (npm)`** (badge above) โ the `npx ruvnet-brain` setup script. Read live from the [npm registry](https://www.npmjs.com/package/ruvnet-brain). Only moves when the installer script itself changes โ rare.
|
|
38
|
-
> - **Brain Release** (the downloadable knowledge bundle, linked from the "download" badge above) โ always resolves to [`releases/latest`](https://github.com/stuinfla/ruvnet-brain/releases/latest)
|
|
38
|
+
> - **Brain Release** (the downloadable knowledge bundle, linked from the "download" badge above) โ always resolves to [`releases/latest`](https://github.com/stuinfla/ruvnet-brain/releases/latest). It moves only when the protected exact-SHA release workflow accepts rebuilt knowledge bytes โ separate again from the two above.
|
|
39
39
|
> - **On an old version? One line makes you current โ and, with `--auto`, keeps you current forever:**
|
|
40
40
|
> ```
|
|
41
41
|
> npx ruvnet-brain@latest --update --auto
|
|
@@ -56,7 +56,31 @@
|
|
|
56
56
|
|
|
57
57
|
---
|
|
58
58
|
|
|
59
|
-
## What's new in 4.
|
|
59
|
+
## What's new in 4.3 โ it loads what rUv ships, without being asked
|
|
60
|
+
|
|
61
|
+
**The corpus gained an explicit new-repository ingestion path.** Until 4.2 nothing ingested a new
|
|
62
|
+
repo. `brain-stamp.mjs` measured the gap, but nothing consumed it until
|
|
63
|
+
`scripts/ingest-new-repos.mjs` shipped. Its mutating mode is now deliberately human-run from a clean
|
|
64
|
+
linked worktree; the former primary-checkout scheduler was retired rather than allowed to trade
|
|
65
|
+
corpus freshness for uncontrolled source changes.
|
|
66
|
+
|
|
67
|
+
- **187 stores, up from 69.** Content-bearing rUv repositories can be pulled newest-first through
|
|
68
|
+
`scripts/ingest-new-repos.mjs`. Empty repos (`size=0KB`) are skipped
|
|
69
|
+
rather than retried forever โ a permanent repeated failure that is actually correct behaviour
|
|
70
|
+
trains you to ignore the failure line, which is how a real one would hide inside it.
|
|
71
|
+
- **174 capability cards, up from 39.** Ingesting a repo is not the same as making it reachable: a
|
|
72
|
+
store with no card is *dark* โ valid bytes no by-description query can find. Cards are written
|
|
73
|
+
from each repo's **own** description and README, never from its name, and where there is no
|
|
74
|
+
grounded source text the store is **left dark and reported** rather than given an invented
|
|
75
|
+
sentence. 13 remain dark for exactly that reason.
|
|
76
|
+
- **The download barely grew** for 2.4x the corpus โ most of rUv's repos are small, so the bulk
|
|
77
|
+
of the bundle was always the large ones. (No size is hand-typed here: a number typed onto a
|
|
78
|
+
public page rots the moment the next bundle is built, and this repo gates against exactly that.)
|
|
79
|
+
- **The nightly stopped discarding its own writes.** It hardcoded a node whose ABI did not match
|
|
80
|
+
agentdb's binding, so `lesson-bridge --apply` and `learning-replay` wrote into a silent
|
|
81
|
+
non-persistent fallback. It now resolves an ABI-matched interpreter and fails loudly instead.
|
|
82
|
+
|
|
83
|
+
## What's new in 4.3 โ it anticipates, and it learns whether it was right
|
|
60
84
|
|
|
61
85
|
**Building toward L4/L5 (3.9.x, dev).** The mechanisms for the top two rungs of the proactivity
|
|
62
86
|
ladder are built and wired โ but they are **not yet verified to 4.0's bar**, which requires all five
|
|
@@ -389,7 +413,7 @@ You install once. After that, three mechanisms keep you on the current brain wit
|
|
|
389
413
|
`๐ง RuvNet Brain jumped in ยท guidance only, no source read ยท v3.4.18-dev`
|
|
390
414
|
An unearned citation is worse than no citation, so the line may only name a path the tools genuinely returned โ and on a prompt where nothing fires, it stays silent rather than manufacture a receipt. The version shown is the one **actually loaded in memory** for this session; if a newer one is staged awaiting a restart, the line says so plainly (`โฆ vX staged, restart to load`). So you never have to wonder whether the brain is on, which version is acting, or whether an answer was grounded or guessed.
|
|
391
415
|
|
|
392
|
-
- **
|
|
416
|
+
- **Separated overnight paths.** The Dream Machine runs evidence-only evaluation with `autoMerge:false`; the optional `com.ruvnet.brain-update` LaunchAgent updates one installed cache from an already-published bundle. The former `com.ruvnet.brain-nightly` source writer was retired on 2026-08-22 because it accumulated generated changes in the primary developer checkout. Author rebuilds are now explicit, clean linked-worktree operations; `self-update.mjs --publish` is refused and only the protected exact-SHA workflow may release. See [Nightly refresh, evaluation, and author rebuilds](docs/NIGHTLY-REFRESH.md).
|
|
393
417
|
|
|
394
418
|
---
|
|
395
419
|
|
|
@@ -485,7 +509,7 @@ node plugin/test/run-tests.mjs # full plugin QA over real JSO
|
|
|
485
509
|
| **L4 "orchestrate"** | **downgraded โ measures speech, not obedience** | L4 asserts the hook's own injected prose contains required words (`must: ['take the wheel','SPARC','swarm',โฆ]`). That proves **the brain spoke**. It cannot fail when the advice is read and ignored โ which is the failure this product exists to prevent. Counterfactual replay against a brain-off control (ADR-058 ยงD4) is what will earn this row back |
|
|
486
510
|
| **Plugin QA** | **60 / 60** | manifests, hook firing, MCP `initialize`/`tools/list`, capability battery |
|
|
487
511
|
| **Clean-room install** | **3 / 3** | download the published bundle fresh โ unzip โ query โ grounded, cited answers |
|
|
488
|
-
| **Unit tests** | **3,035 passing, 161 todo** ยท
|
|
512
|
+
| **Unit tests** | **3,035 passing, 161 todo** ยท 41% of ALL source covered | `npm run test:cov` regenerates both โ the coverage floor fails CI if it slips (`claims:verify` re-derives the %, it is not a hand-typed badge). 41% is the honest number over every shipped file; the previous "75%" measured a hand-picked 8-file subset |
|
|
489
513
|
| **Grounding proof** | `npx ruvnet-brain --doctor` | asks a real question, then checks the cited path really exists in the on-disk store; a citation that doesn't resolve is reported as **NOT grounded** |
|
|
490
514
|
| **Held-out eval** | **grounded 100/100** ยท routed 63/80 | `npm run eval` โ 120 frozen, hash-pinned questions across 5 strata, never used for tuning, graded on ground truth, never by a model |
|
|
491
515
|
|
|
@@ -522,7 +546,7 @@ node forge-ask-all.mjs --dir . --q "How does RuVector implement HNSW vector sear
|
|
|
522
546
|
|
|
523
547
|
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
548
|
|
|
525
|
-
- โ
**The grounding brain is real and proven** โ 70 public stores ยท 140,
|
|
549
|
+
- โ
**The grounding brain is real and proven** โ 70 public stores ยท 140,520 public source chunks (77 built stores incl. private), dual embeddings, cross-encoder rerank, plugin (MCP tool + enforcement hook + skill), all re-runnable.
|
|
526
550
|
- โ
**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
551
|
- โ
**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
552
|
- โ ๏ธ **Two routing residuals** (above) โ surfaced, not hidden.
|
package/bin/install.mjs
CHANGED
|
@@ -547,7 +547,7 @@ async function unzipInto(zipPath, cacheDir, sourceDir = null) {
|
|
|
547
547
|
}
|
|
548
548
|
|
|
549
549
|
/**
|
|
550
|
-
* ISSUE #128 โ STALE PLUGIN GENERATIONS
|
|
550
|
+
* ISSUE #128 / #153 โ STALE PLUGIN GENERATIONS MAY STILL BACK LIVE SESSIONS.
|
|
551
551
|
*
|
|
552
552
|
* `pruneUnlistedStores` below states the rule this repo already believes: "an update is a
|
|
553
553
|
* replacement, not an overlay." That rule was applied to KB stores and never to the plugin cache.
|
|
@@ -563,6 +563,12 @@ async function unzipInto(zipPath, cacheDir, sourceDir = null) {
|
|
|
563
563
|
* restart, one says an update is landing, one mandates a third version), so whichever the model
|
|
564
564
|
* obeys, the user is misinformed by a source that sounds definitive.
|
|
565
565
|
*
|
|
566
|
+
* Registry reachability identifies candidates; it is not deletion proof. Claude freezes
|
|
567
|
+
* CLAUDE_PLUGIN_ROOT at session start, so an unregistered generation can remain live until that
|
|
568
|
+
* session exits. Modern roots expose `.in_use` PID-incarnation leases. Live or ambiguous leases
|
|
569
|
+
* retain the complete root; dead leases are collected. Legacy roots without the protocol receive
|
|
570
|
+
* a 14-day compatibility grace.
|
|
571
|
+
*
|
|
566
572
|
* WHAT MAKES DELETION SAFE HERE, since this removes directories from someone's machine:
|
|
567
573
|
* โข The registry is the ONLY authority. Unreadable, missing, or naming no ruvnet-brain install โ
|
|
568
574
|
* nothing is removed and a reason is returned. Never guess what to delete โ the same discipline
|
|
@@ -574,17 +580,26 @@ async function unzipInto(zipPath, cacheDir, sourceDir = null) {
|
|
|
574
580
|
* here, and deleting through that link would delete the working tree.
|
|
575
581
|
* โข EVERY registered installPath is protected, not just the first โ the plugin may legitimately be
|
|
576
582
|
* installed at both user and project scope, and treating one as stale would delete a live install.
|
|
577
|
-
* โข
|
|
583
|
+
* โข Registered generations are excluded by RESOLVED PATH, never by version string.
|
|
584
|
+
* โข The registry is re-read immediately before deletion; any byte change retains the candidate.
|
|
585
|
+
* โข Malformed, unreadable, symlinked, live, or unverifiable lease state fails closed.
|
|
578
586
|
* โข Report-only unless `apply` is set, and every removal is returned by name, so a silent sweep is
|
|
579
587
|
* impossible.
|
|
580
588
|
*/
|
|
581
589
|
export function prunePluginGenerations({
|
|
582
590
|
registryPath = path.join(os.homedir(), '.claude', 'plugins', 'installed_plugins.json'),
|
|
583
591
|
apply = false,
|
|
592
|
+
now = () => Date.now(),
|
|
593
|
+
graceMs = 14 * 24 * 60 * 60 * 1000,
|
|
594
|
+
processIdentity = pluginProcessIdentity,
|
|
584
595
|
} = {}) {
|
|
585
|
-
const nothing = (why) => ({ active: [], stale: [], removed: [], bytes: 0, why });
|
|
596
|
+
const nothing = (why) => ({ active: [], stale: [], marked: [], removed: [], bytes: 0, staleBytes: 0, cleanupBlocked: [], why });
|
|
597
|
+
let registryRaw;
|
|
586
598
|
let registry;
|
|
587
|
-
try {
|
|
599
|
+
try {
|
|
600
|
+
registryRaw = fs.readFileSync(registryPath, 'utf8');
|
|
601
|
+
registry = JSON.parse(registryRaw);
|
|
602
|
+
} catch (e) {
|
|
588
603
|
return nothing(`plugin registry unreadable (${e.code || e.message}) โ refusing to remove anything`);
|
|
589
604
|
}
|
|
590
605
|
const active = Object.entries(registry?.plugins || {})
|
|
@@ -609,17 +624,126 @@ export function prunePluginGenerations({
|
|
|
609
624
|
stale.push({ path: full, version: name, bytes: dirBytes(full) });
|
|
610
625
|
}
|
|
611
626
|
}
|
|
612
|
-
const
|
|
613
|
-
if (!apply) return { active, stale, removed: [], bytes, why: null };
|
|
627
|
+
const staleBytes = stale.reduce((n, s) => n + s.bytes, 0);
|
|
628
|
+
if (!apply) return { active, stale, marked: [], removed: [], bytes: 0, staleBytes, cleanupBlocked: [], why: null };
|
|
614
629
|
|
|
630
|
+
const marked = [];
|
|
615
631
|
const removed = [];
|
|
632
|
+
const cleanupBlocked = [];
|
|
616
633
|
let freed = 0;
|
|
617
|
-
for (const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
634
|
+
for (const candidate of stale) {
|
|
635
|
+
const marker = path.join(candidate.path, '.orphaned_at');
|
|
636
|
+
if (!fs.existsSync(marker)) {
|
|
637
|
+
const temporary = `${marker}.tmp.${process.pid}.${crypto.randomBytes(4).toString('hex')}`;
|
|
638
|
+
try {
|
|
639
|
+
fs.writeFileSync(temporary, String(now()), { flag: 'wx', mode: 0o600 });
|
|
640
|
+
fs.renameSync(temporary, marker);
|
|
641
|
+
marked.push(candidate.version);
|
|
642
|
+
} catch {
|
|
643
|
+
try { fs.rmSync(temporary, { force: true }); } catch { /* retain on ambiguity */ }
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
let orphanedAt;
|
|
648
|
+
try {
|
|
649
|
+
const markerStat = fs.lstatSync(marker);
|
|
650
|
+
if (!markerStat.isFile() || markerStat.isSymbolicLink()) {
|
|
651
|
+
cleanupBlocked.push({ version: candidate.version, reason: '.orphaned_at is not a trusted file' });
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
orphanedAt = Number(fs.readFileSync(marker, 'utf8').trim());
|
|
655
|
+
if (!Number.isFinite(orphanedAt) || orphanedAt <= 0) {
|
|
656
|
+
cleanupBlocked.push({ version: candidate.version, reason: '.orphaned_at is malformed' });
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
} catch {
|
|
660
|
+
cleanupBlocked.push({ version: candidate.version, reason: '.orphaned_at could not be inspected' });
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const leaseDir = path.join(candidate.path, '.in_use');
|
|
665
|
+
let leaseCapable = false;
|
|
666
|
+
let leaseNames = [];
|
|
667
|
+
try {
|
|
668
|
+
if (fs.existsSync(leaseDir)) {
|
|
669
|
+
const leaseDirStat = fs.lstatSync(leaseDir);
|
|
670
|
+
if (leaseDirStat.isSymbolicLink() || !leaseDirStat.isDirectory()) {
|
|
671
|
+
cleanupBlocked.push({ version: candidate.version, reason: '.in_use is not a trusted directory' });
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
leaseCapable = true;
|
|
675
|
+
leaseNames = fs.readdirSync(leaseDir);
|
|
676
|
+
}
|
|
677
|
+
} catch {
|
|
678
|
+
cleanupBlocked.push({ version: candidate.version, reason: '.in_use could not be inspected' });
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (!leaseCapable && now() - orphanedAt < graceMs) continue;
|
|
683
|
+
let safeToReap = true;
|
|
684
|
+
for (const leaseName of leaseNames) {
|
|
685
|
+
const leasePath = path.join(leaseDir, leaseName);
|
|
686
|
+
let lease;
|
|
687
|
+
try {
|
|
688
|
+
const leaseStat = fs.lstatSync(leasePath);
|
|
689
|
+
if (!leaseStat.isFile() || leaseStat.isSymbolicLink()) throw new Error('not a trusted file');
|
|
690
|
+
lease = JSON.parse(fs.readFileSync(leasePath, 'utf8'));
|
|
691
|
+
if (!Number.isSafeInteger(lease?.pid) || lease.pid <= 0 || typeof lease?.procStart !== 'string' || !lease.procStart.trim()) {
|
|
692
|
+
throw new Error('malformed');
|
|
693
|
+
}
|
|
694
|
+
} catch {
|
|
695
|
+
cleanupBlocked.push({ version: candidate.version, reason: `lease ${leaseName} is malformed or unreadable` });
|
|
696
|
+
safeToReap = false;
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
const state = processIdentity(lease);
|
|
700
|
+
if (state !== 'dead') {
|
|
701
|
+
cleanupBlocked.push({ version: candidate.version, reason: `lease ${leaseName} is ${state === 'live' ? 'live' : 'ambiguous'}` });
|
|
702
|
+
safeToReap = false;
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
try { fs.rmSync(leasePath); } catch {
|
|
706
|
+
cleanupBlocked.push({ version: candidate.version, reason: `dead lease ${leaseName} could not be removed` });
|
|
707
|
+
safeToReap = false;
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (!safeToReap) continue;
|
|
712
|
+
|
|
713
|
+
// The host registry can advance while the collector scans. Any byte change invalidates the
|
|
714
|
+
// original authority snapshot; retain every candidate rather than deleting on stale evidence.
|
|
715
|
+
let currentRaw;
|
|
716
|
+
try { currentRaw = fs.readFileSync(registryPath, 'utf8'); } catch { continue; }
|
|
717
|
+
if (currentRaw !== registryRaw) continue;
|
|
718
|
+
try {
|
|
719
|
+
fs.rmSync(candidate.path, { recursive: true });
|
|
720
|
+
removed.push(candidate.version);
|
|
721
|
+
freed += candidate.bytes;
|
|
722
|
+
} catch { /* retained for a later pass */ }
|
|
621
723
|
}
|
|
622
|
-
return {
|
|
724
|
+
return {
|
|
725
|
+
active, stale, marked, removed, bytes: freed, staleBytes, cleanupBlocked,
|
|
726
|
+
why: stale.length
|
|
727
|
+
? 'lease-proven orphan reconciliation; legacy generations receive a 14-day grace and live or ambiguous generations are retained'
|
|
728
|
+
: null,
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/** Verify the exact PID incarnation recorded by a plugin generation lease. */
|
|
733
|
+
function pluginProcessIdentity({ pid, procStart }) {
|
|
734
|
+
try { process.kill(pid, 0); } catch (error) {
|
|
735
|
+
return error?.code === 'ESRCH' ? 'dead' : 'unknown';
|
|
736
|
+
}
|
|
737
|
+
const result = process.platform === 'win32'
|
|
738
|
+
? spawnSync('powershell.exe', [
|
|
739
|
+
'-NoProfile', '-NonInteractive', '-Command',
|
|
740
|
+
`$p=Get-Process -Id ${pid} -ErrorAction Stop; $p.StartTime.ToUniversalTime().ToString('ddd MMM dd HH:mm:ss yyyy',[Globalization.CultureInfo]::InvariantCulture)`,
|
|
741
|
+
], { encoding: 'utf8' })
|
|
742
|
+
: spawnSync('ps', ['-p', String(pid), '-o', 'lstart='], {
|
|
743
|
+
encoding: 'utf8', env: { ...process.env, TZ: 'UTC', LC_ALL: 'C', LANG: 'C' },
|
|
744
|
+
});
|
|
745
|
+
if (result.status !== 0 || !String(result.stdout || '').trim()) return 'unknown';
|
|
746
|
+
return String(result.stdout).trim() === procStart.trim() ? 'live' : 'dead';
|
|
623
747
|
}
|
|
624
748
|
|
|
625
749
|
/** Size of a directory tree, best-effort โ used only to report what was or would be freed. */
|
|
@@ -2470,9 +2594,18 @@ export function installCronEntry(line, { run = spawnSync } = {}) {
|
|
|
2470
2594
|
return { ok: true, already: false };
|
|
2471
2595
|
}
|
|
2472
2596
|
|
|
2473
|
-
/**
|
|
2597
|
+
/**
|
|
2598
|
+
* launchd's minimal PATH plus the user-level executable homes used by the supported hosts.
|
|
2599
|
+
*
|
|
2600
|
+
* The updater does more than run npx: --host-sync-only executes the installed Claude and Codex
|
|
2601
|
+
* doors. A real 2026-08-22 launchd run found npx but then failed with `claude unavailable` and
|
|
2602
|
+
* `spawnSync codex ENOENT`; interactive shells had supplied ~/.npm-global/bin and ~/.local/bin,
|
|
2603
|
+
* launchd had not. Derive these from HOME so the fix is portable rather than pinned to one user.
|
|
2604
|
+
*/
|
|
2474
2605
|
const launchdPath = () => [...new Set([
|
|
2475
2606
|
path.dirname(process.execPath), path.dirname(npxPath()),
|
|
2607
|
+
path.join(os.homedir(), '.npm-global', 'bin'), path.join(os.homedir(), '.local', 'bin'),
|
|
2608
|
+
...(process.platform === 'darwin' ? ['/Applications/Codex.app/Contents/Resources'] : []),
|
|
2476
2609
|
'/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin',
|
|
2477
2610
|
])].filter(Boolean).join(':');
|
|
2478
2611
|
const cronExample = (kbDir) =>
|
|
@@ -2563,18 +2696,16 @@ export function syncHostsAfterUpdate(cacheDir = resolvedKbDir(), {
|
|
|
2563
2696
|
const applied = runStableSpine(apply);
|
|
2564
2697
|
const okApplied = !applied.error && applied.status === 0;
|
|
2565
2698
|
if (okApplied) {
|
|
2566
|
-
// ISSUE #
|
|
2567
|
-
//
|
|
2568
|
-
// has applied, so every OTHER generation in that cache is unreachable by design and can only
|
|
2569
|
-
// mislead โ its skills still load, its hooks still fire, and its version banner still speaks.
|
|
2570
|
-
// Making it a consequence of updating, rather than a command someone has to remember, is the
|
|
2571
|
-
// whole point; a cleanup that must be invoked is a cleanup that does not happen.
|
|
2572
|
-
// Best-effort and reported by name โ it must never turn a good update into a failed one.
|
|
2699
|
+
// ISSUE #153 โ running Claude sessions freeze their plugin root. Collect only generations whose
|
|
2700
|
+
// leases prove that exact PID incarnation dead, or legacy roots past the compatibility grace.
|
|
2573
2701
|
try {
|
|
2574
2702
|
const gens = prunePluginGenerations({ apply: true });
|
|
2575
2703
|
if (gens.removed.length) {
|
|
2576
|
-
ok(`pruned ${gens.removed.length} stale plugin generation(s), freed ${(gens.bytes / 1048576).toFixed(1)} MB: ${gens.removed.
|
|
2577
|
-
}
|
|
2704
|
+
ok(`pruned ${gens.removed.length} stale plugin generation(s), freed ${(gens.bytes / 1048576).toFixed(1)} MB: ${gens.removed.join(', ')}`);
|
|
2705
|
+
}
|
|
2706
|
+
if (gens.cleanupBlocked?.length) {
|
|
2707
|
+
warn(`plugin cleanup retained ${gens.cleanupBlocked.length} generation(s): ${gens.cleanupBlocked.map((item) => `${item.version} (${item.reason})`).join(', ')}`);
|
|
2708
|
+
} else if (!gens.removed.length && gens.why) {
|
|
2578
2709
|
warn(`stale plugin generations were not pruned: ${gens.why}`);
|
|
2579
2710
|
}
|
|
2580
2711
|
} catch (e) {
|
|
@@ -2775,7 +2906,7 @@ function enableNightly() {
|
|
|
2775
2906
|
<key>Label</key>
|
|
2776
2907
|
<string>${NIGHTLY_LABEL}</string>
|
|
2777
2908
|
<!-- Issue #129: the SAME host-convergent entrypoint the session updater runs, not the KB-only
|
|
2778
|
-
forge-update.mjs it used to schedule. See NIGHTLY_ARGV. Still no
|
|
2909
|
+
forge-update.mjs it used to schedule. See NIGHTLY_ARGV. Still no shell wrapper (ADR-038) โ
|
|
2779
2910
|
this execs npx directly. -->
|
|
2780
2911
|
<key>ProgramArguments</key>
|
|
2781
2912
|
<array>
|
|
@@ -979,33 +979,31 @@ RUVNET_BRAIN_METER=0 # stop the measuring (editing hooks.json stops
|
|
|
979
979
|
</div>
|
|
980
980
|
</details>
|
|
981
981
|
|
|
982
|
-
<!-- 8 ยท
|
|
982
|
+
<!-- 8 ยท EVERGREEN + DREAM -->
|
|
983
983
|
<details class="card rail-prove">
|
|
984
984
|
<summary>
|
|
985
985
|
<span class="card-ic ic-prove" aria-hidden="true">
|
|
986
986
|
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><polyline points="12 6.8 12 12 15.6 14"/></svg>
|
|
987
987
|
</span>
|
|
988
988
|
<span class="card-head">
|
|
989
|
-
<h2>8 ยท
|
|
990
|
-
<span class="q">
|
|
989
|
+
<h2>8 ยท Evergreen + Dream evaluation</h2>
|
|
990
|
+
<span class="q">Updates installed bytes and evaluates changes without writing the primary checkout.</span>
|
|
991
991
|
</span>
|
|
992
992
|
<span class="chips"><span class="chip tone-opt">optional</span><span class="chip tone-cost">changes your machine</span></span>
|
|
993
993
|
<span class="chev" aria-hidden="true">โบ</span>
|
|
994
994
|
</summary>
|
|
995
995
|
<div class="body">
|
|
996
996
|
<dl class="beat">
|
|
997
|
-
<dt>What</dt><dd>
|
|
998
|
-
<dt>Gives</dt><dd>
|
|
999
|
-
<dt>Costs</dt><dd>A launchd plist
|
|
997
|
+
<dt>What</dt><dd>An optional installed-cache updater plus a non-merging Dream Machine evaluation cycle.</dd>
|
|
998
|
+
<dt>Gives</dt><dd>Fresh published bytes on installed hosts and evidence-backed improvement proposals without unattended source writes.</dd>
|
|
999
|
+
<dt>Costs</dt><dd>A launchd plist for Evergreen is a <b>real machine mutation</b>, which is why the installer asks for it behind its own flag. Author rebuild CPU and disk are spent only after an explicit clean linked-worktree run.</dd>
|
|
1000
1000
|
<dt class="lose">Lose</dt><dd class="lose">Freshness, degrading continuously โ but <b>visibly</b>. The staleness line on every search response prints the real age of the stores it queried, so you can always see how bad it has become. You can also refresh by hand at any time.</dd>
|
|
1001
1001
|
</dl>
|
|
1002
|
-
<div class="why"><b>
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
A reserved exit code marks "skipped, previous run still going" so a skip is never recorded as a
|
|
1008
|
-
failure โ <b>silence is never allowed to mean health</b>.</div>
|
|
1002
|
+
<div class="why"><b>The primary checkout is not an automation workspace.</b> The former
|
|
1003
|
+
<code>com.ruvnet.brain-nightly</code> source writer was retired after it accumulated generated
|
|
1004
|
+
changes beside active work. Author rebuilds now refuse primary, nested, non-linked, and dirty
|
|
1005
|
+
worktrees and verify afterward that primary HEAD, index, tracked files, and untracked-file set
|
|
1006
|
+
did not change. Dream evaluation cannot merge; Evergreen updates the installed cache only.</div>
|
|
1009
1007
|
</div>
|
|
1010
1008
|
</details>
|
|
1011
1009
|
|
|
@@ -1171,7 +1169,7 @@ RUVNET_BRAIN_METER=0 # stop the measuring (editing hooks.json stops
|
|
|
1171
1169
|
<tr><td class="cell-name">โฆ/health.json</td><td>brain alarm</td><td>last known retrieval health</td></tr>
|
|
1172
1170
|
<tr><td class="cell-name">โฆ/console-undo.jsonl</td><td>console apply</td><td>the inverse of every change, journalled <i>first</i></td></tr>
|
|
1173
1171
|
<tr><td class="cell-name">~/.config/ruvnet-brain/lessons.json</td><td>you, ratifying</td><td>your corrections</td></tr>
|
|
1174
|
-
<tr><td class="cell-name">launchd plists</td><td><b>only if you say yes</b></td><td>
|
|
1172
|
+
<tr><td class="cell-name">launchd plists</td><td><b>only if you say yes</b></td><td>installed-cache updates and other explicitly enabled jobs</td></tr>
|
|
1175
1173
|
</tbody>
|
|
1176
1174
|
</table>
|
|
1177
1175
|
</div>
|
|
@@ -431,14 +431,14 @@ main { padding: 1.4rem 0 3rem; }
|
|
|
431
431
|
<h3 class="row-title">Keep it auto-updated (Evergreen)</h3>
|
|
432
432
|
<span class="badge badge-rec">Recommended ยท default on</span>
|
|
433
433
|
</div>
|
|
434
|
-
<p class="row-desc">Installs a macOS LaunchAgent (<code>com.ruvnet.brain-
|
|
434
|
+
<p class="row-desc">Installs a macOS LaunchAgent (<code>com.ruvnet.brain-update</code>) that updates the installed brain at 03:47 every night.</p>
|
|
435
435
|
<p class="row-impl"><b>Uncheck this and:</b> updates become manual forever (<code>npx ruvnet-brain --update</code>) โ rUv ships fast, and a stale brain is the single biggest way this stops being useful.</p>
|
|
436
436
|
<p class="row-ev">bin/install.mjs:1802–1868 (offerNightly), plist :1202–1291 ยท default-on parsing :1703–1706</p>
|
|
437
437
|
<p class="row-undo">Undo anytime: <code>npx ruvnet-brain --disable-nightly</code> / <code>--enable-nightly</code></p>
|
|
438
438
|
|
|
439
439
|
<div class="proof" id="proof-4">
|
|
440
440
|
<span class="proof-pill" id="proof-4-pill">✓ verified running</span>
|
|
441
|
-
<span class="proof-detail" id="proof-4-detail">launchctl confirms <code style="background:none;border:0;padding:0;color:inherit;">com.ruvnet.brain-
|
|
441
|
+
<span class="proof-detail" id="proof-4-detail">launchctl confirms <code style="background:none;border:0;padding:0;color:inherit;">com.ruvnet.brain-update</code> is scheduled · last run: not yet — first run tonight at 03:47.</span>
|
|
442
442
|
<span class="proof-caption">Shown here as it will appear once installed. The checkbox alone never earns this pill โ only a live <code style="background:none;border:0;padding:0;color:inherit;">launchctl</code> check does, reusing the same detector <code style="background:none;border:0;padding:0;color:inherit;">capability-registry.mjs</code>'s nightly-refresh row already runs (:754–800), so the installer and the Console can never disagree about whether it's really on.</span>
|
|
443
443
|
</div>
|
|
444
444
|
</div>
|
|
@@ -528,7 +528,7 @@ main { padding: 1.4rem 0 3rem; }
|
|
|
528
528
|
if (input.checked) {
|
|
529
529
|
proof.classList.remove('is-off');
|
|
530
530
|
pill.innerHTML = '✓ verified running';
|
|
531
|
-
detail.innerHTML = 'launchctl confirms <code style="background:none;border:0;padding:0;color:inherit;">com.ruvnet.brain-
|
|
531
|
+
detail.innerHTML = 'launchctl confirms <code style="background:none;border:0;padding:0;color:inherit;">com.ruvnet.brain-update</code> is scheduled · last run: not yet — first run tonight at 03:47.';
|
|
532
532
|
} else {
|
|
533
533
|
proof.classList.add('is-off');
|
|
534
534
|
pill.textContent = 'off';
|
package/kb/zip-extract.mjs
CHANGED
|
@@ -186,7 +186,7 @@ function safeJoin(destDir, name) {
|
|
|
186
186
|
|
|
187
187
|
/**
|
|
188
188
|
* Extract `zipPath` into `destDir`, overwriting existing files (idempotent โ the `-o` of
|
|
189
|
-
* `unzip -q -o`). Returns { entries, files, bytes, crcChecked }.
|
|
189
|
+
* `unzip -q -o`). Returns { entries, entryNames, files, bytes, crcChecked }.
|
|
190
190
|
* Throws an Error naming the archive and the offending entry on ANY problem.
|
|
191
191
|
*/
|
|
192
192
|
export async function extractZip(zipPath, destDir) {
|
|
@@ -322,7 +322,13 @@ export async function extractZip(zipPath, destDir) {
|
|
|
322
322
|
files++;
|
|
323
323
|
bytes += tally.bytes;
|
|
324
324
|
}
|
|
325
|
-
return {
|
|
325
|
+
return {
|
|
326
|
+
entries: entries.length,
|
|
327
|
+
entryNames: entries.map((entry) => entry.name),
|
|
328
|
+
files,
|
|
329
|
+
bytes,
|
|
330
|
+
crcChecked: Boolean(crc32),
|
|
331
|
+
};
|
|
326
332
|
} catch (err) {
|
|
327
333
|
// Always name the archive: "extraction failed" without the file is the message this whole
|
|
328
334
|
// change exists to stop shipping.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvnet-brain",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.1-dev",
|
|
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": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"test:regression": "vitest run tests/regression",
|
|
22
22
|
"qe:ux": "node scripts/qe/ux-suite.mjs",
|
|
23
23
|
"test:cov": "vitest run tests/unit --coverage",
|
|
24
|
+
"test:release:preflight": "vitest run tests/unit/public-verification-inputs.test.mjs",
|
|
24
25
|
"test:all": "npm run test:unit && npm run test:mesh && npm run test:mutation && npm run test:regression && npm run test:integration && npm test",
|
|
25
26
|
"metaharness:receipts": "node scripts/metaharness-receipts.mjs",
|
|
26
27
|
"route:cheap": "node scripts/route-cheap.mjs",
|
|
@@ -34,6 +35,9 @@
|
|
|
34
35
|
"eval:top100": "node scripts/top100-benchmark.mjs",
|
|
35
36
|
"gists:index": "node scripts/ingest-gists.mjs --index-only",
|
|
36
37
|
"gists:sync": "node scripts/ingest-gists.mjs && node kb/forge-big.mjs both --dir kb --name ruv-gists",
|
|
38
|
+
"gists:rebuild-receipts": "node scripts/rebuild-gists-from-receipts.mjs",
|
|
39
|
+
"cards:from-source": "node scripts/card-from-source.mjs",
|
|
40
|
+
"release:abort-stale": "node scripts/release-abort-stale.mjs",
|
|
37
41
|
"test:integration": "vitest run tests/integration",
|
|
38
42
|
"substitution:check": "node scripts/no-silent-substitution.mjs",
|
|
39
43
|
"catalog:verify": "node scripts/verify-model-catalog.mjs",
|
|
@@ -47,6 +51,8 @@
|
|
|
47
51
|
"learning:replay:dry": "node scripts/learning-replay.mjs --dry-run",
|
|
48
52
|
"wired:check": "node scripts/wired-check.mjs --check",
|
|
49
53
|
"doc:currency": "node scripts/doc-currency.mjs --check",
|
|
54
|
+
"integrity:trace:check": "node scripts/product-integrity-contract.mjs --check-markdown docs/reviews/adr-072-traceability.md",
|
|
55
|
+
"integrity:trace:json": "node scripts/product-integrity-contract.mjs",
|
|
50
56
|
"status:check": "node scripts/status-honesty.mjs",
|
|
51
57
|
"cap:collect": "node scripts/rerank-cap-eval.mjs --collect",
|
|
52
58
|
"cap:report": "node scripts/rerank-cap-eval.mjs --report",
|
|
@@ -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 an enforced UserPromptSubmit retrieve-and-inject grounding hook that sharply reduces drift.",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.3.1-dev",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Stuart Kerr"
|
|
7
7
|
},
|
package/plugin/hooks/hooks.json
CHANGED
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
"timeout": 5
|
|
22
22
|
}
|
|
23
23
|
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"matcher": "clear|compact|fork",
|
|
27
|
+
"hooks": [
|
|
28
|
+
{
|
|
29
|
+
"type": "command",
|
|
30
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" session-start || true",
|
|
31
|
+
"timeout": 5
|
|
32
|
+
}
|
|
33
|
+
]
|
|
24
34
|
}
|
|
25
35
|
],
|
|
26
36
|
"PreCompact": [
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"kind": "ruvnet-brain-host-adapter",
|
|
4
|
+
"id": "claude",
|
|
5
|
+
"displayName": "Claude Code",
|
|
6
|
+
"os": ["linux", "macos", "windows"],
|
|
7
|
+
"modes": ["claude", "dual"],
|
|
8
|
+
"manifest": "plugin/.claude-plugin/plugin.json",
|
|
9
|
+
"hooks": "plugin/hooks/hooks.json",
|
|
10
|
+
"loader": "bin/install.mjs"
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"kind": "ruvnet-brain-host-adapter",
|
|
4
|
+
"id": "codex",
|
|
5
|
+
"displayName": "Codex",
|
|
6
|
+
"os": ["linux", "macos", "windows"],
|
|
7
|
+
"modes": ["codex", "dual"],
|
|
8
|
+
"manifest": "plugin/.codex-plugin/plugin.json",
|
|
9
|
+
"hooks": "plugin/hooks/codex-hooks.json",
|
|
10
|
+
"loader": "bin/install.mjs"
|
|
11
|
+
}
|