@mnemom/mnemom 0.17.7 → 0.17.8

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.
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The realized saving of a session that ran through Mnemom: a replay of what stock Claude
3
+ * Code would have sent.
4
+ *
5
+ * Through the gateway, a transcript's top-level `usage` is the FOLDED, billed size (over
6
+ * 365k rows, `usage.iterations` matches the top-level fields, so no pre-fold size is ever
7
+ * recorded). So the client-side size is rebuilt from the fold drops themselves, and stock
8
+ * Claude Code is replayed on it at its DEFAULT window for the session's model, compacting
9
+ * whenever it would have. The saving is what stock would have billed minus what was
10
+ * billed. It can be negative for a session, and is kept negative.
11
+ *
12
+ * Pure: rows and compaction records in, numbers out. No I/O.
13
+ */
14
+ import { type Compaction, type Row } from "./backtest.js";
15
+ /** Main-thread requests with a smaller prompt are side calls (title, summary): ignored. */
16
+ export declare const SIDE_CALL_BELOW = 10000;
17
+ /** A fold: prompt(a) - prompt(b) > this … */
18
+ export declare const FOLD_MIN_DROP = 20000;
19
+ /** … and prompt(b) < this x prompt(a), with no Claude Code compaction in between. */
20
+ export declare const FOLD_MAX_RATIO = 0.7;
21
+ /** Median post-compaction size over 1,460 observed Claude Code compactions. */
22
+ export declare const POST_COMPACT = 17181;
23
+ /** Output tokens of a stock compaction's summary call. */
24
+ export declare const SUMMARY_OUT = 6000;
25
+ export declare const WINDOW_200K = 200000;
26
+ export declare const WINDOW_1M = 1000000;
27
+ /**
28
+ * Models whose Claude Code default context window is 1M (read out of Claude Code
29
+ * 2.1.285's binary). Everything else, unknown models included, is 200k. The user's own
30
+ * autoCompactWindow is deliberately NOT read: the baseline is stock Claude Code.
31
+ */
32
+ export declare const ONE_M_DEFAULT: RegExp;
33
+ /** Claude Code's default window for a model id (a trailing `[...]` is stripped). */
34
+ export declare function defaultWindow(model: string): number;
35
+ /** The most frequent main-thread model (all rows when none is main); first seen wins a tie. */
36
+ export declare function dominantModel(rows: readonly Row[]): string;
37
+ /** The main-thread requests the replay walks: prompt >= 10k, in timestamp order. */
38
+ export declare function replayRows(rows: readonly Row[]): Row[];
39
+ /** A Claude Code compaction with a timestamp in [ta, tb]. */
40
+ export declare function compactedBetween(ct: readonly number[], ta: number, tb: number): boolean;
41
+ /** The size drop from a to b looks like a fold (the compaction check is separate). */
42
+ export declare function isFoldDrop(a: number, b: number): boolean;
43
+ /** Fold evidence: two consecutive main-thread requests with a fold drop and no compaction. */
44
+ export declare function hasFoldEvidence(rows: readonly Row[], comps: readonly Compaction[]): boolean;
45
+ export interface Replay {
46
+ /** The baseline window. */
47
+ window: number;
48
+ /** Σ (stock size - billed prompt) over the replayed requests. */
49
+ tokenDiff: number;
50
+ /** The same, priced at each request's cache-read rate. */
51
+ usdDiff: number;
52
+ stockCompactions: number;
53
+ stockCompactionUsd: number;
54
+ /** Claude Code compactions recorded in the session. */
55
+ actualCompactions: number;
56
+ /** Fold drops the replay found along the main thread. */
57
+ folds: number;
58
+ savedTokens: number;
59
+ savedUsd: number;
60
+ }
61
+ /**
62
+ * Replay stock Claude Code at window W along the session's main thread.
63
+ *
64
+ * client = billed prompt + what the folds removed so far (reset by a real compaction;
65
+ * growth during a fold request counts as 0, the conservative direction)
66
+ * stock = min(client, W) at the first request and after a reset; otherwise it grows
67
+ * with client and compacts above W: count it, charge (W x cache read + 6000 x
68
+ * output + 17181 x cache write 5m) at the request's model price, and continue
69
+ * from 17181 + the overshoot
70
+ * saved = Σ (stock - billed) at cache read + stock compaction cost - the session's own
71
+ * compactions x the mean simulated compaction cost (0 if none was simulated)
72
+ *
73
+ * The last term is deliberately CONSERVATIVE (operator 2026-10-02). At a real compaction
74
+ * the replay resets both worlds to the same size, so that compaction's cost is paid on
75
+ * both sides and cancels. Subtracting it then charges each real compaction against
76
+ * Mnemom a second time, at the mean simulated stock cost, which is 0 when the replay
77
+ * simulated none. It can only lower the saving, never raise it, and with more real
78
+ * compactions than simulated ones it can take the saving below 0.
79
+ *
80
+ * A REALIZED saving comes only from detected folds. With none, savedTokens and savedUsd
81
+ * are capped at 0: a session put through Mnemom by a ledger, window or env signal alone
82
+ * can never add a saving (it adds its usage to the through bucket and 0 to "That saved"),
83
+ * but a replay that comes out NEGATIVE (billed above stock's window) still counts against
84
+ * it, so the cap only ever lowers the bucket total. tokenDiff / usdDiff keep the raw values.
85
+ */
86
+ export declare function replayStock(rows: readonly Row[], comps: readonly Compaction[], W: number): Replay;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * The realized saving of a session that ran through Mnemom: a replay of what stock Claude
3
+ * Code would have sent.
4
+ *
5
+ * Through the gateway, a transcript's top-level `usage` is the FOLDED, billed size (over
6
+ * 365k rows, `usage.iterations` matches the top-level fields, so no pre-fold size is ever
7
+ * recorded). So the client-side size is rebuilt from the fold drops themselves, and stock
8
+ * Claude Code is replayed on it at its DEFAULT window for the session's model, compacting
9
+ * whenever it would have. The saving is what stock would have billed minus what was
10
+ * billed. It can be negative for a session, and is kept negative.
11
+ *
12
+ * Pure: rows and compaction records in, numbers out. No I/O.
13
+ */
14
+ import { origContext, price } from "./backtest.js";
15
+ /** Main-thread requests with a smaller prompt are side calls (title, summary): ignored. */
16
+ export const SIDE_CALL_BELOW = 10_000;
17
+ /** A fold: prompt(a) - prompt(b) > this … */
18
+ export const FOLD_MIN_DROP = 20_000;
19
+ /** … and prompt(b) < this x prompt(a), with no Claude Code compaction in between. */
20
+ export const FOLD_MAX_RATIO = 0.7;
21
+ /** Median post-compaction size over 1,460 observed Claude Code compactions. */
22
+ export const POST_COMPACT = 17_181;
23
+ /** Output tokens of a stock compaction's summary call. */
24
+ export const SUMMARY_OUT = 6_000;
25
+ export const WINDOW_200K = 200_000;
26
+ export const WINDOW_1M = 1_000_000;
27
+ /**
28
+ * Models whose Claude Code default context window is 1M (read out of Claude Code
29
+ * 2.1.285's binary). Everything else, unknown models included, is 200k. The user's own
30
+ * autoCompactWindow is deliberately NOT read: the baseline is stock Claude Code.
31
+ */
32
+ export const ONE_M_DEFAULT = /^claude-(fable-5|opus-4-[6-9]|opus-[5-9]|sonnet-4-[6-9]|sonnet-[5-9])/;
33
+ /** Claude Code's default window for a model id (a trailing `[...]` is stripped). */
34
+ export function defaultWindow(model) {
35
+ return ONE_M_DEFAULT.test(model.replace(/\[[^\]]*\]$/, "")) ? WINDOW_1M : WINDOW_200K;
36
+ }
37
+ const isMain = (r) => r.mainThread && !r.subagent;
38
+ /** The most frequent main-thread model (all rows when none is main); first seen wins a tie. */
39
+ export function dominantModel(rows) {
40
+ const main = rows.filter(isMain);
41
+ const counts = new Map();
42
+ let best = "";
43
+ let bestN = 0;
44
+ for (const r of main.length ? main : rows) {
45
+ const n = (counts.get(r.model) ?? 0) + 1;
46
+ counts.set(r.model, n);
47
+ if (n > bestN) {
48
+ best = r.model;
49
+ bestN = n;
50
+ }
51
+ }
52
+ return best;
53
+ }
54
+ /** The main-thread requests the replay walks: prompt >= 10k, in timestamp order. */
55
+ export function replayRows(rows) {
56
+ return rows
57
+ .filter((r) => isMain(r) && origContext(r) >= SIDE_CALL_BELOW)
58
+ .sort((a, b) => a.t - b.t);
59
+ }
60
+ /** A Claude Code compaction with a timestamp in [ta, tb]. */
61
+ export function compactedBetween(ct, ta, tb) {
62
+ return ct.some((t) => t >= ta && t <= tb);
63
+ }
64
+ /** The size drop from a to b looks like a fold (the compaction check is separate). */
65
+ export function isFoldDrop(a, b) {
66
+ return a - b > FOLD_MIN_DROP && b < FOLD_MAX_RATIO * a;
67
+ }
68
+ /** Fold evidence: two consecutive main-thread requests with a fold drop and no compaction. */
69
+ export function hasFoldEvidence(rows, comps) {
70
+ const m = replayRows(rows);
71
+ const ct = comps.map((c) => c.t);
72
+ for (let i = 1; i < m.length; i++) {
73
+ const a = m[i - 1];
74
+ const b = m[i];
75
+ if (isFoldDrop(origContext(a), origContext(b)) && !compactedBetween(ct, a.t, b.t))
76
+ return true;
77
+ }
78
+ return false;
79
+ }
80
+ /**
81
+ * Replay stock Claude Code at window W along the session's main thread.
82
+ *
83
+ * client = billed prompt + what the folds removed so far (reset by a real compaction;
84
+ * growth during a fold request counts as 0, the conservative direction)
85
+ * stock = min(client, W) at the first request and after a reset; otherwise it grows
86
+ * with client and compacts above W: count it, charge (W x cache read + 6000 x
87
+ * output + 17181 x cache write 5m) at the request's model price, and continue
88
+ * from 17181 + the overshoot
89
+ * saved = Σ (stock - billed) at cache read + stock compaction cost - the session's own
90
+ * compactions x the mean simulated compaction cost (0 if none was simulated)
91
+ *
92
+ * The last term is deliberately CONSERVATIVE (operator 2026-10-02). At a real compaction
93
+ * the replay resets both worlds to the same size, so that compaction's cost is paid on
94
+ * both sides and cancels. Subtracting it then charges each real compaction against
95
+ * Mnemom a second time, at the mean simulated stock cost, which is 0 when the replay
96
+ * simulated none. It can only lower the saving, never raise it, and with more real
97
+ * compactions than simulated ones it can take the saving below 0.
98
+ *
99
+ * A REALIZED saving comes only from detected folds. With none, savedTokens and savedUsd
100
+ * are capped at 0: a session put through Mnemom by a ledger, window or env signal alone
101
+ * can never add a saving (it adds its usage to the through bucket and 0 to "That saved"),
102
+ * but a replay that comes out NEGATIVE (billed above stock's window) still counts against
103
+ * it, so the cap only ever lowers the bucket total. tokenDiff / usdDiff keep the raw values.
104
+ */
105
+ export function replayStock(rows, comps, W) {
106
+ const m = replayRows(rows);
107
+ const ct = comps.map((c) => c.t);
108
+ let removed = 0;
109
+ let prev = null;
110
+ let clientPrev = 0;
111
+ let st = 0;
112
+ let tokenDiff = 0;
113
+ let usdDiff = 0;
114
+ let n = 0;
115
+ let ccost = 0;
116
+ let folds = 0;
117
+ for (const r of m) {
118
+ const p = origContext(r);
119
+ const [, po, pr, w5] = price(r.model);
120
+ let reset = prev === null;
121
+ if (prev !== null) {
122
+ if (compactedBetween(ct, prev.t, r.t)) {
123
+ removed = 0;
124
+ reset = true;
125
+ }
126
+ else if (isFoldDrop(origContext(prev), p)) {
127
+ removed += origContext(prev) - p;
128
+ folds++;
129
+ }
130
+ }
131
+ const client = p + removed;
132
+ if (reset) {
133
+ st = Math.min(client, W);
134
+ }
135
+ else {
136
+ st += Math.max(client - clientPrev, 0);
137
+ while (st > W) {
138
+ n++;
139
+ ccost += (W * pr + SUMMARY_OUT * po + POST_COMPACT * w5) / 1e6;
140
+ st = POST_COMPACT + (st - W);
141
+ }
142
+ }
143
+ tokenDiff += st - p;
144
+ usdDiff += ((st - p) * pr) / 1e6;
145
+ prev = r;
146
+ clientPrev = client;
147
+ }
148
+ // Conservative: see the doc comment. Never raises savedUsd (each >= 0).
149
+ const each = n ? ccost / n : 0;
150
+ const usd = usdDiff + ccost - comps.length * each;
151
+ return {
152
+ window: W,
153
+ tokenDiff,
154
+ usdDiff,
155
+ stockCompactions: n,
156
+ stockCompactionUsd: ccost,
157
+ actualCompactions: comps.length,
158
+ folds,
159
+ savedTokens: folds ? tokenDiff : Math.min(tokenDiff, 0),
160
+ savedUsd: folds ? usd : Math.min(usd, 0),
161
+ };
162
+ }
@@ -1,34 +1,52 @@
1
1
  /**
2
2
  * The first-run screen: one plain terminal screen (at most 22 lines of at most 78
3
- * columns, ASCII) built from the backtest numbers. Pure — no I/O — so every layout is
3
+ * columns, ASCII; with the command's blank line above and below, 24 rows) built from
4
+ * the backtest numbers. Pure — no I/O — so every layout is
4
5
  * unit-tested. Every number on it is recorded in the audit with its raw value and
5
6
  * method.
6
7
  *
7
- * Exactly three variations (operator v1, 2026-10-01); every number has one source and no
8
- * two lines make the same claim:
9
- * A saving the shown saving is >= $5 and >= 5%: the usage at API prices, with Mnemom,
10
- * the saving, the biggest session; plus ONE subscription sentence for a Team /
11
- * Enterprise subscriber whose usage cut rounds to >= 5%. Where sessions ran
12
- * through Mnemom (launch ledger), that part is said to be SAVED: what it
13
- * saved you, and what it would have saved on the rest (operator 2026-10-02).
14
- * B small >= $5 of usage at API prices but a smaller saving: the usage, the honest
15
- * "wouldn't have changed that much" line, and the forward line.
16
- * C none too little history: the forward line only.
17
- * All three end with the price line and the live lines.
8
+ * Sessions come in two buckets (index.ts): THROUGH Mnemom (launch ledger, a window / env
9
+ * signal, or fold evidence), whose saving is REALIZED (replay.ts: stock Claude Code
10
+ * replayed at its default window, can be negative), and NOT through Mnemom, whose saving
11
+ * is the POTENTIAL one (backtest.ts: the flat S46 rule, 23.8% of every request at or
12
+ * above 60k, for dollars and for tokens alike). Tokens and dollars are both shown
13
+ * (operator 2026-10-02). Percentages drop their decimals (rounded DOWN).
18
14
  *
19
- * Operator rulings (2026-09-30 / 2026-10-01), binding:
20
- * - Cost only. Nothing about conduct ("habits", "caught", "checked").
21
- * - Round DOWN, never overstate; frame the number around the biggest session.
22
- * - Every kept session is priced at API list prices whatever it was billed on, and the
23
- * screen says "at API prices". A subscriber gets a usage %, never a seat bill, an
24
- * overage dollar figure or a limit-hit count.
15
+ * Variations; every number has one source and no two lines make the same claim:
16
+ * A no through-Mnemom usage, and the potential saving qualifies: the usage in
17
+ * tokens and at API prices, the cut with Mnemom, the biggest session.
18
+ * mixed both buckets have usage: one block per bucket, each with its saving line
19
+ * only if that BUCKET's saving qualifies, then the largest dollar saving
20
+ * across the shown buckets, worded by its bucket ("biggest saving ... of"
21
+ * through, "biggest session ... less with Mnemom" not through).
22
+ * through only through-Mnemom usage: its block alone, then its biggest saving.
23
+ * B >= $5 of usage but no saving line qualifies: the usage, the honest "wouldn't
24
+ * have changed that much" line (no through usage) or the bucket usage lines,
25
+ * and the forward line.
26
+ * C too little history: the forward line only.
27
+ * A saving qualifies iff it rounds DOWN to >= $5 and its token percent to >= 5%. A
28
+ * Team / Enterprise subscriber also gets the subscription line whenever a saving line is
29
+ * shown; its overage clause only when the account has extra usage enabled. All end with
30
+ * the price line and the live lines.
31
+ *
32
+ * Layout (operator 2026-10-02): a blank line between every block (each bucket's usage
33
+ * and saving lines, the biggest line, the subscriber lines, the price line, the live
34
+ * lines); the saving lines are not indented.
35
+ *
36
+ * Operator rulings (2026-09-30 .. 2026-10-02), binding:
37
+ * - Cost and usage only. Nothing about conduct ("habits", "caught", "checked").
38
+ * - Round DOWN, never overstate. A through session's saving may be negative: the
39
+ * bucket total is the honest sum, never floored per session.
40
+ * - Every session is priced at API list prices whatever it was billed on, and the
41
+ * screen says "at API prices". A subscriber never gets a seat bill, an overage dollar
42
+ * figure or a limit-hit count.
25
43
  * - No live counter is promised: `mnemom agent` has no status bar today.
26
44
  */
