ruvnet-brain 4.3.20 → 4.3.25

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 (143) hide show
  1. package/README.md +5 -5
  2. package/bin/install.mjs +383 -78
  3. package/console/app.js +141 -9
  4. package/console/index.html +51 -24
  5. package/console/scope.css +137 -0
  6. package/console/scope.html +144 -0
  7. package/console/scope.js +209 -0
  8. package/console/tips.html +1 -0
  9. package/kb/corpus-release-identity.mjs +239 -0
  10. package/kb/update-storage-transaction.mjs +20 -3
  11. package/package.json +9 -2
  12. package/plugin/.claude-plugin/plugin.json +2 -2
  13. package/plugin/.codex-plugin/plugin.json +1 -1
  14. package/plugin/commands/checkpoint.md +61 -0
  15. package/plugin/hooks/codex-hooks.json +64 -1
  16. package/plugin/hooks/hook-contracts.json +299 -6
  17. package/plugin/hooks/hooks.json +81 -1
  18. package/plugin/mcp/server.mjs +23 -0
  19. package/plugin/scripts/advocacy-catalog.mjs +245 -0
  20. package/plugin/scripts/advocacy-route.mjs +460 -0
  21. package/plugin/scripts/continuation-gate.mjs +25 -2
  22. package/plugin/scripts/continuation-objective.mjs +7 -1
  23. package/plugin/scripts/continuity-hook-policy.mjs +190 -15
  24. package/plugin/scripts/coverage-integrity.mjs +7 -0
  25. package/plugin/scripts/gates.mjs +113 -10
  26. package/plugin/scripts/grounding-turn-gate.mjs +167 -0
  27. package/plugin/scripts/grounding-turn-mark.mjs +91 -0
  28. package/plugin/scripts/hook-shim.mjs +14 -0
  29. package/plugin/scripts/host-shell-boundary.mjs +43 -0
  30. package/plugin/scripts/nightly-scheduler.mjs +37 -4
  31. package/plugin/scripts/project-progression-checkpoint.mjs +145 -0
  32. package/plugin/scripts/project-progression-contract.mjs +16 -0
  33. package/plugin/scripts/project-progression-hook.mjs +3 -0
  34. package/plugin/scripts/project-progression-producer.mjs +252 -0
  35. package/plugin/scripts/project-progression-reader.mjs +271 -0
  36. package/plugin/scripts/project-progression-session-start.mjs +93 -16
  37. package/plugin/scripts/project-progression-sources.mjs +220 -0
  38. package/plugin/scripts/project-progression-store.mjs +106 -13
  39. package/plugin/scripts/ruvnet-gate1-pattern.mjs +29 -0
  40. package/plugin/scripts/session-snapshot-hook.mjs +115 -7
  41. package/plugin/scripts/session-start-budget.mjs +59 -0
  42. package/plugin/scripts/session-start-core.mjs +234 -457
  43. package/plugin/scripts/session-start-fsutil.mjs +61 -0
  44. package/plugin/scripts/session-start-health.mjs +64 -0
  45. package/plugin/scripts/session-start-hook-description.mjs +45 -0
  46. package/plugin/scripts/session-start-issue-alert.mjs +77 -0
  47. package/plugin/scripts/session-start-repo-identity.mjs +54 -0
  48. package/plugin/scripts/session-start-signals.mjs +73 -0
  49. package/plugin/scripts/session-start-trace.mjs +86 -0
  50. package/plugin/scripts/session-start-update-plane.mjs +104 -0
  51. package/plugin/scripts/unprompted-runtime.mjs +32 -2
  52. package/plugin/scripts/update-apply.mjs +2 -32
  53. package/plugin/skills/ruvnet-brain/PLAYBOOK.md +26 -2
  54. package/plugin/skills/ruvnet-brain/SKILL.md +67 -2
  55. package/scripts/adr-072-completion.mjs +1 -1
  56. package/scripts/agentdb-fleet-doctor.mjs +5 -1
  57. package/scripts/approved-runtime.mjs +197 -0
  58. package/scripts/brain-novice-50.mjs +16 -1
  59. package/scripts/brain-score.mjs +23 -5
  60. package/scripts/build-bundle.mjs +971 -530
  61. package/scripts/build-concepts.mjs +36 -116
  62. package/scripts/console-engine.test.mjs +8 -7
  63. package/scripts/console-runtime-identity.mjs +4 -0
  64. package/scripts/corpus-aggregates.mjs +94 -77
  65. package/scripts/corpus-candidate.mjs +475 -222
  66. package/scripts/corpus-next-seed.mjs +225 -0
  67. package/scripts/corpus-promotion.mjs +58 -0
  68. package/scripts/corpus-reconcile.mjs +411 -105
  69. package/scripts/doc-currency.mjs +16 -1
  70. package/scripts/dual-host-deliberation.mjs +25 -2
  71. package/scripts/dual-host-suggest.mjs +17 -1
  72. package/scripts/falsify.mjs +13 -3
  73. package/scripts/gist-receipts.mjs +482 -87
  74. package/scripts/github-health-watch.mjs +12 -2
  75. package/scripts/handoff-asset.mjs +34 -0
  76. package/scripts/hook-retirement-check.mjs +8 -1
  77. package/scripts/host-registry.mjs +1 -1
  78. package/scripts/ingest-gists.mjs +74 -101
  79. package/scripts/job-heartbeat.sh +77 -14
  80. package/scripts/learning-replay-execution.mjs +10 -4
  81. package/scripts/nightly-gists.sh +27 -13
  82. package/scripts/nightly-two-run-proof.mjs +1 -1
  83. package/scripts/nightly-watchdog.mjs +61 -4
  84. package/scripts/onboarding-console.mjs +319 -27
  85. package/scripts/oracle/produce-questions.mjs +293 -0
  86. package/scripts/oracle/producer-hosts.mjs +235 -0
  87. package/scripts/oracle/repo-recall.mjs +448 -0
  88. package/scripts/oracle/retrieval-accuracy.mjs +818 -0
  89. package/scripts/oracle/source-tree.mjs +165 -0
  90. package/scripts/oracle/source-units.mjs +391 -0
  91. package/scripts/oracle/spike-run.mjs +98 -0
  92. package/scripts/oracle/unit-inventory.mjs +141 -0
  93. package/scripts/oracle/unit-sampling.mjs +128 -0
  94. package/scripts/oracle/validate-labels.mjs +250 -0
  95. package/scripts/private-overlay.mjs +248 -0
  96. package/scripts/product-integrity-contract.mjs +1 -1
  97. package/scripts/proxy/claude-proxied.sh +6 -0
  98. package/scripts/proxy/proxy-revert.sh +5 -0
  99. package/scripts/proxy/proxy-up.sh +6 -0
  100. package/scripts/proxy/proxy-verify.mjs +4 -0
  101. package/scripts/public-inputs.mjs +409 -0
  102. package/scripts/public-verification-inputs.mjs +112 -26
  103. package/scripts/public-verification-lane.mjs +1 -1
  104. package/scripts/published-surface-probe.mjs +34 -4
  105. package/scripts/qe/card-lane-gate.mjs +16 -1
  106. package/scripts/qe/session-start-gate.mjs +16 -1
  107. package/scripts/rebuild-gists-from-receipts.mjs +58 -78
  108. package/scripts/record-lesson.mjs +4 -1
  109. package/scripts/rehearse-corpus-pipeline.mjs +994 -0
  110. package/scripts/release-abort-stale.mjs +5 -1
  111. package/scripts/release-authority.mjs +104 -12
  112. package/scripts/release-channel-kind.mjs +86 -0
  113. package/scripts/release-convergence-watchdog.mjs +7 -2
  114. package/scripts/release-projection.mjs +177 -72
  115. package/scripts/release-transaction-provider.mjs +23 -6
  116. package/scripts/release.mjs +252 -17
  117. package/scripts/retrieval-canary.mjs +87 -0
  118. package/scripts/rvf-index-audit.mjs +573 -13
  119. package/scripts/rvf-wire.mjs +269 -0
  120. package/scripts/seal-gist-receipt.mjs +65 -0
  121. package/scripts/selfcheck.mjs +42 -21
  122. package/scripts/source-coverage.mjs +253 -24
  123. package/scripts/status-honesty.mjs +25 -0
  124. package/scripts/sync-census.mjs +0 -0
  125. package/scripts/sync-version.mjs +2 -0
  126. package/scripts/trismart.mjs +42 -0
  127. package/scripts/updater-manifest.mjs +162 -0
  128. package/scripts/verify-channels.mjs +17 -5
  129. package/scripts/wired-check.mjs +48 -10
  130. package/tri-smart-skill/QUICKSTART.md +37 -0
  131. package/tri-smart-skill/README.md +92 -0
  132. package/tri-smart-skill/install.cmd +14 -0
  133. package/tri-smart-skill/install.command +13 -0
  134. package/tri-smart-skill/install.mjs +51 -0
  135. package/tri-smart-skill/install.sh +9 -0
  136. package/tri-smart-skill/tri-smart/SKILL.md +90 -0
  137. package/tri-smart-skill/tri-smart/evals/evals.json +25 -0
  138. package/tri-smart-skill/tri-smart/references/protocol.md +25 -0
  139. package/tri-smart-skill/tri-smart/references/provider-cli.md +18 -0
  140. package/tri-smart-skill/tri-smart/scripts/review.mjs +154 -0
  141. package/tri-smart-skill/tri-smart/scripts/setup.mjs +97 -0
  142. package/tri-smart-skill/tri-smart/scripts/verify-access.mjs +107 -0
  143. package/scripts/corpus-seed-publish.mjs +0 -110
