llm-entropy-filter 1.1.0 → 1.2.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/dist/index.cjs CHANGED
@@ -42,18 +42,48 @@ function analyzeEntropy(text) {
42
42
  const t = raw.toLowerCase();
43
43
  const flags = [];
44
44
  let score = 0;
45
- if (/\b(ahora|ya|urgente|última|hoy|inmediato)\b/.test(t)) {
45
+ if (/\b(ahora|ya|urgente|urgencia|hoy|inmediato|inmediatamente|últim[oa]s?|solo\s+hoy|ap[uú]rate|rápido|de\s+inmediato)\b/.test(
46
+ t
47
+ )) {
46
48
  flags.push("urgency");
47
49
  score += 0.2;
48
50
  }
49
- if (/\b(compra|oferta|promo|descuento|gratis|clic|click|off)\b/.test(t)) {
51
+ if (/\b(compra|oferta|promo|promoci[oó]n|descuento|rebaja|gratis|free|premio|prize|winner|gana|claim|reward|clic|click)\b/.test(
52
+ t
53
+ )) {
50
54
  flags.push("spam_sales");
51
55
  score += 0.25;
52
56
  }
57
+ const wantsSendVerb = /\b(envi(a|á)me|env[ií]ame|m(a|á)ndame|p(a|á)same|dame|compart(e|a|as)|reenv[ií]a(me)?)\b/.test(
58
+ t
59
+ );
60
+ const mentionsCode = /\b(c[oó]digo|codigo|otp|2fa|token|pin|clave)\b/.test(t);
61
+ const mentionsVerify = /\b(verificaci[oó]n|verificar|confirmar|validar)\b/.test(t);
62
+ const mentionsAccount = /\b(cuenta|account)\b/.test(t);
63
+ const mentionsSms = /\b(sms|por\s+sms)\b/.test(t);
64
+ if (wantsSendVerb && mentionsCode && (mentionsVerify || mentionsAccount || mentionsSms)) {
65
+ flags.push("phishing_2fa_code");
66
+ score += 0.55;
67
+ }
68
+ if (/\bverify\b/.test(t) && /\baccount\b/.test(t) && /\bclick\b/.test(t) && /\b(closed|close|suspend|suspended|disable|disabled|locked)\b/.test(t)) {
69
+ flags.push("phishing_verify_threat_en");
70
+ score += 0.35;
71
+ }
72
+ if (/\b(te\s+deposito|te\s+dep[oó]sito|te\s+transfiero|te\s+transferir[eé]|transferencia|dep[oó]sito)\b/.test(
73
+ t
74
+ ) && /\b(tarjeta|cuenta|clabe|iban|swift|n[uú]mero\s+de\s+tarjeta|numero\s+de\s+tarjeta)\b/.test(t)) {
75
+ flags.push("fraud_payment_request");
76
+ score += 0.35;
77
+ }
78
+ if (/\b(gana(r)?\s+dinero|ingresos|dinero\s+extra)\b/.test(t) && /\b(desde\s+casa|en\s+casa|home)\b/.test(t) && /\b(sin\s+esfuerzo|f[aá]cil|r[aá]pido|easy|fast)\b/.test(t)) {
79
+ flags.push("scam_wfh");
80
+ score += 0.3;
81
+ }
53
82
  const moneyHits = countRegex(raw, /\$+/g);
54
- if (moneyHits > 0) {
83
+ const pctHits = countRegex(raw, /%/g);
84
+ if (moneyHits > 0 || pctHits > 0 || /\b(usd|mxn|eur)\b/i.test(raw)) {
55
85
  flags.push("money_signal");
56
- score += Math.min(0.2, moneyHits * 0.05);
86
+ score += Math.min(0.25, moneyHits * 0.05 + pctHits * 0.05 + 0.1);
57
87
  }
58
88
  const exclam = countRegex(raw, /!/g);
59
89
  const capsRatio = (() => {
@@ -66,39 +96,24 @@ function analyzeEntropy(text) {
66
96
  flags.push("shouting");
67
97
  score += 0.2;
68
98
  }
69
- if (/\b(si de verdad|si me quisieras|es tu culpa|no tienes opción|me debes)\b/.test(t)) {
99
+ if (/\b(si\s+de\s+verdad|si\s+me\s+quisieras|es\s+tu\s+culpa|no\s+tienes\s+opci[oó]n|me\s+debes|hazlo\s+o\s+si\s+no|si\s+no\s+lo\s+haces)\b/.test(
100
+ t
101
+ )) {
70
102
  flags.push("emotional_manipulation");
71
103
  score += 0.35;
72
104
  }
73
- if (/\b(todos lo saben|lo esconden|la verdad oculta|ellos no quieren|simulación)\b/.test(t)) {
105
+ if (/\b(todos\s+lo\s+saben|lo\s+esconden|la\s+verdad\s+oculta|ellos\s+no\s+quieren|simulaci[oó]n)\b/.test(
106
+ t
107
+ )) {
74
108
  flags.push("conspiracy_vague");
75
109
  score += 0.2;
76
110
  }
77
- if (/\b(la cultura lo prueba|es obvio|todo mundo sabe|se sabe|está claro)\b/.test(t)) {
111
+ if (/\b(es\s+obvio|todo\s+mundo\s+sabe|se\s+sabe|est[aá]\s+claro|la\s+cultura\s+lo\s+prueba)\b/.test(
112
+ t
113
+ )) {
78
114
  flags.push("weak_evidence");
79
115
  score += 0.2;
80
116
  }
81
- if (/\b(ellos|la élite|los de arriba)\b/.test(t) && /\b(esconden|ocultan|tapan)\b/.test(t)) {
82
- flags.push("hidden_actor");
83
- score += 0.15;
84
- }
85
- if (/\b(física cuántica|cuantica|cuántico|quantum)\b/.test(t)) {
86
- flags.push("pseudo_science_quantum");
87
- score += 0.2;
88
- }
89
- const manifestHits = countRegex(t, /\b(manifestar|manifestación|decretar|decreto|vibración|vibracion|frecuencia|energía|energia|ley de la atracción|universo me lo dará)\b/g) + countRegex(t, /\b(realine(a|ar)\b.*\bátom|\bátom|\batomos\b)/g);
90
- if (manifestHits > 0) {
91
- flags.push("magic_manifesting");
92
- score += Math.min(0.35, 0.15 + manifestHits * 0.06);
93
- }
94
- if (/\b(no hay una verdad objetiva|no existe la verdad objetiva|tu verdad|mi verdad|la verdad es relativa|lo que importa es lo que sientes)\b/.test(t)) {
95
- flags.push("truth_relativism");
96
- score += 0.35;
97
- }
98
- if (/\b(deben|debe)\b.*\b(obligatoriamente|por lo tanto|por ende)\b/.test(t) || /\b(la materia)\b.*\b(se subordina|obedece)\b/.test(t)) {
99
- flags.push("broken_causality");
100
- score += 0.2;
101
- }
102
117
  score = clamp01(score);
103
118
  return { score, flags };
104
119
  }
@@ -189,39 +204,56 @@ function mergeFlags(base, extra) {
189
204
  }
190
205
  return out;
191
206
  }
192
- function englishSpamBooster(rawText) {
193
- const t = (rawText || "").toLowerCase();
194
- const patterns = [
195
- { re: /\bfree\b/g, score: 0.08, flag: "spam_kw_free" },
196
- { re: /\bwinner\b|\bwon\b|\bcongratulations\b/g, score: 0.1, flag: "spam_kw_winner" },
197
- { re: /\bclaim\b|\bredeem\b/g, score: 0.08, flag: "spam_kw_claim" },
198
- { re: /\bclick\b|\bclick here\b|\bopen link\b|\btap here\b/g, score: 0.1, flag: "spam_kw_click" },
199
- { re: /\blimited time\b|\bact now\b|\bfinal notice\b|\bbefore midnight\b/g, score: 0.1, flag: "spam_kw_urgency_en" },
200
- { re: /\bverify\b|\bconfirm\b|\baccount\b.*\b(suspended|locked)\b/g, score: 0.12, flag: "spam_kw_verify" },
201
- { re: /\bprize\b|\bgift card\b|\bgiftcard\b|\bvoucher\b|\biphone\b|\bsurvey\b/g, score: 0.1, flag: "spam_kw_prize" },
202
- { re: /\bloan\b|\bpre-?approved\b|\bno credit check\b/g, score: 0.12, flag: "spam_kw_loan" },
203
- { re: /\bcrypto\b|\bairdrop\b|\bwallet\b|\bseed phrase\b/g, score: 0.1, flag: "spam_kw_crypto" },
204
- { re: /\bdelivery failed\b|\breschedule\b|\bpackage\b|\bcourier\b/g, score: 0.1, flag: "spam_kw_delivery" },
205
- { re: /\btax refund\b|\bunpaid\b|\brefund\b|\bchargeback\b/g, score: 0.1, flag: "spam_kw_refund" }
207
+ function englishSpamBooster(text) {
208
+ const t = (text || "").toLowerCase();
209
+ const hits = [];
210
+ const kw = [
211
+ ["free", "spam_kw_free"],
212
+ ["winner", "spam_kw_winner"],
213
+ ["claim", "spam_kw_claim"],
214
+ ["click", "spam_kw_click"],
215
+ ["verify", "spam_kw_verify"],
216
+ ["prize", "spam_kw_prize"],
217
+ ["urgent", "spam_kw_urgency_en"],
218
+ ["limited time", "spam_kw_urgency_en"],
219
+ ["today only", "spam_kw_urgency_en"]
206
220
  ];
207
- let addScore = 0;
208
- const addFlags = [];
209
- let hitCount = 0;
210
- for (const p of patterns) {
211
- const m = t.match(p.re);
212
- if (m && m.length > 0) {
213
- hitCount += m.length;
214
- addScore += p.score;
215
- addFlags.push(p.flag);
216
- }
221
+ for (const [s, flag] of kw) {
222
+ if (t.includes(s)) hits.push(flag);
217
223
  }
218
- addScore = Math.min(0.35, addScore);
219
- if (hitCount > 0) addFlags.push("spam_keywords_en");
220
- return { addScore, addFlags, hitCount };
224
+ if (hits.length === 0) return { hitCount: 0, addScore: 0, addFlags: [] };
225
+ const addScore = Math.min(0.25, hits.length * 0.05);
226
+ const addFlags = mergeFlags(["spam_keywords_en"], hits);
227
+ return { hitCount: hits.length, addScore, addFlags };
228
+ }
229
+ function decideAction(params) {
230
+ const {
231
+ score,
232
+ warnT,
233
+ blockT,
234
+ strongSpam,
235
+ intention = "",
236
+ flags,
237
+ policy = {}
238
+ } = params;
239
+ const blockIntentions = new Set(policy.block_intentions ?? []);
240
+ const warnIntentions = new Set(policy.warn_intentions ?? []);
241
+ const blockFlags = new Set(policy.block_flags ?? []);
242
+ const warnFlags = new Set(policy.warn_flags ?? []);
243
+ if (blockIntentions.has(intention)) return "BLOCK";
244
+ if (flags.some((f) => blockFlags.has(f))) return "BLOCK";
245
+ if (warnIntentions.has(intention)) return "WARN";
246
+ if (flags.some((f) => warnFlags.has(f))) return "WARN";
247
+ const strongSpamBlock = policy.strong_spam_block ?? true;
248
+ if (strongSpamBlock && strongSpam) return "BLOCK";
249
+ if (score >= blockT) return "BLOCK";
250
+ if (score >= warnT) return "WARN";
251
+ return "ALLOW";
221
252
  }
222
253
  function gateLLM(text, config = {}) {
223
- const warnT = config.warnThreshold ?? 0.25;
224
- const blockT = config.blockThreshold ?? 0.6;
254
+ const ruleset = config.ruleset;
255
+ const warnT = ruleset?.thresholds?.warn ?? config.warnThreshold ?? 0.25;
256
+ const blockT = ruleset?.thresholds?.block ?? config.blockThreshold ?? 0.6;
225
257
  const r = runEntropyFilter(text);
226
258
  let score = r.entropy_analysis.score;
227
259
  let flags = [...r.entropy_analysis.flags];
@@ -230,20 +262,22 @@ function gateLLM(text, config = {}) {
230
262
  score = clamp013(score + booster.addScore);
231
263
  flags = mergeFlags(flags, booster.addFlags);
232
264
  }
233
- let intention = r.intention_evaluation.intention || "unknown";
234
- let confidence = r.intention_evaluation.confidence ?? 0;
235
- let rationale = r.intention_evaluation.rationale ?? "";
236
- if (booster.hitCount > 0 && intention === "unknown") {
237
- intention = "marketing_spam";
238
- confidence = Math.max(confidence, 0.75);
239
- rationale = (rationale ? rationale + " " : "") + "Detect\xE9 keywords t\xEDpicas de spam/phishing en ingl\xE9s.";
240
- }
241
- const hasSpam = flags.includes("spam_sales") || flags.includes("spam_keywords_en");
242
- const hasMoneySignals = flags.includes("money_signal") || flags.includes("spam_kw_prize") || flags.includes("spam_kw_loan");
265
+ const intention = r.intention_evaluation.intention || "unknown";
266
+ const confidence = r.intention_evaluation.confidence ?? 0;
267
+ const rationale = r.intention_evaluation.rationale ?? "";
268
+ const hasSpam = flags.includes("spam_sales");
269
+ const hasMoneySignals = flags.includes("money_signal") || flags.includes("money_signal_high");
243
270
  const strongSpam = hasSpam && hasMoneySignals;
244
- let action = "ALLOW";
245
- if (score > blockT || strongSpam) action = "BLOCK";
246
- else if (score >= warnT) action = "WARN";
271
+ const policy = ruleset?.policy ?? {};
272
+ const action = decideAction({
273
+ score,
274
+ warnT,
275
+ blockT,
276
+ strongSpam,
277
+ intention,
278
+ flags,
279
+ policy
280
+ });
247
281
  return {
248
282
  action,
249
283
  entropy_score: score,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/entropy.ts","../src/intention.ts","../src/wrapper.ts","../src/middleware.ts","../src/gate.ts"],"sourcesContent":["// src/index.ts\nexport { analyzeEntropy } from \"./entropy\";\nexport { evaluateIntention } from \"./intention\";\nexport { runEntropyFilter } from \"./wrapper\";\nexport { entropyMiddleware } from \"./middleware\";\n\nexport type {\n EntropyResult,\n IntentionEvaluation,\n FilterResult\n} from \"./types\";\nexport { gateLLM as gate } from \"./gate\";\nexport * from \"./gate\";\n","// src/entropy.ts\nexport type EntropyResult = {\n score: number; // 0..1\n flags: string[];\n};\n\nfunction clamp01(x: number) {\n return Math.max(0, Math.min(1, x));\n}\n\nfunction countRegex(text: string, re: RegExp) {\n const m = text.match(re);\n return m ? m.length : 0;\n}\n\nexport function analyzeEntropy(text: string): EntropyResult {\n const raw = text || \"\";\n const t = raw.toLowerCase();\n const flags: string[] = [];\n let score = 0;\n\n // 1) Urgencia / presión\n if (/\\b(ahora|ya|urgente|última|hoy|inmediato)\\b/.test(t)) {\n flags.push(\"urgency\");\n score += 0.20;\n }\n\n // 2) Spam / venta agresiva\n if (/\\b(compra|oferta|promo|descuento|gratis|clic|click|off)\\b/.test(t)) {\n flags.push(\"spam_sales\");\n score += 0.25;\n }\n\n // 3) Señales $$$ / símbolos\n const moneyHits = countRegex(raw, /\\$+/g);\n if (moneyHits > 0) {\n flags.push(\"money_signal\");\n score += Math.min(0.20, moneyHits * 0.05);\n }\n\n // 4) Exceso de signos / gritos\n const exclam = countRegex(raw, /!/g);\n const capsRatio = (() => {\n const letters = raw.match(/[A-Za-zÁÉÍÓÚÜÑáéíóúüñ]/g) || [];\n if (letters.length === 0) return 0;\n const caps = (raw.match(/[A-ZÁÉÍÓÚÜÑ]/g) || []).length;\n return caps / letters.length;\n })();\n\n if (exclam >= 3 || capsRatio >= 0.35) {\n flags.push(\"shouting\");\n score += 0.20;\n }\n\n // 5) Manipulación / culpa / coerción\n if (/\\b(si de verdad|si me quisieras|es tu culpa|no tienes opción|me debes)\\b/.test(t)) {\n flags.push(\"emotional_manipulation\");\n score += 0.35;\n }\n\n // 6) Conspiración vaga / “todos lo saben”\n if (/\\b(todos lo saben|lo esconden|la verdad oculta|ellos no quieren|simulación)\\b/.test(t)) {\n flags.push(\"conspiracy_vague\");\n score += 0.20;\n }\n\n // 6.5) “Prueba vaga” / apelación a cultura como evidencia\n if (/\\b(la cultura lo prueba|es obvio|todo mundo sabe|se sabe|está claro)\\b/.test(t)) {\n flags.push(\"weak_evidence\");\n score += 0.20;\n }\n\n // 6.6) Totalización + agente oculto (“ellos”)\n if (/\\b(ellos|la élite|los de arriba)\\b/.test(t) && /\\b(esconden|ocultan|tapan)\\b/.test(t)) {\n flags.push(\"hidden_actor\");\n score += 0.15;\n }\n\n // ------------------------------------------------------------\n // NUEVO: Entropía por pseudo-ciencia / pensamiento mágico / relativismo\n // ------------------------------------------------------------\n\n // 7) Pseudo-ciencia \"cuántica\" usada como licencia mágica\n if (/\\b(física cuántica|cuantica|cuántico|quantum)\\b/.test(t)) {\n flags.push(\"pseudo_science_quantum\");\n score += 0.20;\n }\n\n // 8) Manifestación mágica / decretos / vibración / energía como causalidad\n const manifestHits =\n countRegex(t, /\\b(manifestar|manifestación|decretar|decreto|vibración|vibracion|frecuencia|energía|energia|ley de la atracción|universo me lo dará)\\b/g) +\n countRegex(t, /\\b(realine(a|ar)\\b.*\\bátom|\\bátom|\\batomos\\b)/g);\n\n if (manifestHits > 0) {\n flags.push(\"magic_manifesting\");\n score += Math.min(0.35, 0.15 + manifestHits * 0.06);\n }\n\n // 9) Relativismo de la verdad / negación explícita de verdad objetiva\n if (/\\b(no hay una verdad objetiva|no existe la verdad objetiva|tu verdad|mi verdad|la verdad es relativa|lo que importa es lo que sientes)\\b/.test(t)) {\n flags.push(\"truth_relativism\");\n score += 0.35;\n }\n\n // 10) Causalidad rota / obligación metafísica (\"debe\" porque lo deseo)\n if (/\\b(deben|debe)\\b.*\\b(obligatoriamente|por lo tanto|por ende)\\b/.test(t) || /\\b(la materia)\\b.*\\b(se subordina|obedece)\\b/.test(t)) {\n flags.push(\"broken_causality\");\n score += 0.20;\n }\n\n // Normaliza: score final 0..1\n score = clamp01(score);\n\n return { score, flags };\n}\n","// src/intention.ts\nimport type { IntentionEvaluation, IntentionType } from \"./types\";\n\nfunction clamp01(x: number) {\n return Math.max(0, Math.min(1, x));\n}\n\nexport function evaluateIntention(text: string): IntentionEvaluation {\n const raw = text || \"\";\n const t = raw.toLowerCase();\n\n // Heurísticas rápidas (MVP)\n const isHelp =\n /\\b(ayuda|ayúdame|explica|resume|resumir|cómo|como|puedes|podrías|por favor)\\b/.test(\n t\n );\n\n const isSpam =\n /\\b(compra|oferta|promo|descuento|gratis|haz clic|click|off|90%|% off)\\b/.test(\n t\n ) || (raw.match(/\\$+/g) || []).length > 0;\n\n const isManip =\n /\\b(si de verdad|si me quisieras|es tu culpa|no tienes opción|me debes)\\b/.test(\n t\n );\n\n const isConsp =\n /\\b(simulación|todos lo saben|lo esconden|verdad oculta|ellos no quieren)\\b/.test(\n t\n );\n\n // NUEVO: pseudo-ciencia / pensamiento mágico / relativismo (desinformación)\n const isMisinformation =\n /\\b(física cuántica|cuantica|cuántica|cuántico|quantum)\\b/.test(t) ||\n /\\b(manifestar|manifestación|decretar|decreto|vibración|vibracion|frecuencia|energía|energia|ley de la atracción)\\b/.test(t) ||\n /\\b(no hay una verdad objetiva|no existe la verdad objetiva|tu verdad|mi verdad|la verdad es relativa)\\b/.test(t) ||\n /\\b(la materia)\\b.*\\b(se subordina|obedece)\\b/.test(t);\n\n let intention: IntentionType = \"unknown\";\n let confidence = 0.0;\n let rationale = \"\";\n\n if (isSpam) {\n intention = \"marketing_spam\";\n confidence = 0.85;\n rationale = \"Detecté señales de venta agresiva/urgencia/dinero.\";\n } else if (isManip) {\n intention = \"manipulation\";\n confidence = 0.85;\n rationale = \"Detecté coerción/culpa/chantaje emocional.\";\n } else if (isConsp) {\n intention = \"conspiracy\";\n confidence = 0.75;\n rationale = \"Detecté marco conspirativo vago ('lo esconden', 'todos lo saben').\";\n } else if (isMisinformation) {\n intention = \"misinformation\";\n confidence = 0.85;\n rationale =\n \"Detecté patrón de pseudo-ciencia/pensamiento mágico/relativismo de la verdad (alta probabilidad de desinformación).\";\n } else if (isHelp) {\n intention = \"request_help\";\n confidence = 0.7;\n rationale = \"Parece una petición legítima de ayuda/explicación.\";\n }\n\n return { intention, confidence: clamp01(confidence), rationale };\n}\n","// src/wrapper.ts\nimport { analyzeEntropy } from \"./entropy\";\nimport { evaluateIntention } from \"./intention\";\nimport type { FilterResult, IntentionEvaluation } from \"./types\";\n\nexport function runEntropyFilter(text: string): FilterResult {\n const entropy_analysis = analyzeEntropy(text);\n let intention_evaluation: IntentionEvaluation = evaluateIntention(text);\n\n // Corrección: si el texto trae señales fuertes de entropía epistemológica,\n // no lo clasifiques como \"request_help\" solo por ser pregunta larga.\n const hardFlags = new Set(entropy_analysis.flags);\n const epistemicEntropy =\n hardFlags.has(\"truth_relativism\") ||\n hardFlags.has(\"magic_manifesting\") ||\n hardFlags.has(\"pseudo_science_quantum\") ||\n hardFlags.has(\"broken_causality\");\n\n if (epistemicEntropy) {\n intention_evaluation = {\n intention: \"misinformation\",\n confidence: Math.max(intention_evaluation.confidence ?? 0.7, 0.8),\n rationale:\n \"Detecté patrón de pseudo-ciencia/pensamiento mágico/relativismo de la verdad; alta probabilidad de desinformación o argumento sin anclaje causal.\"\n };\n }\n\n return { entropy_analysis, intention_evaluation };\n}\n","// src/middleware.ts\nimport { runEntropyFilter } from \"./wrapper\";\nimport type { FilterResult } from \"./types\";\n\n/**\n * Middleware agnóstico (NO depende de express types).\n * Compatible con Express / Next API routes / cualquier framework estilo req-res-next.\n *\n * Espera: req.body.text (string)\n * Escribe: req.entropy = FilterResult\n */\nexport function entropyMiddleware(req: any, _res: any, next: any) {\n const text = req?.body?.text;\n\n const result: FilterResult =\n typeof text === \"string\" ? runEntropyFilter(text) : runEntropyFilter(\"\");\n\n req.entropy = result;\n next?.();\n}\n\n/**\n * Tipo auxiliar opcional (sin forzar dependencias).\n * Útil si quieres tipar tu req en tu app.\n */\nexport type EntropyAugmentedRequest = {\n body?: { text?: unknown };\n entropy?: FilterResult;\n};\n","// src/gate.ts\nimport { runEntropyFilter } from \"./wrapper\";\n\nexport type GateAction = \"ALLOW\" | \"WARN\" | \"BLOCK\";\n\nexport type GateResult = {\n action: GateAction;\n entropy_score: number;\n flags: string[];\n intention: string;\n confidence: number;\n rationale: string;\n};\n\nexport type GateConfig = {\n warnThreshold?: number; // default: 0.25\n blockThreshold?: number; // default: 0.60\n};\n\n/** Clamp 0..1 */\nfunction clamp01(x: number) {\n return Math.max(0, Math.min(1, x));\n}\n\n/** Add unique flags preserving order */\nfunction mergeFlags(base: string[], extra: string[]) {\n const set = new Set(base);\n const out = [...base];\n for (const f of extra) {\n if (!set.has(f)) {\n set.add(f);\n out.push(f);\n }\n }\n return out;\n}\n\n/**\n * Detección de spam/phishing en inglés por keywords.\n * Se implementa como “booster” de score + flags (sin tocar entropy.ts).\n */\nfunction englishSpamBooster(rawText: string): {\n addScore: number;\n addFlags: string[];\n hitCount: number;\n} {\n const t = (rawText || \"\").toLowerCase();\n\n // Patrones típicos (spam / phishing / promos agresivas)\n const patterns: Array<{ re: RegExp; score: number; flag: string }> = [\n { re: /\\bfree\\b/g, score: 0.08, flag: \"spam_kw_free\" },\n { re: /\\bwinner\\b|\\bwon\\b|\\bcongratulations\\b/g, score: 0.10, flag: \"spam_kw_winner\" },\n { re: /\\bclaim\\b|\\bredeem\\b/g, score: 0.08, flag: \"spam_kw_claim\" },\n { re: /\\bclick\\b|\\bclick here\\b|\\bopen link\\b|\\btap here\\b/g, score: 0.10, flag: \"spam_kw_click\" },\n { re: /\\blimited time\\b|\\bact now\\b|\\bfinal notice\\b|\\bbefore midnight\\b/g, score: 0.10, flag: \"spam_kw_urgency_en\" },\n { re: /\\bverify\\b|\\bconfirm\\b|\\baccount\\b.*\\b(suspended|locked)\\b/g, score: 0.12, flag: \"spam_kw_verify\" },\n { re: /\\bprize\\b|\\bgift card\\b|\\bgiftcard\\b|\\bvoucher\\b|\\biphone\\b|\\bsurvey\\b/g, score: 0.10, flag: \"spam_kw_prize\" },\n { re: /\\bloan\\b|\\bpre-?approved\\b|\\bno credit check\\b/g, score: 0.12, flag: \"spam_kw_loan\" },\n { re: /\\bcrypto\\b|\\bairdrop\\b|\\bwallet\\b|\\bseed phrase\\b/g, score: 0.10, flag: \"spam_kw_crypto\" },\n { re: /\\bdelivery failed\\b|\\breschedule\\b|\\bpackage\\b|\\bcourier\\b/g, score: 0.10, flag: \"spam_kw_delivery\" },\n { re: /\\btax refund\\b|\\bunpaid\\b|\\brefund\\b|\\bchargeback\\b/g, score: 0.10, flag: \"spam_kw_refund\" },\n ];\n\n let addScore = 0;\n const addFlags: string[] = [];\n let hitCount = 0;\n\n for (const p of patterns) {\n const m = t.match(p.re);\n if (m && m.length > 0) {\n hitCount += m.length;\n addScore += p.score; // suma “por patrón”, no por ocurrencia (controlado)\n addFlags.push(p.flag);\n }\n }\n\n // cap para no sobre-castigar\n addScore = Math.min(0.35, addScore);\n\n if (hitCount > 0) addFlags.push(\"spam_keywords_en\");\n\n return { addScore, addFlags, hitCount };\n}\n\n/**\n * Gate principal (producto): deterministic + barato.\n */\nexport function gateLLM(text: string, config: GateConfig = {}): GateResult {\n const warnT = config.warnThreshold ?? 0.25;\n const blockT = config.blockThreshold ?? 0.6;\n\n const r = runEntropyFilter(text);\n\n // base\n let score = r.entropy_analysis.score;\n let flags = [...r.entropy_analysis.flags];\n\n // booster EN\n const booster = englishSpamBooster(text);\n if (booster.hitCount > 0) {\n score = clamp01(score + booster.addScore);\n flags = mergeFlags(flags, booster.addFlags);\n }\n\n // intención base (intention.ts)\n let intention = r.intention_evaluation.intention || \"unknown\";\n let confidence = r.intention_evaluation.confidence ?? 0;\n let rationale = r.intention_evaluation.rationale ?? \"\";\n\n // si pegó booster EN y quedó unknown → marketing_spam\n if (booster.hitCount > 0 && intention === \"unknown\") {\n intention = \"marketing_spam\";\n confidence = Math.max(confidence, 0.75);\n rationale = (rationale ? rationale + \" \" : \"\") + \"Detecté keywords típicas de spam/phishing en inglés.\";\n }\n\n // reglas fuertes (para BLOCK “vendible”)\n const hasSpam =\n flags.includes(\"spam_sales\") ||\n flags.includes(\"spam_keywords_en\");\n\n const hasMoneySignals =\n flags.includes(\"money_signal\") ||\n flags.includes(\"spam_kw_prize\") ||\n flags.includes(\"spam_kw_loan\");\n\n const strongSpam = hasSpam && hasMoneySignals;\n\n let action: GateAction = \"ALLOW\";\n if (score > blockT || strongSpam) action = \"BLOCK\";\n else if (score >= warnT) action = \"WARN\";\n\n return {\n action,\n entropy_score: score,\n flags,\n intention,\n confidence: clamp01(confidence),\n rationale,\n };\n}\n\n// Alias “bonito” para tu server: gate(text)\nexport const gate = gateLLM;\n"],"mappings":"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","names":["clamp01","clamp01"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/entropy.ts","../src/intention.ts","../src/wrapper.ts","../src/middleware.ts","../src/gate.ts"],"sourcesContent":["// src/index.ts\r\nexport { analyzeEntropy } from \"./entropy\";\r\nexport { evaluateIntention } from \"./intention\";\r\nexport { runEntropyFilter } from \"./wrapper\";\r\nexport { entropyMiddleware } from \"./middleware\";\r\n\r\nexport type {\r\n EntropyResult,\r\n IntentionEvaluation,\r\n FilterResult\r\n} from \"./types\";\r\nexport { gateLLM as gate } from \"./gate\";\r\nexport * from \"./gate\";\r\n","// src/entropy.ts\r\nexport type EntropyResult = {\r\n score: number; // 0..1\r\n flags: string[];\r\n};\r\n\r\nfunction clamp01(x: number) {\r\n return Math.max(0, Math.min(1, x));\r\n}\r\n\r\nfunction countRegex(text: string, re: RegExp) {\r\n const m = text.match(re);\r\n return m ? m.length : 0;\r\n}\r\n\r\n/**\r\n * Entropy = señales lingüísticas + lógicas (determinístico, barato).\r\n * Nota: aquí SOLO generamos banderas y score. La acción (ALLOW/WARN/BLOCK)\r\n * se decide en gate.ts según thresholds/policy del ruleset.\r\n */\r\nexport function analyzeEntropy(text: string): EntropyResult {\r\n const raw = text || \"\";\r\n const t = raw.toLowerCase();\r\n const flags: string[] = [];\r\n let score = 0;\r\n\r\n // ------------------------------------------------------------\r\n // 1) Urgencia / presión temporal\r\n // ------------------------------------------------------------\r\n if (\r\n /\\b(ahora|ya|urgente|urgencia|hoy|inmediato|inmediatamente|últim[oa]s?|solo\\s+hoy|ap[uú]rate|rápido|de\\s+inmediato)\\b/.test(\r\n t\r\n )\r\n ) {\r\n flags.push(\"urgency\");\r\n score += 0.2;\r\n }\r\n\r\n // ------------------------------------------------------------\r\n // 2) Spam / venta agresiva (ES/EN básico)\r\n // ------------------------------------------------------------\r\n if (\r\n /\\b(compra|oferta|promo|promoci[oó]n|descuento|rebaja|gratis|free|premio|prize|winner|gana|claim|reward|clic|click)\\b/.test(\r\n t\r\n )\r\n ) {\r\n flags.push(\"spam_sales\");\r\n score += 0.25;\r\n }\r\n\r\n // ------------------------------------------------------------\r\n // 3) Phishing / Fraude / Scam (señales de alto riesgo)\r\n // ------------------------------------------------------------\r\n\r\n // 3A) Phishing: pedir código/OTP/token de verificación (ES/EN)\r\n // Ej: \"Envíame tu código de verificación para confirmar tu cuenta.\"\r\n const wantsSendVerb =\r\n /\\b(envi(a|á)me|env[ií]ame|m(a|á)ndame|p(a|á)same|dame|compart(e|a|as)|reenv[ií]a(me)?)\\b/.test(\r\n t\r\n );\r\n\r\n const mentionsCode =\r\n /\\b(c[oó]digo|codigo|otp|2fa|token|pin|clave)\\b/.test(t);\r\n\r\n const mentionsVerify =\r\n /\\b(verificaci[oó]n|verificar|confirmar|validar)\\b/.test(t);\r\n\r\n const mentionsAccount = /\\b(cuenta|account)\\b/.test(t);\r\n\r\n const mentionsSms = /\\b(sms|por\\s+sms)\\b/.test(t);\r\n\r\n // Regla: verbo de “enviar/pasar” + (código/otp/token/pin) + (verificación/cuenta/sms)\r\n if (wantsSendVerb && mentionsCode && (mentionsVerify || mentionsAccount || mentionsSms)) {\r\n flags.push(\"phishing_2fa_code\");\r\n score += 0.55; // fuerte: debe quedar mínimo en WARN con casi cualquier preset\r\n }\r\n\r\n // 3B) Phishing EN: “verify account” + “click” + amenaza de cierre\r\n if (\r\n /\\bverify\\b/.test(t) &&\r\n /\\baccount\\b/.test(t) &&\r\n /\\bclick\\b/.test(t) &&\r\n /\\b(closed|close|suspend|suspended|disable|disabled|locked)\\b/.test(t)\r\n ) {\r\n flags.push(\"phishing_verify_threat_en\");\r\n score += 0.35;\r\n }\r\n\r\n // 3C) Fraude: “te deposito / transfiero” + “tarjeta/cuenta/clabe”\r\n if (\r\n /\\b(te\\s+deposito|te\\s+dep[oó]sito|te\\s+transfiero|te\\s+transferir[eé]|transferencia|dep[oó]sito)\\b/.test(\r\n t\r\n ) &&\r\n /\\b(tarjeta|cuenta|clabe|iban|swift|n[uú]mero\\s+de\\s+tarjeta|numero\\s+de\\s+tarjeta)\\b/.test(t)\r\n ) {\r\n flags.push(\"fraud_payment_request\");\r\n score += 0.35;\r\n }\r\n\r\n // 3D) Scam: “gana dinero desde casa / sin esfuerzo”\r\n if (\r\n /\\b(gana(r)?\\s+dinero|ingresos|dinero\\s+extra)\\b/.test(t) &&\r\n /\\b(desde\\s+casa|en\\s+casa|home)\\b/.test(t) &&\r\n /\\b(sin\\s+esfuerzo|f[aá]cil|r[aá]pido|easy|fast)\\b/.test(t)\r\n ) {\r\n flags.push(\"scam_wfh\");\r\n score += 0.3;\r\n }\r\n\r\n // ------------------------------------------------------------\r\n // 4) Señales de dinero ($$$, %, monedas)\r\n // ------------------------------------------------------------\r\n const moneyHits = countRegex(raw, /\\$+/g);\r\n const pctHits = countRegex(raw, /%/g);\r\n if (moneyHits > 0 || pctHits > 0 || /\\b(usd|mxn|eur)\\b/i.test(raw)) {\r\n flags.push(\"money_signal\");\r\n score += Math.min(0.25, moneyHits * 0.05 + pctHits * 0.05 + 0.1);\r\n }\r\n\r\n // ------------------------------------------------------------\r\n // 5) Exceso de signos / gritos (señal de baja calidad o manipulación)\r\n // ------------------------------------------------------------\r\n const exclam = countRegex(raw, /!/g);\r\n const capsRatio = (() => {\r\n const letters = raw.match(/[A-Za-zÁÉÍÓÚÜÑáéíóúüñ]/g) || [];\r\n if (letters.length === 0) return 0;\r\n const caps = (raw.match(/[A-ZÁÉÍÓÚÜÑ]/g) || []).length;\r\n return caps / letters.length;\r\n })();\r\n\r\n if (exclam >= 3 || capsRatio >= 0.35) {\r\n flags.push(\"shouting\");\r\n score += 0.2;\r\n }\r\n\r\n // ------------------------------------------------------------\r\n // 6) Coerción / culpa / chantaje emocional\r\n // ------------------------------------------------------------\r\n if (\r\n /\\b(si\\s+de\\s+verdad|si\\s+me\\s+quisieras|es\\s+tu\\s+culpa|no\\s+tienes\\s+opci[oó]n|me\\s+debes|hazlo\\s+o\\s+si\\s+no|si\\s+no\\s+lo\\s+haces)\\b/.test(\r\n t\r\n )\r\n ) {\r\n flags.push(\"emotional_manipulation\");\r\n score += 0.35;\r\n }\r\n\r\n // ------------------------------------------------------------\r\n // 7) Conspiración vaga / evidencia débil (baja confiabilidad)\r\n // ------------------------------------------------------------\r\n if (\r\n /\\b(todos\\s+lo\\s+saben|lo\\s+esconden|la\\s+verdad\\s+oculta|ellos\\s+no\\s+quieren|simulaci[oó]n)\\b/.test(\r\n t\r\n )\r\n ) {\r\n flags.push(\"conspiracy_vague\");\r\n score += 0.2;\r\n }\r\n\r\n if (\r\n /\\b(es\\s+obvio|todo\\s+mundo\\s+sabe|se\\s+sabe|est[aá]\\s+claro|la\\s+cultura\\s+lo\\s+prueba)\\b/.test(\r\n t\r\n )\r\n ) {\r\n flags.push(\"weak_evidence\");\r\n score += 0.2;\r\n }\r\n\r\n score = clamp01(score);\r\n return { score, flags };\r\n}\r\n","// src/intention.ts\r\nimport type { IntentionEvaluation, IntentionType } from \"./types\";\r\n\r\nfunction clamp01(x: number) {\r\n return Math.max(0, Math.min(1, x));\r\n}\r\n\r\nexport function evaluateIntention(text: string): IntentionEvaluation {\r\n const raw = text || \"\";\r\n const t = raw.toLowerCase();\r\n\r\n // Heurísticas rápidas (MVP)\r\n const isHelp =\r\n /\\b(ayuda|ayúdame|explica|resume|resumir|cómo|como|puedes|podrías|por favor)\\b/.test(\r\n t\r\n );\r\n\r\n const isSpam =\r\n /\\b(compra|oferta|promo|descuento|gratis|haz clic|click|off|90%|% off)\\b/.test(\r\n t\r\n ) || (raw.match(/\\$+/g) || []).length > 0;\r\n\r\n const isManip =\r\n /\\b(si de verdad|si me quisieras|es tu culpa|no tienes opción|me debes)\\b/.test(\r\n t\r\n );\r\n\r\n const isConsp =\r\n /\\b(simulación|todos lo saben|lo esconden|verdad oculta|ellos no quieren)\\b/.test(\r\n t\r\n );\r\n\r\n // NUEVO: pseudo-ciencia / pensamiento mágico / relativismo (desinformación)\r\n const isMisinformation =\r\n /\\b(física cuántica|cuantica|cuántica|cuántico|quantum)\\b/.test(t) ||\r\n /\\b(manifestar|manifestación|decretar|decreto|vibración|vibracion|frecuencia|energía|energia|ley de la atracción)\\b/.test(t) ||\r\n /\\b(no hay una verdad objetiva|no existe la verdad objetiva|tu verdad|mi verdad|la verdad es relativa)\\b/.test(t) ||\r\n /\\b(la materia)\\b.*\\b(se subordina|obedece)\\b/.test(t);\r\n\r\n let intention: IntentionType = \"unknown\";\r\n let confidence = 0.0;\r\n let rationale = \"\";\r\n\r\n if (isSpam) {\r\n intention = \"marketing_spam\";\r\n confidence = 0.85;\r\n rationale = \"Detecté señales de venta agresiva/urgencia/dinero.\";\r\n } else if (isManip) {\r\n intention = \"manipulation\";\r\n confidence = 0.85;\r\n rationale = \"Detecté coerción/culpa/chantaje emocional.\";\r\n } else if (isConsp) {\r\n intention = \"conspiracy\";\r\n confidence = 0.75;\r\n rationale = \"Detecté marco conspirativo vago ('lo esconden', 'todos lo saben').\";\r\n } else if (isMisinformation) {\r\n intention = \"misinformation\";\r\n confidence = 0.85;\r\n rationale =\r\n \"Detecté patrón de pseudo-ciencia/pensamiento mágico/relativismo de la verdad (alta probabilidad de desinformación).\";\r\n } else if (isHelp) {\r\n intention = \"request_help\";\r\n confidence = 0.7;\r\n rationale = \"Parece una petición legítima de ayuda/explicación.\";\r\n }\r\n\r\n return { intention, confidence: clamp01(confidence), rationale };\r\n}\r\n","// src/wrapper.ts\r\nimport { analyzeEntropy } from \"./entropy\";\r\nimport { evaluateIntention } from \"./intention\";\r\nimport type { FilterResult, IntentionEvaluation } from \"./types\";\r\n\r\nexport function runEntropyFilter(text: string): FilterResult {\r\n const entropy_analysis = analyzeEntropy(text);\r\n let intention_evaluation: IntentionEvaluation = evaluateIntention(text);\r\n\r\n // Corrección: si el texto trae señales fuertes de entropía epistemológica,\r\n // no lo clasifiques como \"request_help\" solo por ser pregunta larga.\r\n const hardFlags = new Set(entropy_analysis.flags);\r\n const epistemicEntropy =\r\n hardFlags.has(\"truth_relativism\") ||\r\n hardFlags.has(\"magic_manifesting\") ||\r\n hardFlags.has(\"pseudo_science_quantum\") ||\r\n hardFlags.has(\"broken_causality\");\r\n\r\n if (epistemicEntropy) {\r\n intention_evaluation = {\r\n intention: \"misinformation\",\r\n confidence: Math.max(intention_evaluation.confidence ?? 0.7, 0.8),\r\n rationale:\r\n \"Detecté patrón de pseudo-ciencia/pensamiento mágico/relativismo de la verdad; alta probabilidad de desinformación o argumento sin anclaje causal.\"\r\n };\r\n }\r\n\r\n return { entropy_analysis, intention_evaluation };\r\n}\r\n","// src/middleware.ts\r\nimport { runEntropyFilter } from \"./wrapper\";\r\nimport type { FilterResult } from \"./types\";\r\n\r\n/**\r\n * Middleware agnóstico (NO depende de express types).\r\n * Compatible con Express / Next API routes / cualquier framework estilo req-res-next.\r\n *\r\n * Espera: req.body.text (string)\r\n * Escribe: req.entropy = FilterResult\r\n */\r\nexport function entropyMiddleware(req: any, _res: any, next: any) {\r\n const text = req?.body?.text;\r\n\r\n const result: FilterResult =\r\n typeof text === \"string\" ? runEntropyFilter(text) : runEntropyFilter(\"\");\r\n\r\n req.entropy = result;\r\n next?.();\r\n}\r\n\r\n/**\r\n * Tipo auxiliar opcional (sin forzar dependencias).\r\n * Útil si quieres tipar tu req en tu app.\r\n */\r\nexport type EntropyAugmentedRequest = {\r\n body?: { text?: unknown };\r\n entropy?: FilterResult;\r\n};\r\n","// src/gate.ts\r\nimport { runEntropyFilter } from \"./wrapper\";\r\n\r\nexport type GateAction = \"ALLOW\" | \"WARN\" | \"BLOCK\";\r\n\r\nexport type GateResult = {\r\n action: GateAction;\r\n entropy_score: number;\r\n flags: string[];\r\n intention: string;\r\n confidence: number;\r\n rationale: string;\r\n};\r\n\r\nexport type RulesetThresholds = {\r\n warn?: number;\r\n block?: number;\r\n};\r\n\r\nexport type RulesetPolicy = {\r\n /** if true, allow strongSpam to force BLOCK */\r\n strong_spam_block?: boolean;\r\n\r\n /** optional overrides */\r\n block_intentions?: string[];\r\n warn_intentions?: string[];\r\n\r\n /** flag overrides */\r\n block_flags?: string[];\r\n warn_flags?: string[];\r\n};\r\n\r\nexport type RulesetConfig = {\r\n name?: string;\r\n version?: number;\r\n description?: string;\r\n thresholds?: RulesetThresholds;\r\n policy?: RulesetPolicy;\r\n // normalization is applied in wrapper/ruleset loader; kept here for completeness\r\n normalization?: Record<string, unknown>;\r\n};\r\n\r\nexport type GateConfig = {\r\n /** Per-call threshold overrides (fallback if ruleset.thresholds not provided) */\r\n warnThreshold?: number; // default: 0.25\r\n blockThreshold?: number; // default: 0.60\r\n\r\n /** Optional ruleset (default/strict/public-api) */\r\n ruleset?: RulesetConfig;\r\n};\r\n\r\n/** Clamp 0..1 */\r\nfunction clamp01(x: number) {\r\n return Math.max(0, Math.min(1, x));\r\n}\r\n\r\n/** Add unique flags preserving order */\r\nfunction mergeFlags(base: string[], extra: string[]) {\r\n const set = new Set(base);\r\n const out = [...base];\r\n for (const f of extra) {\r\n if (!set.has(f)) {\r\n set.add(f);\r\n out.push(f);\r\n }\r\n }\r\n return out;\r\n}\r\n\r\n/**\r\n * Very small EN spam booster (keyword-ish).\r\n * Keep it deterministic and cheap. Tune in src/entropy.ts for real weights/patterns.\r\n */\r\nfunction englishSpamBooster(text: string): {\r\n hitCount: number;\r\n addScore: number;\r\n addFlags: string[];\r\n} {\r\n const t = (text || \"\").toLowerCase();\r\n const hits: string[] = [];\r\n\r\n const kw = [\r\n [\"free\", \"spam_kw_free\"],\r\n [\"winner\", \"spam_kw_winner\"],\r\n [\"claim\", \"spam_kw_claim\"],\r\n [\"click\", \"spam_kw_click\"],\r\n [\"verify\", \"spam_kw_verify\"],\r\n [\"prize\", \"spam_kw_prize\"],\r\n [\"urgent\", \"spam_kw_urgency_en\"],\r\n [\"limited time\", \"spam_kw_urgency_en\"],\r\n [\"today only\", \"spam_kw_urgency_en\"],\r\n ] as const;\r\n\r\n for (const [s, flag] of kw) {\r\n if (t.includes(s)) hits.push(flag);\r\n }\r\n\r\n if (hits.length === 0) return { hitCount: 0, addScore: 0, addFlags: [] };\r\n\r\n // conservative bump: 0.05 per hit, capped\r\n const addScore = Math.min(0.25, hits.length * 0.05);\r\n const addFlags = mergeFlags([\"spam_keywords_en\"], hits);\r\n return { hitCount: hits.length, addScore, addFlags };\r\n}\r\n\r\nfunction decideAction(params: {\r\n score: number;\r\n warnT: number;\r\n blockT: number;\r\n strongSpam: boolean;\r\n intention?: string;\r\n flags: string[];\r\n policy?: RulesetPolicy;\r\n}): GateAction {\r\n const {\r\n score,\r\n warnT,\r\n blockT,\r\n strongSpam,\r\n intention = \"\",\r\n flags,\r\n policy = {},\r\n } = params;\r\n\r\n const blockIntentions = new Set(policy.block_intentions ?? []);\r\n const warnIntentions = new Set(policy.warn_intentions ?? []);\r\n const blockFlags = new Set(policy.block_flags ?? []);\r\n const warnFlags = new Set(policy.warn_flags ?? []);\r\n\r\n // 1) explicit overrides (if provided)\r\n if (blockIntentions.has(intention)) return \"BLOCK\";\r\n if (flags.some((f) => blockFlags.has(f))) return \"BLOCK\";\r\n\r\n if (warnIntentions.has(intention)) return \"WARN\";\r\n if (flags.some((f) => warnFlags.has(f))) return \"WARN\";\r\n\r\n // 2) strong spam override (configurable)\r\n const strongSpamBlock = policy.strong_spam_block ?? true;\r\n if (strongSpamBlock && strongSpam) return \"BLOCK\";\r\n\r\n // 3) thresholds\r\n if (score >= blockT) return \"BLOCK\";\r\n if (score >= warnT) return \"WARN\";\r\n return \"ALLOW\";\r\n}\r\n\r\n/**\r\n * Gate principal (producto): deterministic + barato.\r\n */\r\nexport function gateLLM(text: string, config: GateConfig = {}): GateResult {\r\n const ruleset = config.ruleset;\r\n\r\n // thresholds: ruleset > config > defaults\r\n const warnT = ruleset?.thresholds?.warn ?? config.warnThreshold ?? 0.25;\r\n const blockT = ruleset?.thresholds?.block ?? config.blockThreshold ?? 0.6;\r\n\r\n const r = runEntropyFilter(text);\r\n\r\n // base\r\n let score = r.entropy_analysis.score;\r\n let flags = [...r.entropy_analysis.flags];\r\n\r\n // booster EN\r\n const booster = englishSpamBooster(text);\r\n if (booster.hitCount > 0) {\r\n score = clamp01(score + booster.addScore);\r\n flags = mergeFlags(flags, booster.addFlags);\r\n }\r\n\r\n // intención base (intention.ts)\r\n const intention = r.intention_evaluation.intention || \"unknown\";\r\n const confidence = r.intention_evaluation.confidence ?? 0;\r\n const rationale = r.intention_evaluation.rationale ?? \"\";\r\n\r\n // heuristic: \"strong spam\" only when BOTH spam_sales + money_signal are present\r\n // (tune this in entropy.ts; here we only consume flags)\r\n const hasSpam = flags.includes(\"spam_sales\");\r\n const hasMoneySignals =\r\n flags.includes(\"money_signal\") || flags.includes(\"money_signal_high\");\r\n const strongSpam = hasSpam && hasMoneySignals;\r\n\r\n const policy: RulesetPolicy = ruleset?.policy ?? {};\r\n\r\n const action: GateAction = decideAction({\r\n score,\r\n warnT,\r\n blockT,\r\n strongSpam,\r\n intention,\r\n flags,\r\n policy,\r\n });\r\n\r\n return {\r\n action,\r\n entropy_score: score,\r\n flags,\r\n intention,\r\n confidence: clamp01(confidence),\r\n rationale,\r\n };\r\n}\r\n\r\n// Alias “bonito” para tu server: gate(text)\r\nexport const gate = gateLLM;\r\n"],"mappings":"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","names":["clamp01","clamp01"]}
package/dist/index.d.cts CHANGED
@@ -2,6 +2,11 @@ type EntropyResult$1 = {
2
2
  score: number;
3
3
  flags: string[];
4
4
  };
5
+ /**
6
+ * Entropy = señales lingüísticas + lógicas (determinístico, barato).
7
+ * Nota: aquí SOLO generamos banderas y score. La acción (ALLOW/WARN/BLOCK)
8
+ * se decide en gate.ts según thresholds/policy del ruleset.
9
+ */
5
10
  declare function analyzeEntropy(text: string): EntropyResult$1;
6
11
 
7
12
  type EntropyResult = {
@@ -41,13 +46,38 @@ type GateResult = {
41
46
  confidence: number;
42
47
  rationale: string;
43
48
  };
49
+ type RulesetThresholds = {
50
+ warn?: number;
51
+ block?: number;
52
+ };
53
+ type RulesetPolicy = {
54
+ /** if true, allow strongSpam to force BLOCK */
55
+ strong_spam_block?: boolean;
56
+ /** optional overrides */
57
+ block_intentions?: string[];
58
+ warn_intentions?: string[];
59
+ /** flag overrides */
60
+ block_flags?: string[];
61
+ warn_flags?: string[];
62
+ };
63
+ type RulesetConfig = {
64
+ name?: string;
65
+ version?: number;
66
+ description?: string;
67
+ thresholds?: RulesetThresholds;
68
+ policy?: RulesetPolicy;
69
+ normalization?: Record<string, unknown>;
70
+ };
44
71
  type GateConfig = {
72
+ /** Per-call threshold overrides (fallback if ruleset.thresholds not provided) */
45
73
  warnThreshold?: number;
46
74
  blockThreshold?: number;
75
+ /** Optional ruleset (default/strict/public-api) */
76
+ ruleset?: RulesetConfig;
47
77
  };
48
78
  /**
49
79
  * Gate principal (producto): deterministic + barato.
50
80
  */
51
81
  declare function gateLLM(text: string, config?: GateConfig): GateResult;
52
82
 
53
- export { type EntropyResult, type FilterResult, type GateAction, type GateConfig, type GateResult, type IntentionEvaluation, analyzeEntropy, entropyMiddleware, evaluateIntention, gateLLM as gate, gateLLM, runEntropyFilter };
83
+ export { type EntropyResult, type FilterResult, type GateAction, type GateConfig, type GateResult, type IntentionEvaluation, type RulesetConfig, type RulesetPolicy, type RulesetThresholds, analyzeEntropy, entropyMiddleware, evaluateIntention, gateLLM as gate, gateLLM, runEntropyFilter };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,11 @@ type EntropyResult$1 = {
2
2
  score: number;
3
3
  flags: string[];
4
4
  };
5
+ /**
6
+ * Entropy = señales lingüísticas + lógicas (determinístico, barato).
7
+ * Nota: aquí SOLO generamos banderas y score. La acción (ALLOW/WARN/BLOCK)
8
+ * se decide en gate.ts según thresholds/policy del ruleset.
9
+ */
5
10
  declare function analyzeEntropy(text: string): EntropyResult$1;
6
11
 
7
12
  type EntropyResult = {
@@ -41,13 +46,38 @@ type GateResult = {
41
46
  confidence: number;
42
47
  rationale: string;
43
48
  };
49
+ type RulesetThresholds = {
50
+ warn?: number;
51
+ block?: number;
52
+ };
53
+ type RulesetPolicy = {
54
+ /** if true, allow strongSpam to force BLOCK */
55
+ strong_spam_block?: boolean;
56
+ /** optional overrides */
57
+ block_intentions?: string[];
58
+ warn_intentions?: string[];
59
+ /** flag overrides */
60
+ block_flags?: string[];
61
+ warn_flags?: string[];
62
+ };
63
+ type RulesetConfig = {
64
+ name?: string;
65
+ version?: number;
66
+ description?: string;
67
+ thresholds?: RulesetThresholds;
68
+ policy?: RulesetPolicy;
69
+ normalization?: Record<string, unknown>;
70
+ };
44
71
  type GateConfig = {
72
+ /** Per-call threshold overrides (fallback if ruleset.thresholds not provided) */
45
73
  warnThreshold?: number;
46
74
  blockThreshold?: number;
75
+ /** Optional ruleset (default/strict/public-api) */
76
+ ruleset?: RulesetConfig;
47
77
  };
48
78
  /**
49
79
  * Gate principal (producto): deterministic + barato.
50
80
  */
51
81
  declare function gateLLM(text: string, config?: GateConfig): GateResult;
52
82
 
53
- export { type EntropyResult, type FilterResult, type GateAction, type GateConfig, type GateResult, type IntentionEvaluation, analyzeEntropy, entropyMiddleware, evaluateIntention, gateLLM as gate, gateLLM, runEntropyFilter };
83
+ export { type EntropyResult, type FilterResult, type GateAction, type GateConfig, type GateResult, type IntentionEvaluation, type RulesetConfig, type RulesetPolicy, type RulesetThresholds, analyzeEntropy, entropyMiddleware, evaluateIntention, gateLLM as gate, gateLLM, runEntropyFilter };