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/CHANGELOG.md +1199 -28
- package/README.ar.md +667 -0
- package/README.bn.md +678 -0
- package/README.br.md +706 -0
- package/README.bs.md +690 -0
- package/README.da.md +698 -0
- package/README.de.md +711 -0
- package/README.es.md +709 -0
- package/README.fr.md +714 -0
- package/README.gr.md +705 -0
- package/README.he.md +664 -0
- package/README.it.md +709 -0
- package/README.ja.md +692 -0
- package/README.ko.md +681 -0
- package/README.md +425 -152
- package/README.no.md +696 -0
- package/README.pl.md +699 -0
- package/README.ru.md +701 -0
- package/README.th.md +672 -0
- package/README.tr.md +699 -0
- package/README.uk.md +693 -0
- package/README.vi.md +680 -0
- package/README.zh.md +652 -0
- package/README.zht.md +652 -0
- package/dist/cli.d.mts +469 -22
- package/dist/cli.mjs +9780 -5230
- package/package.json +25 -7
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
|
+
[](https://www.npmjs.com/package/mjolnir-qa)
|
|
12
|
+
[](https://github.com/Sergey-Bar/Mjolnir/actions/workflows/ci.yml)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
[](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>
|