package/console/app.js CHANGED
@@ -109,6 +109,8 @@ const fmtMs = (ms) => {
109
109
  const m = Math.floor(s / 60);
110
110
  return `${m} m ${Math.round(s % 60)} s`;
111
111
  };
112
+ // Signed time. A negative saving means routes were SLOWER than baseline — a finding, never a dash.
113
+ const fmtSignedMs = (ms) => (ms == null || Number.isNaN(Number(ms)) ? '—' : (Number(ms) < 0 ? `−${fmtMs(-Number(ms))}` : fmtMs(ms)));
112
114
 
113
115
  const fmtDate = (iso) => {
114
116
  if (!iso) return '—';
@@ -2011,6 +2013,22 @@ function renderMemory(mem) {
2011
2013
  el('p', { class: 'fineprint' },
2012
2014
  'Dimensions we didn’t probe this session are excluded from the score — shown grey below, never assumed. A known-broken dimension caps the score.'))));
2013
2015
 
2016
+ // Two files can sit in .swarm/. The score is about ONE of them, and the reader is told which — the
2017
+ // card once rendered the MCP coordination store's 15,824 rows as if they were the project memory.
2018
+ if (h.stores && h.stores.coordination && h.stores.coordination.exists && h.stores.canonical && h.stores.canonical.exists) {
2019
+ const k = h.stores.canonical; const c = h.stores.coordination;
2020
+ const scoredIsCanonical = h.stores.scored === k.path;
2021
+ const rows = (s) => (s.rows == null ? '' : ` (${fmtInt(s.rows)} rows)`);
2022
+ main.push(el('p', { class: 'fineprint' },
2023
+ 'Scored store: ', el('code', {}, scoredIsCanonical ? k.path : c.path), rows(scoredIsCanonical ? k : c), '. ',
2024
+ 'Also present: ', el('code', {}, scoredIsCanonical ? c.path : k.path), rows(scoredIsCanonical ? c : k),
2025
+ scoredIsCanonical
2026
+ ? ' — the MCP coordination store, not scored: a different container from the one '
2027
+ : ' — the canonical store, not scored because it holds no rows; ',
2028
+ scoredIsCanonical ? el('code', {}, 'ruflo memory store') : el('code', {}, 'ruflo memory store'),
2029
+ scoredIsCanonical ? ' writes.' : ' writes there once used.'));
2030
+ }
2031
+
2014
2032
  const learn = renderLearnings(mem.learnings);
2015
2033
  if (learn) main.push(learn);
2016
2034
 
@@ -2533,8 +2551,8 @@ function renderSavings(sv) {
2533
2551
  el('div', { class: 'total-num' }, fmtInt(taskCount)),
2534
2552
  el('div', { class: 'total-lab' }, 'tasks routed')),
2535
2553
  el('div', { class: 'total-tile' },
2536
- el('div', { class: 'total-num' }, util ? fmtUsd(util.frontierUsd) : (totals && totals.msSaved >= 0 ? fmtMs(totals.msSaved) : '—')),
2537
- el('div', { class: 'total-lab' }, util ? `if all on ${frontierName}` : 'time saved'))));
2554
+ el('div', { class: 'total-num' }, util ? fmtUsd(util.frontierUsd) : (totals && totals.msSaved != null ? fmtSignedMs(totals.msSaved) : '—')),
2555
+ el('div', { class: 'total-lab' }, util ? `if all on ${frontierName}` : (totals && totals.msSaved < 0 ? 'time cost' : 'time saved')))));
2538
2556
  }
2539
2557
 
2540
2558
  // WP2a — provenance, worn openly: these numbers are receipts, not projections.
