@saulwade/swl-ses 1.3.8 → 1.4.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.
Files changed (128) hide show
  1. package/CLAUDE.md +12 -4
  2. package/README.md +1 -1
  3. package/bin/swl-mcp-server.js +187 -187
  4. package/bin/swl-webhook-server.js +198 -0
  5. package/comandos/swl/.evolved.json +22 -22
  6. package/comandos/swl/adoptar-proyecto.md +21 -1
  7. package/comandos/swl/claudemd.md +14 -1
  8. package/comandos/swl/contribuir.md +233 -233
  9. package/comandos/swl/exportar-vault.md +108 -0
  10. package/comandos/swl/nuevo-proyecto.md +24 -2
  11. package/gateway/adapters/base.js +109 -0
  12. package/gateway/adapters/discord.js +167 -0
  13. package/gateway/adapters/email.js +221 -0
  14. package/gateway/adapters/slack.js +192 -0
  15. package/gateway/adapters/telegram.js +183 -0
  16. package/gateway/adapters/webhook.js +113 -0
  17. package/gateway/adapters/whatsapp.js +214 -0
  18. package/gateway/agent-executor.js +322 -0
  19. package/gateway/command-relay.js +271 -0
  20. package/gateway/cron/jobs.js +263 -0
  21. package/gateway/cron/scheduler.js +322 -0
  22. package/gateway/cron/store.js +335 -0
  23. package/gateway/index.js +320 -0
  24. package/gateway/lib/event-channel.js +191 -0
  25. package/gateway/session.js +131 -0
  26. package/gateway/webhook-server.js +324 -0
  27. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  28. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  29. package/habilidades/build-errors-nextjs/SKILL.md +55 -1
  30. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  31. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  32. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  33. package/habilidades/eval-framework/SKILL.md +212 -212
  34. package/habilidades/extractor-de-aprendizajes/SKILL.md +20 -10
  35. package/habilidades/harness-claude-code/SKILL.md +299 -299
  36. package/habilidades/infra-github-actions/SKILL.md +166 -166
  37. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  38. package/habilidades/manejo-errores/.evolved.json +8 -8
  39. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  40. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  41. package/habilidades/nextjs-testing/SKILL.md +89 -5
  42. package/habilidades/node-experto/SKILL.md +37 -1
  43. package/habilidades/patrones-python/SKILL.md +229 -229
  44. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  45. package/habilidades/planear-fase/SKILL.md +319 -319
  46. package/habilidades/react-experto/SKILL.md +45 -4
  47. package/habilidades/release-semver/.evolved.json +8 -8
  48. package/habilidades/tdd-workflow/SKILL.md +36 -4
  49. package/habilidades/testing-python/SKILL.md +340 -340
  50. package/hooks/claudemd-bloat-detector.js +161 -161
  51. package/hooks/inyeccion-contexto.js +8 -3
  52. package/hooks/lib/agent-routing.js +107 -107
  53. package/hooks/lib/auto-consolidator.js +335 -335
  54. package/hooks/lib/error-classifier.js +308 -308
  55. package/hooks/lib/merkle-audit.js +96 -96
  56. package/hooks/lib/provenance-tracker.js +191 -191
  57. package/hooks/lib/rate-limit-ip.js +177 -0
  58. package/hooks/lib/rate-limit-tracker.js +253 -253
  59. package/hooks/lib/resource-quota.js +122 -122
  60. package/hooks/lib/retry-jitter.js +165 -165
  61. package/hooks/lib/skill-auditor.js +588 -588
  62. package/hooks/lib/sync-status.js +228 -228
  63. package/hooks/lib/taint-tracker.js +107 -107
  64. package/hooks/lib/text-similarity.js +241 -241
  65. package/hooks/lib/toon-compressor.js +245 -245
  66. package/hooks/lib/webhook-dedup.js +184 -0
  67. package/hooks/lib/webhook-verify.js +123 -0
  68. package/hooks/proteccion-rutas.js +120 -15
  69. package/hooks/registro-turnos.js +209 -209
  70. package/hooks/sugerir-regenerar-inventario.js +170 -170
  71. package/hooks/validar-formato-post-subagente.js +140 -140
  72. package/hooks/validar-memoria-hook.js +218 -218
  73. package/instintos/prompt-appendices.yaml +57 -57
  74. package/manifiestos/agent-output-schemas.json +57 -57
  75. package/manifiestos/modulos.json +1 -0
  76. package/manifiestos/skills-lock.json +34 -34
  77. package/package.json +5 -3
  78. package/plantillas/auditor-veto-template.md +105 -105
  79. package/plantillas/github-workflows/README.md +47 -47
  80. package/plantillas/github-workflows/release-please.yml +44 -44
  81. package/plantillas/github-workflows/swl-ci.yml +107 -107
  82. package/plantillas/github-workflows/swl-security.yml +51 -51
  83. package/plugin.json +1 -1
  84. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  85. package/reglas/arreglar-al-detectar.md +147 -147
  86. package/reglas/fragmentos-compartidos.md +152 -152
  87. package/reglas/harness-claude-code.md +213 -213
  88. package/reglas/usar-context7.md +226 -226
  89. package/reglas/usar-sistema-swl.md +251 -0
  90. package/schemas/diary-entry.schema.json +80 -80
  91. package/scripts/benchmark-memoria.js +167 -167
  92. package/scripts/comandos/skills.js +251 -2
  93. package/scripts/configurar-branch-protection.js +418 -418
  94. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  95. package/scripts/field-report.js +199 -199
  96. package/scripts/generar-checklists-consolidados.js +273 -273
  97. package/scripts/generar-inventario.js +420 -420
  98. package/scripts/generar-matriz-lenguajes.js +271 -271
  99. package/scripts/lib/artefactos-python.js +43 -43
  100. package/scripts/lib/benchmark-metrics.js +160 -160
  101. package/scripts/lib/budget-enforcer.js +252 -252
  102. package/scripts/lib/configurar-ci.js +380 -380
  103. package/scripts/lib/contadores-inventario.js +217 -217
  104. package/scripts/lib/detectar-stack-detallado.js +307 -307
  105. package/scripts/lib/diary-entry.js +234 -234
  106. package/scripts/lib/eval-metrics-store.js +218 -218
  107. package/scripts/lib/eval-quality.js +171 -171
  108. package/scripts/lib/eval-schemas.js +144 -144
  109. package/scripts/lib/eval-self-correct.js +106 -106
  110. package/scripts/lib/eval-validator.js +185 -185
  111. package/scripts/lib/jaccard-similarity.js +98 -98
  112. package/scripts/lib/longmemeval-runner.js +125 -125
  113. package/scripts/lib/npm-version.js +261 -261
  114. package/scripts/lib/paquetes-conocidos.js +50 -50
  115. package/scripts/lib/prompt-builder.js +264 -264
  116. package/scripts/lib/rrf-fusion.js +175 -175
  117. package/scripts/lib/scoring-instintos.js +277 -277
  118. package/scripts/lib/semantic-search.js +252 -252
  119. package/scripts/limpiar-artefactos-python.js +131 -131
  120. package/scripts/mcp-server/README.md +128 -128
  121. package/scripts/mcp-server/handlers.js +206 -206
  122. package/scripts/migrar-csv-a-array.js +168 -168
  123. package/scripts/migrar-fase-dominio.js +201 -201
  124. package/scripts/publicar.js +511 -511
  125. package/scripts/run-eval.js +141 -141
  126. package/scripts/validar-manifest.js +195 -195
  127. package/scripts/validar-userland-vacio.js +110 -110
  128. package/scripts/verificar-release.js +110 -0
