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
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,16 +56,17 @@
|
|
|
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
60
|
|
|
61
|
-
**The corpus
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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.
|
|
65
66
|
|
|
66
|
-
- **187 stores, up from 69.**
|
|
67
|
-
`scripts/ingest-new-repos.mjs
|
|
68
|
-
rather than retried forever โ a permanent
|
|
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
|
|
69
70
|
trains you to ignore the failure line, which is how a real one would hide inside it.
|
|
70
71
|
- **174 capability cards, up from 39.** Ingesting a repo is not the same as making it reachable: a
|
|
71
72
|
store with no card is *dark* โ valid bytes no by-description query can find. Cards are written
|
|
@@ -79,7 +80,7 @@ night, but nothing consumed it until the ingestion loop shipped.
|
|
|
79
80
|
agentdb's binding, so `lesson-bridge --apply` and `learning-replay` wrote into a silent
|
|
80
81
|
non-persistent fallback. It now resolves an ABI-matched interpreter and fails loudly instead.
|
|
81
82
|
|
|
82
|
-
## What's new in 4.
|
|
83
|
+
## What's new in 4.3 โ it anticipates, and it learns whether it was right
|
|
83
84
|
|
|
84
85
|
**Building toward L4/L5 (3.9.x, dev).** The mechanisms for the top two rungs of the proactivity
|
|
85
86
|
ladder are built and wired โ but they are **not yet verified to 4.0's bar**, which requires all five
|
|
@@ -412,7 +413,7 @@ You install once. After that, three mechanisms keep you on the current brain wit
|
|
|
412
413
|
`๐ง RuvNet Brain jumped in ยท guidance only, no source read ยท v3.4.18-dev`
|
|
413
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.
|
|
414
415
|
|
|
415
|
-
- **
|
|
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).
|
|
416
417
|
|
|
417
418
|
---
|
|
418
419
|
|
|
@@ -508,7 +509,7 @@ node plugin/test/run-tests.mjs # full plugin QA over real JSO
|
|
|
508
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 |
|
|
509
510
|
| **Plugin QA** | **60 / 60** | manifests, hook firing, MCP `initialize`/`tools/list`, capability battery |
|
|
510
511
|
| **Clean-room install** | **3 / 3** | download the published bundle fresh โ unzip โ query โ grounded, cited answers |
|
|
511
|
-
| **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 |
|
|
512
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** |
|
|
513
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 |
|
|
514
515
|
|
package/bin/install.mjs
CHANGED
|
@@ -2594,9 +2594,18 @@ export function installCronEntry(line, { run = spawnSync } = {}) {
|
|
|
2594
2594
|
return { ok: true, already: false };
|
|
2595
2595
|
}
|
|
2596
2596
|
|
|
2597
|
-
/**
|
|
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
|
+
*/
|
|
2598
2605
|
const launchdPath = () => [...new Set([
|
|
2599
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'] : []),
|
|
2600
2609
|
'/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin',
|
|
2601
2610
|
])].filter(Boolean).join(':');
|
|
2602
2611
|
const cronExample = (kbDir) =>
|
|
@@ -2897,7 +2906,7 @@ function enableNightly() {
|
|
|
2897
2906
|
<key>Label</key>
|
|
2898
2907
|
<string>${NIGHTLY_LABEL}</string>
|
|
2899
2908
|
<!-- Issue #129: the SAME host-convergent entrypoint the session updater runs, not the KB-only
|
|
2900
|
-
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) โ
|
|
2901
2910
|
this execs npx directly. -->
|
|
2902
2911
|
<key>ProgramArguments</key>
|
|
2903
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
|
+
}
|
|
@@ -3,6 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { loadRuntimePreferences, runtimeChildEnv } from '../scripts/runtime-preferences.mjs';
|
|
6
|
+
import { recordManagedCliObservation, recordRegistryLatestObservation } from '../scripts/capability-claim-evidence.mjs';
|
|
6
7
|
|
|
7
8
|
export const MANAGED_EXECUTABLES = Object.freeze([
|
|
8
9
|
'ruflo',
|
|
@@ -15,6 +16,15 @@ export const MANAGED_EXECUTABLES = Object.freeze([
|
|
|
15
16
|
]);
|
|
16
17
|
|
|
17
18
|
const MANAGED = new Set(MANAGED_EXECUTABLES);
|
|
19
|
+
const REGISTRY_PACKAGES = Object.freeze({
|
|
20
|
+
ruflo: 'ruflo',
|
|
21
|
+
'claude-flow': '@claude-flow/cli',
|
|
22
|
+
'agentic-flow': 'agentic-flow',
|
|
23
|
+
'agentic-qe': 'agentic-qe',
|
|
24
|
+
ruvector: 'ruvector',
|
|
25
|
+
'agent-browser': 'agent-browser',
|
|
26
|
+
'ruv-swarm': 'ruv-swarm',
|
|
27
|
+
});
|
|
18
28
|
const SUBCOMMAND = /^[a-z][a-z0-9-]*$/;
|
|
19
29
|
const MAX_ARGS = 256;
|
|
20
30
|
const MAX_ARG_BYTES = 8192;
|
|
@@ -29,6 +39,17 @@ const executableSchema = {
|
|
|
29
39
|
};
|
|
30
40
|
|
|
31
41
|
export const MANAGED_CLI_TOOLS = Object.freeze([
|
|
42
|
+
{
|
|
43
|
+
name: 'ruvnet_registry_latest',
|
|
44
|
+
description: 'Read the exact npm registry latest version for a managed RuvNet executable and record a content-bound public-registry receipt.',
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
additionalProperties: false,
|
|
48
|
+
properties: { executable: executableSchema },
|
|
49
|
+
required: ['executable'],
|
|
50
|
+
},
|
|
51
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
52
|
+
},
|
|
32
53
|
{
|
|
33
54
|
name: 'ruvnet_cli_help',
|
|
34
55
|
description: 'Read a managed CLI interface from the executable itself. Runs only the supplied subcommand path plus --help and records a fresh stamp only after exit 0.',
|
|
@@ -146,20 +167,43 @@ function writeStamps(executable, argv, env) {
|
|
|
146
167
|
export function resolveManagedExecutable(executable, env = process.env) {
|
|
147
168
|
if (executable !== 'ruflo') return executable;
|
|
148
169
|
const home = env.HOME || os.homedir();
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return
|
|
153
|
-
}
|
|
154
|
-
|
|
170
|
+
const candidates = [path.join(home, '.npm-global', 'bin', 'ruflo')];
|
|
171
|
+
if (process.platform === 'win32') candidates.push(`${candidates[0]}.cmd`);
|
|
172
|
+
return candidates.find((candidate) => {
|
|
173
|
+
try { fs.accessSync(candidate, fs.constants.X_OK); return true; } catch { return false; }
|
|
174
|
+
}) || executable;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function quoteWindowsCommandArg(value) {
|
|
178
|
+
// cmd.exe is required for npm's .cmd shims. Keep the child_process call itself shell:false,
|
|
179
|
+
// and quote every literal token so shell metacharacters remain data rather than syntax.
|
|
180
|
+
// Delayed expansion is disabled by default; doubling percent signs prevents environment
|
|
181
|
+
// expansion while cmd parses the /c command line.
|
|
182
|
+
return `"${value.replace(/%/g, '%%').replace(/"/g, '""')}"`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function spawnSpec(executable, argv, env) {
|
|
186
|
+
const resolved = resolveManagedExecutable(executable, env);
|
|
187
|
+
if (process.platform !== 'win32' || !resolved.toLowerCase().endsWith('.cmd')) {
|
|
188
|
+
return { file: resolved, args: argv, windowsVerbatimArguments: false };
|
|
155
189
|
}
|
|
190
|
+
// `/s /c` removes the first and last quote characters from its command string. The outer
|
|
191
|
+
// envelope therefore preserves the inner quotes around a shim path containing spaces.
|
|
192
|
+
const command = `"${[resolved, ...argv].map(quoteWindowsCommandArg).join(' ')}"`;
|
|
193
|
+
return {
|
|
194
|
+
file: env.ComSpec || env.COMSPEC || 'cmd.exe',
|
|
195
|
+
args: ['/d', '/s', '/c', command],
|
|
196
|
+
windowsVerbatimArguments: true,
|
|
197
|
+
};
|
|
156
198
|
}
|
|
157
199
|
|
|
158
200
|
function execute(executable, argv, env) {
|
|
159
201
|
return new Promise((resolve) => {
|
|
160
|
-
const
|
|
202
|
+
const spec = spawnSpec(executable, argv, env);
|
|
203
|
+
const child = spawn(spec.file, spec.args, {
|
|
161
204
|
env,
|
|
162
205
|
shell: false,
|
|
206
|
+
windowsVerbatimArguments: spec.windowsVerbatimArguments,
|
|
163
207
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
164
208
|
});
|
|
165
209
|
const stdout = [];
|
|
@@ -213,16 +257,37 @@ function resultOf(executable, argv, result) {
|
|
|
213
257
|
};
|
|
214
258
|
}
|
|
215
259
|
|
|
216
|
-
export async function callManagedCli(toolName, args, env = process.env) {
|
|
260
|
+
export async function callManagedCli(toolName, args, env = process.env, fetchImpl = globalThis.fetch) {
|
|
217
261
|
try {
|
|
218
262
|
const executable = assertExecutable(args?.executable);
|
|
219
|
-
const argv = literalArgv(args?.argv);
|
|
263
|
+
const argv = literalArgv(args?.argv ?? []);
|
|
264
|
+
|
|
265
|
+
if (toolName === 'ruvnet_registry_latest') {
|
|
266
|
+
const packageName = REGISTRY_PACKAGES[executable];
|
|
267
|
+
const registryUrl = `https://registry.npmjs.org/${packageName.replace('/', '%2F')}/latest`;
|
|
268
|
+
const response = await fetchImpl(registryUrl, {
|
|
269
|
+
headers: { accept: 'application/json' },
|
|
270
|
+
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS),
|
|
271
|
+
});
|
|
272
|
+
const body = await response.text();
|
|
273
|
+
if (!response.ok) throw new Error(`registry latest lookup failed with HTTP ${response.status}`);
|
|
274
|
+
let metadata;
|
|
275
|
+
try { metadata = JSON.parse(body); } catch { throw new Error('registry latest response was not JSON'); }
|
|
276
|
+
const receipt = recordRegistryLatestObservation({
|
|
277
|
+
executable, packageName, version: metadata?.version, registryUrl, responseBody: body, env,
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
content: [{ type: 'text', text: `${packageName} latest version: ${receipt.observedVersion} (registry receipt ${receipt.receiptSha256})` }],
|
|
281
|
+
isError: false,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
220
284
|
|
|
221
285
|
if (toolName === 'ruvnet_cli_help') {
|
|
222
286
|
stampKeysForHelp(executable, argv);
|
|
223
287
|
const commandArgv = [...argv, '--help'];
|
|
224
288
|
const execution = await execute(executable, commandArgv, env);
|
|
225
289
|
if (execution.code === 0 && !execution.error) writeStamps(executable, argv, env);
|
|
290
|
+
recordManagedCliObservation({ toolName, executable, argv: commandArgv, execution, env });
|
|
226
291
|
return resultOf(executable, commandArgv, execution);
|
|
227
292
|
}
|
|
228
293
|
|
|
@@ -263,7 +328,9 @@ export async function callManagedCli(toolName, args, env = process.env) {
|
|
|
263
328
|
const childEnv = (executable === 'agentic-flow' || executable === 'agentic-qe')
|
|
264
329
|
? runtimeChildEnv({ env, cwd: env.RUVNET_BRAIN_PROJECT_DIR || process.cwd() })
|
|
265
330
|
: env;
|
|
266
|
-
|
|
331
|
+
const execution = await execute(executable, argv, childEnv);
|
|
332
|
+
recordManagedCliObservation({ toolName, executable, argv, execution, env });
|
|
333
|
+
return resultOf(executable, argv, execution);
|
|
267
334
|
}
|
|
268
335
|
|
|
269
336
|
return {
|
package/plugin/mcp/server.mjs
CHANGED
|
@@ -327,7 +327,8 @@ async function handleClient(msg) {
|
|
|
327
327
|
return clientOk(id, { tools: FALLBACK_TOOLS });
|
|
328
328
|
}
|
|
329
329
|
case 'tools/call': {
|
|
330
|
-
if (params?.name === 'ruvnet_cli_help' || params?.name === 'ruvnet_cli_run'
|
|
330
|
+
if (params?.name === 'ruvnet_cli_help' || params?.name === 'ruvnet_cli_run'
|
|
331
|
+
|| params?.name === 'ruvnet_registry_latest') {
|
|
331
332
|
return clientOk(id, await callManagedCli(params.name, params.arguments || {}));
|
|
332
333
|
}
|
|
333
334
|
if (params?.name !== 'search_ruvnet') return clientErr(id, -32602, `unknown tool: ${params?.name}`);
|
|
@@ -195,6 +195,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
195
195
|
const MODE = process.env.RUVNET_ANTICIPATE_MODE || 'suggest';
|
|
196
196
|
const ARG = process.env.RUVNET_ANTICIPATE_ARG || '';
|
|
197
197
|
const SELF = process.env.RUVNET_ANTICIPATE_SELF || 'anticipate.sh';
|
|
198
|
+
const SELF_DIR = path.dirname(SELF);
|
|
198
199
|
|
|
199
200
|
// CANDIDATE MODE (ADR-040 / DDD-0004 "the enforcement chokepoint"). Set by unprompted-runtime.mjs on
|
|
200
201
|
// every producer child. When on, this hook writes ZERO user-facing prose: it emits ONE advocacy
|
|
@@ -414,13 +415,18 @@ const sessions = st.sessions && typeof st.sessions === 'object' ? st.sessions :
|
|
|
414
415
|
const said = new Set(strings(sessions[sid]?.said));
|
|
415
416
|
if (said.size >= MAX_PER_SESSION) quit();
|
|
416
417
|
|
|
417
|
-
let auditAll, matchGoal, floor;
|
|
418
|
+
let auditAll, matchGoal, floor, buildCapabilityRoutingReceipt, hasToolPreference;
|
|
418
419
|
try { ({ auditAll } = await import(pathToFileURL(process.env.RUVNET_CAPABILITY_REGISTRY).href)); } catch { quit(); }
|
|
419
420
|
try {
|
|
420
421
|
const gm = await import(pathToFileURL(process.env.RUVNET_GOAL_MATCH).href);
|
|
421
422
|
matchGoal = gm.matchGoal;
|
|
422
423
|
floor = gm.CONFIDENCE_FLOOR;
|
|
423
424
|
} catch { quit(); }
|
|
425
|
+
try {
|
|
426
|
+
const routingPath = process.env.RUVNET_CAPABILITY_ROUTING_MODULE
|
|
427
|
+
|| path.join(SELF_DIR, 'capability-routing.mjs');
|
|
428
|
+
({ buildCapabilityRoutingReceipt, hasToolPreference } = await import(pathToFileURL(routingPath).href));
|
|
429
|
+
} catch { /* older packed hosts predate the optional routing receipt */ }
|
|
424
430
|
if (typeof auditAll !== 'function' || typeof matchGoal !== 'function') quit();
|
|
425
431
|
const MIN_CONFIDENCE = typeof floor === 'number' && Number.isFinite(floor) ? floor : FALLBACK_CONFIDENCE_FLOOR;
|
|
426
432
|
|
|
@@ -459,6 +465,28 @@ let matches = [];
|
|
|
459
465
|
try { matches = matchGoal(prompt, dormant) || []; } catch { quit(); }
|
|
460
466
|
if (!Array.isArray(matches) || !matches.length) quit();
|
|
461
467
|
|
|
468
|
+
// Only an evidence-bound route to an existing RuvNet building block may leave this hook.
|
|
469
|
+
let routingReceipt;
|
|
470
|
+
try {
|
|
471
|
+
routingReceipt = typeof buildCapabilityRoutingReceipt === 'function'
|
|
472
|
+
? buildCapabilityRoutingReceipt({ prompt, matches, capabilities: rows }) : null;
|
|
473
|
+
// Test/host adapters may provide synthetic capabilities outside the shipped registry. Preserve
|
|
474
|
+
// their existing protocol; real RuvNet capabilities are fail-closed when no route receipt exists.
|
|
475
|
+
const knownRoute = typeof hasToolPreference === 'function' && matches.some((match) => {
|
|
476
|
+
const key = match?.capability?.key || match?.capability;
|
|
477
|
+
const liveRow = rows.find((row) => row?.key === key);
|
|
478
|
+
return hasToolPreference(key) && typeof liveRow?.evidence === 'string' && liveRow.evidence.trim()
|
|
479
|
+
&& typeof liveRow?.evidenceDigest === 'string' && liveRow.evidenceDigest.length === 64;
|
|
480
|
+
});
|
|
481
|
+
if (!routingReceipt && knownRoute) quit();
|
|
482
|
+
if (!routingReceipt) routingReceipt = { receiptSha256: 'synthetic-adapter' };
|
|
483
|
+
const receiptFile = process.env.RUVNET_CAPABILITY_ROUTING_RECEIPTS;
|
|
484
|
+
if (receiptFile) {
|
|
485
|
+
fs.mkdirSync(path.dirname(receiptFile), { recursive: true });
|
|
486
|
+
fs.appendFileSync(receiptFile, `${JSON.stringify(routingReceipt)}\n`, { mode: 0o600 });
|
|
487
|
+
}
|
|
488
|
+
} catch { quit(); }
|
|
489
|
+
|
|
462
490
|
// The matcher is trusted to rank, never to assert existence: every match is re-resolved against the
|
|
463
491
|
// dormant rows THIS audit produced. A capability the matcher names that is not dormant right now is
|
|
464
492
|
// dropped, so a stale or over-eager matcher can only ever cause silence, never a false claim.
|
|
@@ -534,6 +562,7 @@ if (EMIT_CANDIDATES) {
|
|
|
534
562
|
findingId: best.row.key,
|
|
535
563
|
severity: best.row.severity || 'normal',
|
|
536
564
|
observationHash: stateHashOf(best.row.evidence),
|
|
565
|
+
routingReceiptSha256: routingReceipt.receiptSha256,
|
|
537
566
|
}));
|
|
538
567
|
quit();
|
|
539
568
|
}
|