@@ -2543,7 +2561,21 @@ function renderSavings(sv) {
2543
2561
  el('span', { class: 'prov-dot', 'aria-hidden': 'true' }),
2544
2562
  el('span', {}, 'real numbers — recomputed from your ',
2545
2563
  el('b', {}, `${fmtInt(receiptCount)} receipt${receiptCount === 1 ? '' : 's'}`),
2546
- ', never projected')));
2564
+ ', never projected',
2565
+ sv && sv.skippedUnmeasured
2566
+ ? el('span', { class: 'muted' }, ` · ${fmtInt(sv.skippedUnmeasured)} row${sv.skippedUnmeasured === 1 ? '' : 's'} carried no $ or time and ${sv.skippedUnmeasured === 1 ? 'is' : 'are'} not counted`)
2567
+ : '')));
2568
+
2569
+ // TIME, WITH ITS SIGN, ALWAYS. When the utilization hero is present the tile above shows frontier $
2570
+ // instead of time, and the only other place the time result appeared turned negatives into a dash.
2571
+ // A net loss is a finding about the router, not an embarrassment to hide.
2572
+ if (totals && totals.msSaved != null) {
2573
+ const neg = totals.msSaved < 0;
2574
+ blocks.push(el('p', { class: `fineprint${neg ? ' bp-warn' : ''}` },
2575
+ neg ? 'Time cost: ' : 'Time saved: ', el('b', {}, fmtSignedMs(totals.msSaved)),
2576
+ totals.timedCount != null ? ` across ${fmtInt(totals.timedCount)} timed route${totals.timedCount === 1 ? '' : 's'}` : '',
2577
+ neg ? ' — routed tasks took longer than their baseline in aggregate.' : '.'));
2578
+ }
2547
2579
 
2548
2580
  // The distribution — how many tasks went to each bucket, and the saved-vs-frontier math.
2549
2581
  // (computed above, so the totals-strip can stand down when this hero is doing the talking)
@@ -2833,6 +2865,25 @@ function buildSettingsForm(cfg, { endpoint }) {
2833
2865
  * now just the advocacy dial (ADR-032 §DDD-0004 "the three channels": this control is the volume knob
2834
2866
  * on the speech channel). Two stores, two forms, one shared widget — see buildSettingsField/Form.
2835
2867
  */
2868
+ // THREE FACTS, SIDE BY SIDE: "switch: off · agent: loaded · last completed run: never". The toggle
2869
+ // above this line shows a single state only when the saved choice and the scheduler agree (see
2870
+ // reconcileNightly on the server); this line shows all three regardless, so "on" can never again be
2871
+ // printed over a config file that says off and a job that has never completed a run.
2872
+ function nightlyFactsLine(f) {
2873
+ const choice = f.choice === true ? 'on' : f.choice === false ? 'off' : 'never chosen';
2874
+ const agent = f.enforcement === 'on' ? 'loaded'
2875
+ : f.enforcement === 'off' ? 'not scheduled'
2876
+ : f.enforcement === 'degraded' ? 'degraded'
2877
+ : f.enforcement === 'unsupported' ? 'unsupported here' : String(f.enforcement || 'unknown');
2878
+ const run = f.lastRun === 'ok' ? (f.lastRunAt ? fmtDate(f.lastRunAt) : 'completed')
2879
+ : f.lastRun === 'never-ran' ? 'never'
2880
+ : f.lastRun === 'failed' ? 'failed' : String(f.lastRun || 'unknown');
2881
+ return el('p', { class: `fineprint field-facts${f.agree === false ? ' bp-warn' : ''}` },
2882
+ 'switch: ', el('b', {}, choice), ' · agent: ', el('b', {}, agent), ' · last completed run: ', el('b', {}, run),
2883
+ f.agree === false ? ' — these disagree, so the toggle above shows no single state until they do.' : '',
2884
+ f.enforcementEvidence ? el('span', { class: 'muted' }, ` (${f.enforcementEvidence})`) : '');
2885
+ }
2886
+
2836
2887
  function renderSettings(cfg, us, bp) {
2837
2888
  const body = $('#body-settings');
2838
2889
  // `bp` is NOT counted here any more: its field is rendered by #card-brain at the top of the page,
@@ -2876,7 +2927,13 @@ function renderSettings(cfg, us, bp) {
2876
2927
  onclick: () => { document.getElementById('card-brain')?.scrollIntoView({ behavior: 'smooth', block: 'center' }); },
2877
2928
  }, 'Take me to it')));
2878
2929
  }
2879
- if (cfg && Array.isArray(cfg.schema) && cfg.schema.length) main.push(buildSettingsForm(cfg, { endpoint: '/api/save-config' }));
2930
+ if (cfg && Array.isArray(cfg.schema) && cfg.schema.length) {
2931
+ const form = buildSettingsForm(cfg, { endpoint: '/api/save-config' });
2932
+ const facts = cfg.runtime && cfg.runtime.nightly && cfg.runtime.nightly.facts;
2933
+ const slot = facts ? form.querySelector('#field-nightly') : null;
2934
+ if (slot) slot.append(nightlyFactsLine(facts));
2935
+ main.push(form);
2936
+ }
2880
2937
  if (us && Array.isArray(us.schema) && us.schema.length) main.push(buildSettingsForm(us, { endpoint: '/api/save-advocacy' }));
