ruvnet-brain 4.2.2-dev โ†’ 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +15 -14
  2. package/bin/install.mjs +11 -2
  3. package/console/architecture.html +12 -14
  4. package/console/install-mockup.html +3 -3
  5. package/kb/zip-extract.mjs +8 -2
  6. package/package.json +7 -1
  7. package/plugin/.claude-plugin/plugin.json +1 -1
  8. package/plugin/.codex-plugin/plugin.json +1 -1
  9. package/plugin/hooks/codex-hooks.json +1 -1
  10. package/plugin/hooks/hooks.json +10 -0
  11. package/plugin/host-adapters/claude.json +11 -0
  12. package/plugin/host-adapters/codex.json +11 -0
  13. package/plugin/mcp/managed-cli-interface.mjs +77 -10
  14. package/plugin/mcp/server.mjs +2 -1
  15. package/plugin/scripts/anticipate.sh +30 -1
  16. package/plugin/scripts/capability-claim-evidence.mjs +431 -0
  17. package/plugin/scripts/capability-inventory-receipt.mjs +225 -0
  18. package/plugin/scripts/capability-registry.mjs +6 -0
  19. package/plugin/scripts/capability-routing.mjs +70 -0
  20. package/plugin/scripts/continuation-gate.mjs +68 -2
  21. package/plugin/scripts/coverage-integrity.mjs +575 -0
  22. package/plugin/scripts/ground-ruvnet.sh +2 -2
  23. package/plugin/scripts/nightly-controller.mjs +19 -2
  24. package/plugin/scripts/project-progression-contract.mjs +409 -0
  25. package/plugin/scripts/project-progression-hook.mjs +196 -0
  26. package/plugin/scripts/project-progression-outbox.mjs +95 -0
  27. package/plugin/scripts/project-progression-session-start.mjs +183 -0
  28. package/plugin/scripts/project-progression-store.mjs +248 -0
  29. package/plugin/scripts/project-store-resolver.mjs +106 -0
  30. package/plugin/scripts/session-snapshot-hook.mjs +29 -1
  31. package/plugin/scripts/session-start-core.mjs +22 -18
  32. package/plugin/scripts/spend-guard.mjs +6 -1
  33. package/scripts/adr-072-completion.mjs +98 -0
  34. package/scripts/behavioral-l1-l4.mjs +11 -8
  35. package/scripts/brain-score.mjs +7 -2
  36. package/scripts/build-bundle.mjs +82 -6
  37. package/scripts/card-from-source.mjs +32 -6
  38. package/scripts/corpus-aggregates.mjs +194 -0
  39. package/scripts/corpus-candidate.mjs +56 -36
  40. package/scripts/corpus-reconcile.mjs +295 -28
  41. package/scripts/corpus-seed-publish.mjs +3 -3
  42. package/scripts/coverage-integrity.mjs +3 -0
  43. package/scripts/gist-receipts.mjs +183 -0
  44. package/scripts/git-hooks/pre-push +21 -0
  45. package/scripts/host-registry.mjs +118 -0
  46. package/scripts/independent-review-receipt.mjs +499 -0
  47. package/scripts/ingest-gists.mjs +34 -5
  48. package/scripts/ingest-new-repos.mjs +5 -0
  49. package/scripts/metaharness-gate.mjs +58 -0
  50. package/scripts/nightly-gists.sh +3 -3
  51. package/scripts/nightly-wrapper.sh +75 -69
  52. package/scripts/product-integrity-contract.mjs +156 -0
  53. package/scripts/public-inventory.mjs +4 -0
  54. package/scripts/public-verification-aggregate.mjs +342 -0
  55. package/scripts/public-verification-finalizer.mjs +59 -0
  56. package/scripts/public-verification-inputs.mjs +473 -0
  57. package/scripts/public-verification-lane.mjs +244 -0
  58. package/scripts/publication-receipt.mjs +50 -6
  59. package/scripts/rebuild-gists-from-receipts.mjs +50 -5
  60. package/scripts/release-projection.mjs +86 -0
  61. package/scripts/release-transaction-provider.mjs +43 -102
  62. package/scripts/release-transaction.mjs +139 -51
  63. package/scripts/release.mjs +15 -7
  64. package/scripts/restore-local-ingests.mjs +2 -2
  65. package/scripts/retrieval-canary.mjs +533 -0
  66. package/scripts/routing-flywheel.mjs +12 -1
  67. package/scripts/rvf-generation.mjs +9 -3
  68. package/scripts/self-update.mjs +19 -44
  69. package/scripts/source-coverage.mjs +415 -0
  70. package/scripts/source-scope-receipt.mjs +84 -0
  71. package/scripts/sync-census.mjs +0 -0
  72. package/scripts/wired-check.mjs +114 -24
  73. 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 โ€” [![RuvNet Brain version 4.2.2-dev โ€” updated 2026-07-30 03:24 EDT](https://img.shields.io/badge/version_4.2.2--dev-updated_2026--07--30_03:24_EDT-1E90FF?style=for-the-badge&labelColor=0757BA)](https://github.com/stuinfla/ruvnet-brain/blob/main/plugin/.claude-plugin/plugin.json)
7
+ ### ๐Ÿง  RuvNet Brain โ€” [![RuvNet Brain version 4.3.1 โ€” updated 2026-07-30 03:24 EDT](https://img.shields.io/badge/version_4.3.1-updated_2026--07--30_03:24_EDT-1E90FF?style=for-the-badge&labelColor=0757BA)](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
  [![explainer](https://img.shields.io/badge/โ–ถ%20see%20it%20live-isovision.ai%2Fruvnet--brain-e8a13a?style=flat-square)](https://isovision.ai/ruvnet-brain/)
31
31
  [![license](https://img.shields.io/badge/license-MIT-8ecae6?style=flat-square)](LICENSE)
32
32
  [![grounded](https://img.shields.io/badge/answers-cited%20rUv%20source-333?style=flat-square)](#testing--proof)
33
- [![coverage](https://img.shields.io/badge/coverage-36%25%20of%20ALL%20source%20ยท%20honest-b58900?style=flat-square)](#testing--proof)
33
+ [![coverage](https://img.shields.io/badge/coverage-41%25%20of%20ALL%20source%20ยท%20honest-b58900?style=flat-square)](#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) (the nightly publishes fresh bundles as the corpus grows). Only moves when the underlying knowledge base is rebuilt โ€” separate again from the two above.
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.2 โ€” it loads what rUv ships, without being asked
59
+ ## What's new in 4.3 โ€” it loads what rUv ships, without being asked
60
60
 
61
- **The corpus stopped drifting behind the org.** Until 4.2 nothing ever ingested a new repo: the
62
- nightly refreshed lessons, health and proofs and contained *zero* ingestion, so a repo entered the
63
- brain only when a human typed the command. `brain-stamp.mjs` had been measuring that gap every
64
- night, but nothing consumed it until the ingestion loop shipped.
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.** Everything rUv ships that has content, pulled in and kept level by
67
- `scripts/ingest-new-repos.mjs` running nightly, newest-first. Empty repos (`size=0KB`) are skipped
68
- rather than retried forever โ€” a permanent nightly failure that is actually correct behaviour
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.2 โ€” it anticipates, and it learns whether it was right
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
- - **Nightly publish โ†’ `releases/latest` chain** (`scripts/self-update.mjs --publish`, run by the `deploy/com.ruvnet.brain-nightly.plist` LaunchAgent at 03:15). The nightly rebuilds only the repos whose upstream changed, and **if anything was rebuilt** it bumps the product version, cuts a GitHub Release, and advances [`releases/latest`](https://github.com/stuinfla/ruvnet-brain/releases/latest). Plugin and knowledge bundle move under **one** version number, so the heartbeat above picks up both automatically. The exact author-vs-end-user schedules, incremental algorithm, failure behavior, and hosting recommendation are documented in [Nightly refresh and publish](docs/NIGHTLY-REFRESH.md). (The LaunchAgent is not auto-installed โ€” enabling a system scheduler needs explicit owner approval.)
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** ยท 36% 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). 36% is the honest number over every shipped file; the previous "75%" measured a hand-picked 8-file subset |
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
- /** launchd's own minimal PATH, plus the directory node/npx actually live in on this machine. */
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 `/bin/sh -c` (ADR-038) โ€”
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 ยท NIGHTLY -->
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 ยท The nightly refresh</h2>
990
- <span class="q">Rebuilds the corpus so it does not rot. The only piece that installs a scheduled job.</span>
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>A scheduled job that rebuilds the corpus from upstream.</dd>
998
- <dt>Gives</dt><dd>A corpus that does not go stale. rUv ships fast โ€” one of his tools went from 3.26 to 3.28 inside an 18-hour window.</dd>
999
- <dt>Costs</dt><dd>A launchd plist: a <b>real machine mutation</b>, which is why the installer asks for it behind its own flag and a blanket "yes to everything" cannot silently accept it. Hours of CPU on rebuild nights. Network.</dd>
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>Its operating discipline: no noise for success or a no-op.</b> Run once โ†’ on
1003
- failure wait and retry <i>once</i> (the only class a blind retry can fix) โ†’ on a second failure, a
1004
- loud alert <i>plus</i> a durable marker file that the session-start hook surfaces unprompted at
1005
- the top of the very next session. A single-instance lock guards against a rebuild that can run for
1006
- hours (verified: 2 h 29 m and still embedding) still being alive when the next night's run fires.
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>the nightly refresh and friends</td></tr>
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-nightly</code>) that updates the brain at 03:47 every night.</p>
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&ndash;1868 (offerNightly), plist :1202&ndash;1291 ยท default-on parsing :1703&ndash;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">&#10003; 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-nightly</code> is scheduled &middot; last run: not yet &mdash; first run tonight at 03:47.</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-update</code> is scheduled &middot; last run: not yet &mdash; 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&ndash;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 = '&#10003; verified running';
531
- detail.innerHTML = 'launchctl confirms <code style="background:none;border:0;padding:0;color:inherit;">com.ruvnet.brain-nightly</code> is scheduled &middot; last run: not yet &mdash; first run tonight at 03:47.';
531
+ detail.innerHTML = 'launchctl confirms <code style="background:none;border:0;padding:0;color:inherit;">com.ruvnet.brain-update</code> is scheduled &middot; last run: not yet &mdash; first run tonight at 03:47.';
532
532
  } else {
533
533
  proof.classList.add('is-off');
534
534
  pill.textContent = 'off';
@@ -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 { entries: entries.length, files, bytes, crcChecked: Boolean(crc32) };
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.2.2-dev",
3
+ "version": "4.3.1",
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.2.2-dev",
4
+ "version": "4.3.1",
5
5
  "author": {
6
6
  "name": "Stuart Kerr"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvnet-brain",
3
- "version": "4.2.2-dev",
3
+ "version": "4.3.1",
4
4
  "description": "Source-grounded RuvNet knowledge, lifecycle enforcement, and learning for Codex.",
5
5
  "author": {
6
6
  "name": "Stuart Kerr"
@@ -3,7 +3,7 @@
3
3
  "hooks": {
4
4
  "SessionStart": [
5
5
  {
6
- "matcher": "startup|resume|clear|compact",
6
+ "matcher": "startup|resume|clear|compact|fork",
7
7
  "hooks": [
8
8
  {
9
9
  "type": "command",
@@ -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 canonical = path.join(home, '.npm-global', 'bin', 'ruflo');
150
- try {
151
- fs.accessSync(canonical, fs.constants.X_OK);
152
- return canonical;
153
- } catch {
154
- return executable;
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 child = spawn(resolveManagedExecutable(executable, env), argv, {
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
- return resultOf(executable, argv, await execute(executable, argv, childEnv));
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 {
@@ -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
  }