@trazum/cli 1.34.0 → 1.36.0

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/src/index.ts CHANGED
@@ -23,6 +23,8 @@ import {
23
23
  coverageDrift,
24
24
  driversBetween,
25
25
  explainGateFailure,
26
+ labelCoverage,
27
+ measuredUsage,
26
28
  gateMargin,
27
29
  GATE_MARGIN_TIGHT,
28
30
  estimateTokens,
@@ -72,6 +74,7 @@ import {
72
74
  import { cacheDir, cacheStats, cachingProvider, clearCache } from './suggest-cache.js';
73
75
  import { dayOf, formatGap, median, spanDays } from './time.js';
74
76
  import type {
77
+ MeasuredUsage,
75
78
  BaselineBreach,
76
79
  BaselineChange,
77
80
  BaselineComparison,
@@ -165,6 +168,7 @@ interface Args {
165
168
 
166
169
  const VALUE_FLAGS = new Set([
167
170
  'against',
171
+ 'from-log',
168
172
  // `route` takes a path here, and the flag is deliberately not `--prompt`:
169
173
  // everywhere else in this tool `--prompt` names a marked prompt *inside* a
170
174
  // source file, and reusing it for a path would be a trap laid for the reader.
@@ -305,6 +309,23 @@ function levelFlag(args: Args, config: TrazumConfig, t: CliMessages): RuleLevel
305
309
  * model id. It beats the default because reading the code is better than
306
310
  * assuming, and loses to config because being told is better than reading.
307
311
  */
312
+ /**
313
+ * One usage log, gzip included, shared by every command that reads one.
314
+ *
315
+ * A `.gz` that will not decompress is an error naming the file — skipping it
316
+ * would be a figure quietly missing a day, the failure this repository
317
+ * refuses everywhere it can occur.
318
+ */
319
+ async function readUsageLog(file: string, t: CliMessages): Promise<string> {
320
+ if (!file.endsWith('.gz')) return readFile(file, 'utf8');
321
+ const compressed = await readFile(file);
322
+ try {
323
+ return gunzipSync(compressed).toString('utf8');
324
+ } catch (error) {
325
+ throw new Error(t.profile.badGzip(file, error instanceof Error ? error.message : String(error)));
326
+ }
327
+ }
328
+
308
329
  function usageFrom(
309
330
  args: Args,
310
331
  config: TrazumConfig,
@@ -436,11 +457,11 @@ const COMMAND_FLAGS: Record<string, string[]> = {
436
457
  'level', 'model', 'calls', 'output-tokens', 'cache-hit-rate', 'batch',
437
458
  'disable', 'llm', 'exact-tokens', 'diff', 'reorder', 'out', 'o',
438
459
  'tokens-only', 'cost', 'prompt', 'suggest', 'apply-suggestions',
439
- 'cache-suggestions',
460
+ 'cache-suggestions', 'from-log', 'label', 'all-labels',
440
461
  ],
441
462
  check: ['max-tokens', 'level', 'exact-tokens', 'markdown-out', 'baseline'],
442
463
  baseline: ['model', 'calls', 'output-tokens', 'cache-hit-rate', 'batch', 'exact-tokens', 'out', 'o'],
443
- profile: ['json', 'pricing', 'pricing-live', 'against', 'what-if', 'markdown-out', 'csv-out', 'csv-shape', 'max-usd', 'max-growth-usd', 'max-cache-loss-usd', 'max-day-usd', 'max-session-usd', 'label', 'since', 'until', 'dry-run'],
464
+ profile: ['json', 'pricing', 'pricing-live', 'against', 'what-if', 'markdown-out', 'csv-out', 'csv-shape', 'max-usd', 'max-growth-usd', 'max-cache-loss-usd', 'max-day-usd', 'max-session-usd', 'label', 'since', 'until', 'dry-run', 'markdown-summary'],
444
465
  route: ['prompt-file', 'cases', 'label', 'concurrency', 'json', 'yes', 'pricing', 'pricing-live'],
445
466
  eval: ['cases', 'level', 'concurrency', 'export', 'out', 'o', 'model'],
446
467
  prune: ['cases', 'concurrency', 'json', 'yes'],
@@ -629,6 +650,8 @@ function printReport(
629
650
  suggestions: { result: SuggestResult; applied: boolean; locale: Locale } | null = null,
630
651
  /** They named a scenario, and the host is suppressing the money anyway. */
631
652
  namedScenario = false,
653
+ /** Present when the usage came from a log rather than from typing. */
654
+ measured: MeasuredUsage | null = null,
632
655
  ): void {
633
656
  const n = (value: number): string => value.toLocaleString(t.numberLocale);
634
657
  const sourceNote =
@@ -776,7 +799,7 @@ function printReport(
776
799
  if (tokensOnly) {
777
800
  printTokensOnly(result, host, t, n, namedScenario);
778
801
  } else {
779
- printMoney(result, t, n);
802
+ printMoney(result, t, n, measured);
780
803
  }
781
804
 
782
805
  // On a subscription, an advisory whose entire pitch is money is not weaker
@@ -852,17 +875,61 @@ function printReport(
852
875
  }
853
876
 
854
877
  /** The cost section, for anyone billed by the token. */
855
- function printMoney(result: OptimizationResult, t: CliMessages, n: (v: number) => string): void {
878
+ function printMoney(
879
+ result: OptimizationResult,
880
+ t: CliMessages,
881
+ n: (v: number) => string,
882
+ /** Present when the usage came from a log rather than from typing. */
883
+ measured: MeasuredUsage | null = null,
884
+ ): void {
856
885
  const { savings } = result;
857
886
  console.log();
858
887
  console.log(c.bold(t.report.costWith(savings.modelDisplayName)));
859
- console.log(
860
- ` ${t.report.usageLine(
861
- n(result.usage.callsPerMonth),
862
- result.usage.avgOutputTokens,
863
- result.usage.batchEligible,
864
- )}`,
865
- );
888
+ /**
889
+ * The usage line names its provenance. "1,000 calls/month" typed and
890
+ * "1,043 calls measured over 12 days, scaled" are different claims about
891
+ * the same multiplication, and the reader budgeting on the result must
892
+ * know which one they are holding. Under the week floor nothing is scaled
893
+ * and nothing says "month": the figures cover exactly the period measured.
894
+ */
895
+ if (measured !== null) {
896
+ if (measured.scaled !== null) {
897
+ console.log(
898
+ ` ${t.report.usageLineMeasured(
899
+ n(measured.calls),
900
+ measured.scaled.fromDays.toFixed(1),
901
+ n(result.usage.callsPerMonth),
902
+ result.usage.avgOutputTokens,
903
+ result.usage.batchEligible,
904
+ )}`,
905
+ );
906
+ } else {
907
+ console.log(
908
+ ` ${t.report.usageLineMeasuredPeriod(
909
+ n(measured.calls),
910
+ measured.spanDays === null ? null : measured.spanDays.toFixed(1),
911
+ result.usage.avgOutputTokens,
912
+ result.usage.batchEligible,
913
+ )}`,
914
+ );
915
+ }
916
+ if (measured.models.count > 1) {
917
+ console.log(
918
+ ` ${c.dim(wrap(t.report.measuredModelShare(measured.models.chosen, `${(measured.models.chosenShareOfSpend * 100).toFixed(0)}%`, n(measured.models.count)), 74, ' '))}`,
919
+ );
920
+ }
921
+ if (measured.outputUnmeasured) {
922
+ console.log(` ${c.dim(wrap(t.report.measuredNoOutput(), 74, ' '))}`);
923
+ }
924
+ } else {
925
+ console.log(
926
+ ` ${t.report.usageLine(
927
+ n(result.usage.callsPerMonth),
928
+ result.usage.avgOutputTokens,
929
+ result.usage.batchEligible,
930
+ )}`,
931
+ );
932
+ }
866
933
  // Said, not assumed. Once prices can be overlaid locally, a figure from the
867
934
  // bundled catalogue and a figure from somebody's JSON file look identical, and
868
935
  // the reader has to be able to tell which one they are about to budget against.
@@ -875,17 +942,27 @@ function printMoney(result: OptimizationResult, t: CliMessages, n: (v: number) =
875
942
  ` ${c.yellow(t.report.pricingOverlaid(touched.join(', '), result.pricingSource.lastReviewed))}`,
876
943
  );
877
944
  }
945
+ const periodOnly = measured !== null && measured.scaled === null;
878
946
  console.log(
879
947
  ` ${formatUsd(savings.perMonth.before.totalUsd)} → ` +
880
948
  `${c.green(formatUsd(savings.perMonth.after.totalUsd))} ` +
881
949
  c.bold(
882
- t.report.perMonthSaving(
883
- formatUsd(savings.monthlySavingsUsd),
884
- savings.monthlySavingsPct.toFixed(1),
885
- ),
950
+ periodOnly
951
+ ? t.report.perPeriodSaving(
952
+ formatUsd(savings.monthlySavingsUsd),
953
+ savings.monthlySavingsPct.toFixed(1),
954
+ )
955
+ : t.report.perMonthSaving(
956
+ formatUsd(savings.monthlySavingsUsd),
957
+ savings.monthlySavingsPct.toFixed(1),
958
+ ),
886
959
  ),
887
960
  );
888
-
961
+ if (periodOnly) {
962
+ console.log(
963
+ ` ${c.dim(wrap(t.report.periodNotScaled(measured!.spanDays === null ? null : measured!.spanDays.toFixed(1)), 74, ' '))}`,
964
+ );
965
+ }
889
966
  }
890
967
 
891
968
  /**
@@ -1372,6 +1449,97 @@ async function commandOptimize(
1372
1449
  t: CliMessages,
1373
1450
  locale: Locale,
1374
1451
  ): Promise<void> {
1452
+ /**
1453
+ * `--all-labels`: every mapped prompt against its own measured traffic,
1454
+ * ranked by what the change is worth — the list a person actually wants,
1455
+ * which is "which prompt do I edit first".
1456
+ *
1457
+ * Requires `--from-log`, because ranking estimated savings that were all
1458
+ * multiplied by the same typed guess ranks the prompts by length, and calls
1459
+ * that a priority. And it renders both coverage mismatches at the end: a
1460
+ * prompt mapped to a label with no traffic is dead weight or a rename, and
1461
+ * a label carrying real money with no prompt mapped is the workload nobody
1462
+ * can optimise because nobody said where it lives.
1463
+ */
1464
+ if (boolFlag(args, 'all-labels')) {
1465
+ const fromLogPath = stringFlag(args, 'from-log');
1466
+ if (fromLogPath === undefined) throw new Error(t.errors.allLabelsNeedsLog());
1467
+ const labelsMap = config.labels ?? {};
1468
+ if (Object.keys(labelsMap).length === 0) throw new Error(t.errors.allLabelsNeedsMap());
1469
+ const report = profileUsage(await readUsageLog(fromLogPath, t), { catalogue: pricing });
1470
+ const coverage = labelCoverage(report, labelsMap);
1471
+ const level = levelFlag(args, config, t);
1472
+
1473
+ interface Row {
1474
+ label: string;
1475
+ path: string;
1476
+ tokensBefore: number;
1477
+ tokensAfter: number;
1478
+ savingUsd: number;
1479
+ periodOnly: boolean;
1480
+ spentUsd: number;
1481
+ }
1482
+ const rows: Row[] = [];
1483
+ const unreadable: { label: string; path: string }[] = [];
1484
+ for (const { label, promptPath } of coverage.joined) {
1485
+ const m = measuredUsage(report, label, { batchEligible: config.usage?.batchEligible ?? false });
1486
+ if (m === null) continue;
1487
+ let text: string;
1488
+ try {
1489
+ text = await readFile(promptPath, 'utf8');
1490
+ } catch {
1491
+ unreadable.push({ label, path: promptPath });
1492
+ continue;
1493
+ }
1494
+ const r = optimize(text, { level, usage: m.profile, locale, pricing });
1495
+ rows.push({
1496
+ label,
1497
+ path: promptPath,
1498
+ tokensBefore: r.tokensBefore,
1499
+ tokensAfter: r.tokensAfter,
1500
+ savingUsd: r.savings.monthlySavingsUsd,
1501
+ periodOnly: m.scaled === null,
1502
+ spentUsd: m.spentUsd,
1503
+ });
1504
+ }
1505
+ rows.sort((a, b) => b.savingUsd - a.savingUsd);
1506
+
1507
+ const n = (value: number): string => value.toLocaleString(t.numberLocale);
1508
+ console.log(c.bold(t.report.allLabelsHeading(n(rows.length))));
1509
+ for (const row of rows) {
1510
+ const saving = row.periodOnly
1511
+ ? t.report.allLabelsRowPeriod(formatUsd(row.savingUsd))
1512
+ : t.report.allLabelsRow(formatUsd(row.savingUsd));
1513
+ console.log(
1514
+ ` ${row.savingUsd > 0 ? c.green('→') : c.dim('·')} ${c.bold(row.label)} ${saving} ${c.dim(`${row.path} · ${n(row.tokensBefore)} → ${n(row.tokensAfter)} tokens · ${formatUsd(row.spentUsd)} measured`)}`,
1515
+ );
1516
+ }
1517
+ if (rows.length > 0) {
1518
+ console.log(` ${c.dim(wrap(t.report.allLabelsFooter(), 74, ' '))}`);
1519
+ }
1520
+
1521
+ /**
1522
+ * The mismatches, both directions, never silently. These are the two
1523
+ * failures neither side can see alone.
1524
+ */
1525
+ for (const gap of coverage.trafficWithoutPrompt.slice(0, 5)) {
1526
+ console.log(
1527
+ ` ${c.yellow('!')} ${wrap(t.report.allLabelsUnmapped(gap.label, formatUsd(gap.spentUsd)), 74, ' ')}`,
1528
+ );
1529
+ }
1530
+ for (const dead of coverage.mappedWithoutTraffic) {
1531
+ console.log(
1532
+ ` ${c.dim(wrap(t.report.allLabelsDead(dead.label, dead.promptPath), 74, ' '))}`,
1533
+ );
1534
+ }
1535
+ for (const miss of unreadable) {
1536
+ console.log(
1537
+ ` ${c.yellow('!')} ${wrap(t.report.allLabelsUnreadable(miss.label, miss.path), 74, ' ')}`,
1538
+ );
1539
+ }
1540
+ return;
1541
+ }
1542
+
1375
1543
  const target = args.positional[0];
1376
1544
  const raw = await readInput(target, t);
1377
1545
  const level = levelFlag(args, config, t);
@@ -1393,7 +1561,61 @@ async function commandOptimize(
1393
1561
  // Detection sits between config and defaults, as everywhere: a flag beats
1394
1562
  // config, config beats what the code says, and what the code says beats a
1395
1563
  // built-in default that has no idea which provider you use.
1396
- const usage = usageFrom(args, config, t, source?.model);
1564
+ let usage = usageFrom(args, config, t, source?.model);
1565
+
1566
+ /**
1567
+ * `--from-log`: the multiplication stops guessing.
1568
+ *
1569
+ * The saving printed below is `token delta × usage`, and until now every
1570
+ * part of `usage` was typed by a human. A usage log knows the real call
1571
+ * count, the real output size, the real cache share and the model the
1572
+ * calls actually went to — so `--from-log` measures them, and the typed
1573
+ * flags are refused beside it rather than merged: measuring and typing the
1574
+ * same figure is a contradiction, not a preference order.
1575
+ */
1576
+ const fromLog = stringFlag(args, 'from-log');
1577
+ let measured: MeasuredUsage | null = null;
1578
+ if (fromLog !== undefined) {
1579
+ for (const flag of ['calls', 'output-tokens', 'cache-hit-rate', 'model']) {
1580
+ if (args.flags.get(flag) !== undefined) {
1581
+ throw new Error(t.errors.fromLogConflict(flag));
1582
+ }
1583
+ }
1584
+ const report = profileUsage(await readUsageLog(fromLog, t), { catalogue: pricing });
1585
+
1586
+ /**
1587
+ * Which label this prompt is. `--label` says it outright; otherwise the
1588
+ * config's `labels` map is read in reverse — it maps labels to prompt
1589
+ * files, and the file on the command line is looked up among its values.
1590
+ * Ambiguity (two labels mapped to one file) is an error naming both,
1591
+ * never a silent first match.
1592
+ */
1593
+ let label = stringFlag(args, 'label');
1594
+ if (label === undefined && target !== undefined && config.labels !== undefined) {
1595
+ const hits = Object.entries(config.labels)
1596
+ .filter(([, path]) => resolvePath(path) === resolvePath(target))
1597
+ .map(([name]) => name);
1598
+ if (hits.length > 1) throw new Error(t.errors.fromLogAmbiguousLabel(target, hits.join(', ')));
1599
+ label = hits[0];
1600
+ }
1601
+ if (label === undefined) {
1602
+ const available = report.byLabel
1603
+ .map((row) => (row.label === UNLABELLED ? t.profile.unlabelled() : row.label))
1604
+ .join(', ');
1605
+ throw new Error(t.errors.fromLogNeedsLabel(available || '—'));
1606
+ }
1607
+
1608
+ measured = measuredUsage(report, label, {
1609
+ batchEligible: boolFlag(args, 'batch', config.usage?.batchEligible ?? false),
1610
+ });
1611
+ if (measured === null) {
1612
+ const available = report.byLabel
1613
+ .map((row) => (row.label === UNLABELLED ? t.profile.unlabelled() : row.label))
1614
+ .join(', ');
1615
+ throw new Error(t.errors.fromLogLabelEmpty(label, available || '—'));
1616
+ }
1617
+ usage = measured.profile;
1618
+ }
1397
1619
 
1398
1620
  const disableRules = disabledRules(args, config) ?? [];
1399
1621
  for (const id of disableRules) {
@@ -1586,7 +1808,15 @@ async function commandOptimize(
1586
1808
  // Cursor wants the dollars, and they should not have to leave the editor to
1587
1809
  // see them.
1588
1810
  const host = detectHost();
1589
- const tokensOnly = boolFlag(args, 'cost')
1811
+ /**
1812
+ * `--from-log` implies `--cost`, and the reasoning is different from the
1813
+ * `--calls` case documented below: `--calls` is a typed scenario parameter,
1814
+ * but a usage log with billed token counts is *evidence* — proof this
1815
+ * prompt's traffic goes to a metered API, whatever the terminal running
1816
+ * the command bills like. Withholding the money there would suppress
1817
+ * exactly the figures the person measured in order to see.
1818
+ */
1819
+ const tokensOnly = boolFlag(args, 'cost') || measured !== null
1590
1820
  ? false
1591
1821
  : boolFlag(args, 'tokens-only') || host.billing === 'subscription';
1592
1822
  /**
@@ -1609,6 +1839,7 @@ async function commandOptimize(
1609
1839
  ? { result: suggestions, applied: boolFlag(args, 'apply-suggestions'), locale }
1610
1840
  : null,
1611
1841
  namedScenario,
1842
+ measured,
1612
1843
  );
1613
1844
  if (outPath) {
1614
1845
  console.log(c.dim(t.report.wroteTo(outPath)));
@@ -2031,16 +2262,7 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
2031
2262
  * alternative — skipping it — is a total quietly missing a day, which is
2032
2263
  * the failure this repository refuses in every other place it can occur.
2033
2264
  */
2034
- const readLog = async (file: string): Promise<string> => {
2035
- if (!file.endsWith('.gz')) return readFile(file, 'utf8');
2036
- const compressed = await readFile(file);
2037
- try {
2038
- return gunzipSync(compressed).toString('utf8');
2039
- } catch (error) {
2040
- throw new Error(t.profile.badGzip(file, error instanceof Error ? error.message : String(error)));
2041
- }
2042
- };
2043
- const logTexts = await Promise.all(logFiles.map((file) => readLog(file)));
2265
+ const logTexts = await Promise.all(logFiles.map((file) => readUsageLog(file, t)));
2044
2266
  // A file that does not end in a newline would otherwise glue its last record
2045
2267
  // to the next file's first one, and both would be reported as unreadable.
2046
2268
  const raw = logTexts.map((text) => (text.endsWith('\n') ? text : `${text}\n`)).join('');
@@ -2206,7 +2428,7 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
2206
2428
  // The same reader as the log itself, so `--against last-month.jsonl.gz`
2207
2429
  // works: a comparison that could only read one of the two formats would
2208
2430
  // be a flag that fails on exactly the rotated file it exists to read.
2209
- ? profileUsage(await readLog(againstPath), {
2431
+ ? profileUsage(await readUsageLog(againstPath, t), {
2210
2432
  catalogue: pricing,
2211
2433
  label: onlyLabel,
2212
2434
  // The same window on both sides, for the same reason as the label:
@@ -2703,6 +2925,8 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
2703
2925
  // The verdict, where the person reading CI will see it. recordGates()
2704
2926
  // runs before the side files for exactly this.
2705
2927
  ...(gateVerdicts.length > 0 ? { gates: { failed: gateFailed, lines: gateVerdicts } } : {}),
2928
+ // The short form, for a reader who is not in the terminal.
2929
+ ...(boolFlag(args, 'markdown-summary') ? { summary: true } : {}),
2706
2930
  // The repricing, when --what-if was given: computed once above and
2707
2931
  // handed over, so the summary in a pull request cannot disagree
2708
2932
  // with the terminal about what a move would cost.
package/src/markdown.ts CHANGED
@@ -733,6 +733,20 @@ export interface ProfileMarkdownInput {
733
733
  * copy; this is a rendering and must not decide anything a gate decides.
734
734
  */
735
735
  gates?: { failed: boolean; lines: string[] };
736
+ /**
737
+ * `--markdown-summary`: the short form, for a pull-request body or a weekly
738
+ * note rather than a full report.
739
+ *
740
+ * The person who owns the budget usually does not run the CLI, and handing
741
+ * them the whole report is handing them a document to skim — where the one
742
+ * figure that changed is as easy to miss as it was in the terminal. The
743
+ * summary states what changed, the single lever worth the most, and stops.
744
+ *
745
+ * It is a *view*, never a different set of figures: every number in it is
746
+ * taken from the same report the full rendering uses, so a reader who opens
747
+ * both cannot find them disagreeing.
748
+ */
749
+ summary?: boolean;
736
750
  /**
737
751
  * The `--since`/`--until` values as the user typed them, when a window was
738
752
  * applied. Passed through rather than re-derived from `timeWindow`'s epoch
@@ -793,7 +807,7 @@ export interface ProfileMarkdownInput {
793
807
  * reading CI instead of machines.
794
808
  */
795
809
  export function renderProfileMarkdown(input: ProfileMarkdownInput): string {
796
- const { report, levers, cache, t, window, stalePricing, against, whatIf, pressure = [], gates } = input;
810
+ const { report, levers, cache, t, window, stalePricing, against, whatIf, pressure = [], gates, summary = false } = input;
797
811
  const n = (value: number): string => value.toLocaleString(t.numberLocale);
798
812
  const pct = (share: number): string => `${(share * 100).toFixed(1)}%`;
799
813
  const shares = sharesOf(report.total);
@@ -812,6 +826,85 @@ export function renderProfileMarkdown(input: ProfileMarkdownInput): string {
812
826
  * surface further out. A failure is quoted so it survives being skimmed; a
813
827
  * pass is stated plainly and does not shout.
814
828
  */
829
+ /**
830
+ * The summary: what changed, the biggest lever, and nothing else.
831
+ *
832
+ * Returned before the full rendering rather than filtered out of it, so a
833
+ * section added later cannot leak into the short form by forgetting to opt
834
+ * out. Every figure here comes from the same report the long form uses.
835
+ */
836
+ if (summary) {
837
+ const short: string[] = [];
838
+ short.push(`### ${t.profile.heading()}`);
839
+ short.push('');
840
+ if (gates !== undefined && gates.lines.length > 0) {
841
+ const [verdict] = gates.lines;
842
+ short.push(gates.failed ? `> ❌ **${mdText(verdict!)}**` : `_${mdText(verdict!)}_`);
843
+ short.push('');
844
+ }
845
+ short.push(`**${mdText(t.profile.spent(t.profile.calls(report.total.calls), formatUsd(report.total.totalUsd)))}**`);
846
+ short.push('');
847
+ // What changed, when there is a previous log to change from. Without one
848
+ // the summary states the bill and says so, rather than implying stability
849
+ // nobody measured.
850
+ if (against !== undefined) {
851
+ const delta = report.total.totalUsd - against.previousTotalUsd;
852
+ const growthPct =
853
+ against.previousTotalUsd > 0
854
+ ? `${delta >= 0 ? '+' : ''}${((delta / against.previousTotalUsd) * 100).toFixed(1)}%`
855
+ : '—';
856
+ short.push(
857
+ mdText(
858
+ t.profile.againstTotals(
859
+ formatUsd(against.previousTotalUsd),
860
+ formatUsd(report.total.totalUsd),
861
+ formatSignedUsd(delta),
862
+ growthPct,
863
+ t.profile.calls(against.previousCalls),
864
+ t.profile.calls(report.total.calls),
865
+ ),
866
+ ),
867
+ );
868
+ short.push('');
869
+ // The one driver that moved most — not five, because a summary that
870
+ // lists everything is the report again with a shorter heading.
871
+ const [driver] = against.labelDrivers;
872
+ if (driver !== undefined) {
873
+ const shown = driver.key === UNLABELLED ? t.profile.unlabelled() : driver.key;
874
+ short.push(
875
+ `- ${mdText(
876
+ driver.was === null
877
+ ? t.profile.againstDriverNew(formatSignedUsd(driver.delta), shown)
878
+ : driver.now === null
879
+ ? t.profile.againstDriverGone(formatSignedUsd(driver.delta), shown)
880
+ : t.profile.againstDriver(formatSignedUsd(driver.delta), shown, formatUsd(driver.was), formatUsd(driver.now)),
881
+ )}`,
882
+ );
883
+ short.push('');
884
+ }
885
+ } else {
886
+ short.push(`_${mdText(t.profile.summaryNoComparison())}_`);
887
+ short.push('');
888
+ }
889
+ // The single lever worth the most, with the hedge every lever carries.
890
+ const [lever] = levers.slices;
891
+ if (lever !== undefined) {
892
+ short.push(
893
+ `- ${mdText(
894
+ t.profile.leverSlice(
895
+ showLabel(lever.label),
896
+ lever.modelName,
897
+ formatUsd(lever.combinedUsd),
898
+ pct(lever.shareOfBill),
899
+ ),
900
+ )}`,
901
+ );
902
+ short.push('');
903
+ }
904
+ short.push(`_${mdText(t.profile.summaryFooter())}_`);
905
+ return short.join('\n');
906
+ }
907
+
815
908
  if (gates !== undefined && gates.lines.length > 0) {
816
909
  // One mark, on the verdict. The lines under it explain that verdict and
817
910
  // are not themselves failures — marking each would turn one red build