2881
2938
  if (unavailable.length) {
2882
2939
  main.push(el('section', { class: 'settings-unavailable', 'aria-labelledby': 'settings-unavailable-h' },
@@ -2997,6 +3054,22 @@ function bpProfileControl(profile) {
2997
3054
  },
2998
3055
  ];
2999
3056
  const radios = [];
3057
+ // What the number on the card IS, said in words. `installed` is the brain on this disk; the
3058
+ // bundle is what a restore would copy from. A choice with neither measured says so — a dash
3059
+ // for a measured zero was the "— stores · 0 MB" lie this card carried until 2026-09-11.
3060
+ const metaFor = (option, choice) => {
3061
+ if (option.value === 'ruvector') return `1 store · ${bpSize(choice.bytes)}`;
3062
+ if (choice.installed) return `${choice.installed.storeCount} stores · ${bpSize(choice.installed.bytes)} installed`;
3063
+ if (choice.restoreBundle?.present) return `${choice.restoreBundle.storeCount} stores · ${bpSize(choice.restoreBundle.bytes)} in the local bundle`;
3064
+ return 'not installed · size measured after restore';
3065
+ };
3066
+ // The restore mechanism, named — the same branch order saveBrainProfile() takes on Apply.
3067
+ const restoreFor = (choice) => {
3068
+ const where = choice.restoreBundle?.path || 'the release bundle path';
3069
+ if (choice.restoreVia === 'local-bundle') return { warn: false, text: `Apply restores from the signed local bundle at ${where}.` };
3070
+ if (choice.restoreVia === 'signed-download') return { warn: false, text: `No local bundle on this machine (${where}) — Apply downloads the signed complete release with forge-update and verifies it before anything lands. Size is measured after it does.` };
3071
+ return { warn: true, text: `Complete Brain cannot be restored on this machine: no local bundle at ${where} and forge-update.mjs is not installed. Run the Brain update first.` };
3072
+ };
3000
3073
  const cards = options.map((option) => {
3001
3074
  const choice = profile.choices?.[option.value] || {};
3002
3075
  const input = el('input', {
@@ -3013,12 +3086,13 @@ function bpProfileControl(profile) {
3013
3086
  el('span', { class: 'bp-profile-copy' },
3014
3087
  el('span', { class: 'bp-profile-title' }, option.title,
3015
3088
  option.value === current ? chip('active', 'green') : null),
3016
- el('span', { class: 'bp-profile-meta' },
3017
- `${choice.storeCount || (option.value === 'ruvector' ? 1 : '—')} ${choice.storeCount === 1 ? 'store' : 'stores'} · ${bpSize(choice.bytes)}`),
3089
+ el('span', { class: 'bp-profile-meta' }, metaFor(option, choice)),
3018
3090
  el('span', { class: 'bp-profile-desc' }, option.copy),
3019
- choice.available === false
3020
- ? el('span', { class: 'bp-profile-desc bp-warn' }, 'The complete release bundle is not available on this machine; run the Brain update first.')
3021
- : null));
3091
+ option.value === 'complete'
3092
+ ? (() => { const r = restoreFor(choice); return el('span', { class: `bp-profile-desc${r.warn ? ' bp-warn' : ''}` }, r.text); })()
3093
+ : (choice.available === false
3094
+ ? el('span', { class: 'bp-profile-desc bp-warn' }, 'No RuVector store exists on this machine or in a local bundle; run the Brain update first.')
3095
+ : null)));
3022
3096
  });
3023
3097
 
3024
3098
  apply.onclick = async () => {
@@ -3189,6 +3263,11 @@ const TRUST_INFO = {
3189
3263
  { k: 'Why does it matter?', t: 'This stack ships fast — latest keeps you current. Pinning holds a known-good release when you need repeatable builds.' },
3190
3264
  { k: 'How does it help me?', t: 'Read from your plugin cache on disk, never assumed. Version pinning is planned — both choices will live on this row.' },
3191
3265
  ],
3266
+ versions: [
3267
+ { k: 'What is this?', t: 'The three version numbers this machine is actually running: the knowledge-base generation (RVF-GENERATIONS.json), the running brain (runtime-identity.json, else the plugin cache), and the installed plugin (installed_plugins.json). Each is read from its own file.' },
3268
+ { k: 'Why does it matter?', t: 'After an update they should all match. While they differ, this page is describing more than one release at once — the header chip, the install-channel row and the knowledge base were once three different numbers with nothing saying so.' },
3269
+ { k: 'How does it help me?', t: 'If they disagree, finish the update (one restart picks up boot-level declarations) and re-check. "Not measured" means the file was not there to read — never a guess.' },
3270
+ ],
3192
3271
  advisor: [
3193
3272
  { k: 'What is this?', t: 'A coming mode switch. Full lets the console apply consent-gated, undoable fixes; Advisor makes every Apply button read-only — it shows the exact command and steps aside.' },
3194
3273
  { k: 'Why does it matter?', t: 'Some machines want eyes-only — work laptops, shared rigs, cautious first weeks. The right choice should be easy in both directions.' },
@@ -3284,6 +3363,29 @@ function renderTrust(t) {
3284
3363
  ],
3285
3364
  }));
3286
3365
 
3366
+ /* 3b · versions — three files, three numbers; either they agree or the page says they do not.
3367
+ The header chip (running brain), this card's channel row (installed plugin) and the KB's own
3368
+ ledger (generation) were three different numbers on one page with nothing reconciling them. */
3369
+ const v = t.versions || {};
3370
+ const vChip = v.agree === true ? chip('in sync', 'green', 'KB generation, running brain and installed plugin all report the same version')
3371
+ : v.agree === false ? chip('differ', 'warn', 'At least two of the measured versions disagree')
3372
+ : chip('partly measured', 'grey', 'Fewer than two versions could be read on this machine');
3373
+ const vShow = (x) => (x ? `v${x}` : 'not measured');
3374
+ rows.push(trustRow({
3375
+ name: 'Versions', info: TRUST_INFO.versions,
3376
+ status: vChip,
3377
+ value: [
3378
+ el('p', {}, 'KB generation ', el('b', {}, vShow(v.kbGeneration)),
3379
+ ' · running brain ', el('b', {}, vShow(v.runningBrain)),
3380
+ ' · installed plugin ', el('b', {}, v.installedPlugin ? `v${v.installedPlugin}` : 'none'),
3381
+ ...(v.latestRelease ? [' · latest release ', el('b', {}, `v${v.latestRelease}`)] : [])),
3382
+ v.agree === false
3383
+ ? el('p', { class: 'bp-warn' }, 'These should match once an update has fully landed. While they differ, this page is describing more than one release at once.')
3384
+ : null,
3385
+ el('span', { class: 'trust-src' }, 'RVF-GENERATIONS.json · runtime-identity.json / plugin cache · installed_plugins.json'),
3386
+ ],
3387
+ }));
3388
+
3287
3389
  /* 4 · advisor mode — display-only preview, clearly labeled */
3288
3390
  const advNote = el('p', { class: 'adv-note' },
3289
3391
  'Full is on: every change stays consent-gated, with its undo recorded first. The switch itself is planned — today it’s a preview and changes nothing.');
@@ -3611,6 +3713,9 @@ function renderGates(g) {
3611
3713
  setChips('chips-gates', [
3612
3714
  chip(`${s.blocking} can block`, caught ? 'green' : 'cyan'),
3613
3715
  chip(caught ? `${caught} caught` : 'nothing caught yet', caught ? 'green' : 'grey'),
3716
+ ...(s.unregisteredBlocking
3717
+ ? [chip(`${s.unregisteredBlocking} unwired`, 'warn', 'Blocking gates that exist in the plugin but are registered in no hooks.json — they cannot stop anything until wired')]
3718
+ : []),
3614
3719
  ]);
3615
3720
 
3616
3721
  const main = [];
@@ -3632,6 +3737,33 @@ function renderGates(g) {
3632
3737
  : '',
3633
3738
  infoBtn('What caught Claude', GATES_INFO)));
3634
3739
 
3740
+ // WHICH hooks.json the plugin numbers came from. On an installed host the console's own repo path
3741
+ // has no plugin/hooks/, and the card used to silently count zero plugin gates while Claude Code
3742
+ // was loading them from the plugin cache. Named here so a zero can never again pass for absence.
3743
+ main.push(el('p', { class: 'fineprint' },
3744
+ g.pluginPath
3745
+ ? ['Plugin gates read from ', el('code', {}, g.pluginPath),
3746
+ g.pluginSource === 'installed' ? ' — the plugin Claude Code loads on this machine.'
3747
+ : g.pluginSource === 'repo' ? ' — this repository checkout, not an installed plugin.' : '.']
3748
+ : ['No plugin hooks.json was found on this machine — only machine-wide hooks are counted above.']));
3749
+
3750
+ // Blocking gates the launcher knows that NOTHING registers. "0 can block" and "4 enforcers are
3751
+ // unplugged" are different sentences; only the second tells the owner what to do.
3752
+ const unplugged = Array.isArray(g.unregistered) ? g.unregistered : [];
3753
+ if (unplugged.length) {
3754
+ main.push(el('p', { class: 'lead-stat' },
3755
+ el('b', {}, String(unplugged.length)),
3756
+ ` blocking gate${unplugged.length === 1 ? '' : 's'} exist${unplugged.length === 1 ? 's' : ''} in the plugin but ${unplugged.length === 1 ? 'is' : 'are'} registered in no hooks.json — ${unplugged.length === 1 ? 'it' : 'they'} cannot stop anything until wired: `,
3757
+ ...unplugged.flatMap((u, i) => [i ? ', ' : '', el('code', {}, u.name), u.onDisk ? '' : el('span', { class: 'muted' }, ' (file missing)')]),
3758
+ '.'));
3759
+ }
3760
+ if (Array.isArray(g.git) && g.git.length) {
3761
+ main.push(el('p', { class: 'fineprint' },
3762
+ 'Git hooks in this checkout — they stop commits, not tool calls, so they are not in the count above: ',
3763
+ ...g.git.flatMap((h, i) => [i ? ', ' : '', el('code', {}, h.name), h.executable ? '' : el('span', { class: 'muted' }, ' (not executable — will not run)')]),
3764
+ '.'));
3765
+ }
3766
+
3635
3767
  if (caught) {
3636
3768
  // Deliberately NOT the .wire-lane grid: its fixed columns are sized for (count, label, meaning)
3637
3769
  // and fling a gate name and its subject to opposite sides of a dead gap. A catch is a sentence —
@@ -55,6 +55,7 @@
55
55
  <div class="head-right">
56
56
  <!-- Its own class on purpose: this is a DOOR, not machine chrome. It once borrowed
57
57
  .host-chip and vanished with the readout on phones (see THE TIPS DOOR in style.css). -->
58
+ <a class="tips-link mono" href="scope.html" title="Every repo and gist the installed brain covers — rUv's last change beside the date the brain read it"><span class="tl-long">what's in the brain</span><span class="tl-short">scope</span> →</a>
58
59
  <a class="tips-link mono" href="tips.html" title="How to actually use the Brain — three depths, one visual page"><span class="tl-long">how to use it</span><span class="tl-short">tips</span> →</a>
59
60
  <span class="host-chip mono" id="host-chip" hidden></span>
60
61
  <button class="theme-toggle" id="recheck-btn" type="button" aria-label="Re-check my machine now"
@@ -270,7 +271,7 @@
270
271
  <span class="card-head">
271
272
  <span class="stage st-mirror">Mirror</span>
272
273
  <h2 id="group-inventory-h">What have I got?</h2>
273
- <span class="q">Every rUv tool on this machine — what's installed, whether it's really what we published, and where each project launches it from.</span>
274
+ <span class="q">Every rUv tool on this machine — what's installed, and where each project launches it from.</span>
274
275
  </span>
275
276
  </div>
276
277
  <div class="qgroup-body">
@@ -306,29 +307,8 @@
306
307
  </div>
307
308
  </details>
308
309
 
309
- <!-- 1b · TRUST & PROVENANCE (v3.3 preview)
310
- PROVE-stage: what you install is what we published. One number is REAL today (the release
311
- bundle's published sha256); the empty rows say exactly what will fill them and when —
312
- never placeholder data. -->
313
- <details class="card sub-card rail-prove" id="card-trust">
314
- <summary>
315
- <span class="card-ic ic-prove" aria-hidden="true">
316
- <svg viewBox="0 0 24 24"><path d="M12 3.5 5 6v5.5c0 4.4 2.9 7.5 7 9 4.1-1.5 7-4.6 7-9V6l-7-2.5Z"/><path d="m8.8 12 2.3 2.3 4.1-4.6"/></svg>
317
- </span>
318
- <span class="card-head">
319
- <span class="stage st-prove">Prove</span>
320
- <h2>Is this really what we published?</h2>
321
- <span class="q">Is the Brain you're running the one we published? Checked against the release, not asserted.</span>
322
- </span>
323
- <span class="chips" id="chips-trust"><span class="chip tone-wait">checking…</span></span>
324
- <span class="chev" aria-hidden="true">›</span>
325
- </summary>
326
- <div class="body" id="body-trust">
327
- <div class="skeleton" aria-hidden="true"><div class="sk-bar w45"></div><div class="sk-bar w70"></div></div>
328
- <p class="loading-note">Reading the published release fingerprint from GitHub (read-only metadata —
329
- the one network touch this card makes) and your plugin cache on disk.</p>
330
- </div>
331
- </details>
310
+ <!-- 1b · (was TRUST & PROVENANCE — moved to #card-advanced at the bottom of the page on
311
+ 2026-09-12: release provenance is a maintainer's question, not an end user's.) -->
332
312
 
333
313
  <!-- 1c · HOW IT'S WIRED -->
334
314
  <details class="card sub-card rail-mirror" id="card-wiring">
@@ -628,6 +608,53 @@
628
608
  </div>
629
609
  </section>
630
610
 
611
+ <!-- ================================================= MAINTAINER · ADVANCED (closed by default)
612
+ Release provenance — "is this really what we published?" — is a maintainer's question, not an
613
+ end user's (owner, 2026-09-12: "that doesn't belong in an end-user configuration screen. That's
614
+ something for you and I to work through."). The card keeps its ids (#card-trust / #chips-trust /
615
+ #body-trust), so app.js hydrates it exactly as before; only its parent moved. Nothing is hidden
616
+ from anyone — it is one click away, below the questions a person actually asks. -->
617
+ <details class="card rail-prove" id="card-advanced">
618
+ <summary>
619
+ <span class="card-ic ic-prove" aria-hidden="true">
620
+ <svg viewBox="0 0 24 24"><path d="M12 3.5 5 6v5.5c0 4.4 2.9 7.5 7 9 4.1-1.5 7-4.6 7-9V6l-7-2.5Z"/><path d="m8.8 12 2.3 2.3 4.1-4.6"/></svg>
621
+ </span>
622
+ <span class="card-head">
623
+ <span class="stage st-prove">Prove</span>
624
+ <h2>Advanced — verify this install against the release</h2>
625
+ <span class="q">For maintainers and the security-minded: the published fingerprint and bill of materials of the Brain you are running. Everything above works without ever opening this.</span>
626
+ </span>
627
+ <span class="chev" aria-hidden="true">›</span>
628
+ </summary>
629
+ <div class="body">
630
+
631
+ <!-- TRUST & PROVENANCE (v3.3 preview)
632
+ PROVE-stage: what you install is what we published. One number is REAL today (the release
633
+ bundle's published sha256); the empty rows say exactly what will fill them and when —
634
+ never placeholder data. -->
635
+ <details class="card sub-card rail-prove" id="card-trust">
636
+ <summary>
637
+ <span class="card-ic ic-prove" aria-hidden="true">
638
+ <svg viewBox="0 0 24 24"><path d="M12 3.5 5 6v5.5c0 4.4 2.9 7.5 7 9 4.1-1.5 7-4.6 7-9V6l-7-2.5Z"/><path d="m8.8 12 2.3 2.3 4.1-4.6"/></svg>
639
+ </span>
640
+ <span class="card-head">
641
+ <span class="stage st-prove">Prove</span>
642
+ <h2>Is this really what we published?</h2>
643
+ <span class="q">Is the Brain you're running the one we published? Checked against the release, not asserted.</span>
644
+ </span>
645
+ <span class="chips" id="chips-trust"><span class="chip tone-wait">checking…</span></span>
646
+ <span class="chev" aria-hidden="true">›</span>
647
+ </summary>
648
+ <div class="body" id="body-trust">
649
+ <div class="skeleton" aria-hidden="true"><div class="sk-bar w45"></div><div class="sk-bar w70"></div></div>
650
+ <p class="loading-note">Reading the published release fingerprint from GitHub (read-only metadata —
651
+ the one network touch this card makes) and your plugin cache on disk.</p>
652
+ </div>
653
+ </details>
654
+
655
+ </div>
656
+ </details>
657
+
631
658
  </main>
632
659
 
633
660
  <footer class="foot">
@@ -0,0 +1,137 @@
1
+ /* scope.css — "what's in the brain": every repo and gist the installed brain covers, rUv's last
2
+ change beside the date the brain read it. Layered on style.css (the console's tokens, header,
3
+ .wrap, .card, .mono, .eyebrow, .muted) — only what this page adds lives here. */
4
+
5
+ .back-chip {
6
+ font-size: 12.5px; color: var(--ink-2);
7
+ border: 1px solid var(--hair); border-radius: 100px; padding: 4px 13px;
8
+ white-space: nowrap; letter-spacing: 0.06em; text-decoration: none;
9
+ transition: border-color 0.18s var(--ease), color 0.18s var(--ease), background 0.18s var(--ease);
10
+ }
11
+ .back-chip:hover { border-color: var(--amber); color: var(--amber-text); background: var(--fill); }
12
+ .brand-sub { color: var(--muted); font-weight: 400; font-size: 0.86em; margin-left: 6px; }
13
+
14
+ main.wrap { padding-block: 34px 72px; }
15
+
16
+ /* ── lead ─────────────────────────────────────────────────────────────────── */
17
+ .scope-lead h1 { font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.3rem); margin: 0 0 12px; }
18
+ .scope-rule {
19
+ font-size: 1.02rem; line-height: 1.6; color: var(--ink-2); max-width: 62ch;
20
+ border-left: 3px solid var(--amber); padding-left: 14px; margin: 0 0 18px;
21
+ }
22
+ .asof {
23
+ display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap;
24
+ font-family: var(--mono); font-size: 12.5px; color: var(--ink-2);
25
+ border: 1px solid var(--hair-strong); border-radius: 100px; padding: 5px 12px;
26
+ }
27
+ .asof .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); flex: none; }
28
+ .asof.is-old .dot { background: var(--warn); }
29
+ .asof strong { color: var(--ink); font-weight: 600; }
30
+
31
+ /* ── buckets ──────────────────────────────────────────────────────────────── */
32
+ .buckets {
33
+ display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px;
34
+ margin: 26px 0 8px;
35
+ }
36
+ .bucket {
37
+ border: 1px solid var(--ridge); border-radius: var(--radius-s); padding: 14px 16px;
38
+ background: linear-gradient(180deg, var(--surface) 0%, color-mix(in srgb, var(--surface) 55%, var(--bg-2)) 100%);
39
+ position: relative; overflow: hidden;
40
+ }
41
+ .bucket::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; }
42
+ .bucket.b-current::before { background: var(--green); }
43
+ .bucket.b-behind::before { background: var(--amber); }
44
+ .bucket.b-unverified::before{ background: var(--faint); }
45
+ .bucket.b-notin::before { background: var(--red); }
46
+ .bucket .n { font-family: var(--display); font-size: 2rem; line-height: 1; color: var(--ink); }
47
+ .bucket .n small { font-family: var(--mono); font-size: 0.78rem; color: var(--muted); margin-left: 6px; }
48
+ .bucket .l { font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); margin-top: 6px; }
49
+ .bucket .d { font-size: 12.5px; color: var(--ink-2); margin-top: 8px; line-height: 1.45; }
50
+ .bucket-foot { font-size: 12.5px; color: var(--muted); margin: 6px 0 0; }
51
+ .bucket-foot code { font-family: var(--mono); color: var(--ink-2); }
52
+
53
+ /* ── search + tables ──────────────────────────────────────────────────────── */
54
+ .scope-tools {
55
+ display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
56
+ margin: 30px 0 12px;
57
+ }
58
+ .scope-tools h2 { margin: 0; font-size: 1.15rem; }
59
+ .scope-tools .count { color: var(--muted); font-family: var(--mono); font-size: 12.5px; margin-left: 8px; }
60
+ .search {
61
+ flex: 1 1 260px; max-width: 420px;
62
+ font: inherit; font-size: 0.95rem; color: var(--ink);
63
+ background: var(--surface); border: 1px solid var(--ridge); border-radius: 100px;
64
+ padding: 9px 16px; outline: none;
65
+ transition: border-color 0.18s var(--ease);
66
+ }
67
+ .search::placeholder { color: var(--faint); }
68
+ .search:focus { border-color: var(--cyan); }
69
+
70
+ /* the one-line purpose under the title — the question a person came here with */
71
+ .scope-purpose { font-size: 1.05rem; color: var(--ink); margin: 10px 0 6px; max-width: 62ch; }
72
+
73
+ /* three views + the search, one row: newest first · A–Z · behind first */
74
+ .scope-views {
75
+ display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
76
+ margin: 26px 0 4px;
77
+ }
78
+ .views-cap { font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); margin-right: 2px; }
79
+ .view {
80
+ font: inherit; font-size: 0.9rem; color: var(--ink-2);
81
+ background: var(--surface); border: 1px solid var(--ridge); border-radius: 100px;
82
+ padding: 7px 14px; cursor: pointer;
83
+ transition: border-color 0.18s var(--ease), color 0.18s var(--ease), background 0.18s var(--ease);
84
+ }
85
+ .view:hover { border-color: var(--cyan); color: var(--ink); }
86
+ .view[aria-pressed="true"] { border-color: var(--cyan); color: var(--ink); background: color-mix(in srgb, var(--cyan) 14%, var(--surface)); }
87
+ .view:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
88
+ .scope-views .search { margin-left: auto; }
89
+
90
+ /* what the repo does, under its name — the words the search box also matches */
91
+ table.scope td.name .desc {
92
+ display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
93
+ color: var(--muted); font-size: 12px; line-height: 1.35; margin-top: 2px; max-width: 56ch;
94
+ }
95
+
96
+ .tbl-wrap { overflow-x: auto; border: 1px solid var(--ridge); border-radius: var(--radius-s); background: var(--surface); }
97
+ table.scope { width: 100%; border-collapse: collapse; font-size: 13.5px; min-width: 640px; }
98
+ table.scope th, table.scope td { padding: 9px 12px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--hair); }
99
+ table.scope thead th {
100
+ position: sticky; top: 58px; z-index: 2;
101
+ background: color-mix(in srgb, var(--surface-2) 92%, var(--bg));
102
+ font-family: var(--mono); font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted);
103
+ cursor: pointer; user-select: none; white-space: nowrap;
104
+ }
105
+ table.scope thead th[aria-sort="descending"]::after { content: " ▾"; color: var(--amber-text); }
106
+ table.scope thead th[aria-sort="ascending"]::after { content: " ▴"; color: var(--amber-text); }
107
+ table.scope tbody tr:nth-child(even) td { background: color-mix(in srgb, var(--surface-2) 40%, transparent); }
108
+ table.scope tbody tr:hover td { background: var(--fill); }
109
+ table.scope td.name a { color: var(--ink); text-decoration: none; border-bottom: 1px dotted var(--hair-strong); }
110
+ table.scope td.name a:hover { color: var(--cyan-text); border-bottom-color: var(--cyan); }
111
+ table.scope td.date { font-family: var(--mono); font-size: 12.5px; white-space: nowrap; color: var(--ink-2); }
112
+ table.scope td.date.unknown { color: var(--faint); font-style: italic; }
113
+ table.scope td.verdict { white-space: nowrap; }
114
+ table.scope td.sha { font-family: var(--mono); font-size: 11px; color: var(--faint); }
115
+
116
+ .badge {
117
+ display: inline-flex; align-items: center; gap: 6px;
118
+ font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.04em;
119
+ padding: 2px 9px; border-radius: 100px; border: 1px solid var(--hair-strong);
120
+ }
121
+ .badge::before { content: ""; width: 7px; height: 7px; border-radius: 50%; }
122
+ .badge.b-current { color: var(--green-text); border-color: color-mix(in srgb, var(--green) 35%, transparent); } .badge.b-current::before { background: var(--green); }
123
+ .badge.b-behind { color: var(--amber-text); border-color: color-mix(in srgb, var(--amber) 40%, transparent); } .badge.b-behind::before { background: var(--amber); }
124
+ .badge.b-unverified { color: var(--muted); } .badge.b-unverified::before { background: var(--faint); }
125
+ .badge.b-notin { color: var(--red-text); border-color: color-mix(in srgb, var(--red) 40%, transparent); } .badge.b-notin::before { background: var(--red); }
126
+ .gap { color: var(--amber-text); font-family: var(--mono); font-size: 11.5px; margin-left: 6px; }
127
+
128
+ .empty { padding: 26px 16px; color: var(--muted); text-align: center; }
129
+ .scope-foot { margin-top: 30px; font-size: 12.5px; color: var(--muted); line-height: 1.6; }
130
+ .scope-foot code { font-family: var(--mono); color: var(--ink-2); }
131
+ .state { padding: 40px 0; color: var(--muted); }
132
+ .state.error { color: var(--red-text); }
133
+
134
+ @media (max-width: 640px) {
135
+ .bucket .n { font-size: 1.6rem; }
136
+ table.scope thead th { top: 0; }
137
+ }
@@ -0,0 +1,144 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="color-scheme" content="dark light" />
7
+ <title>RuvNet Brain — what's in the brain</title>
8
+ <!-- Same version-free brain mark as the console. -->
9
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 64 64%22><rect x=%222%22 y=%222%22 width=%2260%22 height=%2260%22 rx=%2216%22 fill=%22%230b0d12%22 stroke=%22%232c3850%22 stroke-width=%221.5%22/><path d=%22M25 48 C17 48 11.5 41.5 13.5 34.5 C10 29 13 21 19 19 C21 12.5 29 9.5 35 12 C43 9 50.5 14.5 50.5 22 C55 26 55 34.5 50 38 C49 43.5 43 47.5 37 46.5 L31 47.5 Z%22 fill=%22none%22 stroke=%22%23f0a830%22 stroke-width=%223.4%22 stroke-linejoin=%22round%22/><path d=%22M31 47 L31 33 M31 33 L24 27 M31 33 L40 24.5 M31 33 L41 36.5%22 fill=%22none%22 stroke=%22%23f0a830%22 stroke-width=%222.4%22 stroke-linecap=%22round%22 opacity=%22.9%22/><circle cx=%2224%22 cy=%2227%22 r=%223.4%22 fill=%22%235ad6ff%22/><circle cx=%2240%22 cy=%2224.5%22 r=%223.4%22 fill=%22%23f4b13a%22/><circle cx=%2241%22 cy=%2236.5%22 r=%223.4%22 fill=%22%235fd38a%22/></svg>" />
10
+
11
+ <script>
12
+ /* Resolve theme BEFORE first paint — same mechanism as the console (index.html). */
13
+ (function () {
14
+ var t = null;
15
+ try { t = localStorage.getItem('rbc-theme'); } catch (e) {}
16
+ if (t !== 'light' && t !== 'dark') {
17
+ try { t = matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'; }
18
+ catch (e) { t = 'dark'; }
19
+ }
20
+ document.documentElement.dataset.theme = t;
21
+ })();
22
+ </script>
23
+
24
+ <link rel="stylesheet" href="style.css" />
25
+ <link rel="stylesheet" href="scope.css" />
26
+ </head>
27
+ <body>
28
+
29
+ <!-- ============================================================================
30
+ WHAT'S IN THE BRAIN — the coverage page the owner asked for on 2026-09-11.
31
+
32
+ One question, answered per row from the installed brain's OWN receipts: for every repo and
33
+ gist rUv publishes, when did rUv last change it, and when did the brain last read it. The
34
+ verdict is never the shipped `status` word (release-projection stamps every seeded row
35
+ CURRENT); it is `artifact.sourceCommit === upstream.sha` for repos and the gist receipt's
36
+ `versionSha === upstream.sha` for gists — see scripts/onboarding-console.mjs gatherScope().
37
+
38
+ HONESTY CONSTRAINT: the record this page reads shipped WITH the installed bytes and carries
39
+ its own `observedAt`. That date is printed first, before any count, and the page never says
40
+ "live". Every number here is computed from the payload; nothing is typed into this file.
41
+ ============================================================================ -->
42
+
43
+ <a class="skip-link" href="#main">Skip to content</a>
44
+
45
+ <header class="head">
46
+ <div class="wrap head-inner">
47
+ <div class="brand">
48
+ <svg class="mark" viewBox="0 0 64 64" aria-hidden="true">
49
+ <rect class="mk-plate" x="2" y="2" width="60" height="60" rx="16" />
50
+ <path class="mk-brain" d="M25 48 C17 48 11.5 41.5 13.5 34.5 C10 29 13 21 19 19 C21 12.5 29 9.5 35 12 C43 9 50.5 14.5 50.5 22 C55 26 55 34.5 50 38 C49 43.5 43 47.5 37 46.5 L31 47.5 Z" />
51
+ <path class="mk-branch" d="M31 47 L31 33 M31 33 L24 27 M31 33 L40 24.5 M31 33 L41 36.5" />
52
+ <circle class="n2" cx="24" cy="27" r="4" /><circle class="n1" cx="40" cy="24.5" r="4" /><circle class="n3" cx="41" cy="36.5" r="4" />
53
+ </svg>
54
+ <span class="brand-text">RuvNet Brain <span class="brand-sub">what's in the brain</span></span>
55
+ </div>
56
+ <div class="head-right">
57
+ <a class="back-chip mono" href="tips.html">how to use it</a>
58
+ <a class="back-chip mono" href="./">‹ console</a>
59
+ <button class="theme-toggle" id="theme-toggle" type="button" aria-label="Switch color theme">
60
+ <svg class="ic-sun" viewBox="0 0 24 24" aria-hidden="true">
61
+ <circle cx="12" cy="12" r="4.5" />
62
+ <g class="rays"><line x1="12" y1="2" x2="12" y2="5"/><line x1="12" y1="19" x2="12" y2="22"/><line x1="2" y1="12" x2="5" y2="12"/><line x1="19" y1="12" x2="22" y2="12"/><line x1="4.9" y1="4.9" x2="7" y2="7"/><line x1="17" y1="17" x2="19.1" y2="19.1"/><line x1="4.9" y1="19.1" x2="7" y2="17"/><line x1="17" y1="7" x2="19.1" y2="4.9"/></g>
63
+ </svg>
64
+ <svg class="ic-moon" viewBox="0 0 24 24" aria-hidden="true">
65
+ <path d="M20.4 14.3A8.5 8.5 0 0 1 9.7 3.6a8.5 8.5 0 1 0 10.7 10.7Z" />
66
+ </svg>
67
+ </button>
68
+ </div>
69
+ </div>
70
+ <div class="spectrum-edge" aria-hidden="true"></div>
71
+ </header>
72
+
73
+ <main id="main" class="wrap">
74
+
75
+ <section class="scope-lead">
76
+ <p class="eyebrow">coverage · every repo and gist the installed brain knows</p>
77
+ <h1>What's in the brain</h1>
78
+ <p class="scope-purpose">Is the latest thing rUv shipped in here? Search a repo or gist below; if rUv's last change is on or before the brain's read date, it's in.</p>
79
+ <p class="scope-rule">If rUv's last change to a repo is on or before the date the brain read it, everything in that repo is in the brain. If it's after, the brain is behind on that repo by the difference.</p>
80
+ <div id="asof" class="asof"><span class="dot"></span>reading the installed brain's coverage record…</div>
81
+ <p id="asof-note" class="muted" style="font-size:12.5px;margin:8px 0 0;"></p>
82
+ </section>
83
+
84
+ <div id="state" class="state">Reading COVERAGE.json and RVF-GENERATIONS.json from the installed store root…</div>
85
+ <noscript><p class="state">This page needs the console's JavaScript to render the listing — every number on it is read from the installed brain, not written here.</p></noscript>
86
+
87
+ <div id="content" hidden>
88
+ <div id="buckets" class="buckets"></div>
89
+ <p id="bucket-foot" class="bucket-foot"></p>
90
+
91
+ <!-- Three ways to look, one search — the owner's ask (2026-09-12): "alphabetically, or thematically,
92
+ or by date". There is no theme taxonomy in the installed data (measured), so "thematically" is
93
+ served honestly by searching what each repo DOES (its capability-card line), not by invented
94
+ categories. The buttons drive BOTH tables; a column-header click still sorts one table. -->
95
+ <div class="scope-views" role="group" aria-label="How to list them">
96
+ <span class="views-cap mono">show</span>
97
+ <button type="button" class="view" data-view="newest" aria-pressed="true">Newest first</button>
98
+ <button type="button" class="view" data-view="az" aria-pressed="false">A–Z</button>
99
+ <button type="button" class="view" data-view="behind" aria-pressed="false">Behind first</button>
100
+ <input id="search" class="search" type="search" placeholder="search repos and gists — by name or what they do…" autocomplete="off" spellcheck="false" />
101
+ </div>
102
+
103
+ <div class="scope-tools">
104
+ <h2>Repositories <span id="repos-count" class="count"></span></h2>
105
+ </div>
106
+ <div class="tbl-wrap">
107
+ <table id="repos-table" class="scope">
108
+ <thead><tr>
109
+ <th data-key="name" aria-sort="none">repository</th>
110
+ <th data-key="ruvChangedAt" aria-sort="descending" title="date of the default-branch HEAD commit — what the brain ingests">rUv's last change</th>
111
+ <th data-key="brainReadAt" aria-sort="none" title="artifact.ingestedAt, or the generation ledger's builtUtc">brain read it</th>
112
+ <th data-key="bucket" aria-sort="none">verdict</th>
113
+ <th title="brain's source commit → upstream HEAD">commits</th>
114
+ </tr></thead>
115
+ <tbody id="repos-body"></tbody>
116
+ </table>
117
+ </div>
118
+
119
+ <div class="scope-tools">
120
+ <h2>Gists <span id="gists-count" class="count"></span></h2>
121
+ </div>
122
+ <div class="tbl-wrap">
123
+ <table id="gists-table" class="scope">
124
+ <thead><tr>
125
+ <th data-key="name" aria-sort="none">gist</th>
126
+ <th data-key="ruvChangedAt" aria-sort="descending" title="gist updated_at — gists have no branches, so this is the content date">rUv's last change</th>
127
+ <th data-key="brainReadAt" aria-sort="none">brain read it</th>
128
+ <th data-key="bucket" aria-sort="none">verdict</th>
129
+ <th title="revision the brain holds → live revision">revisions</th>
130
+ </tr></thead>
131
+ <tbody id="gists-body"></tbody>
132
+ </table>
133
+ </div>
134
+
135
+ <p class="scope-foot">
136
+ <strong>How the verdict is decided.</strong> <em>Current</em> means the brain's recorded source commit (or gist revision) equals the one live upstream at the as-of date. <em>Behind</em> means they differ — rUv changed it after the brain read it. <em>Unverified</em> means the store is installed here but no source commit was recorded for it, so neither claim can be proven. <em>Not in the brain</em> means no store file exists at the installed root. The shipped status label is never used. Dates: repos use the default-branch commit date, gists their <code>updated_at</code>; the read date is the ingest receipt, falling back to the generation ledger. Store root: <code id="foot-root"></code>
137
+ </p>
138
+ </div>
139
+
140
+ </main>
141
+
142
+ <script src="scope.js"></script>
143
+ </body>
144
+ </html>