estela 0.1.39 → 0.1.40

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,245 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HISTORY_WEEKS = exports.WINDOW_HOURS = exports.QUOTA_AGENTS = void 0;
4
+ exports.isQuotaAgent = isQuotaAgent;
5
+ exports.breakdown = breakdown;
6
+ exports.fiveHourWindows = fiveHourWindows;
7
+ exports.windowAt = windowAt;
8
+ exports.weekStart = weekStart;
9
+ exports.sameStretch = sameStretch;
10
+ exports.compareWithHistory = compareWithHistory;
11
+ exports.buildQuota = buildQuota;
12
+ exports.quotaRange = quotaRange;
13
+ exports.quotaToJson = quotaToJson;
14
+ /**
15
+ * "¿Qué proyecto se come mi cuota?"
16
+ *
17
+ * Quien paga Claude Max/Pro o Codex con suscripción agota la cuota antes de
18
+ * tiempo y no sabe en qué. Estela ya tiene cada turno con su carpeta, así que
19
+ * puede repartir el consumo por proyecto. Funciones puras, como el resto de
20
+ * `metrics/`: entran turnos, salen cifras, y cada cifra tiene un test.
21
+ *
22
+ * Lo que esto NO hace, a propósito: decir cuánto te queda. Ni Anthropic ni
23
+ * OpenAI publican los límites exactos de cada plan, y la cuota se comparte con
24
+ * lo que uses fuera de esta máquina (claude.ai, el móvil, otro portátil), que
25
+ * Estela no ve. Un "te quedan X tokens" sería inventado. Lo único que se puede
26
+ * defender es el consumo real y la comparación con tu propio historial.
27
+ *
28
+ * Los turnos llegan ya deduplicados: los lectores de `watchers/` descartan las
29
+ * filas repetidas (Claude Code por `message.id`, Codex por su ordinal sobre
30
+ * `last_token_usage`) y `agent_turns` los guarda con `turn_id` como clave
31
+ * primaria. Aquí no se repite esa lógica.
32
+ */
33
+ /**
34
+ * Los agentes que cuentan para la cuota.
35
+ *
36
+ * Copilot no: guarda `promptTokens`/`completionTokens` sin caché ni nada que
37
+ * permita fiarse de ellos (ver `watchers/copilot.ts`), y además se paga por
38
+ * peticiones, no por tokens. Cursor y Gemini CLI no tienen lector todavía.
39
+ *
40
+ * Cada agente va por separado y no se suman nunca: Claude Code gasta la cuota
41
+ * de Anthropic y Codex la de OpenAI. Un "% del total" mezclando los dos no
42
+ * describiría ninguna cuota que exista.
43
+ */
44
+ exports.QUOTA_AGENTS = ["claude-code", "codex"];
45
+ function isQuotaAgent(agent) {
46
+ return exports.QUOTA_AGENTS.includes(agent);
47
+ }
48
+ const ZERO = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
49
+ function add(u, t) {
50
+ const cacheWrite = t.cacheWrite5m + t.cacheWrite1h;
51
+ return {
52
+ input: u.input + t.input,
53
+ output: u.output + t.output,
54
+ cacheRead: u.cacheRead + t.cacheRead,
55
+ cacheWrite: u.cacheWrite + cacheWrite,
56
+ total: u.total + t.input + t.output + t.cacheRead + cacheWrite,
57
+ };
58
+ }
59
+ function bucketKey(b) {
60
+ return b.kind === "project" ? `p:${b.projectId}` : b.kind === "folder" ? `f:${b.folder}` : "-";
61
+ }
62
+ /**
63
+ * Reparto por proyecto de los turnos con `from <= at < to`, de mayor a menor.
64
+ * Un proyecto sin actividad en el periodo no sale: una fila a cero en una
65
+ * tabla de "quién se come la cuota" es ruido.
66
+ */
67
+ function breakdown(turns, from, to, bucketOf) {
68
+ const acc = new Map();
69
+ let total = ZERO;
70
+ for (const t of turns) {
71
+ if (t.at < from || t.at >= to)
72
+ continue;
73
+ const bucket = bucketOf(t.repoPath);
74
+ const key = bucketKey(bucket);
75
+ const prev = acc.get(key) ?? { bucket, usage: ZERO };
76
+ acc.set(key, { bucket, usage: add(prev.usage, t.tokens) });
77
+ total = add(total, t.tokens);
78
+ }
79
+ const rows = [...acc.values()]
80
+ .map(({ bucket, usage }) => ({ bucket, usage, share: total.total > 0 ? usage.total / total.total : 0 }))
81
+ .sort((a, b) => b.usage.total - a.usage.total);
82
+ return { rows, total };
83
+ }
84
+ // ---------------------------------------------------------------------------
85
+ // La ventana de 5 horas
86
+ // ---------------------------------------------------------------------------
87
+ exports.WINDOW_HOURS = 5;
88
+ const WINDOW_MS = exports.WINDOW_HOURS * 3600_000;
89
+ /**
90
+ * Las ventanas de 5 h que se deducen de unos turnos ordenados en el tiempo.
91
+ *
92
+ * Es una ESTIMACIÓN, y por eso se dice en la ayuda y en la salida:
93
+ *
94
+ * - Una ventana empieza con el primer mensaje después de que acabe la
95
+ * anterior, y dura 5 horas. Es lo que describen Anthropic y OpenAI, pero
96
+ * ninguno publica cómo redondea el inicio (ccusage, por ejemplo, lo lleva a
97
+ * la hora en punto). Aquí se usa la hora exacta del primer turno: no
98
+ * inventar un redondeo es preferible a acertar por casualidad.
99
+ * - La cuota se comparte con lo que uses fuera de esta máquina. Un mensaje en
100
+ * claude.ai puede haber abierto la ventana antes, y Estela no lo ve: la
101
+ * ventana real puede empezar antes, nunca después, de la que sale aquí.
102
+ */
103
+ function fiveHourWindows(times) {
104
+ const sorted = [...times].sort((a, b) => a.getTime() - b.getTime());
105
+ const out = [];
106
+ for (const at of sorted) {
107
+ const last = out[out.length - 1];
108
+ if (last && at < last.end)
109
+ continue;
110
+ out.push({ start: at, end: new Date(at.getTime() + WINDOW_MS) });
111
+ }
112
+ return out;
113
+ }
114
+ /** La ventana abierta ahora, o null. Y si no hay, cuándo acabó la última. */
115
+ function windowAt(times, now) {
116
+ const windows = fiveHourWindows(times.filter((t) => t <= now));
117
+ const last = windows[windows.length - 1];
118
+ if (!last)
119
+ return { current: null, lastEnded: null };
120
+ return last.end > now ? { current: last, lastEnded: null } : { current: null, lastEnded: last.end };
121
+ }
122
+ // ---------------------------------------------------------------------------
123
+ // La semana y el historial
124
+ // ---------------------------------------------------------------------------
125
+ /**
126
+ * El lunes de esta semana a las 00:00, en la hora de quien pregunta.
127
+ *
128
+ * Con el calendario local (`new Date(año, mes, día)`) y no restando
129
+ * milisegundos: la semana del cambio de hora tiene 167 o 169 horas, y restar
130
+ * 7 × 24 h dejaría el "lunes" a las 23:00 del domingo.
131
+ */
132
+ function weekStart(now) {
133
+ const back = (now.getDay() + 6) % 7; // lunes = 0, domingo = 6
134
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate() - back);
135
+ }
136
+ /**
137
+ * El mismo tramo de la semana, `weeksBack` semanas atrás: si hoy es miércoles a
138
+ * las 15:00, de lunes 00:00 a miércoles 15:00 de aquella semana. Así se compara
139
+ * lo comparable: un miércoles no puede llevar lo que lleva una semana entera.
140
+ */
141
+ function sameStretch(now, weeksBack) {
142
+ const monday = weekStart(now);
143
+ const day = (now.getDay() + 6) % 7;
144
+ const start = new Date(monday.getFullYear(), monday.getMonth(), monday.getDate() - 7 * weeksBack);
145
+ const end = new Date(start.getFullYear(), start.getMonth(), start.getDate() + day, now.getHours(), now.getMinutes(), now.getSeconds(), now.getMilliseconds());
146
+ return { start, end };
147
+ }
148
+ /** Las semanas completas con las que se compara. */
149
+ exports.HISTORY_WEEKS = 4;
150
+ /**
151
+ * Esta semana frente a la media de las cuatro anteriores, en el mismo tramo.
152
+ *
153
+ * Una semana pasada solo cuenta como historial si empezó el día del primer
154
+ * turno de ese agente o después: antes de eso no había nada que medir, y
155
+ * meter un cero inventado bajaría la media y haría parecer excesiva una semana
156
+ * normal. Una semana posterior sin uso sí cuenta como cero: es real.
157
+ */
158
+ function compareWithHistory(times, firstTurnAt, now) {
159
+ const sum = (w) => times.reduce((s, t) => (t.at >= w.start && t.at < w.end ? s + t.total : s), 0);
160
+ const firstDay = firstTurnAt
161
+ ? new Date(firstTurnAt.getFullYear(), firstTurnAt.getMonth(), firstTurnAt.getDate())
162
+ : null;
163
+ const past = Array.from({ length: exports.HISTORY_WEEKS }, (_, i) => sameStretch(now, i + 1));
164
+ const covered = past.filter((w) => firstDay !== null && w.start >= firstDay);
165
+ if (covered.length < exports.HISTORY_WEEKS) {
166
+ return { status: "insufficient", completeWeeks: covered.length, weeks: exports.HISTORY_WEEKS };
167
+ }
168
+ const current = sum({ start: weekStart(now), end: new Date(now.getTime() + 1) });
169
+ const average = past.reduce((s, w) => s + sum(w), 0) / exports.HISTORY_WEEKS;
170
+ return { status: "ok", current, average, ratio: average > 0 ? current / average : null, weeks: exports.HISTORY_WEEKS };
171
+ }
172
+ /**
173
+ * Monta el informe. `turns` debe cubrir al menos las cuatro semanas anteriores
174
+ * a esta (ver `quotaRange`); `firstTurnAt`, el primer turno que hay guardado de
175
+ * cada agente, sea de cuando sea.
176
+ */
177
+ function buildQuota(input) {
178
+ const { now, bucketOf } = input;
179
+ const monday = weekStart(now);
180
+ const until = new Date(now.getTime() + 1);
181
+ const turns = input.turns.filter((t) => t.at <= now);
182
+ const agents = [];
183
+ for (const agent of exports.QUOTA_AGENTS) {
184
+ const own = turns.filter((t) => t.agent === agent);
185
+ const { current, lastEnded } = windowAt(own.map((t) => t.at), now);
186
+ const week = breakdown(own, monday, until, bucketOf);
187
+ if (!current && week.rows.length === 0)
188
+ continue;
189
+ agents.push({
190
+ agent,
191
+ window: current ? { ...current, ...breakdown(own, current.start, until, bucketOf) } : null,
192
+ lastWindowEnded: lastEnded,
193
+ week,
194
+ comparison: compareWithHistory(own.map((t) => ({ at: t.at, total: add(ZERO, t.tokens).total })), input.firstTurnAt.get(agent) ?? null, now),
195
+ });
196
+ }
197
+ return {
198
+ generatedAt: now,
199
+ weekStart: monday,
200
+ agents,
201
+ copilotExcluded: turns.some((t) => t.agent === "copilot" && t.at >= monday),
202
+ };
203
+ }
204
+ /** Desde cuándo hay que leer turnos para montar el informe de `now`. */
205
+ function quotaRange(now) {
206
+ return { start: sameStretch(now, exports.HISTORY_WEEKS).start, end: new Date(now.getTime() + 1) };
207
+ }
208
+ // ---------------------------------------------------------------------------
209
+ // Salida para máquinas
210
+ // ---------------------------------------------------------------------------
211
+ /**
212
+ * El informe como JSON estable, para `--json` y para el panel web. Fechas en
213
+ * ISO, y las filas con `projectId`/`projectName`/`folder` a null cuando no
214
+ * aplican, para que un script no tenga que mirar de qué tipo es cada una.
215
+ */
216
+ function quotaToJson(report) {
217
+ const rows = (b) => b.rows.map((r) => ({
218
+ projectId: r.bucket.kind === "project" ? r.bucket.projectId : null,
219
+ projectName: r.bucket.kind === "project" ? r.bucket.projectName : null,
220
+ folder: r.bucket.kind === "folder" ? r.bucket.folder : null,
221
+ ...r.usage,
222
+ share: Math.round(r.share * 10_000) / 10_000,
223
+ }));
224
+ return {
225
+ generatedAt: report.generatedAt.toISOString(),
226
+ weekStart: report.weekStart.toISOString(),
227
+ // Para que ningún script lo trate como un dato exacto sin haberlo leído.
228
+ windowIsEstimate: true,
229
+ windowHours: exports.WINDOW_HOURS,
230
+ agents: report.agents.map((a) => ({
231
+ agent: a.agent,
232
+ window: a.window
233
+ ? { start: a.window.start.toISOString(), end: a.window.end.toISOString(),
234
+ projects: rows(a.window), total: a.window.total }
235
+ : null,
236
+ lastWindowEnded: a.lastWindowEnded?.toISOString() ?? null,
237
+ week: { projects: rows(a.week), total: a.week.total },
238
+ comparison: a.comparison.status === "ok"
239
+ ? { ...a.comparison, ratio: a.comparison.ratio === null ? null : Math.round(a.comparison.ratio * 1000) / 1000 }
240
+ : a.comparison,
241
+ })),
242
+ excluded: report.copilotExcluded ? [{ agent: "copilot", reason: "no-reliable-tokens" }] : [],
243
+ };
244
+ }
245
+ //# sourceMappingURL=quota.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota.js","sourceRoot":"","sources":["../../src/metrics/quota.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=quota.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota.test.d.ts","sourceRoot":"","sources":["../../src/metrics/quota.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const strict_1 = __importDefault(require("node:assert/strict"));
40
+ const node_test_1 = require("node:test");
41
+ const schema_js_1 = require("../db/schema.js");
42
+ const store = __importStar(require("../db/store.js"));
43
+ const quota_js_1 = require("../quota.js");
44
+ const quota_js_2 = require("./quota.js");
45
+ /**
46
+ * "¿Qué proyecto se come mi cuota?"
47
+ *
48
+ * Lo que no puede fallar: que un turno repetido cuente dos veces (inflaría a un
49
+ * proyecto), que la semana empiece donde no es, y que se compare con un
50
+ * historial que no existe. Todo lo demás es presentación.
51
+ */
52
+ const tok = (total, over = {}) => ({ input: 0, output: 0, cacheRead: total, cacheWrite5m: 0, cacheWrite1h: 0, ...over });
53
+ const turno = (at, repoPath, total, agent = "claude-code") => ({ agent, at: new Date(at), repoPath, tokens: tok(total) });
54
+ const porCarpeta = (repoPath) => repoPath ? { kind: "project", projectId: repoPath, projectName: repoPath } : { kind: "none" };
55
+ /** Fechas en hora local: los tests no dependen de la zona de quien los corre. */
56
+ const local = (y, m, d, h = 0, min = 0) => new Date(y, m - 1, d, h, min);
57
+ // ── La semana ───────────────────────────────────────────────────────────
58
+ (0, node_test_1.test)("la semana empieza el lunes a las 00:00 en hora local, también un domingo por la noche", () => {
59
+ // 2026-10-11 es domingo; 2026-10-05, lunes.
60
+ strict_1.default.deepEqual((0, quota_js_2.weekStart)(local(2026, 10, 11, 23, 59)), local(2026, 10, 5));
61
+ strict_1.default.deepEqual((0, quota_js_2.weekStart)(local(2026, 10, 5, 0, 0)), local(2026, 10, 5), "el propio lunes a medianoche");
62
+ strict_1.default.deepEqual((0, quota_js_2.weekStart)(local(2026, 10, 7, 15, 0)), local(2026, 10, 5));
63
+ });
64
+ (0, node_test_1.test)("cambio de semana: lo del domingo por la noche no cuenta en la semana que empieza el lunes", () => {
65
+ const now = local(2026, 10, 12, 0, 30); // lunes 00:30
66
+ const r = (0, quota_js_2.buildQuota)({
67
+ turns: [
68
+ { agent: "claude-code", at: local(2026, 10, 11, 23, 50), repoPath: "/a", tokens: tok(1000) },
69
+ { agent: "claude-code", at: local(2026, 10, 12, 0, 10), repoPath: "/b", tokens: tok(10) },
70
+ ],
71
+ firstTurnAt: new Map(), now, bucketOf: porCarpeta,
72
+ });
73
+ const semana = r.agents[0].week;
74
+ strict_1.default.deepEqual(semana.rows.map((x) => x.bucket.kind === "project" && x.bucket.projectId), ["/b"]);
75
+ strict_1.default.equal(semana.total.total, 10);
76
+ // Pero la ventana de 5 h sí cruza la medianoche: la cuota no sabe de semanas.
77
+ strict_1.default.equal(r.agents[0].window.total.total, 1010);
78
+ });
79
+ (0, node_test_1.test)("la semana del cambio de hora sigue empezando el lunes a las 00:00", () => {
80
+ // En Madrid, el domingo 25 de octubre de 2026 tiene 25 horas. Restar 7×24 h
81
+ // dejaría el "lunes" a las 23:00 del domingo.
82
+ const antes = process.env["TZ"];
83
+ process.env["TZ"] = "Europe/Madrid";
84
+ try {
85
+ const lunes = (0, quota_js_2.weekStart)(local(2026, 10, 28, 12));
86
+ strict_1.default.equal(lunes.getDay(), 1);
87
+ strict_1.default.equal(lunes.getHours(), 0);
88
+ const atras = (0, quota_js_2.sameStretch)(local(2026, 10, 28, 12), 1);
89
+ strict_1.default.equal(atras.start.getDay(), 1);
90
+ strict_1.default.equal(atras.start.getHours(), 0, "el lunes anterior, a medianoche y no a las 23:00 del domingo");
91
+ strict_1.default.equal(atras.end.getDay(), 3);
92
+ strict_1.default.equal(atras.end.getHours(), 12);
93
+ }
94
+ finally {
95
+ if (antes === undefined)
96
+ delete process.env["TZ"];
97
+ else
98
+ process.env["TZ"] = antes;
99
+ }
100
+ });
101
+ (0, node_test_1.test)("el mismo tramo de semanas anteriores: de lunes 00:00 al mismo día y hora", () => {
102
+ const t = (0, quota_js_2.sameStretch)(local(2026, 10, 7, 15, 0), 2); // miércoles 15:00, dos semanas atrás
103
+ strict_1.default.deepEqual(t.start, local(2026, 9, 21));
104
+ strict_1.default.deepEqual(t.end, local(2026, 9, 23, 15, 0));
105
+ });
106
+ // ── La ventana de 5 h ───────────────────────────────────────────────────
107
+ (0, node_test_1.test)("una ventana empieza con el primer turno después de que acabe la anterior y dura 5 h", () => {
108
+ const w = (0, quota_js_2.fiveHourWindows)([
109
+ local(2026, 10, 7, 9, 0), local(2026, 10, 7, 13, 59), // misma ventana
110
+ local(2026, 10, 7, 14, 0), // justo al cerrar: nueva
111
+ local(2026, 10, 7, 18, 30), // dentro de la segunda
112
+ ]);
113
+ strict_1.default.deepEqual(w.map((x) => [x.start.getHours(), x.end.getHours()]), [[9, 14], [14, 19]]);
114
+ });
115
+ (0, node_test_1.test)("ventana abierta ahora, o la hora en que acabó la última", () => {
116
+ const veces = [local(2026, 10, 7, 9, 0), local(2026, 10, 7, 10, 0)];
117
+ const abierta = (0, quota_js_2.windowAt)(veces, local(2026, 10, 7, 12, 0));
118
+ strict_1.default.deepEqual(abierta.current?.start, local(2026, 10, 7, 9, 0));
119
+ strict_1.default.equal(abierta.lastEnded, null);
120
+ const cerrada = (0, quota_js_2.windowAt)(veces, local(2026, 10, 7, 16, 0));
121
+ strict_1.default.equal(cerrada.current, null);
122
+ strict_1.default.deepEqual(cerrada.lastEnded, local(2026, 10, 7, 14, 0));
123
+ strict_1.default.deepEqual((0, quota_js_2.windowAt)([], local(2026, 10, 7, 16, 0)), { current: null, lastEnded: null });
124
+ });
125
+ // ── Reparto ─────────────────────────────────────────────────────────────
126
+ (0, node_test_1.test)("el reparto va de mayor a menor, con su porcentaje, y suma bien los cinco campos", () => {
127
+ const now = local(2026, 10, 7, 12);
128
+ const r = (0, quota_js_2.buildQuota)({
129
+ turns: [
130
+ { agent: "claude-code", at: local(2026, 10, 7, 10), repoPath: "/a",
131
+ tokens: { input: 1, output: 2, cacheRead: 3, cacheWrite5m: 4, cacheWrite1h: 5 } },
132
+ turno("2026-10-06T10:00:00", "/b", 45),
133
+ ],
134
+ firstTurnAt: new Map(), now, bucketOf: porCarpeta,
135
+ });
136
+ const w = r.agents[0].week;
137
+ strict_1.default.deepEqual(w.rows.map((x) => x.usage.total), [45, 15]);
138
+ strict_1.default.deepEqual(w.rows[1].usage, { input: 1, output: 2, cacheRead: 3, cacheWrite: 9, total: 15 });
139
+ strict_1.default.equal(w.rows[0].share, 0.75);
140
+ strict_1.default.equal(w.total.total, 60);
141
+ });
142
+ (0, node_test_1.test)("un proyecto sin actividad en el periodo no sale en la tabla", () => {
143
+ const db = (0, schema_js_1.openDatabase)(":memory:");
144
+ try {
145
+ store.upsertClient(db, { id: "c", name: "C", currency: "EUR" });
146
+ for (const [id, repo] of [["activo", "/repos/activo"], ["parado", "/repos/parado"]]) {
147
+ store.upsertProject(db, { id, clientId: "c", name: id, repoPaths: [repo], billable: true,
148
+ roundingMinutes: 0, aiCostPolicy: "absorbed", kind: "client" });
149
+ }
150
+ store.saveTurns(db, [turnoGuardado("t1", "/repos/activo", "2026-10-07T10:00:00")]);
151
+ const r = (0, quota_js_1.loadQuotaReport)(db, local(2026, 10, 7, 12));
152
+ const ids = r.agents[0].week.rows.map((x) => x.bucket.kind === "project" ? x.bucket.projectId : "?");
153
+ strict_1.default.deepEqual(ids, ["activo"]);
154
+ }
155
+ finally {
156
+ db.close();
157
+ }
158
+ });
159
+ (0, node_test_1.test)("un turno fuera de cualquier proyecto cuenta, a nombre de su carpeta, y uno sin carpeta también", () => {
160
+ const db = (0, schema_js_1.openDatabase)(":memory:");
161
+ try {
162
+ store.saveTurns(db, [
163
+ turnoGuardado("t1", "/home/yo/notas", "2026-10-07T10:00:00"),
164
+ { ...turnoGuardado("t2", null, "2026-10-07T10:05:00") },
165
+ ]);
166
+ const filas = (0, quota_js_1.loadQuotaReport)(db, local(2026, 10, 7, 12)).agents[0].week.rows.map((x) => x.bucket);
167
+ strict_1.default.deepEqual(filas, [{ kind: "folder", folder: "notas" }, { kind: "none" }]);
168
+ }
169
+ finally {
170
+ db.close();
171
+ }
172
+ });
173
+ (0, node_test_1.test)("deduplicación: el mismo turno leído dos veces cuenta una sola vez", () => {
174
+ // Es la garantía de los lectores (por message.id en Claude Code) y de la
175
+ // clave primaria de agent_turns. La cuota no la repite: la hereda.
176
+ const db = (0, schema_js_1.openDatabase)(":memory:");
177
+ try {
178
+ const t = turnoGuardado("msg_01ABC", "/repo", "2026-10-07T10:00:00", 1000);
179
+ store.saveTurns(db, [t, t]);
180
+ store.saveTurns(db, [t]);
181
+ const r = (0, quota_js_1.loadQuotaReport)(db, local(2026, 10, 7, 12));
182
+ strict_1.default.equal(r.agents[0].week.total.total, 1000);
183
+ }
184
+ finally {
185
+ db.close();
186
+ }
187
+ });
188
+ // ── Agentes ─────────────────────────────────────────────────────────────
189
+ (0, node_test_1.test)("Claude Code y Codex van por separado; Copilot queda fuera y se dice", () => {
190
+ const now = local(2026, 10, 7, 12);
191
+ const r = (0, quota_js_2.buildQuota)({
192
+ turns: [
193
+ turno("2026-10-07T10:00:00", "/a", 100, "claude-code"),
194
+ turno("2026-10-07T10:00:00", "/a", 7, "codex"),
195
+ turno("2026-10-07T10:00:00", "/a", 999_999, "copilot"),
196
+ ],
197
+ firstTurnAt: new Map(), now, bucketOf: porCarpeta,
198
+ });
199
+ strict_1.default.deepEqual(r.agents.map((a) => [a.agent, a.week.total.total]), [["claude-code", 100], ["codex", 7]]);
200
+ strict_1.default.equal(r.copilotExcluded, true);
201
+ });
202
+ (0, node_test_1.test)("un agente sin actividad no aparece; sin Copilot no se menciona", () => {
203
+ const r = (0, quota_js_2.buildQuota)({
204
+ turns: [turno("2026-10-07T10:00:00", "/a", 100, "claude-code")],
205
+ firstTurnAt: new Map(), now: local(2026, 10, 7, 12), bucketOf: porCarpeta,
206
+ });
207
+ strict_1.default.deepEqual(r.agents.map((a) => a.agent), ["claude-code"]);
208
+ strict_1.default.equal(r.copilotExcluded, false);
209
+ });
210
+ // ── Historial ───────────────────────────────────────────────────────────
211
+ const semanal = (n) => Array.from({ length: n }, (_, i) => ({ at: (0, quota_js_2.sameStretch)(local(2026, 10, 7, 15), i + 1).start, total: 100 }));
212
+ (0, node_test_1.test)("semana sin historial: no se compara, y se dice cuántas semanas hay", () => {
213
+ const now = local(2026, 10, 7, 15);
214
+ // El primer turno es de hace 10 días: solo una semana completa anterior.
215
+ const c = (0, quota_js_2.compareWithHistory)([], local(2026, 9, 28, 9), now);
216
+ strict_1.default.deepEqual(c, { status: "insufficient", completeWeeks: 1, weeks: 4 });
217
+ // Sin ningún turno guardado, ninguna.
218
+ strict_1.default.deepEqual((0, quota_js_2.compareWithHistory)([], null, now), { status: "insufficient", completeWeeks: 0, weeks: 4 });
219
+ });
220
+ (0, node_test_1.test)("con cuatro semanas de historial, media del mismo tramo; una semana vacía cuenta como cero", () => {
221
+ const now = local(2026, 10, 7, 15); // miércoles
222
+ const veces = [
223
+ ...semanal(3), // 100 en cada una de las tres últimas
224
+ { at: local(2026, 10, 6, 10), total: 150 }, // esta semana
225
+ { at: local(2026, 10, 1, 9), total: 9999 }, // jueves de la semana pasada: fuera del tramo
226
+ ];
227
+ // La cuarta semana atrás está cubierta (el primer turno es anterior) pero vacía.
228
+ const c = (0, quota_js_2.compareWithHistory)(veces, local(2026, 8, 1), now);
229
+ strict_1.default.equal(c.status, "ok");
230
+ if (c.status !== "ok")
231
+ return;
232
+ strict_1.default.equal(c.current, 150);
233
+ strict_1.default.equal(c.average, 75, "(100+100+100+0)/4: el jueves pasado no entra en un tramo que acaba el miércoles");
234
+ strict_1.default.equal(c.ratio, 2);
235
+ });
236
+ (0, node_test_1.test)("una media de cero no divide: el ratio va a null", () => {
237
+ const c = (0, quota_js_2.compareWithHistory)([{ at: local(2026, 10, 6, 10), total: 5 }], local(2026, 1, 1), local(2026, 10, 7, 15));
238
+ strict_1.default.equal(c.status, "ok");
239
+ if (c.status === "ok")
240
+ strict_1.default.equal(c.ratio, null);
241
+ });
242
+ // ── JSON ────────────────────────────────────────────────────────────────
243
+ (0, node_test_1.test)("el JSON lleva fechas en ISO, filas homogéneas y avisa de que la ventana es una estimación", () => {
244
+ const r = (0, quota_js_2.buildQuota)({
245
+ turns: [turno("2026-10-07T10:00:00", "/a", 100), turno("2026-10-07T10:30:00", null, 100)],
246
+ firstTurnAt: new Map(), now: local(2026, 10, 7, 12), bucketOf: porCarpeta,
247
+ });
248
+ const j = (0, quota_js_2.quotaToJson)(r);
249
+ strict_1.default.equal(j.windowIsEstimate, true);
250
+ strict_1.default.deepEqual(Object.keys(j.agents[0].week.projects[0]).sort(), ["cacheRead", "cacheWrite", "folder", "input", "output", "projectId", "projectName", "share", "total"]);
251
+ strict_1.default.equal(j.agents[0].week.projects[1]["projectId"], null);
252
+ strict_1.default.deepEqual(j.excluded, []);
253
+ strict_1.default.doesNotThrow(() => JSON.parse(JSON.stringify(j)));
254
+ });
255
+ // ---------------------------------------------------------------------------
256
+ function turnoGuardado(turnId, repoPath, at, cacheRead = 100) {
257
+ return {
258
+ agent: "claude-code", turnId, sessionId: "s", at: new Date(at), model: "claude-sonnet-4-6",
259
+ repoPath, branch: "main", tokens: tok(cacheRead), producerVersion: null,
260
+ };
261
+ }
262
+ //# sourceMappingURL=quota.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota.test.js","sourceRoot":"","sources":["../../src/metrics/quota.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ import { type QuotaReport } from "./metrics/quota.js";
3
+ /**
4
+ * La cuota sobre la base: lo que comparten `estela quota` y el panel web.
5
+ * El cálculo vive en `metrics/quota.ts`; aquí solo se leen los datos.
6
+ */
7
+ export declare function loadQuotaReport(db: DatabaseSync, now?: Date): QuotaReport;
8
+ /**
9
+ * Trae los turnos de esta semana antes de calcular.
10
+ *
11
+ * Sin esto, la "ventana actual" sería la de la última vez que alguien ejecutó
12
+ * `estela import`, que puede ser de ayer. Solo se leen los transcripts desde el
13
+ * lunes, y solo se guardan turnos: `INSERT OR IGNORE` sobre el `turn_id` hace
14
+ * que repetirlo no duplique nada, y el siguiente `import` reconstruye las horas
15
+ * con lo que lee él mismo, así que no se le quita trabajo.
16
+ */
17
+ export declare function refreshQuotaTurns(db: DatabaseSync, since: Date): Promise<number>;
18
+ //# sourceMappingURL=quota.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota.d.ts","sourceRoot":"","sources":["../src/quota.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,EAA4C,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGhG;;;GAGG;AAEH,wBAAgB,eAAe,CAAC,EAAE,EAAE,YAAY,EAAE,GAAG,GAAE,IAAiB,GAAG,WAAW,CA0BrF;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAGtF"}