mjolnir-qa 0.4.0 → 0.5.2

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/README.es.md ADDED
@@ -0,0 +1,709 @@
1
+ <div align="center">
2
+
3
+ <img src="assets/readme/logo.png" alt="Mjölnir — Verification Trust Engine" width="800" />
4
+
5
+ ### Tus tests te mienten. Nosotros lo demostramos.
6
+
7
+ **Verification Trust Engine para QA.** Mjölnir audita suites de tests y
8
+ pipelines de CI, reporta una puntuación de idoneidad y muestra
9
+ exactamente dónde se rompe la confianza.
10
+
11
+ [![npm](https://img.shields.io/npm/v/mjolnir-qa.svg?style=flat-square&color=C9A227&labelColor=0B0F17)](https://www.npmjs.com/package/mjolnir-qa)
12
+ [![ci](https://img.shields.io/github/actions/workflow/status/Sergey-Bar/Mjolnir/ci.yml?branch=main&style=flat-square&label=ci&labelColor=0B0F17)](https://github.com/Sergey-Bar/Mjolnir/actions/workflows/ci.yml)
13
+ [![license](https://img.shields.io/badge/license-MIT-C9A227.svg?style=flat-square&labelColor=0B0F17)](LICENSE)
14
+ [![node](https://img.shields.io/badge/node-%E2%89%A5%2022.18-2E8C7F.svg?style=flat-square&labelColor=0B0F17)](https://nodejs.org)
15
+
16
+ [English](README.md) | [简体中文](README.zh.md) | [繁體中文](README.zht.md) | [한국어](README.ko.md) | [Deutsch](README.de.md) | Español | [Français](README.fr.md) | [Italiano](README.it.md) | [Dansk](README.da.md) | [日本語](README.ja.md) | [Polski](README.pl.md) | [Русский](README.ru.md) | [Norsk](README.no.md) | [Português (Brasil)](README.br.md) | [ไทย](README.th.md) | [Türkçe](README.tr.md) | [Українська](README.uk.md) | [বাংলা](README.bn.md) | [Ελληνικά](README.gr.md) | [Tiếng Việt](README.vi.md) | [עברית](README.he.md) | [العربية](README.ar.md) | [Bosanski](README.bs.md)
17
+
18
+ > 🤖 Machine-assisted translation. The [English README](README.md) is canonical. Last synced: 2026-09-04.
19
+
20
+ ```bash
21
+ npx mjolnir-qa@latest
22
+ ```
23
+
24
+ **¿Son tus tests dignos de confianza?**
25
+
26
+ [Míralo en acción](#-míralo-en-acción) ·
27
+ [Inicio rápido](#-inicio-rápido) ·
28
+ [Qué comprueba](#-qué-comprueba-mjölnir) ·
29
+ [Puntuación](#cómo-funciona-la-puntuación) ·
30
+ [CI](#-integración-ci) · [Configuración](#configuración) ·
31
+ [Documentación](#-documentación)
32
+
33
+ </div>
34
+
35
+ ---
36
+
37
+ ## 🎬 Míralo en acción
38
+
39
+ <p align="center">
40
+ <img src="assets/readme/demo.svg" alt="El informe --verbose completo de Mjölnir sobre un repo de demostración: WORTHINESS 75/100 NEEDS WORK, un desglose de diagnósticos por categoría, una lista FIX THIS FIRST y cada hallazgo con su ID de regla y número de línea a través de CI, Playwright, higiene de tests y reglas de Python" width="900" />
41
+ </p>
42
+
43
+ <sub>La salida completa de `npx mjolnir-qa ./examples/demo-repo --verbose`,
44
+ renderizada por el reporter real — nada recortado. Regenerada con
45
+ `npm run docs:demo`;
46
+ [`tests/demo-asset-reproducibility.spec.ts`](tests/demo-asset-reproducibility.spec.ts)
47
+ hace fallar la CI si se desvía de lo que la herramienta imprime.</sub>
48
+
49
+ **Lo que acaba de pasar:**
50
+
51
+ 1. Mjölnir descubrió los specs de Playwright, su configuración, el
52
+ workflow de CI y un archivo de test de Python — cuatro
53
+ lenguajes/formatos, una sola pasada.
54
+ 2. Encontró evidencia que debilita la confianza en la suite — un
55
+ `continue-on-error` que enmascara un job, un `|| true` que traga un
56
+ código de salida, sleeps en duro, un selector frágil, URLs de
57
+ staging hardcodeadas, una espera `networkidle`.
58
+ 3. Convirtió cada uno en un hallazgo concreto con ID de regla,
59
+ ubicación y arreglo — y una única puntuación sobre la que puedes
60
+ hacer gate a una PR.
61
+
62
+ ### Un hallazgo de cerca
63
+
64
+ Ejecuta `mjolnir explain QA-CI-001` sobre el primer hallazgo de arriba
65
+ y obtienes:
66
+
67
+ ```text
68
+ ▚▞ QA-CI-001 — continue-on-error masks a failing verification gate
69
+
70
+ Severity: error
71
+ Confidence: high
72
+ Evidence: E2
73
+ Measured FP: not yet measured — this rule ships on assumption (see docs/FP-AUDIT.md)
74
+
75
+ WHAT WAS FOUND (real detector output, not a mockup)
76
+ Job `security-scan` runs a verification gate under `continue-on-error: true`.
77
+
78
+ WHY IT MATTERS
79
+ This job can fail every day and CI will still show green. The checkmark
80
+ on this workflow cannot be trusted.
81
+
82
+ HOW TO FIX
83
+ Remove continue-on-error, or scope it to individual non-blocking steps only.
84
+ ```
85
+
86
+ Esa es la unidad de valor: no un detalle de estilo, sino un lugar donde
87
+ tu CI te dice que algo pasó cuando no pasó.
88
+
89
+ ---
90
+
91
+ ## ⚡ Inicio rápido
92
+
93
+ Ejecútalo contra un repo para un informe completo y una puntuación de
94
+ idoneidad:
95
+
96
+ ```bash
97
+ npx mjolnir-qa@latest
98
+ ```
99
+
100
+ **En CI, el producto es un solo comando.** Escanea solo lo que la
101
+ rama tocó y sale con código distinto de cero ante problemas nuevos:
102
+
103
+ ```bash
104
+ npx mjolnir-qa@latest --scope changed
105
+ ```
106
+
107
+ Suelta eso en un check de PR — `mjolnir ci install` escribe el
108
+ workflow — y listo. Todo lo demás es opcional.
109
+
110
+ | Comando | Qué hace |
111
+ | ----------------------------------- | ------------------------------------------------------------------ |
112
+ | `mjolnir` | Escaneo completo del repo + puntuación de idoneidad |
113
+ | `mjolnir --scope changed` | Solo lo que introdujo tu rama — la forma de CI |
114
+ | `mjolnir ci install` | Genera el workflow de PR asesor |
115
+ | `mjolnir explain QA-CI-001` | Qué / por qué / arreglo + tasa de FP medida de una regla |
116
+ | `mjolnir rules --unmeasured` | Las reglas que corren por suposición, no por medición |
117
+ | `mjolnir --json` / `--format sarif` | Legible por máquina / GitHub Code Scanning |
118
+ | `mjolnir --strict` | También ejecuta reglas del tier de cuarentena (mayor riesgo de FP) |
119
+
120
+ <details>
121
+ <summary><strong>Cuando algo es inestable</strong></summary>
122
+
123
+ | Comando | Qué hace |
124
+ | ----------------------------------- | ------------------------------------------------------------------------- |
125
+ | `mjolnir forensics ./test-results/` | Datos reales de ejecución → veredictos `TRUE-FLAKE`, `FLAKY.md` |
126
+ | `mjolnir triage ./test-results/` | Propuesta de cuarentena a partir del historial de ejecución |
127
+ | `mjolnir pw-report ./test-results/` | Resumen de ejecución de Playwright — reintentos / inestables / más lentos |
128
+ | `mjolnir doctor:playwright` | Escaneo profundo solo Playwright + Selector Health Score |
129
+
130
+ </details>
131
+
132
+ <details>
133
+ <summary><strong>Ocasional / informes</strong></summary>
134
+
135
+ | Comando | Qué hace |
136
+ | ------------------------------- | ----------------------------------------------------------- |
137
+ | `mjolnir fix --dry-run` / `fix` | Autocorrevisiones seguras con prueba |
138
+ | `mjolnir baseline` / `diff` | Snapshot de hallazgos, luego reporta solo nuevos/empeorados |
139
+ | `mjolnir impact --since <ref>` | Qué cambió desde un commit anterior |
140
+ | `mjolnir debt` | Registro de deuda de tests con un modelo de coste |
141
+ | `mjolnir handover` | Mapa de onboarding de la suite para QA recién incorporado |
142
+ | `mjolnir stats` | Contadores locales de todos los tiempos de arreglos vistos |
143
+ | `mjolnir badge` | JSON de endpoint de shields.io + snippet |
144
+ | `mjolnir rules --md` | Catálogo completo de reglas (JSON o Markdown) |
145
+ | `mjolnir doctor` | Autoauditoría de la propia base de reglas de Mjölnir |
146
+ | `mjolnir create-rule <ID>` | Genera el esqueleto de una regla nueva + fixtures |
147
+ | `mjolnir --format mermaid` | Diagrama de arquitectura de tests para un comentario de PR |
148
+
149
+ </details>
150
+
151
+ Instálalo globalmente en lugar de `npx` si lo prefieres:
152
+ `npm i -g mjolnir-qa`. Requiere Node.js ≥ 22.18. Funciona en Windows,
153
+ macOS y Linux.
154
+
155
+ ---
156
+
157
+ ## 👥 ¿Para quién es esto?
158
+
159
+ - **QA / SDET** dueños de una suite e2e o de integración que necesitan
160
+ evidencia de que la suite realmente merece el visto verde que
161
+ produce.
162
+ - **Equipos de Plataforma / DevEx** responsables de la integridad de CI
163
+ y de los release gates — la gente a la que le importa que un
164
+ `continue-on-error` nunca vuelva verde en silencio una pipeline roja.
165
+ - **Mantenedores de OSS** que quieren un gate de verificación barato,
166
+ siempre activo, que corra en local y en CI sin llamadas de red.
167
+
168
+ ---
169
+
170
+ ## 🔨 Qué comprueba Mjölnir
171
+
172
+ | | |
173
+ | --- | ------------------------------------------------------------------------------------------------------------------------------------- |
174
+ | ⚖️ | **Puntuación de idoneidad** — un número, tabla de deducciones transparente, sin caja negra |
175
+ | 🎭 | **Selector Health Score** — califica tus locators de Playwright, no solo tu tasa de aprobación |
176
+ | 🔬 | **Forense de ejecución** — lee datos reales de ejecución de Playwright/JUnit para detectar `TRUE-FLAKE`, no solo conjeturas estáticas |
177
+ | 🚨 | **Reglas de integridad de CI** — detecta `continue-on-error`, `\|\| true` y otros trucos de verde falso |
178
+ | 🐍 | **Los cuatro bindings de Playwright** — TypeScript, Python, Java, C#/.NET — más pytest, JUnit/TestNG y workflows de CI |
179
+ | 🔒 | **Local-first** — cero llamadas de red al escanear, cero telemetría, corre en segundos |
180
+
181
+ ### Las reglas
182
+
183
+ Cada regla llega con fixtures must-fire **y** must-not-fire. Una regla
184
+ que se dispara sobre su propia fixture negativa no puede publicarse —
185
+ ese es el cortafuegos de falsos positivos.
186
+
187
+ <details>
188
+ <summary><strong>Higiene de tests</strong></summary>
189
+
190
+ | ID | Regla | Severity |
191
+ | ----------- | ------------------------------------------------------ | -------- |
192
+ | QA-TEST-001 | Test enfocado committeado (`.only`, `fit`) | error |
193
+ | QA-TEST-002 | Test saltado sin justificación | error |
194
+ | QA-TEST-002 | Test saltado con justificación registrada | warning |
195
+ | QA-TEST-003 | Test sin aserciones | error |
196
+ | QA-TEST-004 | Sleep en duro (`waitForTimeout`, `sleep()`, `delay()`) | warning |
197
+ | QA-TEST-006 | Abuso de reintentos que esconde inestabilidad | warning |
198
+ | QA-TEST-010 | Cuerpo de test vacío | error |
199
+
200
+ </details>
201
+
202
+ <details>
203
+ <summary><strong>Calidad de tests</strong></summary>
204
+
205
+ | ID | Regla | Severity |
206
+ | ------------ | ----------------------------- | -------- |
207
+ | QA-TQUAL-001 | Verificación solo con mocks | info |
208
+ | QA-TQUAL-002 | Aserción tautológica | error |
209
+ | QA-TQUAL-009 | Aserción de promesa sin await | error |
210
+ | QA-TQUAL-011 | Tests comentados | warning |
211
+
212
+ </details>
213
+
214
+ <details>
215
+ <summary><strong>Playwright 🎭</strong></summary>
216
+
217
+ | ID | Regla | Severity |
218
+ | --------- | --------------------------------------------- | -------- |
219
+ | QA-PW-002 | Aserción de locator sin await | error |
220
+ | QA-PW-003 | `page.pause()` / `test.only()` committeados | error |
221
+ | QA-PW-004 | Selectores CSS/XPath frágiles | warning |
222
+ | QA-PW-005 | Lógica de negocio dentro de `page.evaluate()` | info |
223
+ | QA-PW-114 | Element handles heredados (`page.$`) | info |
224
+ | QA-PW-118 | Esperas `networkidle` (inestables por diseño) | info |
225
+ | QA-PW-123 | URLs de entorno hardcodeadas | warning |
226
+
227
+ </details>
228
+
229
+ <details>
230
+ <summary><strong>Integridad de CI</strong></summary>
231
+
232
+ | ID | Regla | Severity |
233
+ | --------- | ----------------------------------------------------------------------- | -------- |
234
+ | QA-CI-001 | `continue-on-error` enmascara fallos | error |
235
+ | QA-CI-002 | `\|\| true` traga códigos de salida | error |
236
+ | QA-CI-005 | Reporte consumido pero nunca generado | error |
237
+ | QA-CI-007 | Wrappers de reintento alrededor de tests | warning |
238
+ | QA-CI-008 | Paso siempre exitoso enmascara fallos | error |
239
+ | QA-CI-009 | Código de salida del test no propagado (`\|` sin pipefail, cadenas `;`) | error |
240
+ | QA-CI-010 | Tests saltados donde deben bloquear (guardas skip-on-PR) | error |
241
+
242
+ </details>
243
+
244
+ <details>
245
+ <summary><strong>Python / pytest 🐍</strong></summary>
246
+
247
+ | ID | Regla | Severity |
248
+ | --------- | ------------------------------------------ | -------- |
249
+ | QA-PY-002 | Test saltado (`skip`, `xfail` no estricto) | warning |
250
+ | QA-PY-003 | Función de test sin aserciones | error |
251
+ | QA-PY-005 | `time.sleep()` en tests | warning |
252
+ | QA-PY-006 | Cuerpo de test vacío (`pass`) | info |
253
+ | QA-PY-010 | Dependencia de azar/tiempo sin freeze | info |
254
+ | QA-PY-012 | Aserción tautológica | error |
255
+
256
+ 20 reglas de Python en total (QA-PY-001…012 higiene pytest + QA-PY-101…108 Playwright-Python).
257
+
258
+ </details>
259
+
260
+ <details>
261
+ <summary><strong>Java / JUnit · TestNG ☕</strong></summary>
262
+
263
+ | ID | Regla | Severity |
264
+ | --------- | ---------------------------------------------- | -------- |
265
+ | QA-JV-101 | Test deshabilitado (`@Disabled`) | warning |
266
+ | QA-JV-102 | Sleep en duro (`Thread.sleep()`) | warning |
267
+ | QA-JV-103 | Método de test sin aserciones | error |
268
+ | QA-JV-105 | Sleep en duro de Playwright `waitForTimeout()` | warning |
269
+ | QA-JV-106 | Selector frágil en vez de role locator | warning |
270
+ | QA-JV-108 | URL de entorno hardcodeada en el test | info |
271
+ | QA-JV-111 | Mock en blanquete `page.route("**")` | info |
272
+
273
+ </details>
274
+
275
+ <details>
276
+ <summary><strong>C# / .NET — NUnit · xUnit · MSTest 🟣</strong></summary>
277
+
278
+ | ID | Regla | Severity |
279
+ | --------- | --------------------------------------------- | -------- |
280
+ | QA-CS-101 | Test saltado (`[Ignore]`, `[Fact(Skip=)]`) | warning |
281
+ | QA-CS-102 | Sleep en duro (`Thread.Sleep` / `Task.Delay`) | warning |
282
+ | QA-CS-103 | Método de test sin aserciones | error |
283
+ | QA-CS-105 | Sleep en duro `WaitForTimeoutAsync()` | warning |
284
+ | QA-CS-106 | Selector frágil en vez de role locator | warning |
285
+ | QA-CS-108 | URL de entorno hardcodeada en el test | info |
286
+ | QA-CS-111 | Mock en blanquete `page.RouteAsync("**")` | info |
287
+
288
+ </details>
289
+
290
+ > El catálogo vivo completo — cada regla con tier, confidence, riesgo de
291
+ > falso positivo y disponibilidad de autofix — se genera desde el
292
+ > registro:
293
+ >
294
+ > ```bash
295
+ > mjolnir rules --md
296
+ > ```
297
+ >
298
+ > Las páginas por regla viven en [`docs/rules/`](docs/rules/).
299
+
300
+ ### Cuánto está medido
301
+
302
+ **74 de 99 reglas llevan una tasa de falsos positivos medida contra
303
+ código OSS real** (≥ 10 hallazgos clasificados a mano cada una; ver
304
+ [docs/FP-AUDIT.md](docs/FP-AUDIT.md)). Las otras 19 salen sobre la
305
+ estimación del autor. Cada pie de escaneo te dice cuántas de las reglas
306
+ _que se dispararon_ están medidas; `mjolnir rules --unmeasured` lista
307
+ las que no; la página `mjolnir explain` de cada regla declara su
308
+ estado. Publicamos la tasa aunque sea fea — QA-CS-103 audita al 95 % y
309
+ está en cuarentena por ello. Hacer crecer ese 78 es el trabajo continuo
310
+ del proyecto.
311
+
312
+ ### Tiers de reglas y madurez por lenguaje
313
+
314
+ Cada regla es `core`, `extended` o `quarantine`, asignado según su tasa
315
+ de falsos positivos **medida**:
316
+
317
+ | Tier | Significación | Escaneo por defecto | `--strict` |
318
+ | ------------ | --------------------------------------------- | :-----------------: | :--------: |
319
+ | `core` | ≤ 10 % de FP medido | ✅ | ✅ |
320
+ | `extended` | ≤ 30 % de FP medido | ✅ | ✅ |
321
+ | `quarantine` | por encima del 30 %, o aún sin medir (n < 10) | ❌ | ✅ |
322
+
323
+ | Lenguaje | Adaptador | Cobertura hoy |
324
+ | --------------- | ------------------ | ------------------------------------------------------------ |
325
+ | TypeScript / JS | AST del compilador | la más amplia y medida — sobre todo `core`/`extended` |
326
+ | Python / pytest | Capa de regex | amplia, auditada sobre corpus — sobre todo `core`/`extended` |
327
+ | Java | Capa de regex | más nuevo — sobre todo `extended`/`quarantine` |
328
+ | C# / .NET | Capa de regex | más nuevo — sobre todo `extended`/`quarantine` |
329
+
330
+ TypeScript y Python tienen la cobertura medida más amplia. Java y C#
331
+ están publicados, documentados, y permanecen fuera del número titular
332
+ hasta que una suite consumidora real (no los propios tests de una
333
+ librería de binding) haya sido auditada.
334
+
335
+ ---
336
+
337
+ ## Cómo funciona la puntuación
338
+
339
+ <p align="center">
340
+ <img src="assets/readme/terminal-hero.svg" alt="Salida de terminal de Mjölnir — WORTHINESS 75/100 NEEDS WORK, un desglose de diagnósticos por categoría y una lista FIX THIS FIRST" width="820" />
341
+ </p>
342
+
343
+ <sub>Regenerada con `npm run docs:hero`;
344
+ [`tests/hero-asset-reproducibility.spec.ts`](tests/hero-asset-reproducibility.spec.ts)
345
+ hace fallar la CI si se desvía de lo que el reporter realmente imprime.</sub>
346
+
347
+ La puntuación es transparente: **error −8, warning −3, info −1**, luego
348
+ normalizada por la exposición de la suite (deducciones por declaración
349
+ de test). Las deducciones ponderadas por evidencia significan que las
350
+ señales débiles cuestan menos. La terminal muestra los mismos números
351
+ descontados que usa la puntuación — sin caja negra. Método completo:
352
+ [docs/SCORING.md](docs/SCORING.md).
353
+
354
+ **Veredictos**
355
+
356
+ | Score | Veredicto |
357
+ | ------- | ---------------- |
358
+ | ≥ 80 | ✓ **WORTHY** |
359
+ | 50 – 79 | ⚠ **NEEDS WORK** |
360
+ | < 50 | ✖ **UNWORTHY** |
361
+
362
+ **Niveles de evidencia** — cada hallazgo lleva uno; fija el peso del
363
+ hallazgo en la puntuación:
364
+
365
+ | Nivel | Significación | Impacto en la puntuación | Ejemplo |
366
+ | ----- | -------------------- | ------------------------ | ------------------------------------------------------- |
367
+ | E2 | Defecto determinista | Deducción completa | `.only` committeado — estructuralmente demostrable |
368
+ | E1 | Patrón heurístico | Media deducción | `sleep()` detectado por regex — señal fuerte, no prueba |
369
+ | E0 | Observación | Cero (solo info) | Reportado pero nunca hace gate a CI ni deduce |
370
+
371
+ La mayoría de las reglas son **E1**. El lema «we prove it» se refiere a
372
+ este sistema: los hallazgos E2 son prueba estructural; los hallazgos E1
373
+ son advertencias correctamente posicionadas, no pruebas formales.
374
+
375
+ Un repo vacío puntúa `null`, nunca un falso 100 — ver
376
+ [Modelo de confianza](#modelo-de-confianza).
377
+
378
+ ---
379
+
380
+ ## 🎭 Selector Health Score
381
+
382
+ La métrica titular para suites de Playwright — qué tan resilientes son
383
+ tus locators:
384
+
385
+ ```text
386
+ ▚▞ SELECTOR HEALTH — e2e/checkout.spec.ts
387
+
388
+ [█████████████████░░░] 83 / 100
389
+ role/text: 2 · testid: 1 · css-chains: 1 ⚠ · xpath: 0
390
+ ```
391
+
392
+ Los locators basados en roles obtienen la puntuación completa. Las
393
+ cadenas de clases CSS y XPath hunden la puntuación — se rompen con
394
+ cualquier refactor del DOM sin decirte qué comportamiento regredijo.
395
+
396
+ ---
397
+
398
+ ## 🔬 Evidencia de ejecución
399
+
400
+ La detección estática de inestabilidad es adivinar. Mjölnir lee
401
+ **datos reales de ejecución** — reportes JSON de Playwright y XML de
402
+ JUnit de cualquier runner:
403
+
404
+ ```bash
405
+ mjolnir forensics ./test-results/
406
+ ```
407
+
408
+ ```text
409
+ ▚▞ FLAKINESS LEADERBOARD
410
+
411
+ 3 tests · 1 failed · 1 flaky · 1 retried
412
+
413
+ TRUE-FLAKE completes checkout with saved card (e2e/checkout.spec.ts)
414
+ ████████████████████ 6.0s · 2 attempts
415
+ FAILING declines an expired card (e2e/checkout.spec.ts)
416
+ ████░░░░░░░░░░░░░░░░ 1.1s · 1 attempt
417
+ ```
418
+
419
+ Un test que solo pasa en el intento ≥ 2 no es un test que pasa — es un
420
+ test con suerte. Se marca `TRUE-FLAKE` sin importar el visto verde
421
+ final.
422
+
423
+ ---
424
+
425
+ ## ⚡ Mjölnir no es otro linter
426
+
427
+ Los linters te dicen si el código sigue reglas. Mjölnir te dice si tu
428
+ verificación puede fiarse.
429
+
430
+ | | ESLint / SonarQube | Herramientas de coverage | Revisión manual | **Mjölnir** |
431
+ | ------------------------------------------------------------------- | :----------------: | :----------------------: | :-------------: | :---------: |
432
+ | Integridad de workflows de CI (`continue-on-error`, `\|\| true`) | ❌ | ❌ | raramente | ✅ |
433
+ | Multi-lenguaje (TS, Python, Java, C#) desde una sola herramienta | ❌ | ❌ | ❌ | ✅ |
434
+ | Califica la resiliencia de locators de Playwright (Selector Health) | ❌ | ❌ | raramente | ✅ |
435
+ | Marca tests sin aserciones reales | ✅ (plugin)\* | ❌ | a veces | ✅ |
436
+ | Detecta sleeps en duro (`waitForTimeout`, `time.sleep`) | ✅ (plugin)\* | ❌ | a veces | ✅ |
437
+ | Corre en segundos, cero llamadas de red al escanear | ✅ | ✅ | — | ✅ |
438
+
439
+ \*`eslint-plugin-jest` (`expect-expect`) y `eslint-plugin-playwright`
440
+ (`expect-expect`, `no-wait-for-timeout`) cubren esto para sus
441
+ frameworks respectivos.
442
+
443
+ **El análisis de ejecución** es una categoría aparte del linting
444
+ estático:
445
+
446
+ | | Playwright retry reporter | Allure / ReportPortal | **Mjölnir forensics** |
447
+ | ---------------------------------------------------------- | :-----------------------: | :-------------------: | :-------------------: |
448
+ | Lee datos reales de ejecución para veredictos `TRUE-FLAKE` | parcial\* | parcial (tag) | ✅ |
449
+ | Informe de triage de inestabilidad desde el historial | ❌ | ✅ | ✅ |
450
+ | Se integra con la puntuación de idoneidad estática | ❌ | ❌ | ✅ |
451
+
452
+ \*Playwright rastrea los reintentos internamente pero no produce un
453
+ informe de inestabilidad independiente con etiquetas de veredicto.
454
+
455
+ ---
456
+
457
+ ## 🤖 ¿Por qué no usar simplemente revisión de código con IA?
458
+
459
+ Problema distinto, capa distinta. Una revisión con IA puede detectar un
460
+ cambio de test sospechoso en un diff; no demuestra que el sistema de
461
+ verificación en su conjunto sea confiable — y solo ve el diff que le
462
+ muestras.
463
+
464
+ | | Revisión de código con IA (Copilot, etc.) | **Mjölnir** |
465
+ | ------------------------------------------- | :---------------------------------------: | :---------------------------------: |
466
+ | Coste por escaneo | Tokens (escala con el tamaño del diff) | **Cero** (local, instalado) |
467
+ | Ve toda la suite + todas las configs de CI | Solo el diff de PR que le muestras | **Todo, cada vez** |
468
+ | Determinista (misma entrada → misma salida) | ❌ (no determinista) | **✅** |
469
+ | Detecta patrones dormidos durante meses | Solo si está en el contexto | **✅** (escanea todos los archivos) |
470
+ | Recuerda hallazgos entre ejecuciones | ❌ (sin memoria entre sesiones) | **✅** (baseline + diff) |
471
+ | Corre sin disparador humano | Necesita una PR o un prompt | **✅** (hook de CI, 3 segundos) |
472
+
473
+ **Usa ambos.** La IA capta el matiz, la intención y los defectos de
474
+ diseño que ninguna regex encuentra. Mjölnir capta los patrones
475
+ estructurales que la IA pasa por alto porque parecen "intencionales" —
476
+ un `.only` committeado, un código de salida tragado, un
477
+ `continue-on-error` en un job de test. No son bugs que necesiten
478
+ razonamiento; son hechos que necesitan escaneo.
479
+
480
+ ---
481
+
482
+ ## 🤖 Integración CI
483
+
484
+ Un comando genera un workflow de PR — asesor por defecto, nunca
485
+ bloqueante:
486
+
487
+ ```bash
488
+ mjolnir ci install
489
+ ```
490
+
491
+ O conéctalo de forma nativa a GitHub Code Scanning vía SARIF:
492
+
493
+ ```yaml
494
+ - run: npx mjolnir-qa@latest --format sarif > mjolnir.sarif
495
+ - uses: github/codeql-action/upload-sarif@v3
496
+ with:
497
+ sarif_file: mjolnir.sarif
498
+ ```
499
+
500
+ Configuración para editor y pipeline de SARIF:
501
+ [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md).
502
+
503
+ ### Cobertura de scope cambiado
504
+
505
+ `--scope changed` atribuye hallazgos a líneas añadidas en tu rama
506
+ frente al merge-base con `main`. Cubre archivos de tests
507
+ (`*.spec.*`, `*.test.*`) más archivos de workflow de GitHub y
508
+ configuraciones de Playwright en el diff. Cuando el merge-base no puede
509
+ resolverse — clone superficial, HEAD detached, objetivo sin git, rama
510
+ por defecto distinta — degrada con honestidad: los hallazgos vuelven a
511
+ la atribución por archivo completo y el reporte lo dice. Sobrescribe la
512
+ ref base con `--base <ref>`.
513
+
514
+ ---
515
+
516
+ ## Configuración
517
+
518
+ Mjölnir es cero-config. Un `mjolnir.config.json` opcional (o
519
+ `.mjolnir.json`) en la raíz del repo ajusta severidad, gating y scope —
520
+ nunca cambia la semántica de detección.
521
+
522
+ | Key | Tipo | Efecto |
523
+ | ------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
524
+ | `exclude` | `string[]` | Globs de ignore adicionales (subconjunto de gitignore), encima de los predeterminados integrados |
525
+ | `gate` | `"advisory" \| "error" \| "warning"` | Qué severidades salen con código distinto de cero (por defecto `error`; `advisory` nunca bloquea) |
526
+ | `severityOverrides` | `{ "<RULE-ID>": severity }` | Reordena los hallazgos de una regla para tu repo |
527
+ | `ignore` | `IgnoreEntry[]` | Suprime hallazgos — **`reason` es obligatorio**; las entradas expiran tras 90 días (una fecha `expires` explícita, o la fecha de última modificación del archivo de config para las entradas sin ella) |
528
+ | `plugins` | `string[]` | Paquetes de reglas de terceros (ver [Modelo de confianza](#modelo-de-confianza)) |
529
+
530
+ ```json
531
+ {
532
+ "gate": "error",
533
+ "exclude": ["legacy/**"],
534
+ "severityOverrides": { "QA-PW-118": "warning" },
535
+ "ignore": [
536
+ {
537
+ "ruleId": "QA-TEST-004",
538
+ "files": ["e2e/legacy-login.spec.ts"],
539
+ "reason": "Third-party widget needs a settle delay; tracked in JIRA-4821",
540
+ "expires": "2026-12-31"
541
+ }
542
+ ]
543
+ }
544
+ ```
545
+
546
+ - **`.mjolnirignore`** — un archivo sencillo estilo gitignore para
547
+ exclusiones de rutas, mismo dialecto que `exclude`. Úsalo para ruido
548
+ de máquina; usa `exclude` cuando la lista pertenece al control de
549
+ versiones junto con el resto de la configuración.
550
+ - **Overrides de CLI** — `--strict` (incluir reglas en cuarentena),
551
+ `--width <cols>` y `--ascii` / `--no-ascii` (renderizado de terminal),
552
+ `--tone blunt` (mensajes más secos), `--max-duration <sec>` (escaneo
553
+ parcial acotado).
554
+ - Supresión de reglas y ciclo de vida de deprecación:
555
+ [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md).
556
+
557
+ Las entradas `ignore` también alimentan el comando independiente
558
+ `mjolnir suppressions`, que lista lo que está suprimido actualmente y
559
+ cuándo expira cada entrada.
560
+
561
+ ---
562
+
563
+ ## 📐 Códigos de salida y contratos
564
+
565
+ Congelados — seguros para construir lógica de CI encima:
566
+
567
+ | Código de salida | Significado |
568
+ | ---------------- | ----------------------------------------------------------------------------------- |
569
+ | `0` | Limpio — sin hallazgos en o por encima del gate |
570
+ | `1` | Hallazgos en o por encima del gate |
571
+ | `2` | Escaneo parcial (presupuesto de tiempo agotado, archivos ilegibles) — nunca bloquea |
572
+ | `10` | Error de uso (flag inválido, objetivo faltante) |
573
+ | `20` | Error interno |
574
+
575
+ El reporte JSON/SARIF es `schemaVersion: 1`. Los IDs de reglas
576
+ (`QA-<FAMILY>-NNN`) son inmutables una vez publicados y nunca se
577
+ reutilizan.
578
+
579
+ ---
580
+
581
+ ## Modelo de confianza
582
+
583
+ - **Local-first** — cero llamadas de red durante el escaneo. Nunca.
584
+ Cero telemetría.
585
+ - **Sin prueba falsa** — preferimos decir "desconocido" a "verificado".
586
+ Un repo vacío recibe `score: null`, nunca un falso 100.
587
+ - **Honestidad parcial** — si el análisis se acortó, la salida lo dice.
588
+ Nunca "complete" cuando no lo es.
589
+ - **Cortafuegos FP** — la detección corre sobre una vista del código
590
+ sin comentarios ni cadenas (las reglas de TypeScript usan el AST del
591
+ compilador): un patrón dentro de un comentario de prosa o una cadena
592
+ de ejemplo de documentación es documentación, no un hallazgo.
593
+ - **Medido, no afirmado** — solo reglas con tasa de falsos positivos de
594
+ código OSS real salen en los tiers titulares (ver
595
+ [Cuánto está medido](#cuánto-está-medido)); el pie del escaneo y
596
+ `mjolnir rules --unmeasured` te dicen cuál es cuál.
597
+ - **Confianza en plugins** — los plugins son paquetes npm declarados
598
+ bajo `"plugins"`. **No hay sandbox**: el código del plugin corre con
599
+ todos los privilegios de Node, el mismo modelo de confianza que los
600
+ plugins de ESLint o Vitest. Los prefijos de IDs de reglas core están
601
+ reservados y se rechazan de los plugins para evitar suplantación.
602
+ - **Reglas externas locales al workspace** (basadas en carpeta, cero
603
+ red) — un directorio `mjolnir-rules/` junto al objetivo del escaneo
604
+ carga reglas personalizadas: archivos JSON declaran patrones regex
605
+ (sin código ejecutado), los módulos `.mjs`/`.js` exportan `rules`
606
+ (confianza plena de Node, igual que los plugins). Las reglas externas
607
+ llevan los mismos metadatos de confianza que core; nunca pueden
608
+ salir en el tier core (core exige una tasa de FP medida del sidecar
609
+ de corpus — un `tier: "core"` declarado se restringe a `extended`),
610
+ obedecen los topes de tier y se verifican contra la deriva:
611
+ `mjolnir rules --md --external` renderiza el catálogo desde los
612
+ archivos cargados (procedencia `external`), y el generador de matriz
613
+ acepta `--external <root>`.
614
+
615
+ ---
616
+
617
+ ## 🏗️ Arquitectura
618
+
619
+ <details>
620
+ <summary>Desplegar árbol</summary>
621
+
622
+ ```
623
+ mjolnir/
624
+ ├── src/
625
+ │ ├── engine/ # LanguageAdapter interface + rule runner
626
+ │ ├── adapters/ # typescript · python · java · csharp · github-actions
627
+ │ ├── rules/ # rules across 8 families + the measured-FP table
628
+ │ ├── playwright/ # Selector Health Score engine
629
+ │ ├── discovery/ # workspace, frameworks, ignore resolution
630
+ │ ├── scope/ # git merge-base changed-scope engine
631
+ │ ├── scorer/ # transparent deduction table + prioritization
632
+ │ ├── reporter/ # terminal · JSON · SARIF 2.1 · Mermaid
633
+ │ ├── forensics/ # run-data ingestion · flake verdicts · triage
634
+ │ ├── config/ # mjolnir.config.json + suppressions
635
+ │ ├── plugins/ # third-party rule loading (no sandbox)
636
+ │ └── commands/ # every subcommand
637
+ └── tests/
638
+ ├── fixtures/ # must-fire / must-not-fire per rule
639
+ └── golden/ # frozen score regression locks
640
+ ```
641
+
642
+ </details>
643
+
644
+ - **Las reglas son funciones puras** —
645
+ `(SourceFileContext) → Finding[]`, sin I/O, sin globales. Añadir un
646
+ ecosistema = un adaptador + sus reglas.
647
+ - **TypeScript/Playwright usa el AST del compilador** (ts-morph).
648
+ Python, Java y C# corren sobre una capa de regex compartida con
649
+ comentarios/cadenas enmascarados.
650
+ - Existe una capa de AST tree-sitter WASM para Java y C# y es el
651
+ siguiente paso de precisión — aún no está cableada al pipeline de
652
+ escaneo síncrono.
653
+
654
+ ---
655
+
656
+ ## 📚 Documentación
657
+
658
+ | Documento | Qué contiene |
659
+ | ------------------------------------------------------ | ---------------------------------------------------------- |
660
+ | [docs/SCORING.md](docs/SCORING.md) | Normalización de la puntuación + ponderación por evidencia |
661
+ | [docs/FP-AUDIT.md](docs/FP-AUDIT.md) | Tasas de falsos positivos medidas + método |
662
+ | [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md) | Estados de reglas, supresión, deprecación |
663
+ | [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md) | Salida SARIF + configuración de editor/CI |
664
+ | [docs/rules/](docs/rules/) | Catálogo generado por regla |
665
+ | [CONTRIBUTING.md](CONTRIBUTING.md) | Setup de desarrollo + flujo de contribución |
666
+ | [CHANGELOG.md](CHANGELOG.md) | Historial de versiones |
667
+ | [SECURITY.md](SECURITY.md) | Reporte de vulnerabilidades |
668
+
669
+ ---
670
+
671
+ ## 📈 Estado
672
+
673
+ **v0.5.x · beta abierta.** El esquema JSON y los códigos de salida son
674
+ contratos congelados. TypeScript y Python tienen la cobertura medida
675
+ más amplia; Java y C# son más nuevos — léelos a través de la
676
+ [tabla de tiers](#tiers-de-reglas-y-madurez-por-lenguaje).
677
+
678
+ ---
679
+
680
+ ## 🤝 Contribuir
681
+
682
+ Las reglas nuevas son la contribución inicial más sencilla — un comando
683
+ genera el esqueleto de la regla más sus fixtures must-fire **y**
684
+ must-not-fire (la regla generada falla sus fixtures intencionalmente
685
+ hasta que implementes detección real — un stub no puede publicarse):
686
+
687
+ ```bash
688
+ mjolnir create-rule QA-PW-140 --title "Screenshot without diff bound"
689
+ ```
690
+
691
+ El setup completo de desarrollo, los comandos de la barrera permanente
692
+ y las leyes anti-creep / cortafuegos de fixtures están en
693
+ [CONTRIBUTING.md](CONTRIBUTING.md).
694
+
695
+ ---
696
+
697
+ <div align="center">
698
+
699
+ **Deja de publicar tests en los que no puedes confiar.**
700
+
701
+ ```bash
702
+ npx mjolnir-qa@latest
703
+ ```
704
+
705
+ **Star ⭐ · Watch 👀 · Contribute 🤝**
706
+
707
+ Construido por [Sergey Bar](https://www.linkedin.com/in/sergeybar/)
708
+
709
+ </div>