ruvnet-brain 4.3.21 → 4.3.26
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 +5 -5
- package/bin/install.mjs +275 -60
- package/console/app.js +189 -9
- package/console/index.html +70 -24
- package/console/scope.css +137 -0
- package/console/scope.html +144 -0
- package/console/scope.js +209 -0
- package/console/style.css +26 -0
- package/console/tips.html +1 -0
- package/kb/corpus-release-identity.mjs +239 -0
- package/kb/update-storage-transaction.mjs +20 -3
- package/package.json +9 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/commands/checkpoint.md +61 -0
- package/plugin/hooks/codex-hooks.json +64 -1
- package/plugin/hooks/hook-contracts.json +299 -6
- package/plugin/hooks/hooks.json +81 -1
- package/plugin/mcp/server.mjs +23 -0
- package/plugin/scripts/advocacy-catalog.mjs +245 -0
- package/plugin/scripts/advocacy-route.mjs +460 -0
- package/plugin/scripts/continuation-gate.mjs +25 -2
- package/plugin/scripts/continuation-objective.mjs +7 -1
- package/plugin/scripts/continuity-hook-policy.mjs +190 -15
- package/plugin/scripts/coverage-integrity.mjs +7 -0
- package/plugin/scripts/gates.mjs +113 -10
- package/plugin/scripts/grounding-turn-gate.mjs +167 -0
- package/plugin/scripts/grounding-turn-mark.mjs +91 -0
- package/plugin/scripts/hook-shim.mjs +14 -0
- package/plugin/scripts/nightly-scheduler.mjs +37 -4
- package/plugin/scripts/project-progression-checkpoint.mjs +145 -0
- package/plugin/scripts/project-progression-contract.mjs +16 -0
- package/plugin/scripts/project-progression-hook.mjs +3 -0
- package/plugin/scripts/project-progression-producer.mjs +252 -0
- package/plugin/scripts/project-progression-reader.mjs +271 -0
- package/plugin/scripts/project-progression-session-start.mjs +93 -16
- package/plugin/scripts/project-progression-sources.mjs +220 -0
- package/plugin/scripts/project-progression-store.mjs +106 -13
- package/plugin/scripts/ruvnet-gate1-pattern.mjs +29 -0
- package/plugin/scripts/session-snapshot-hook.mjs +115 -7
- package/plugin/scripts/session-start-budget.mjs +59 -0
- package/plugin/scripts/session-start-core.mjs +234 -457
- package/plugin/scripts/session-start-fsutil.mjs +61 -0
- package/plugin/scripts/session-start-health.mjs +64 -0
- package/plugin/scripts/session-start-hook-description.mjs +45 -0
- package/plugin/scripts/session-start-issue-alert.mjs +77 -0
- package/plugin/scripts/session-start-repo-identity.mjs +54 -0
- package/plugin/scripts/session-start-signals.mjs +73 -0
- package/plugin/scripts/session-start-trace.mjs +86 -0
- package/plugin/scripts/session-start-update-plane.mjs +104 -0
- package/plugin/scripts/unprompted-runtime.mjs +32 -2
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +26 -2
- package/plugin/skills/ruvnet-brain/SKILL.md +67 -2
- package/scripts/adr-072-completion.mjs +1 -1
- package/scripts/agentdb-fleet-doctor.mjs +5 -1
- package/scripts/approved-runtime.mjs +197 -0
- package/scripts/brain-novice-50.mjs +16 -1
- package/scripts/brain-score.mjs +23 -5
- package/scripts/build-bundle.mjs +971 -530
- package/scripts/build-concepts.mjs +36 -116
- package/scripts/console-engine.test.mjs +8 -7
- package/scripts/console-runtime-identity.mjs +4 -0
- package/scripts/corpus-aggregates.mjs +94 -77
- package/scripts/corpus-candidate.mjs +475 -222
- package/scripts/corpus-next-seed.mjs +225 -0
- package/scripts/corpus-promotion.mjs +58 -0
- package/scripts/corpus-reconcile.mjs +411 -105
- package/scripts/doc-currency.mjs +16 -1
- package/scripts/dual-host-deliberation.mjs +25 -2
- package/scripts/dual-host-suggest.mjs +17 -1
- package/scripts/falsify.mjs +13 -3
- package/scripts/gist-receipts.mjs +482 -87
- package/scripts/github-health-watch.mjs +12 -2
- package/scripts/handoff-asset.mjs +34 -0
- package/scripts/hook-retirement-check.mjs +8 -1
- package/scripts/host-registry.mjs +1 -1
- package/scripts/ingest-gists.mjs +74 -101
- package/scripts/job-heartbeat.sh +77 -14
- package/scripts/learning-replay-execution.mjs +10 -4
- package/scripts/nightly-gists.sh +27 -13
- package/scripts/nightly-two-run-proof.mjs +1 -1
- package/scripts/nightly-watchdog.mjs +61 -4
- package/scripts/onboarding-console.mjs +364 -28
- package/scripts/oracle/produce-questions.mjs +293 -0
- package/scripts/oracle/producer-hosts.mjs +235 -0
- package/scripts/oracle/repo-recall.mjs +448 -0
- package/scripts/oracle/retrieval-accuracy.mjs +818 -0
- package/scripts/oracle/source-tree.mjs +165 -0
- package/scripts/oracle/source-units.mjs +391 -0
- package/scripts/oracle/spike-run.mjs +98 -0
- package/scripts/oracle/unit-inventory.mjs +141 -0
- package/scripts/oracle/unit-sampling.mjs +128 -0
- package/scripts/oracle/validate-labels.mjs +250 -0
- package/scripts/private-overlay.mjs +248 -0
- package/scripts/product-integrity-contract.mjs +1 -1
- package/scripts/proxy/claude-proxied.sh +6 -0
- package/scripts/proxy/proxy-revert.sh +5 -0
- package/scripts/proxy/proxy-up.sh +6 -0
- package/scripts/proxy/proxy-verify.mjs +4 -0
- package/scripts/public-inputs.mjs +409 -0
- package/scripts/public-verification-inputs.mjs +112 -26
- package/scripts/public-verification-lane.mjs +1 -1
- package/scripts/published-surface-probe.mjs +34 -4
- package/scripts/qe/card-lane-gate.mjs +16 -1
- package/scripts/qe/session-start-gate.mjs +16 -1
- package/scripts/rebuild-gists-from-receipts.mjs +58 -78
- package/scripts/record-lesson.mjs +4 -1
- package/scripts/rehearse-corpus-pipeline.mjs +994 -0
- package/scripts/release-abort-stale.mjs +5 -1
- package/scripts/release-authority.mjs +104 -12
- package/scripts/release-channel-kind.mjs +86 -0
- package/scripts/release-convergence-watchdog.mjs +7 -2
- package/scripts/release-projection.mjs +177 -72
- package/scripts/release-transaction-provider.mjs +47 -10
- package/scripts/release-transaction.mjs +40 -11
- package/scripts/release.mjs +252 -17
- package/scripts/retrieval-canary.mjs +87 -0
- package/scripts/rvf-index-audit.mjs +573 -13
- package/scripts/rvf-wire.mjs +269 -0
- package/scripts/seal-gist-receipt.mjs +65 -0
- package/scripts/selfcheck.mjs +42 -21
- package/scripts/source-coverage.mjs +253 -24
- package/scripts/status-honesty.mjs +25 -0
- package/scripts/sync-census.mjs +0 -0
- package/scripts/sync-version.mjs +2 -0
- package/scripts/trismart.mjs +42 -0
- package/scripts/updater-manifest.mjs +162 -0
- package/scripts/verify-channels.mjs +17 -5
- package/scripts/wired-check.mjs +48 -10
- package/tri-smart-skill/QUICKSTART.md +37 -0
- package/tri-smart-skill/README.md +92 -0
- package/tri-smart-skill/install.cmd +14 -0
- package/tri-smart-skill/install.command +13 -0
- package/tri-smart-skill/install.mjs +51 -0
- package/tri-smart-skill/install.sh +9 -0
- package/tri-smart-skill/tri-smart/SKILL.md +90 -0
- package/tri-smart-skill/tri-smart/evals/evals.json +25 -0
- package/tri-smart-skill/tri-smart/references/protocol.md +25 -0
- package/tri-smart-skill/tri-smart/references/provider-cli.md +18 -0
- package/tri-smart-skill/tri-smart/scripts/review.mjs +154 -0
- package/tri-smart-skill/tri-smart/scripts/setup.mjs +97 -0
- package/tri-smart-skill/tri-smart/scripts/verify-access.mjs +107 -0
- 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
|
|
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)
|
|
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' },
|
|
@@ -2916,6 +2973,53 @@ function bpField(bp) {
|
|
|
2916
2973
|
return (bp && Array.isArray(bp.schema) && bp.schema[0]) || null;
|
|
2917
2974
|
}
|
|
2918
2975
|
|
|
2976
|
+
/* ------------------------------------------------------------ inventory */
|
|
2977
|
+
|
|
2978
|
+
// Renders /api/state's `inventory` section: the installed brain's stores BY NAME, joined with their
|
|
2979
|
+
// upstream repo and build commit. Ranked by recency (server-side) — never alphabetical. Read-only.
|
|
2980
|
+
function renderInventory(inv) {
|
|
2981
|
+
const body = $('#body-inventory');
|
|
2982
|
+
if (!body) return;
|
|
2983
|
+
if (!inv || inv.available !== true) {
|
|
2984
|
+
setChips('chips-inventory', [chip('can’t read the manifest', 'nt')]);
|
|
2985
|
+
body.replaceChildren(el('p', { class: 'muted' },
|
|
2986
|
+
inv && inv.reason ? inv.reason : 'The console could not read RVF-GENERATIONS.json from the installed brain, so it is not guessing a list.'));
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
const chips = [chip(`${fmtInt(inv.count)} repositories`, 'green')];
|
|
2990
|
+
if (inv.privateCount > 0) chips.push(chip(`${fmtInt(inv.privateCount)} private`, 'cyan', 'Fenced private stores — never leave this machine'));
|
|
2991
|
+
if (inv.releaseTag) chips.push(chip(`generation ${inv.releaseTag}`, 'grey'));
|
|
2992
|
+
setChips('chips-inventory', chips);
|
|
2993
|
+
|
|
2994
|
+
const mb = inv.totalBytes ? `${(inv.totalBytes / 1e6).toFixed(0)} MB on disk` : null;
|
|
2995
|
+
const summary = el('p', { class: 'muted' },
|
|
2996
|
+
`${fmtInt(inv.count)} searchable stores`,
|
|
2997
|
+
inv.withProvenance != null ? `, ${fmtInt(inv.withProvenance)} with a recorded upstream commit` : '',
|
|
2998
|
+
mb ? `, ${mb}.` : '.');
|
|
2999
|
+
|
|
3000
|
+
const rows = (inv.stores || []).map((s) => el('li', { class: 'inv-row', 'data-name': s.name.toLowerCase() },
|
|
3001
|
+
el('span', { class: 'mono inv-name' }, s.name),
|
|
3002
|
+
s.private ? chip('private', 'cyan') : null,
|
|
3003
|
+
s.sourceRepo && /^https?:\/\//.test(s.sourceRepo)
|
|
3004
|
+
? el('a', { class: 'inv-src', href: s.sourceRepo, target: '_blank', rel: 'noopener noreferrer' }, s.sourceRepo.replace(/^https?:\/\/(www\.)?github\.com\//, ''))
|
|
3005
|
+
: el('span', { class: 'muted inv-src' }, s.sourceRepo || '—'),
|
|
3006
|
+
el('span', { class: 'mono muted inv-meta' },
|
|
3007
|
+
s.sourceCommit ? `@${s.sourceCommit}` : '', s.builtUtc ? ` · built ${String(s.builtUtc).slice(0, 10)}` : ''),
|
|
3008
|
+
));
|
|
3009
|
+
const list = el('ul', { class: 'inv-list', 'aria-label': 'Repositories in the brain' }, ...rows);
|
|
3010
|
+
const filter = el('input', {
|
|
3011
|
+
class: 'inv-filter', type: 'search', placeholder: 'Filter by name…', 'aria-label': 'Filter repositories by name',
|
|
3012
|
+
oninput: (e) => {
|
|
3013
|
+
const q = String(e.target.value || '').trim().toLowerCase();
|
|
3014
|
+
let shown = 0;
|
|
3015
|
+
for (const li of list.children) { const hit = !q || li.dataset.name.includes(q); li.hidden = !hit; if (hit) shown += 1; }
|
|
3016
|
+
count.textContent = q ? `${fmtInt(shown)} of ${fmtInt(inv.count)} match` : '';
|
|
3017
|
+
},
|
|
3018
|
+
});
|
|
3019
|
+
const count = el('span', { class: 'muted inv-count', 'aria-live': 'polite' });
|
|
3020
|
+
body.replaceChildren(summary, el('div', { class: 'inv-tools' }, filter, count), list);
|
|
3021
|
+
}
|
|
3022
|
+
|
|
2919
3023
|
function renderBrainPower(bp) {
|
|
2920
3024
|
const body = $('#body-brain');
|
|
2921
3025
|
if (!body) return;
|
|
@@ -2997,6 +3101,22 @@ function bpProfileControl(profile) {
|
|
|
2997
3101
|
},
|
|
2998
3102
|
];
|
|
2999
3103
|
const radios = [];
|
|
3104
|
+
// What the number on the card IS, said in words. `installed` is the brain on this disk; the
|
|
3105
|
+
// bundle is what a restore would copy from. A choice with neither measured says so — a dash
|
|
3106
|
+
// for a measured zero was the "— stores · 0 MB" lie this card carried until 2026-09-11.
|
|
3107
|
+
const metaFor = (option, choice) => {
|
|
3108
|
+
if (option.value === 'ruvector') return `1 store · ${bpSize(choice.bytes)}`;
|
|
3109
|
+
if (choice.installed) return `${choice.installed.storeCount} stores · ${bpSize(choice.installed.bytes)} installed`;
|
|
3110
|
+
if (choice.restoreBundle?.present) return `${choice.restoreBundle.storeCount} stores · ${bpSize(choice.restoreBundle.bytes)} in the local bundle`;
|
|
3111
|
+
return 'not installed · size measured after restore';
|
|
3112
|
+
};
|
|
3113
|
+
// The restore mechanism, named — the same branch order saveBrainProfile() takes on Apply.
|
|
3114
|
+
const restoreFor = (choice) => {
|
|
3115
|
+
const where = choice.restoreBundle?.path || 'the release bundle path';
|
|
3116
|
+
if (choice.restoreVia === 'local-bundle') return { warn: false, text: `Apply restores from the signed local bundle at ${where}.` };
|
|
3117
|
+
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.` };
|
|
3118
|
+
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.` };
|
|
3119
|
+
};
|
|
3000
3120
|
const cards = options.map((option) => {
|
|
3001
3121
|
const choice = profile.choices?.[option.value] || {};
|
|
3002
3122
|
const input = el('input', {
|
|
@@ -3013,12 +3133,13 @@ function bpProfileControl(profile) {
|
|
|
3013
3133
|
el('span', { class: 'bp-profile-copy' },
|
|
3014
3134
|
el('span', { class: 'bp-profile-title' }, option.title,
|
|
3015
3135
|
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)}`),
|
|
3136
|
+
el('span', { class: 'bp-profile-meta' }, metaFor(option, choice)),
|
|
3018
3137
|
el('span', { class: 'bp-profile-desc' }, option.copy),
|
|
3019
|
-
|
|
3020
|
-
? el('span', { class:
|
|
3021
|
-
:
|
|
3138
|
+
option.value === 'complete'
|
|
3139
|
+
? (() => { const r = restoreFor(choice); return el('span', { class: `bp-profile-desc${r.warn ? ' bp-warn' : ''}` }, r.text); })()
|
|
3140
|
+
: (choice.available === false
|
|
3141
|
+
? 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.')
|
|
3142
|
+
: null)));
|
|
3022
3143
|
});
|
|
3023
3144
|
|
|
3024
3145
|
apply.onclick = async () => {
|
|
@@ -3189,6 +3310,11 @@ const TRUST_INFO = {
|
|
|
3189
3310
|
{ 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
3311
|
{ 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
3312
|
],
|
|
3313
|
+
versions: [
|
|
3314
|
+
{ 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.' },
|
|
3315
|
+
{ 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.' },
|
|
3316
|
+
{ 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.' },
|
|
3317
|
+
],
|
|
3192
3318
|
advisor: [
|
|
3193
3319
|
{ 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
3320
|
{ 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 +3410,29 @@ function renderTrust(t) {
|
|
|
3284
3410
|
],
|
|
3285
3411
|
}));
|
|
3286
3412
|
|
|
3413
|
+
/* 3b · versions — three files, three numbers; either they agree or the page says they do not.
|
|
3414
|
+
The header chip (running brain), this card's channel row (installed plugin) and the KB's own
|
|
3415
|
+
ledger (generation) were three different numbers on one page with nothing reconciling them. */
|
|
3416
|
+
const v = t.versions || {};
|
|
3417
|
+
const vChip = v.agree === true ? chip('in sync', 'green', 'KB generation, running brain and installed plugin all report the same version')
|
|
3418
|
+
: v.agree === false ? chip('differ', 'warn', 'At least two of the measured versions disagree')
|
|
3419
|
+
: chip('partly measured', 'grey', 'Fewer than two versions could be read on this machine');
|
|
3420
|
+
const vShow = (x) => (x ? `v${x}` : 'not measured');
|
|
3421
|
+
rows.push(trustRow({
|
|
3422
|
+
name: 'Versions', info: TRUST_INFO.versions,
|
|
3423
|
+
status: vChip,
|
|
3424
|
+
value: [
|
|
3425
|
+
el('p', {}, 'KB generation ', el('b', {}, vShow(v.kbGeneration)),
|
|
3426
|
+
' · running brain ', el('b', {}, vShow(v.runningBrain)),
|
|
3427
|
+
' · installed plugin ', el('b', {}, v.installedPlugin ? `v${v.installedPlugin}` : 'none'),
|
|
3428
|
+
...(v.latestRelease ? [' · latest release ', el('b', {}, `v${v.latestRelease}`)] : [])),
|
|
3429
|
+
v.agree === false
|
|
3430
|
+
? 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.')
|
|
3431
|
+
: null,
|
|
3432
|
+
el('span', { class: 'trust-src' }, 'RVF-GENERATIONS.json · runtime-identity.json / plugin cache · installed_plugins.json'),
|
|
3433
|
+
],
|
|
3434
|
+
}));
|
|
3435
|
+
|
|
3287
3436
|
/* 4 · advisor mode — display-only preview, clearly labeled */
|
|
3288
3437
|
const advNote = el('p', { class: 'adv-note' },
|
|
3289
3438
|
'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.');
|
|
@@ -3561,6 +3710,7 @@ async function loadState({ landed = false } = {}) {
|
|
|
3561
3710
|
renderHost(state.host, state.generatedAt);
|
|
3562
3711
|
const s = state.sections || {};
|
|
3563
3712
|
renderBrainPower(s.brainPower);
|
|
3713
|
+
renderInventory(s.inventory);
|
|
3564
3714
|
renderWiring(s.wiring);
|
|
3565
3715
|
lastMemory = s.memory;
|
|
3566
3716
|
renderMemory(s.memory);
|
|
@@ -3611,6 +3761,9 @@ function renderGates(g) {
|
|
|
3611
3761
|
setChips('chips-gates', [
|
|
3612
3762
|
chip(`${s.blocking} can block`, caught ? 'green' : 'cyan'),
|
|
3613
3763
|
chip(caught ? `${caught} caught` : 'nothing caught yet', caught ? 'green' : 'grey'),
|
|
3764
|
+
...(s.unregisteredBlocking
|
|
3765
|
+
? [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')]
|
|
3766
|
+
: []),
|
|
3614
3767
|
]);
|
|
3615
3768
|
|
|
3616
3769
|
const main = [];
|
|
@@ -3632,6 +3785,33 @@ function renderGates(g) {
|
|
|
3632
3785
|
: '',
|
|
3633
3786
|
infoBtn('What caught Claude', GATES_INFO)));
|
|
3634
3787
|
|
|
3788
|
+
// WHICH hooks.json the plugin numbers came from. On an installed host the console's own repo path
|
|
3789
|
+
// has no plugin/hooks/, and the card used to silently count zero plugin gates while Claude Code
|
|
3790
|
+
// was loading them from the plugin cache. Named here so a zero can never again pass for absence.
|
|
3791
|
+
main.push(el('p', { class: 'fineprint' },
|
|
3792
|
+
g.pluginPath
|
|
3793
|
+
? ['Plugin gates read from ', el('code', {}, g.pluginPath),
|
|
3794
|
+
g.pluginSource === 'installed' ? ' — the plugin Claude Code loads on this machine.'
|
|
3795
|
+
: g.pluginSource === 'repo' ? ' — this repository checkout, not an installed plugin.' : '.']
|
|
3796
|
+
: ['No plugin hooks.json was found on this machine — only machine-wide hooks are counted above.']));
|
|
3797
|
+
|
|
3798
|
+
// Blocking gates the launcher knows that NOTHING registers. "0 can block" and "4 enforcers are
|
|
3799
|
+
// unplugged" are different sentences; only the second tells the owner what to do.
|
|
3800
|
+
const unplugged = Array.isArray(g.unregistered) ? g.unregistered : [];
|
|
3801
|
+
if (unplugged.length) {
|
|
3802
|
+
main.push(el('p', { class: 'lead-stat' },
|
|
3803
|
+
el('b', {}, String(unplugged.length)),
|
|
3804
|
+
` 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: `,
|
|
3805
|
+
...unplugged.flatMap((u, i) => [i ? ', ' : '', el('code', {}, u.name), u.onDisk ? '' : el('span', { class: 'muted' }, ' (file missing)')]),
|
|
3806
|
+
'.'));
|
|
3807
|
+
}
|
|
3808
|
+
if (Array.isArray(g.git) && g.git.length) {
|
|
3809
|
+
main.push(el('p', { class: 'fineprint' },
|
|
3810
|
+
'Git hooks in this checkout — they stop commits, not tool calls, so they are not in the count above: ',
|
|
3811
|
+
...g.git.flatMap((h, i) => [i ? ', ' : '', el('code', {}, h.name), h.executable ? '' : el('span', { class: 'muted' }, ' (not executable — will not run)')]),
|
|
3812
|
+
'.'));
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3635
3815
|
if (caught) {
|
|
3636
3816
|
// Deliberately NOT the .wire-lane grid: its fixed columns are sized for (count, label, meaning)
|
|
3637
3817
|
// and fling a gate name and its subject to opposite sides of a dead gap. A catch is a sentence —
|
package/console/index.html
CHANGED
|
@@ -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"
|
|
@@ -107,6 +108,25 @@
|
|
|
107
108
|
</div>
|
|
108
109
|
</section>
|
|
109
110
|
|
|
111
|
+
<!-- The one thing this page never showed: WHICH repositories are in the brain, by name. It always
|
|
112
|
+
carried the count and never the list. Read-only mirror of the installed manifest. -->
|
|
113
|
+
<section class="card rail-mirror" id="card-inventory" aria-labelledby="card-inventory-h">
|
|
114
|
+
<div class="qgroup-head">
|
|
115
|
+
<span class="card-ic ic-mirror" aria-hidden="true">
|
|
116
|
+
<svg viewBox="0 0 24 24"><path d="M4 6h16"/><path d="M4 12h16"/><path d="M4 18h10"/></svg>
|
|
117
|
+
</span>
|
|
118
|
+
<span class="card-head">
|
|
119
|
+
<span class="stage st-mirror">Mirror</span>
|
|
120
|
+
<h2 id="card-inventory-h">What’s in your brain</h2>
|
|
121
|
+
<span class="q">Every repository the brain can search right now, by name — where it came from and when it was built.</span>
|
|
122
|
+
</span>
|
|
123
|
+
<span class="chips" id="chips-inventory"><span class="chip tone-wait">counting…</span></span>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="body" id="body-inventory">
|
|
126
|
+
<div class="skeleton" aria-hidden="true"><div class="sk-bar w40"></div><div class="sk-bar w75"></div></div>
|
|
127
|
+
</div>
|
|
128
|
+
</section>
|
|
129
|
+
|
|
110
130
|
<!-- ======================================================= HERO -->
|
|
111
131
|
<section class="intro">
|
|
112
132
|
<div class="intro-grid">
|
|
@@ -270,7 +290,7 @@
|
|
|
270
290
|
<span class="card-head">
|
|
271
291
|
<span class="stage st-mirror">Mirror</span>
|
|
272
292
|
<h2 id="group-inventory-h">What have I got?</h2>
|
|
273
|
-
<span class="q">Every rUv tool on this machine — what's installed,
|
|
293
|
+
<span class="q">Every rUv tool on this machine — what's installed, and where each project launches it from.</span>
|
|
274
294
|
</span>
|
|
275
295
|
</div>
|
|
276
296
|
<div class="qgroup-body">
|
|
@@ -306,29 +326,8 @@
|
|
|
306
326
|
</div>
|
|
307
327
|
</details>
|
|
308
328
|
|
|
309
|
-
<!-- 1b · TRUST & PROVENANCE
|
|
310
|
-
|
|
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>
|
|
329
|
+
<!-- 1b · (was TRUST & PROVENANCE — moved to #card-advanced at the bottom of the page on
|
|
330
|
+
2026-09-12: release provenance is a maintainer's question, not an end user's.) -->
|
|
332
331
|
|
|
333
332
|
<!-- 1c · HOW IT'S WIRED -->
|
|
334
333
|
<details class="card sub-card rail-mirror" id="card-wiring">
|
|
@@ -628,6 +627,53 @@
|
|
|
628
627
|
</div>
|
|
629
628
|
</section>
|
|
630
629
|
|
|
630
|
+
<!-- ================================================= MAINTAINER · ADVANCED (closed by default)
|
|
631
|
+
Release provenance — "is this really what we published?" — is a maintainer's question, not an
|
|
632
|
+
end user's (owner, 2026-09-12: "that doesn't belong in an end-user configuration screen. That's
|
|
633
|
+
something for you and I to work through."). The card keeps its ids (#card-trust / #chips-trust /
|
|
634
|
+
#body-trust), so app.js hydrates it exactly as before; only its parent moved. Nothing is hidden
|
|
635
|
+
from anyone — it is one click away, below the questions a person actually asks. -->
|
|
636
|
+
<details class="card rail-prove" id="card-advanced">
|
|
637
|
+
<summary>
|
|
638
|
+
<span class="card-ic ic-prove" aria-hidden="true">
|
|
639
|
+
<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>
|
|
640
|
+
</span>
|
|
641
|
+
<span class="card-head">
|
|
642
|
+
<span class="stage st-prove">Prove</span>
|
|
643
|
+
<h2>Advanced — verify this install against the release</h2>
|
|
644
|
+
<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>
|
|
645
|
+
</span>
|
|
646
|
+
<span class="chev" aria-hidden="true">›</span>
|
|
647
|
+
</summary>
|
|
648
|
+
<div class="body">
|
|
649
|
+
|
|
650
|
+
<!-- TRUST & PROVENANCE (v3.3 preview)
|
|
651
|
+
PROVE-stage: what you install is what we published. One number is REAL today (the release
|
|
652
|
+
bundle's published sha256); the empty rows say exactly what will fill them and when —
|
|
653
|
+
never placeholder data. -->
|
|
654
|
+
<details class="card sub-card rail-prove" id="card-trust">
|
|
655
|
+
<summary>
|
|
656
|
+
<span class="card-ic ic-prove" aria-hidden="true">
|
|
657
|
+
<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>
|
|
658
|
+
</span>
|
|
659
|
+
<span class="card-head">
|
|
660
|
+
<span class="stage st-prove">Prove</span>
|
|
661
|
+
<h2>Is this really what we published?</h2>
|
|
662
|
+
<span class="q">Is the Brain you're running the one we published? Checked against the release, not asserted.</span>
|
|
663
|
+
</span>
|
|
664
|
+
<span class="chips" id="chips-trust"><span class="chip tone-wait">checking…</span></span>
|
|
665
|
+
<span class="chev" aria-hidden="true">›</span>
|
|
666
|
+
</summary>
|
|
667
|
+
<div class="body" id="body-trust">
|
|
668
|
+
<div class="skeleton" aria-hidden="true"><div class="sk-bar w45"></div><div class="sk-bar w70"></div></div>
|
|
669
|
+
<p class="loading-note">Reading the published release fingerprint from GitHub (read-only metadata —
|
|
670
|
+
the one network touch this card makes) and your plugin cache on disk.</p>
|
|
671
|
+
</div>
|
|
672
|
+
</details>
|
|
673
|
+
|
|
674
|
+
</div>
|
|
675
|
+
</details>
|
|
676
|
+
|
|
631
677
|
</main>
|
|
632
678
|
|
|
633
679
|
<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
|
+
}
|