@@ -1,252 +1,252 @@
1
- 'use strict';
2
-
3
- /**
4
- * budget-enforcer.js
5
- *
6
- * Control de presupuesto con thresholds escalonados, backpressure y
7
- * idempotency keys. Patrón adaptado del BudgetManager de Shannon
8
- * (`temp/Shannon-main/go/orchestrator/internal/budget/manager.go`),
9
- * portado a Node.js zero-deps.
10
- *
11
- * Diferencias con `hooks/tracking-costos.js`:
12
- * - tracking-costos: registra consumo y emite warnings simples (80%, 100%).
13
- * - budget-enforcer: 4 niveles (warning/approval/hard/backpressure),
14
- * idempotency keys para reintentos, decisiones puras (no I/O).
15
- *
16
- * El módulo es PURO. No escribe a disco. El consumidor decide qué hacer
17
- * con el resultado (alertar, pausar, bloquear, retrasar).
18
- *
19
- * Modelo de niveles:
20
- * - PASS : por debajo de warning. Continúa sin restricción.
21
- * - WARNING : ≥ warning_pct. Emite alerta, continúa.
22
- * - APPROVAL : ≥ approval_pct. Requiere confirmación humana antes de seguir.
23
- * - BACKPRESSURE : ≥ backpressure_pct. Continúa pero con delay configurable.
24
- * - HARD_LIMIT : ≥ hard_pct. Detiene la operación; sin override.
25
- *
26
- * Defaults razonables (modificables vía opts):
27
- * warning_pct = 0.70 (70%)
28
- * approval_pct = 0.85 (85%)
29
- * backpressure_pct = 0.90 (90%)
30
- * hard_pct = 1.00 (100%)
31
- * backpressure_delay_ms = 2000
32
- *
33
- * Idempotency:
34
- * recordUsage acepta una idempotency_key. Si ya se contabilizó esa key,
35
- * devuelve el estado SIN re-incrementar. Permite reintentos seguros.
36
- *
37
- * @module scripts/lib/budget-enforcer
38
- */
39
-
40
- // ── constantes ────────────────────────────────────────────────────────────────
41
-
42
- const DEFAULTS = Object.freeze({
43
- warning_pct: 0.70,
44
- approval_pct: 0.85,
45
- backpressure_pct: 0.90,
46
- hard_pct: 1.00,
47
- backpressure_delay_ms: 2000,
48
- });
49
-
50
- const LEVEL = Object.freeze({
51
- PASS: 'pass',
52
- WARNING: 'warning',
53
- APPROVAL: 'approval',
54
- BACKPRESSURE: 'backpressure',
55
- HARD_LIMIT: 'hard_limit',
56
- });
57
-
58
- // ── helpers ───────────────────────────────────────────────────────────────────
59
-
60
- function clamp(n, min, max) {
61
- if (Number.isNaN(n)) return min;
62
- return Math.max(min, Math.min(max, n));
63
- }
64
-
65
- function mergeOpts(opts) {
66
- return { ...DEFAULTS, ...(opts || {}) };
67
- }
68
-
69
- // ── API pública ───────────────────────────────────────────────────────────────
70
-
71
- /**
72
- * Crea un nuevo estado de presupuesto.
73
- *
74
- * @param {object} params
75
- * @param {number} params.maxUsd - Presupuesto máximo en USD
76
- * @param {number} params.maxTokens - Presupuesto máximo en tokens
77
- * @returns {object} estado inicial
78
- */
79
- function createBudget({ maxUsd = 10.0, maxTokens = 500_000 } = {}) {
80
- return {
81
- maxUsd,
82
- maxTokens,
83
- usedUsd: 0,
84
- usedTokens: 0,
85
- requestCount: 0,
86
- idempotency: {}, // key → { usd, tokens, recordedAt }
87
- createdAt: new Date().toISOString(),
88
- };
89
- }
90
-
91
- /**
92
- * Registra consumo de presupuesto. Pure: devuelve nuevo estado.
93
- *
94
- * @param {object} budget - estado actual
95
- * @param {object} usage - consumo a registrar
96
- * @param {number} usage.usd
97
- * @param {number} usage.tokens
98
- * @param {string} [usage.idempotencyKey] - si presente, evita doble-conteo
99
- * @returns {{ budget: object, deduplicated: boolean }}
100
- */
101
- function recordUsage(budget, usage) {
102
- const next = { ...budget, idempotency: { ...budget.idempotency } };
103
- const usd = Number(usage.usd) || 0;
104
- const tokens = Number(usage.tokens) || 0;
105
- const key = usage.idempotencyKey;
106
-
107
- if (key && next.idempotency[key]) {
108
- // Ya contabilizado, retornar sin cambios
109
- return { budget: next, deduplicated: true };
110
- }
111
-
112
- next.usedUsd = next.usedUsd + usd;
113
- next.usedTokens = next.usedTokens + tokens;
114
- next.requestCount = next.requestCount + 1;
115
-
116
- if (key) {
117
- next.idempotency[key] = {
118
- usd,
119
- tokens,
120
- recordedAt: new Date().toISOString(),
121
- };
122
- }
123
-
124
- return { budget: next, deduplicated: false };
125
- }
126
-
127
- /**
128
- * Calcula la fracción del presupuesto consumida.
129
- * Devuelve max(usd_pct, tokens_pct) — el más cercano al límite manda.
130
- *
131
- * @param {object} budget
132
- * @returns {number} en [0, ∞) — sí puede pasar 1.0 si hay overshoot
133
- */
134
- function consumptionRatio(budget) {
135
- const usdPct = budget.maxUsd > 0 ? budget.usedUsd / budget.maxUsd : 0;
136
- const tokensPct = budget.maxTokens > 0 ? budget.usedTokens / budget.maxTokens : 0;
137
- return Math.max(usdPct, tokensPct);
138
- }
139
-
140
- /**
141
- * Determina el nivel de threshold actual.
142
- *
143
- * @param {object} budget
144
- * @param {object} [opts] - thresholds custom
145
- * @returns {string} LEVEL.*
146
- */
147
- function currentLevel(budget, opts) {
148
- const cfg = mergeOpts(opts);
149
- const ratio = consumptionRatio(budget);
150
-
151
- if (ratio >= cfg.hard_pct) return LEVEL.HARD_LIMIT;
152
- if (ratio >= cfg.backpressure_pct) return LEVEL.BACKPRESSURE;
153
- if (ratio >= cfg.approval_pct) return LEVEL.APPROVAL;
154
- if (ratio >= cfg.warning_pct) return LEVEL.WARNING;
155
- return LEVEL.PASS;
156
- }
157
-
158
- /**
159
- * Decisión combinada: nivel + acción recomendada + delay si aplica.
160
- *
161
- * @param {object} budget
162
- * @param {object} [opts]
163
- * @returns {object} {
164
- * level, ratio, allow, requiresApproval, delayMs, reason
165
- * }
166
- */
167
- function decide(budget, opts) {
168
- const cfg = mergeOpts(opts);
169
- const ratio = consumptionRatio(budget);
170
- const level = currentLevel(budget, cfg);
171
-
172
- const decision = {
173
- level,
174
- ratio: Math.round(ratio * 1000) / 1000,
175
- allow: true,
176
- requiresApproval: false,
177
- delayMs: 0,
178
- reason: null,
179
- };
180
-
181
- switch (level) {
182
- case LEVEL.HARD_LIMIT:
183
- decision.allow = false;
184
- decision.reason = `Presupuesto agotado (${(ratio * 100).toFixed(1)}% del máximo). Hard limit alcanzado.`;
185
- break;
186
- case LEVEL.BACKPRESSURE:
187
- decision.delayMs = cfg.backpressure_delay_ms;
188
- decision.reason = `Cerca del límite (${(ratio * 100).toFixed(1)}%). Aplicando backpressure de ${cfg.backpressure_delay_ms}ms.`;
189
- break;
190
- case LEVEL.APPROVAL:
191
- decision.requiresApproval = true;
192
- decision.reason = `Presupuesto al ${(ratio * 100).toFixed(1)}%. Requiere confirmación humana antes de continuar.`;
193
- break;
194
- case LEVEL.WARNING:
195
- decision.reason = `Aviso: presupuesto al ${(ratio * 100).toFixed(1)}% del máximo.`;
196
- break;
197
- case LEVEL.PASS:
198
- default:
199
- break;
200
- }
201
-
202
- return decision;
203
- }
204
-
205
- /**
206
- * Estima si una operación futura cabe en el presupuesto.
207
- * Útil para decidir antes de invocar un agente costoso.
208
- *
209
- * @param {object} budget
210
- * @param {object} estimate
211
- * @param {number} estimate.usd
212
- * @param {number} estimate.tokens
213
- * @param {object} [opts]
214
- * @returns {object} mismo formato que decide() — proyecta el estado post-operación
215
- */
216
- function projectDecision(budget, estimate, opts) {
217
- const projected = {
218
- ...budget,
219
- usedUsd: budget.usedUsd + (Number(estimate.usd) || 0),
220
- usedTokens: budget.usedTokens + (Number(estimate.tokens) || 0),
221
- };
222
- return decide(projected, opts);
223
- }
224
-
225
- /**
226
- * Resetea el contador de uso manteniendo límites e idempotency cache.
227
- * Útil para nuevos ciclos de facturación.
228
- */
229
- function resetUsage(budget) {
230
- return {
231
- ...budget,
232
- usedUsd: 0,
233
- usedTokens: 0,
234
- requestCount: 0,
235
- idempotency: {},
236
- resetAt: new Date().toISOString(),
237
- };
238
- }
239
-
240
- // ── exports ───────────────────────────────────────────────────────────────────
241
-
242
- module.exports = {
243
- createBudget,
244
- recordUsage,
245
- consumptionRatio,
246
- currentLevel,
247
- decide,
248
- projectDecision,
249
- resetUsage,
250
- LEVEL,
251
- DEFAULTS,
252
- };
1
+ 'use strict';
2
+
3
+ /**
4
+ * budget-enforcer.js
5
+ *
6
+ * Control de presupuesto con thresholds escalonados, backpressure y
7
+ * idempotency keys. Patrón adaptado del BudgetManager de Shannon
8
+ * (`temp/Shannon-main/go/orchestrator/internal/budget/manager.go`),
9
+ * portado a Node.js zero-deps.
10
+ *
11
+ * Diferencias con `hooks/tracking-costos.js`:
12
+ * - tracking-costos: registra consumo y emite warnings simples (80%, 100%).
13
+ * - budget-enforcer: 4 niveles (warning/approval/hard/backpressure),
14
+ * idempotency keys para reintentos, decisiones puras (no I/O).
15
+ *
16
+ * El módulo es PURO. No escribe a disco. El consumidor decide qué hacer
17
+ * con el resultado (alertar, pausar, bloquear, retrasar).
18
+ *
19
+ * Modelo de niveles:
20
+ * - PASS : por debajo de warning. Continúa sin restricción.
21
+ * - WARNING : ≥ warning_pct. Emite alerta, continúa.
22
+ * - APPROVAL : ≥ approval_pct. Requiere confirmación humana antes de seguir.
23
+ * - BACKPRESSURE : ≥ backpressure_pct. Continúa pero con delay configurable.
24
+ * - HARD_LIMIT : ≥ hard_pct. Detiene la operación; sin override.
25
+ *
26
+ * Defaults razonables (modificables vía opts):
27
+ * warning_pct = 0.70 (70%)
28
+ * approval_pct = 0.85 (85%)
29
+ * backpressure_pct = 0.90 (90%)
30
+ * hard_pct = 1.00 (100%)
31
+ * backpressure_delay_ms = 2000
32
+ *
33
+ * Idempotency:
34
+ * recordUsage acepta una idempotency_key. Si ya se contabilizó esa key,
35
+ * devuelve el estado SIN re-incrementar. Permite reintentos seguros.
36
+ *
37
+ * @module scripts/lib/budget-enforcer
38
+ */
39
+
40
+ // ── constantes ────────────────────────────────────────────────────────────────
41
+
42
+ const DEFAULTS = Object.freeze({
43
+ warning_pct: 0.70,
44
+ approval_pct: 0.85,
45
+ backpressure_pct: 0.90,
46
+ hard_pct: 1.00,
47
+ backpressure_delay_ms: 2000,
48
+ });
49
+
50
+ const LEVEL = Object.freeze({
51
+ PASS: 'pass',
52
+ WARNING: 'warning',
53
+ APPROVAL: 'approval',
54
+ BACKPRESSURE: 'backpressure',
55
+ HARD_LIMIT: 'hard_limit',
56
+ });
57
+
58
+ // ── helpers ───────────────────────────────────────────────────────────────────
59
+
60
+ function clamp(n, min, max) {
61
+ if (Number.isNaN(n)) return min;
62
+ return Math.max(min, Math.min(max, n));
63
+ }
64
+
65
+ function mergeOpts(opts) {
66
+ return { ...DEFAULTS, ...(opts || {}) };
67
+ }
68
+
69
+ // ── API pública ───────────────────────────────────────────────────────────────
70
+
71
+ /**
72
+ * Crea un nuevo estado de presupuesto.
73
+ *
74
+ * @param {object} params
75
+ * @param {number} params.maxUsd - Presupuesto máximo en USD
76
+ * @param {number} params.maxTokens - Presupuesto máximo en tokens
77
+ * @returns {object} estado inicial
78
+ */
79
+ function createBudget({ maxUsd = 10.0, maxTokens = 500_000 } = {}) {
80
+ return {
81
+ maxUsd,
82
+ maxTokens,
83
+ usedUsd: 0,
84
+ usedTokens: 0,
85
+ requestCount: 0,
86
+ idempotency: {}, // key → { usd, tokens, recordedAt }
87
+ createdAt: new Date().toISOString(),
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Registra consumo de presupuesto. Pure: devuelve nuevo estado.
93
+ *
94
+ * @param {object} budget - estado actual
95
+ * @param {object} usage - consumo a registrar
96
+ * @param {number} usage.usd
97
+ * @param {number} usage.tokens
98
+ * @param {string} [usage.idempotencyKey] - si presente, evita doble-conteo
99
+ * @returns {{ budget: object, deduplicated: boolean }}
100
+ */
101
+ function recordUsage(budget, usage) {
102
+ const next = { ...budget, idempotency: { ...budget.idempotency } };
103
+ const usd = Number(usage.usd) || 0;
104
+ const tokens = Number(usage.tokens) || 0;
105
+ const key = usage.idempotencyKey;
106
+
107
+ if (key && next.idempotency[key]) {
108
+ // Ya contabilizado, retornar sin cambios
109
+ return { budget: next, deduplicated: true };
110
+ }
111
+
112
+ next.usedUsd = next.usedUsd + usd;
113
+ next.usedTokens = next.usedTokens + tokens;
114
+ next.requestCount = next.requestCount + 1;
115
+
116
+ if (key) {
117
+ next.idempotency[key] = {
118
+ usd,
119
+ tokens,
120
+ recordedAt: new Date().toISOString(),
121
+ };
122
+ }
123
+
124
+ return { budget: next, deduplicated: false };
125
+ }
126
+
127
+ /**
128
+ * Calcula la fracción del presupuesto consumida.
129
+ * Devuelve max(usd_pct, tokens_pct) — el más cercano al límite manda.
130
+ *
131
+ * @param {object} budget
132
+ * @returns {number} en [0, ∞) — sí puede pasar 1.0 si hay overshoot
133
+ */
134
+ function consumptionRatio(budget) {
135
+ const usdPct = budget.maxUsd > 0 ? budget.usedUsd / budget.maxUsd : 0;
136
+ const tokensPct = budget.maxTokens > 0 ? budget.usedTokens / budget.maxTokens : 0;
137
+ return Math.max(usdPct, tokensPct);
138
+ }
139
+
140
+ /**
141
+ * Determina el nivel de threshold actual.
142
+ *
143
+ * @param {object} budget
144
+ * @param {object} [opts] - thresholds custom
145
+ * @returns {string} LEVEL.*
146
+ */
147
+ function currentLevel(budget, opts) {
148
+ const cfg = mergeOpts(opts);
149
+ const ratio = consumptionRatio(budget);
150
+
151
+ if (ratio >= cfg.hard_pct) return LEVEL.HARD_LIMIT;
152
+ if (ratio >= cfg.backpressure_pct) return LEVEL.BACKPRESSURE;
153
+ if (ratio >= cfg.approval_pct) return LEVEL.APPROVAL;
154
+ if (ratio >= cfg.warning_pct) return LEVEL.WARNING;
155
+ return LEVEL.PASS;
156
+ }
157
+
158
+ /**
159
+ * Decisión combinada: nivel + acción recomendada + delay si aplica.
160
+ *
161
+ * @param {object} budget
162
+ * @param {object} [opts]
163
+ * @returns {object} {
164
+ * level, ratio, allow, requiresApproval, delayMs, reason
165
+ * }
166
+ */
167
+ function decide(budget, opts) {
168
+ const cfg = mergeOpts(opts);
169
+ const ratio = consumptionRatio(budget);
170
+ const level = currentLevel(budget, cfg);
171
+
172
+ const decision = {
173
+ level,
174
+ ratio: Math.round(ratio * 1000) / 1000,
175
+ allow: true,
176
+ requiresApproval: false,
177
+ delayMs: 0,
178
+ reason: null,
179
+ };
180
+
181
+ switch (level) {
182
+ case LEVEL.HARD_LIMIT:
183
+ decision.allow = false;
184
+ decision.reason = `Presupuesto agotado (${(ratio * 100).toFixed(1)}% del máximo). Hard limit alcanzado.`;
185
+ break;
186
+ case LEVEL.BACKPRESSURE:
187
+ decision.delayMs = cfg.backpressure_delay_ms;
188
+ decision.reason = `Cerca del límite (${(ratio * 100).toFixed(1)}%). Aplicando backpressure de ${cfg.backpressure_delay_ms}ms.`;
189
+ break;
190
+ case LEVEL.APPROVAL:
191
+ decision.requiresApproval = true;
192
+ decision.reason = `Presupuesto al ${(ratio * 100).toFixed(1)}%. Requiere confirmación humana antes de continuar.`;
193
+ break;
194
+ case LEVEL.WARNING:
195
+ decision.reason = `Aviso: presupuesto al ${(ratio * 100).toFixed(1)}% del máximo.`;
196
+ break;
197
+ case LEVEL.PASS:
198
+ default:
199
+ break;
200
+ }
201
+
202
+ return decision;
203
+ }
204
+
205
+ /**
206
+ * Estima si una operación futura cabe en el presupuesto.
207
+ * Útil para decidir antes de invocar un agente costoso.
208
+ *
209
+ * @param {object} budget
210
+ * @param {object} estimate
211
+ * @param {number} estimate.usd
212
+ * @param {number} estimate.tokens
213
+ * @param {object} [opts]
214
+ * @returns {object} mismo formato que decide() — proyecta el estado post-operación
215
+ */
216
+ function projectDecision(budget, estimate, opts) {
217
+ const projected = {
218
+ ...budget,
219
+ usedUsd: budget.usedUsd + (Number(estimate.usd) || 0),
220
+ usedTokens: budget.usedTokens + (Number(estimate.tokens) || 0),
221
+ };
222
+ return decide(projected, opts);
223
+ }
224
+
225
+ /**
226
+ * Resetea el contador de uso manteniendo límites e idempotency cache.
227
+ * Útil para nuevos ciclos de facturación.
228
+ */
229
+ function resetUsage(budget) {
230
+ return {
231
+ ...budget,
232
+ usedUsd: 0,
233
+ usedTokens: 0,
234
+ requestCount: 0,
235
+ idempotency: {},
236
+ resetAt: new Date().toISOString(),
237
+ };
238
+ }
239
+
240
+ // ── exports ───────────────────────────────────────────────────────────────────
241
+
242
+ module.exports = {
243
+ createBudget,
244
+ recordUsage,
245
+ consumptionRatio,
246
+ currentLevel,
247
+ decide,
248
+ projectDecision,
249
+ resetUsage,
250
+ LEVEL,
251
+ DEFAULTS,
252
+ };