27
45
  export declare const WIDTH = 78;
28
46
  export declare const MAX_LINES = 22;
29
47
  /** Less history than this (API prices, in the window) is too little to backtest. */
30
48
  export declare const MIN_HISTORY_USD = 5;
31
- /** Variation A iff the shown saving is at least this many dollars and this many percent. */
49
+ /** A saving line is shown iff its saving rounds to at least this many dollars and percent. */
32
50
  export declare const STRONG_MIN_USD = 5;
33
51
  export declare const STRONG_MIN_PCT = 5;
34
52
  export declare const DAYS = 30;
@@ -42,6 +60,14 @@ export declare const FORWARD_LINE = "Long sessions are where Mnemom saves the mo
42
60
  /** Prices, promotions and plan allocations move; the saving is in usage (operator 2026-10-01). */
43
61
  export declare const PRICE_LINE = "Prices and plan limits change. Mnemom cuts what you use, so you stay ahead.";
44
62
  /** What `mnemom agent` does today: routes through the gateway, records, keeps context lean. */
63
+ /**
64
+ * The subscriber lines, fixed like the live lines: the operator's break, so the overage
65
+ * form reads as two lines and never as one 78-column line.
66
+ */
67
+ export declare const SUB_LINES: {
68
+ limit: string[];
69
+ overage: string[];
70
+ };
45
71
  export declare const LIVE_LINES: string[];
