ruvnet-brain 4.0.1 → 4.0.4
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/.claude-plugin/marketplace.json +1 -0
- package/README.md +4 -4
- package/bin/install.mjs +303 -24
- package/console/CONTRACT.md +172 -0
- package/console/activity.js +753 -0
- package/console/app.js +4189 -0
- package/console/architecture.html +1221 -0
- package/console/assets/depth-1.webp +0 -0
- package/console/assets/depth-2.webp +0 -0
- package/console/assets/depth-3.webp +0 -0
- package/console/assets/harness-vs-plain.svg +259 -0
- package/console/assets/hero.webp +0 -0
- package/console/assets/memory.webp +0 -0
- package/console/assets/metaharness.svg +247 -0
- package/console/index.html +777 -0
- package/console/install-architecture.html +162 -0
- package/console/install-mockup.html +543 -0
- package/console/style.css +2144 -0
- package/console/tips.css +926 -0
- package/console/tips.html +858 -0
- package/console/tips.js +128 -0
- package/docs/RELEASE-NOTES-4.0.md +88 -0
- package/kb/model-requirements.mjs +37 -6
- package/keys/ruvnet-brain-signing.pub.pem +3 -0
- package/package.json +8 -22
- package/plugin/.claude-plugin/marketplace.json +1 -0
- package/plugin/.claude-plugin/plugin.json +2 -3
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/commands/brain-console.md +2 -2
- package/plugin/commands/configure.md +3 -2
- package/plugin/commands/rvbc.md +4 -3
- package/plugin/commands/rvcb.md +2 -2
- package/plugin/commands/whats-new.md +6 -6
- package/plugin/docs/RELEASE-NOTES-4.0.md +88 -0
- package/plugin/hooks/hooks.json +1 -2
- package/plugin/mcp/managed-cli-interface.mjs +47 -4
- package/plugin/mcp/server.mjs +90 -32
- package/plugin/scripts/detach.mjs +14 -0
- package/plugin/scripts/first-session-worker.mjs +38 -0
- package/plugin/scripts/ground-ruvnet.sh +16 -6
- package/plugin/scripts/hook-shim.mjs +34 -29
- package/plugin/scripts/learn-capture.sh +22 -3
- package/plugin/scripts/learn-flush.mjs +21 -4
- package/plugin/scripts/runtime-preferences.mjs +269 -0
- package/plugin/scripts/session-start-core.mjs +503 -0
- package/plugin/scripts/session-start.sh +3 -858
- package/plugin/scripts/whats-new.mjs +42 -0
- package/plugin/skills/brain-console/SKILL.md +4 -2
- package/plugin/skills/release-proof/SKILL.md +98 -0
- package/plugin/skills/release-proof/agents/openai.yaml +4 -0
- package/plugin/skills/release-proof/references/receipt-contract.md +44 -0
- package/plugin/skills/release-proof/scripts/release-proof.mjs +286 -0
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +5 -1
- package/plugin/skills/ruvnet-brain/SKILL.md +22 -7
- package/plugin/skills/rvbc/SKILL.md +9 -6
- package/plugin/skills/whats-new/SKILL.md +4 -4
- package/scripts/adr-backfill.mjs +107 -0
- package/scripts/advocacy-outcomes.mjs +808 -0
- package/scripts/agentdb-context.mjs +216 -0
- package/scripts/agentdb-fleet-doctor.mjs +101 -0
- package/scripts/ascii-drift.mjs +236 -0
- package/scripts/behavioral-l1-l4.mjs +210 -0
- package/scripts/brain-capability-check.mjs +72 -0
- package/scripts/brain-grade-groundtruth.mjs +100 -0
- package/scripts/brain-latency-50.mjs +227 -0
- package/scripts/brain-novice-50.mjs +189 -0
- package/scripts/brain-stamp.mjs +94 -0
- package/scripts/brain-state.mjs +212 -0
- package/scripts/build-bundle.mjs +531 -0
- package/scripts/build-concepts.mjs +132 -0
- package/scripts/build-l2.mjs +71 -0
- package/scripts/build-primer.mjs +73 -0
- package/scripts/build-symbols.mjs +68 -0
- package/scripts/calibrate-router.mjs +97 -0
- package/scripts/capability-audit.mjs +321 -0
- package/scripts/capability-registry.mjs +876 -0
- package/scripts/check-indexation.mjs +108 -0
- package/scripts/check-legibility.mjs +189 -0
- package/scripts/ci/build-fixture-kb.mjs +67 -0
- package/scripts/ci/learning-replay-codex-adapter.mjs +62 -0
- package/scripts/ci/learning-replay-recorder.mjs +59 -0
- package/scripts/ci/mutate-hook-timeout.mjs +70 -0
- package/scripts/ci/stranger-fixture-stage.mjs +17 -0
- package/scripts/ci/stranger-scenario.mjs +228 -0
- package/scripts/ci/stranger-timeout.mjs +25 -0
- package/scripts/ci-verdict.mjs +29 -0
- package/scripts/claims-verify.mjs +710 -0
- package/scripts/clear-claude-tmp.sh +31 -0
- package/scripts/console-engine.mjs +434 -0
- package/scripts/console-engine.test.mjs +125 -0
- package/scripts/corpus-qa.mjs +250 -0
- package/scripts/correction-detect-embed.mjs +346 -0
- package/scripts/correction-detect-measure.mjs +270 -0
- package/scripts/correction-detect.mjs +686 -0
- package/scripts/count-chunks.mjs +54 -0
- package/scripts/described-questions.json +30 -0
- package/scripts/design-grade.mjs +58 -0
- package/scripts/dev-plugin-link.sh +105 -0
- package/scripts/distill-project.mjs +200 -0
- package/scripts/doc-currency.mjs +801 -0
- package/scripts/eval-brain.mjs +244 -0
- package/scripts/fix-metaharness-memretrieve.mjs +121 -0
- package/scripts/fix-workstream.mjs +291 -0
- package/scripts/full-hints.mjs +87 -0
- package/scripts/gate.sh +39 -0
- package/scripts/gates.mjs +146 -0
- package/scripts/gen-console-images.mjs +54 -0
- package/scripts/gen-images.mjs +47 -0
- package/scripts/git-clone-refresh.mjs +52 -0
- package/scripts/git-hooks/pre-push +126 -0
- package/scripts/goal-match.mjs +398 -0
- package/scripts/goldie-research.mjs +223 -0
- package/scripts/goldie-weekly.sh +67 -0
- package/scripts/health-repair.mjs +237 -0
- package/scripts/helix-scenario-questions.json +10 -0
- package/scripts/ingest-gists.mjs +230 -0
- package/scripts/ingest-meeting.mjs +115 -0
- package/scripts/ingest-repo.mjs +79 -0
- package/scripts/install-npx-witness.sh +49 -0
- package/scripts/issue-fix.mjs +558 -0
- package/scripts/issue-watch.mjs +276 -0
- package/scripts/issue4-close-note.md +31 -0
- package/scripts/key-canary.mjs +91 -0
- package/scripts/latency-to-surface.mjs +233 -0
- package/scripts/learning-enable.mjs +380 -0
- package/scripts/learning-replay.mjs +1570 -0
- package/scripts/learnings.mjs +62 -0
- package/scripts/lesson-gate.mjs +680 -0
- package/scripts/lesson-lifecycle.mjs +449 -0
- package/scripts/lesson-promote.mjs +262 -0
- package/scripts/lesson-ratify.mjs +98 -0
- package/scripts/lesson-seed.mjs +252 -0
- package/scripts/lesson-store.mjs +447 -0
- package/scripts/loop-checkpoint.mjs +86 -0
- package/scripts/memdb-health.sh +14 -0
- package/scripts/memory-doctor.mjs +326 -0
- package/scripts/model-catalog.mjs +79 -0
- package/scripts/nightly-controller.mjs +66 -0
- package/scripts/nightly-gists.sh +72 -0
- package/scripts/nightly-wrapper.sh +172 -0
- package/scripts/notify.sh +12 -0
- package/scripts/npx-witness.sh +56 -0
- package/scripts/onboarding-console.mjs +2922 -0
- package/scripts/private-fence.mjs +69 -0
- package/scripts/proactivity-metrics.mjs +118 -0
- package/scripts/proof-questions.json +56 -0
- package/scripts/protected-release-invocation.mjs +76 -0
- package/scripts/prove.mjs +95 -0
- package/scripts/proxy/claude-proxied.sh +57 -0
- package/scripts/proxy/proxy-revert.sh +59 -0
- package/scripts/proxy/proxy-up.sh +60 -0
- package/scripts/proxy/proxy-verify.mjs +142 -0
- package/scripts/publication-receipt.mjs +307 -0
- package/scripts/published-surface-probe.mjs +241 -0
- package/scripts/qe/card-lane-gate.mjs +162 -0
- package/scripts/qe/session-start-gate.mjs +229 -0
- package/scripts/qe/ux-suite.mjs +323 -0
- package/scripts/reconcile-project.mjs +0 -0
- package/scripts/record-lesson.mjs +113 -0
- package/scripts/refresh-model-catalog.mjs +99 -0
- package/scripts/release-authority.mjs +93 -0
- package/scripts/release-proof.mjs +9 -0
- package/scripts/release-vector.mjs +281 -0
- package/scripts/release.mjs +439 -0
- package/scripts/remedy-registry.mjs +247 -0
- package/scripts/rerank-cap-eval.mjs +265 -0
- package/scripts/rerank-cap-warm-ab.mjs +129 -0
- package/scripts/route-cheap.mjs +20 -15
- package/scripts/router-utilization.mjs +182 -0
- package/scripts/routing-flywheel.mjs +596 -0
- package/scripts/rvf-generation.mjs +104 -0
- package/scripts/rvf-index-audit.mjs +138 -0
- package/scripts/self-update.mjs +296 -0
- package/scripts/selfcheck.mjs +7 -1
- package/scripts/sign-bundle.mjs +69 -0
- package/scripts/signal-watch.mjs +171 -0
- package/scripts/stabilization-receipt.mjs +108 -0
- package/scripts/stack-sync.mjs +469 -0
- package/scripts/stamp-existing-rvf-generations.mjs +53 -0
- package/scripts/stamp-sweep.mjs +144 -0
- package/scripts/status-honesty.mjs +102 -0
- package/scripts/sync-version.mjs +217 -0
- package/scripts/token-report.mjs +102 -0
- package/scripts/top100-benchmark.mjs +479 -0
- package/scripts/top100-corpus.mjs +112 -0
- package/scripts/top100-semantic-assertions.mjs +449 -0
- package/scripts/update-apply.mjs +9 -0
- package/scripts/upgrade-notice.mjs +14 -0
- package/scripts/verify-bundle.mjs +51 -0
- package/scripts/verify-channels.mjs +184 -0
- package/scripts/verify-model-catalog.mjs +104 -0
- package/scripts/verify-nightly-close-issue4.sh +31 -0
- package/scripts/version.mjs +40 -0
- package/scripts/wired-check.mjs +867 -0
- package/plugin/scripts/finalize-token-meter.mjs +0 -25
package/scripts/route-cheap.mjs
CHANGED
|
@@ -27,6 +27,7 @@ import path from 'node:path';
|
|
|
27
27
|
import os from 'node:os';
|
|
28
28
|
import { spawnSync } from 'node:child_process';
|
|
29
29
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
30
|
+
import { loadRuntimePreferences, runtimeChildEnv } from '../plugin/scripts/runtime-preferences.mjs';
|
|
30
31
|
|
|
31
32
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
32
33
|
|
|
@@ -90,20 +91,15 @@ export function receiptLine(model, costs) {
|
|
|
90
91
|
return `\x1b[2m⚡ MetaHarness: routed to ${model} — ~${pct}% cheaper (est. ${fmt$(costs.cost)} vs ${fmt$(costs.frontier)} ${ref}, saved ~${fmt$(costs.saved)})\x1b[0m`;
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
function agenticFlowExecutable(env) {
|
|
95
|
+
const home = env.HOME || os.homedir();
|
|
96
|
+
const global = path.join(home, '.npm-global', 'bin', process.platform === 'win32' ? 'agentic-flow.cmd' : 'agentic-flow');
|
|
96
97
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (m && m[1].trim()) {
|
|
100
|
-
process.env.OPENROUTER_API_KEY = m[1].trim().replace(/^["']|["']$/g, '');
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
98
|
+
fs.accessSync(global, fs.constants.X_OK);
|
|
99
|
+
return global;
|
|
103
100
|
} catch {
|
|
104
|
-
|
|
101
|
+
return 'agentic-flow';
|
|
105
102
|
}
|
|
106
|
-
return false;
|
|
107
103
|
}
|
|
108
104
|
|
|
109
105
|
function parseArgs(argv) {
|
|
@@ -125,16 +121,25 @@ function main() {
|
|
|
125
121
|
console.error(`Unknown model "${args.model}" — no verified pricing, refusing to invent savings. Known: ${Object.keys(PRICING).join(', ')}`);
|
|
126
122
|
process.exit(2);
|
|
127
123
|
}
|
|
128
|
-
|
|
129
|
-
|
|
124
|
+
const policy = loadRuntimePreferences();
|
|
125
|
+
if (policy.values.routing !== 'auto') {
|
|
126
|
+
console.error(policy.values.routing === 'off'
|
|
127
|
+
? 'Token-smart routing is off in RuvNet Brain Console — nothing was dispatched.'
|
|
128
|
+
: 'Token-smart routing has not been enabled in RuvNet Brain Console — nothing was dispatched.');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
const childEnv = runtimeChildEnv();
|
|
132
|
+
if (!childEnv.OPENROUTER_API_KEY) {
|
|
133
|
+
console.error('OPENROUTER_API_KEY is not configured in the environment or encrypted Brain credential store — cannot route. (Key value is never printed.)');
|
|
130
134
|
process.exit(1);
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
const started = Date.now();
|
|
134
|
-
const run = spawnSync(
|
|
138
|
+
const run = spawnSync(agenticFlowExecutable(childEnv), ['--agent', args.agent, '--model', args.model, '--task', args.task], {
|
|
135
139
|
encoding: 'utf8',
|
|
136
140
|
timeout: 180_000,
|
|
137
|
-
env:
|
|
141
|
+
env: childEnv,
|
|
142
|
+
shell: false,
|
|
138
143
|
});
|
|
139
144
|
|
|
140
145
|
if (run.error || run.status !== 0) {
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/router-utilization.mjs — the ONGOING view. router-optimizer.mjs answers "what SHOULD each
|
|
3
|
+
// bucket route to"; this answers "once you've actually been using it, how many tasks landed in each
|
|
4
|
+
// bucket, and what did you save versus sending every one to the frontier model?"
|
|
5
|
+
//
|
|
6
|
+
// It implements the shape rUv specifies in ruflo ADR-149 §6 (Observability, Proposed):
|
|
7
|
+
// • modelDistribution — per-model/per-band task counts ("how many are going to each bucket")
|
|
8
|
+
// • costOptimalitySaved — USD vs always paying the frontier ("what the savings are off the frontier")
|
|
9
|
+
//
|
|
10
|
+
// Pure/deterministic: it reads the real receipts ledger (route-cheap.mjs + subagent dispatch write it)
|
|
11
|
+
// and makes NO model or network call. Every number is grounded:
|
|
12
|
+
// • realized cost = the receipt's own recorded est_cost (what the routed model actually cost).
|
|
13
|
+
// • frontier baseline = RECOMPUTED for every receipt against the CURRENT frontier model
|
|
14
|
+
// (route-cheap FRONTIER — now Fable 5) from that receipt's own est token counts. Recomputing (rather
|
|
15
|
+
// than trusting each receipt's stored est_frontier_cost) means switching the frontier re-prices the
|
|
16
|
+
// whole history consistently, so the "vs Fable 5" figure is never a stale Opus-era number.
|
|
17
|
+
// • a receipt whose model has no verified price is counted under `unpriced` and left OUT of the $ math
|
|
18
|
+
// — never assigned an invented cost.
|
|
19
|
+
//
|
|
20
|
+
// Usage: node scripts/router-utilization.mjs [--json]
|
|
21
|
+
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
import { FRONTIER, priceOf, receiptsPath } from './route-cheap.mjs';
|
|
24
|
+
|
|
25
|
+
// Band = a human-legible grouping over the continuous complexity/cost axis (ADR-149: "tier_label is
|
|
26
|
+
// metadata, not control flow"). The four bands mirror router-optimizer.mjs. Known models are mapped
|
|
27
|
+
// explicitly; anything unknown is bucketed by its blended $/Mtok via bandOf()'s fallback.
|
|
28
|
+
const BAND_BY_MODEL = {
|
|
29
|
+
'agent-booster': 'mechanical',
|
|
30
|
+
'inclusionai/ling-2.6-flash': 'cheap',
|
|
31
|
+
'claude-haiku-4.5': 'cheap',
|
|
32
|
+
'deepseek/deepseek-chat': 'cheap',
|
|
33
|
+
'deepseek/deepseek-v4-flash': 'cheap',
|
|
34
|
+
'meta-llama/llama-3.3-70b-instruct': 'mid',
|
|
35
|
+
'openai/gpt-4.1': 'mid',
|
|
36
|
+
'x-ai/grok-4.5': 'mid',
|
|
37
|
+
'claude-sonnet-5': 'mid',
|
|
38
|
+
'claude-opus-4.8': 'frontier',
|
|
39
|
+
'claude-fable-5': 'frontier',
|
|
40
|
+
};
|
|
41
|
+
export const BAND_ORDER = ['mechanical', 'cheap', 'mid', 'frontier'];
|
|
42
|
+
const BAND_LABEL = { mechanical: 'Mechanical', cheap: 'Cheap', mid: 'Mid', frontier: 'Frontier' };
|
|
43
|
+
|
|
44
|
+
/** Which band did this model's task land in? Known → explicit map; unknown → blended-price fallback. */
|
|
45
|
+
export function bandOf(model) {
|
|
46
|
+
if (BAND_BY_MODEL[model]) return BAND_BY_MODEL[model];
|
|
47
|
+
const p = priceOf(model);
|
|
48
|
+
if (!p) return null; // unpriced → caller records it under `unpriced`, never invents a cost
|
|
49
|
+
const blended = (p.in + p.out) / 2;
|
|
50
|
+
if (blended === 0) return 'mechanical';
|
|
51
|
+
if (blended < 2) return 'cheap';
|
|
52
|
+
if (blended < 15) return 'mid';
|
|
53
|
+
return 'frontier';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function tokensOf(r) {
|
|
57
|
+
const i = Number(r.est_in_tokens);
|
|
58
|
+
const o = Number(r.est_out_tokens);
|
|
59
|
+
if (Number.isFinite(i) && Number.isFinite(o)) return { in: i, out: o };
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Summarize the receipts ledger into a per-band distribution + realized-vs-frontier savings.
|
|
65
|
+
* @param {{ receiptsFile?: string }} [opts]
|
|
66
|
+
*/
|
|
67
|
+
export function utilization({ receiptsFile, frontier } = {}) {
|
|
68
|
+
const file = receiptsFile || receiptsPath();
|
|
69
|
+
// Frontier = the user's OWN house flagship (passed in by the console from the live-verified catalog);
|
|
70
|
+
// falls back to route-cheap's default. The counterfactual is recomputed vs THIS, so an OpenAI shop sees
|
|
71
|
+
// "vs GPT-5.6 Sol" and a Claude shop sees "vs Fable 5" — personalized, never one house for everyone.
|
|
72
|
+
const frModel = frontier?.model || FRONTIER.name;
|
|
73
|
+
const fr = (frontier && Number.isFinite(frontier.in) && Number.isFinite(frontier.out))
|
|
74
|
+
? { in: frontier.in, out: frontier.out }
|
|
75
|
+
: priceOf(FRONTIER.name);
|
|
76
|
+
|
|
77
|
+
const bands = Object.fromEntries(
|
|
78
|
+
BAND_ORDER.map((b) => [b, { band: b, label: BAND_LABEL[b], tasks: 0, realizedUsd: 0, frontierUsd: 0, models: {} }])
|
|
79
|
+
);
|
|
80
|
+
let tasks = 0;
|
|
81
|
+
let unpriced = 0;
|
|
82
|
+
let realizedUsd = 0;
|
|
83
|
+
let frontierUsd = 0;
|
|
84
|
+
let since = null;
|
|
85
|
+
let until = null;
|
|
86
|
+
|
|
87
|
+
let lines = [];
|
|
88
|
+
try {
|
|
89
|
+
lines = fs.readFileSync(file, 'utf8').split('\n');
|
|
90
|
+
} catch {
|
|
91
|
+
/* no receipts yet — return the empty shape below */
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
if (!line.trim()) continue;
|
|
96
|
+
let r;
|
|
97
|
+
try { r = JSON.parse(line); } catch { continue; }
|
|
98
|
+
if (!r.model) continue;
|
|
99
|
+
tasks++;
|
|
100
|
+
if (r.ts) {
|
|
101
|
+
since = since && since < r.ts ? since : r.ts;
|
|
102
|
+
until = until && until > r.ts ? until : r.ts;
|
|
103
|
+
}
|
|
104
|
+
const band = bandOf(r.model);
|
|
105
|
+
const p = priceOf(r.model);
|
|
106
|
+
const isMech = band === 'mechanical';
|
|
107
|
+
// Mechanical ($0, no LLM — e.g. Agent Booster) has no per-token price but a real realized cost of $0.
|
|
108
|
+
// Everything else needs a verified price AND a priced frontier, or it's excluded (never invented).
|
|
109
|
+
if (!band || (!isMech && (!p || !fr))) { unpriced++; continue; }
|
|
110
|
+
|
|
111
|
+
const tok = tokensOf(r);
|
|
112
|
+
const realized = isMech ? 0
|
|
113
|
+
: Number.isFinite(Number(r.est_cost)) ? Number(r.est_cost)
|
|
114
|
+
: tok ? (tok.in * p.in + tok.out * p.out) / 1e6 : 0;
|
|
115
|
+
// Frontier counterfactual — ALWAYS recomputed vs the current frontier from the receipt's tokens.
|
|
116
|
+
const front = (tok && fr) ? (tok.in * fr.in + tok.out * fr.out) / 1e6 : realized;
|
|
117
|
+
|
|
118
|
+
const b = bands[band];
|
|
119
|
+
b.tasks++;
|
|
120
|
+
b.realizedUsd += realized;
|
|
121
|
+
b.frontierUsd += front;
|
|
122
|
+
b.models[r.model] = (b.models[r.model] || 0) + 1;
|
|
123
|
+
realizedUsd += realized;
|
|
124
|
+
frontierUsd += front;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const round = (n) => +Number(n).toFixed(4);
|
|
128
|
+
const distribution = BAND_ORDER.map((b) => {
|
|
129
|
+
const x = bands[b];
|
|
130
|
+
return {
|
|
131
|
+
band: b,
|
|
132
|
+
label: x.label,
|
|
133
|
+
tasks: x.tasks,
|
|
134
|
+
pctOfTasks: tasks ? Math.round((x.tasks / tasks) * 100) : 0,
|
|
135
|
+
realizedUsd: round(x.realizedUsd),
|
|
136
|
+
frontierUsd: round(x.frontierUsd),
|
|
137
|
+
savedUsd: round(x.frontierUsd - x.realizedUsd),
|
|
138
|
+
models: Object.entries(x.models)
|
|
139
|
+
.sort((a, c) => c[1] - a[1])
|
|
140
|
+
.map(([model, n]) => ({ model, tasks: n })),
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const savedUsd = round(frontierUsd - realizedUsd);
|
|
145
|
+
return {
|
|
146
|
+
generatedAt: new Date().toISOString(),
|
|
147
|
+
frontierModel: frModel,
|
|
148
|
+
tasks,
|
|
149
|
+
unpriced,
|
|
150
|
+
since,
|
|
151
|
+
until,
|
|
152
|
+
realizedUsd: round(realizedUsd),
|
|
153
|
+
frontierUsd: round(frontierUsd),
|
|
154
|
+
costOptimalitySaved: savedUsd, // ADR-149 name
|
|
155
|
+
pctSaved: frontierUsd > 0 ? Math.round((savedUsd / frontierUsd) * 100) : null,
|
|
156
|
+
distribution, // ADR-149 modelDistribution, grouped into the four legible bands
|
|
157
|
+
note: `Recomputed live from ${tasks} receipt(s) against your frontier (${frModel}); token counts are the receipts’ own est. values, never projected.`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function printUtil(u) {
|
|
162
|
+
const money = (v) => (v == null ? '—' : v === 0 ? '$0' : '$' + v);
|
|
163
|
+
console.log(`\nRouting utilization — ${u.tasks} task(s), frontier = ${u.frontierModel}`);
|
|
164
|
+
if (u.since) console.log(` window: ${u.since} → ${u.until}`);
|
|
165
|
+
console.log(` ${'band'.padEnd(11)} ${'tasks'.padStart(6)} ${'%'.padStart(4)} ${'realized'.padStart(11)} ${'vs frontier'.padStart(12)} ${'saved'.padStart(11)}`);
|
|
166
|
+
for (const d of u.distribution) {
|
|
167
|
+
console.log(` ${d.label.padEnd(11)} ${String(d.tasks).padStart(6)} ${String(d.pctOfTasks).padStart(3)}% ${money(d.realizedUsd).padStart(11)} ${money(d.frontierUsd).padStart(12)} ${money(d.savedUsd).padStart(11)}`);
|
|
168
|
+
}
|
|
169
|
+
console.log(` ${'—'.repeat(11)}`);
|
|
170
|
+
console.log(` TOTAL saved vs all-frontier: ${money(u.costOptimalitySaved)} (${u.pctSaved == null ? '—' : u.pctSaved + '%'}); realized ${money(u.realizedUsd)} vs ${money(u.frontierUsd)} on ${u.frontierModel}.`);
|
|
171
|
+
if (u.unpriced) console.log(` (${u.unpriced} receipt(s) had an unpriced model and were excluded from the $ math.)`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function mainUtil() {
|
|
175
|
+
const u = utilization();
|
|
176
|
+
if (process.argv.includes('--json')) console.log(JSON.stringify(u, null, 2));
|
|
177
|
+
else printUtil(u);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
import { fileURLToPath } from 'node:url';
|
|
181
|
+
import path from 'node:path';
|
|
182
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) mainUtil();
|