46
72
  /** 2 significant figures, rounded DOWN; 0 below $1. */
47
73
  export declare function sig2(x: number): number;
@@ -56,31 +82,41 @@ export declare function pair(total: number, saved: number): [number, number, num
56
82
  export declare function pctDown(part: number, whole: number): number;
57
83
  export declare function day(t: number): string;
58
84
  export declare function usd(n: number): string;
85
+ /** A token count rounded DOWN to 2 significant figures (the number it stands for). */
86
+ export declare function tokShown(n: number): number;
87
+ /**
88
+ * A token count in words, 2 significant figures DOWN: "9.0 billion", "39 billion",
89
+ * "450 million", "12 thousand"; below a thousand, the plain number.
90
+ */
91
+ export declare function tokens(n: number): string;
59
92
  export declare function clean(s: string): string;
60
93
  /** A short, safe project label from Claude Code's encoded project dir, or null. */
61
94
  export declare function labelFor(pretty: string): string | null;
62
95
  export declare function wrap(text: string, width?: number): string[];
63
- /** One kept session's requests in the window, priced at API list prices. */
96
+ /** One session's requests in the window, priced at API list prices. */
64
97
  export interface SessionCost {
65
98
  label: string | null;
66
99
  end: number;
100
+ /** What it used, at API prices (through Mnemom: the folded, billed size). */
67
101
  actual: number;
102
+ /** Tokens it used: prompt + output. */
103
+ tokens: number;
104
+ /** Through: the realized saving vs stock Claude Code (may be negative). Not: potential. */
68
105
  saved: number;
69
- /** The launch ledger proves it ran through Mnemom: its saving is said to be SAVED. */
70
- viaMnemom?: boolean;
106
+ savedTokens: number;
107
+ /** It ran through Mnemom (index.ts throughEvidence). */
108
+ through: boolean;
71
109
  }
72
- /** A Team / Enterprise subscriber's usage, at API prices (local evidence only). */
73
- export interface SubUsage {
74
- /** Subscription-billed sessions in the window (all sessions when none is tagged). */
75
- sessions: number;
76
- actual: number;
77
- saved: number;
110
+ /** A Team / Enterprise subscriber (local evidence only). */
111
+ export interface SubInfo {
112
+ /** The account has extra usage (overage) enabled: `hasExtraUsageEnabled`. */
113
+ extraUsage: boolean;
78
114
  }
79
115
  export interface ScreenInput {
80
- /** Every kept session with requests in the window. */
116
+ /** Every session with requests in the window. */
81
117
  sessions: SessionCost[];
82
118
  /** null: not a Team / Enterprise subscriber. */
83
- sub: SubUsage | null;
119
+ sub: SubInfo | null;
84
120
  }
85
121
  export interface AuditEntry {
86
122
  shown: unknown;
@@ -88,7 +124,7 @@ export interface AuditEntry {
88
124
  method: string;
89
125
  }
90
126
  export type Audit = Record<string, AuditEntry>;
91
- export type Variation = "A" | "B" | "C";
127
+ export type Variation = "A" | "mixed" | "through" | "B" | "C";
92
128
  /** -> the screen lines, the variation and the audit of every number on them. */
93
129
  export declare function buildScreen(inp: ScreenInput): {
94
130
  lines: string[];