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.br.md
ADDED
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="assets/readme/logo.png" alt="Mjölnir — Verification Trust Engine" width="800" />
|
|
4
|
+
|
|
5
|
+
### Seus testes estão mentindo para você. Nós provamos.
|
|
6
|
+
|
|
7
|
+
**Verification Trust Engine para QA.** O Mjölnir audita suítes de testes
|
|
8
|
+
e pipelines de CI, reporta uma pontuação de merecimento e mostra
|
|
9
|
+
exatamente onde a confiança se quebra.
|
|
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](README.es.md) | [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.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
|
+
**Seus testes merecem confiança?**
|
|
25
|
+
|
|
26
|
+
[Veja funcionando](#-veja-funcionando) ·
|
|
27
|
+
[Início rápido](#-início-rápido) ·
|
|
28
|
+
[O que ele verifica](#-o-que-o-mjölnir-verifica) ·
|
|
29
|
+
[Pontuação](#como-a-pontuação-funciona) ·
|
|
30
|
+
[CI](#-integração-ci) · [Configuração](#configuração) ·
|
|
31
|
+
[Documentação](#-documentação)
|
|
32
|
+
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🎬 Veja funcionando
|
|
38
|
+
|
|
39
|
+
<p align="center">
|
|
40
|
+
<img src="assets/readme/demo.svg" alt="O relatório --verbose completo do Mjölnir sobre um repo de demonstração: WORTHINESS 75/100 NEEDS WORK, um detalhamento de diagnósticos por categoria, uma lista FIX THIS FIRST e cada finding com o ID da regra e o número da linha através de CI, Playwright, higiene de testes e regras Python" width="900" />
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<sub>A saída completa de `npx mjolnir-qa ./examples/demo-repo --verbose`,
|
|
44
|
+
renderizada pelo reporter real — nada cortado. Regenerada com
|
|
45
|
+
`npm run docs:demo`;
|
|
46
|
+
[`tests/demo-asset-reproducibility.spec.ts`](tests/demo-asset-reproducibility.spec.ts)
|
|
47
|
+
faz a CI falhar se ela divergir do que a ferramenta imprime.</sub>
|
|
48
|
+
|
|
49
|
+
**O que acabou de acontecer:**
|
|
50
|
+
|
|
51
|
+
1. O Mjölnir descobriu as specs do Playwright, a configuração dele, o
|
|
52
|
+
workflow de CI e um arquivo de teste Python — quatro
|
|
53
|
+
linguagens/formatos, uma única passada.
|
|
54
|
+
2. Ele encontrou evidências que enfraquecem a confiança na suíte — um
|
|
55
|
+
`continue-on-error` mascarando um job, um `|| true` engolindo um
|
|
56
|
+
código de saída, sleeps fixos, um seletor frágil, URLs de staging
|
|
57
|
+
fixas no código, uma espera `networkidle`.
|
|
58
|
+
3. Ele transformou cada uma em um finding concreto com ID de regra,
|
|
59
|
+
localização e correção — e uma única pontuação sobre a qual você
|
|
60
|
+
pode fazer o gate de uma PR.
|
|
61
|
+
|
|
62
|
+
### Um finding de perto
|
|
63
|
+
|
|
64
|
+
Execute `mjolnir explain QA-CI-001` no primeiro finding acima e você
|
|
65
|
+
recebe:
|
|
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
|
+
Essa é a unidade de valor: não um detalhe de estilo, mas um lugar onde
|
|
87
|
+
o seu CI diz que algo passou quando não passou.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## ⚡ Início rápido
|
|
92
|
+
|
|
93
|
+
Execute contra um repositório para um relatório completo e uma
|
|
94
|
+
pontuação de merecimento:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npx mjolnir-qa@latest
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Em CI, o produto é um comando.** Ele escaneia apenas o que a branch
|
|
101
|
+
tocou e sai com código diferente de zero diante de problemas novos:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npx mjolnir-qa@latest --scope changed
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Coloque isso em um check de PR — `mjolnir ci install` escreve o
|
|
108
|
+
workflow — e pronto. Todo o resto é opcional.
|
|
109
|
+
|
|
110
|
+
| Comando | O que faz |
|
|
111
|
+
| ----------------------------------- | ------------------------------------------------------------ |
|
|
112
|
+
| `mjolnir` | Escaneio completo do repo + pontuação de merecimento |
|
|
113
|
+
| `mjolnir --scope changed` | Apenas o que a sua branch introduziu — a forma de CI |
|
|
114
|
+
| `mjolnir ci install` | Gera o workflow de PR consultivo |
|
|
115
|
+
| `mjolnir explain QA-CI-001` | O quê / por quê / correção + taxa de FP medida de uma regra |
|
|
116
|
+
| `mjolnir rules --unmeasured` | As regras rodando por suposição, não por medição |
|
|
117
|
+
| `mjolnir --json` / `--format sarif` | Legível por máquina / GitHub Code Scanning |
|
|
118
|
+
| `mjolnir --strict` | Também executa regras do tier quarentena (maior risco de FP) |
|
|
119
|
+
|
|
120
|
+
<details>
|
|
121
|
+
<summary><strong>Quando algo está instável</strong></summary>
|
|
122
|
+
|
|
123
|
+
| Comando | O que faz |
|
|
124
|
+
| ----------------------------------- | ----------------------------------------------------------------- |
|
|
125
|
+
| `mjolnir forensics ./test-results/` | Dados reais de execução → vereditos `TRUE-FLAKE`, `FLAKY.md` |
|
|
126
|
+
| `mjolnir triage ./test-results/` | Proposta de quarentena a partir do histórico de execução |
|
|
127
|
+
| `mjolnir pw-report ./test-results/` | Resumo de execução do Playwright — retries / flakes / mais lentos |
|
|
128
|
+
| `mjolnir doctor:playwright` | Escaneio profundo só do Playwright + Selector Health Score |
|
|
129
|
+
|
|
130
|
+
</details>
|
|
131
|
+
|
|
132
|
+
<details>
|
|
133
|
+
<summary><strong>Ocasional / relatórios</strong></summary>
|
|
134
|
+
|
|
135
|
+
| Comando | O que faz |
|
|
136
|
+
| ------------------------------- | ---------------------------------------------------------- |
|
|
137
|
+
| `mjolnir fix --dry-run` / `fix` | Correções automáticas seguras com prova |
|
|
138
|
+
| `mjolnir baseline` / `diff` | Snapshot de findings, depois reporta só novos/piorados |
|
|
139
|
+
| `mjolnir impact --since <ref>` | O que mudou desde um commit anterior |
|
|
140
|
+
| `mjolnir debt` | Registro de dívida de testes com um modelo de custo |
|
|
141
|
+
| `mjolnir handover` | Mapa de onboarding da suíte para um novo QA |
|
|
142
|
+
| `mjolnir stats` | Contadores locais históricos de correções vistas |
|
|
143
|
+
| `mjolnir badge` | JSON de endpoint do shields.io + snippet |
|
|
144
|
+
| `mjolnir rules --md` | Catálogo completo de regras (JSON ou Markdown) |
|
|
145
|
+
| `mjolnir doctor` | Autoauditoria da própria base de regras do Mjölnir |
|
|
146
|
+
| `mjolnir create-rule <ID>` | Gera o esqueleto de uma nova regra + fixtures |
|
|
147
|
+
| `mjolnir --format mermaid` | Diagrama de arquitetura de testes para um comentário de PR |
|
|
148
|
+
|
|
149
|
+
</details>
|
|
150
|
+
|
|
151
|
+
Instale globalmente em vez de usar `npx` se preferir:
|
|
152
|
+
`npm i -g mjolnir-qa`. Requer Node.js ≥ 22.18. Funciona no Windows,
|
|
153
|
+
macOS e Linux.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 👥 Para quem é isto?
|
|
158
|
+
|
|
159
|
+
- **QA / SDET** donos de uma suíte e2e ou de integração que precisam de
|
|
160
|
+
evidências de que a suíte realmente merece o visto verde que produz.
|
|
161
|
+
- **Equipes de Plataforma / DevEx** responsáveis pela integridade de CI
|
|
162
|
+
e pelos release gates — as pessoas que se importam que um
|
|
163
|
+
`continue-on-error` nunca torne uma pipeline vermelha verde em
|
|
164
|
+
silêncio.
|
|
165
|
+
- **Mantenedores de OSS** que querem um gate de verificação barato,
|
|
166
|
+
sempre ativo, que roda localmente e na CI sem chamadas de rede.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 🔨 O que o Mjölnir verifica
|
|
171
|
+
|
|
172
|
+
| | |
|
|
173
|
+
| --- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
174
|
+
| ⚖️ | **Pontuação de merecimento** — um número, tabela de deduções transparente, sem caixa-preta |
|
|
175
|
+
| 🎭 | **Selector Health Score** — avalia seus locators do Playwright, não só sua taxa de aprovação |
|
|
176
|
+
| 🔬 | **Forense de runtime** — lê dados reais de execução Playwright/JUnit para detectar `TRUE-FLAKE`, não apenas palpites estáticos |
|
|
177
|
+
| 🚨 | **Regras de integridade de CI** — pega `continue-on-error`, `\|\| true` e outros truques de falso verde |
|
|
178
|
+
| 🐍 | **Todos os quatro bindings do Playwright** — TypeScript, Python, Java, C#/.NET — mais pytest, JUnit/TestNG e workflows de CI |
|
|
179
|
+
| 🔒 | **Local-first** — zero chamadas de rede durante o escaneio, zero telemetria, roda em segundos |
|
|
180
|
+
|
|
181
|
+
### As regras
|
|
182
|
+
|
|
183
|
+
Cada regra vem com fixtures must-fire **e** must-not-fire. Uma regra
|
|
184
|
+
que dispara na própria fixture negativa não pode ser publicada — esse
|
|
185
|
+
é o firewall de falsos positivos.
|
|
186
|
+
|
|
187
|
+
<details>
|
|
188
|
+
<summary><strong>Higiene de testes</strong></summary>
|
|
189
|
+
|
|
190
|
+
| ID | Regra | Severity |
|
|
191
|
+
| ----------- | --------------------------------------------------- | -------- |
|
|
192
|
+
| QA-TEST-001 | Teste focado commitado (`.only`, `fit`) | error |
|
|
193
|
+
| QA-TEST-002 | Teste pulado sem justificativa | error |
|
|
194
|
+
| QA-TEST-002 | Teste pulado com justificativa registrada | warning |
|
|
195
|
+
| QA-TEST-003 | Teste sem asserções | error |
|
|
196
|
+
| QA-TEST-004 | Sleep fixo (`waitForTimeout`, `sleep()`, `delay()`) | warning |
|
|
197
|
+
| QA-TEST-006 | Abuso de retry escondendo instabilidade | warning |
|
|
198
|
+
| QA-TEST-010 | Corpo de teste vazio | error |
|
|
199
|
+
|
|
200
|
+
</details>
|
|
201
|
+
|
|
202
|
+
<details>
|
|
203
|
+
<summary><strong>Qualidade de testes</strong></summary>
|
|
204
|
+
|
|
205
|
+
| ID | Regra | Severity |
|
|
206
|
+
| ------------ | ----------------------------- | -------- |
|
|
207
|
+
| QA-TQUAL-001 | Verificação só com mocks | info |
|
|
208
|
+
| QA-TQUAL-002 | Asserção tautológica | error |
|
|
209
|
+
| QA-TQUAL-009 | Asserção de promise sem await | error |
|
|
210
|
+
| QA-TQUAL-011 | Testes comentados | warning |
|
|
211
|
+
|
|
212
|
+
</details>
|
|
213
|
+
|
|
214
|
+
<details>
|
|
215
|
+
<summary><strong>Playwright 🎭</strong></summary>
|
|
216
|
+
|
|
217
|
+
| ID | Regra | Severity |
|
|
218
|
+
| --------- | --------------------------------------------- | -------- |
|
|
219
|
+
| QA-PW-002 | Asserção de locator sem await | error |
|
|
220
|
+
| QA-PW-003 | `page.pause()` / `test.only()` commitados | error |
|
|
221
|
+
| QA-PW-004 | Seletores CSS/XPath frágeis | warning |
|
|
222
|
+
| QA-PW-005 | Lógica de negócio dentro de `page.evaluate()` | info |
|
|
223
|
+
| QA-PW-114 | Element handles legados (`page.$`) | info |
|
|
224
|
+
| QA-PW-118 | Esperas `networkidle` (instáveis por design) | info |
|
|
225
|
+
| QA-PW-123 | URLs de ambiente fixas no código | warning |
|
|
226
|
+
|
|
227
|
+
</details>
|
|
228
|
+
|
|
229
|
+
<details>
|
|
230
|
+
<summary><strong>Integridade de CI</strong></summary>
|
|
231
|
+
|
|
232
|
+
| ID | Regra | Severity |
|
|
233
|
+
| --------- | ----------------------------------------------------------------------- | -------- |
|
|
234
|
+
| QA-CI-001 | `continue-on-error` mascara falhas | error |
|
|
235
|
+
| QA-CI-002 | `\|\| true` engole códigos de saída | error |
|
|
236
|
+
| QA-CI-005 | Relatório consumido mas nunca gerado | error |
|
|
237
|
+
| QA-CI-007 | Wrappers de retry em torno de testes | warning |
|
|
238
|
+
| QA-CI-008 | Step sempre bem-sucedido mascara falhas | error |
|
|
239
|
+
| QA-CI-009 | Código de saída do teste não propagado (`\|` sem pipefail, cadeias `;`) | error |
|
|
240
|
+
| QA-CI-010 | Testes pulados onde devem bloquear (guardas skip-on-PR) | error |
|
|
241
|
+
|
|
242
|
+
</details>
|
|
243
|
+
|
|
244
|
+
<details>
|
|
245
|
+
<summary><strong>Python / pytest 🐍</strong></summary>
|
|
246
|
+
|
|
247
|
+
| ID | Regra | Severity |
|
|
248
|
+
| --------- | --------------------------------------------- | -------- |
|
|
249
|
+
| QA-PY-002 | Teste pulado (`skip`, `xfail` não estrito) | warning |
|
|
250
|
+
| QA-PY-003 | Função de teste sem asserções | error |
|
|
251
|
+
| QA-PY-005 | `time.sleep()` em testes | warning |
|
|
252
|
+
| QA-PY-006 | Corpo de teste vazio (`pass`) | info |
|
|
253
|
+
| QA-PY-010 | Dependência de aleatoriedade/tempo sem freeze | info |
|
|
254
|
+
| QA-PY-012 | Asserção tautológica | error |
|
|
255
|
+
|
|
256
|
+
20 regras Python no 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 | Regra | Severity |
|
|
264
|
+
| --------- | ------------------------------------------- | -------- |
|
|
265
|
+
| QA-JV-101 | Teste desabilitado (`@Disabled`) | warning |
|
|
266
|
+
| QA-JV-102 | Sleep fixo (`Thread.sleep()`) | warning |
|
|
267
|
+
| QA-JV-103 | Método de teste sem asserções | error |
|
|
268
|
+
| QA-JV-105 | Sleep fixo do Playwright `waitForTimeout()` | warning |
|
|
269
|
+
| QA-JV-106 | Seletor frágil em vez de role locator | warning |
|
|
270
|
+
| QA-JV-108 | URL de ambiente fixa no teste | info |
|
|
271
|
+
| QA-JV-111 | Mock generalizado `page.route("**")` | info |
|
|
272
|
+
|
|
273
|
+
</details>
|
|
274
|
+
|
|
275
|
+
<details>
|
|
276
|
+
<summary><strong>C# / .NET — NUnit · xUnit · MSTest 🟣</strong></summary>
|
|
277
|
+
|
|
278
|
+
| ID | Regra | Severity |
|
|
279
|
+
| --------- | ------------------------------------------ | -------- |
|
|
280
|
+
| QA-CS-101 | Teste pulado (`[Ignore]`, `[Fact(Skip=)]`) | warning |
|
|
281
|
+
| QA-CS-102 | Sleep fixo (`Thread.Sleep` / `Task.Delay`) | warning |
|
|
282
|
+
| QA-CS-103 | Método de teste sem asserções | error |
|
|
283
|
+
| QA-CS-105 | Sleep fixo `WaitForTimeoutAsync()` | warning |
|
|
284
|
+
| QA-CS-106 | Seletor frágil em vez de role locator | warning |
|
|
285
|
+
| QA-CS-108 | URL de ambiente fixa no teste | info |
|
|
286
|
+
| QA-CS-111 | Mock generalizado `page.RouteAsync("**")` | info |
|
|
287
|
+
|
|
288
|
+
</details>
|
|
289
|
+
|
|
290
|
+
> O catálogo completo e vivo — cada regra com tier, confidence, risco de
|
|
291
|
+
> falso positivo e disponibilidade de autofix — é gerado a partir do
|
|
292
|
+
> registro:
|
|
293
|
+
>
|
|
294
|
+
> ```bash
|
|
295
|
+
> mjolnir rules --md
|
|
296
|
+
> ```
|
|
297
|
+
>
|
|
298
|
+
> As páginas por regra ficam em [`docs/rules/`](docs/rules/).
|
|
299
|
+
|
|
300
|
+
### Quanto disso é medido
|
|
301
|
+
|
|
302
|
+
**74 de 99 regras carregam uma taxa de falsos positivos medida contra
|
|
303
|
+
código OSS real** (≥ 10 findings classificados à mão cada; veja
|
|
304
|
+
[docs/FP-AUDIT.md](docs/FP-AUDIT.md)). As outras 19 são publicadas com
|
|
305
|
+
a estimativa do autor. O rodapé de cada escaneio diz quantas das regras
|
|
306
|
+
_que dispararam_ são medidas; `mjolnir rules --unmeasured` lista as que
|
|
307
|
+
não são; a página `mjolnir explain` de cada regra declara seu status.
|
|
308
|
+
Publicamos a taxa mesmo quando ela é feia — QA-CS-103 audita em 95 % e
|
|
309
|
+
está em quarentena por isso. Fazer esse 78 crescer é o trabalho contínuo
|
|
310
|
+
do projeto.
|
|
311
|
+
|
|
312
|
+
### Tiers de regras e maturidade por linguagem
|
|
313
|
+
|
|
314
|
+
Cada regra é `core`, `extended` ou `quarantine`, atribuído a partir de
|
|
315
|
+
sua taxa de falsos positivos **medida**:
|
|
316
|
+
|
|
317
|
+
| Tier | Significado | Escaneio padrão | `--strict` |
|
|
318
|
+
| ------------ | ------------------------------------------- | :-------------: | :--------: |
|
|
319
|
+
| `core` | ≤ 10 % de FP medido | ✅ | ✅ |
|
|
320
|
+
| `extended` | ≤ 30 % de FP medido | ✅ | ✅ |
|
|
321
|
+
| `quarantine` | acima de 30 %, ou ainda não medido (n < 10) | ❌ | ✅ |
|
|
322
|
+
|
|
323
|
+
| Linguagem | Adaptador | Cobertura hoje |
|
|
324
|
+
| --------------- | ----------------- | ---------------------------------------------------------------- |
|
|
325
|
+
| TypeScript / JS | AST do compilador | a mais ampla, a mais medida — majoritariamente `core`/`extended` |
|
|
326
|
+
| Python / pytest | Camada regex | ampla, auditada em corpus — majoritariamente `core`/`extended` |
|
|
327
|
+
| Java | Camada regex | mais novo — majoritariamente `extended`/`quarantine` |
|
|
328
|
+
| C# / .NET | Camada regex | mais novo — majoritariamente `extended`/`quarantine` |
|
|
329
|
+
|
|
330
|
+
TypeScript e Python têm a cobertura medida mais ampla. Java e C# são
|
|
331
|
+
publicados, documentados, e ficam fora do número de destaque até que uma
|
|
332
|
+
suíte consumidora real (não os próprios testes de uma biblioteca de
|
|
333
|
+
binding) seja auditada.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Como a pontuação funciona
|
|
338
|
+
|
|
339
|
+
<p align="center">
|
|
340
|
+
<img src="assets/readme/terminal-hero.svg" alt="Saída de terminal do Mjölnir — WORTHINESS 75/100 NEEDS WORK, um detalhamento de diagnósticos por categoria e uma lista FIX THIS FIRST" width="820" />
|
|
341
|
+
</p>
|
|
342
|
+
|
|
343
|
+
<sub>Regenerada com `npm run docs:hero`;
|
|
344
|
+
[`tests/hero-asset-reproducibility.spec.ts`](tests/hero-asset-reproducibility.spec.ts)
|
|
345
|
+
faz a CI falhar se ela divergir do que o reporter realmente imprime.</sub>
|
|
346
|
+
|
|
347
|
+
A pontuação é transparente: **error −8, warning −3, info −1**, depois
|
|
348
|
+
normalizada pela exposição da suíte (deduções por declaração de teste).
|
|
349
|
+
Deduções ponderadas por evidência significam que sinais fracos custam
|
|
350
|
+
menos. O terminal mostra os mesmos números descontados que a pontuação
|
|
351
|
+
usa — sem caixa-preta. Método completo:
|
|
352
|
+
[docs/SCORING.md](docs/SCORING.md).
|
|
353
|
+
|
|
354
|
+
**Vereditos**
|
|
355
|
+
|
|
356
|
+
| Score | Veredito |
|
|
357
|
+
| ------- | ---------------- |
|
|
358
|
+
| ≥ 80 | ✓ **WORTHY** |
|
|
359
|
+
| 50 – 79 | ⚠ **NEEDS WORK** |
|
|
360
|
+
| < 50 | ✖ **UNWORTHY** |
|
|
361
|
+
|
|
362
|
+
**Níveis de evidência** — cada finding carrega um; ele define o peso do
|
|
363
|
+
finding na pontuação:
|
|
364
|
+
|
|
365
|
+
| Nível | Significado | Impacto na pontuação | Exemplo |
|
|
366
|
+
| ----- | ---------------------- | -------------------- | ------------------------------------------------------ |
|
|
367
|
+
| E2 | Defeito determinístico | Dedução total | `.only` commitado — estruturalmente provável |
|
|
368
|
+
| E1 | Padrão heurístico | Meia dedução | `sleep()` detectado por regex — sinal forte, não prova |
|
|
369
|
+
| E0 | Observação | Zero (só info) | Reportado mas nunca faz gate de CI nem deduz |
|
|
370
|
+
|
|
371
|
+
A maioria das regras é **E1**. O lema "we prove it" se refere a este
|
|
372
|
+
sistema: findings E2 são prova estrutural; findings E1 são avisos
|
|
373
|
+
corretamente posicionados, não provas formais.
|
|
374
|
+
|
|
375
|
+
Um repo vazio pontua `null`, nunca um falso 100 — veja o
|
|
376
|
+
[Modelo de confiança](#modelo-de-confiança).
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## 🎭 Selector Health Score
|
|
381
|
+
|
|
382
|
+
A métrica de destaque para suítes Playwright — quão resilientes são os
|
|
383
|
+
seus 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
|
+
Locators baseados em role pontuam o máximo. Cadeias de classes CSS e
|
|
393
|
+
XPath afundam a pontuação — eles quebram em qualquer refactor do DOM
|
|
394
|
+
sem dizer qual comportamento regrediu.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## 🔬 Evidência de runtime
|
|
399
|
+
|
|
400
|
+
Detecção estática de instabilidade é adivinhação. O Mjölnir lê **dados
|
|
401
|
+
reais de execução** — relatórios JSON do Playwright e XML do JUnit de
|
|
402
|
+
qualquer 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
|
+
Um teste que só passa no attempt ≥ 2 não é um teste que passa — é um
|
|
420
|
+
teste sortudo. Ele é marcado como `TRUE-FLAKE` independentemente do
|
|
421
|
+
visto verde final.
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## ⚡ O Mjölnir não é mais um linter
|
|
426
|
+
|
|
427
|
+
Linters dizem se o código segue regras. O Mjölnir diz se a sua
|
|
428
|
+
verificação pode ser confiada.
|
|
429
|
+
|
|
430
|
+
| | ESLint / SonarQube | Ferramentas de coverage | Revisão manual | **Mjölnir** |
|
|
431
|
+
| ----------------------------------------------------------------- | :----------------: | :---------------------: | :------------: | :---------: |
|
|
432
|
+
| Integridade de workflows de CI (`continue-on-error`, `\|\| true`) | ❌ | ❌ | raramente | ✅ |
|
|
433
|
+
| Multilinguagem (TS, Python, Java, C#) a partir de uma ferramenta | ❌ | ❌ | ❌ | ✅ |
|
|
434
|
+
| Avalia a resiliência de locators do Playwright (Selector Health) | ❌ | ❌ | raramente | ✅ |
|
|
435
|
+
| Sinaliza testes sem asserções reais | ✅ (plugin)\* | ❌ | às vezes | ✅ |
|
|
436
|
+
| Pega sleeps fixos (`waitForTimeout`, `time.sleep`) | ✅ (plugin)\* | ❌ | às vezes | ✅ |
|
|
437
|
+
| Roda em segundos, zero chamadas de rede durante o escaneio | ✅ | ✅ | — | ✅ |
|
|
438
|
+
|
|
439
|
+
\*`eslint-plugin-jest` (`expect-expect`) e `eslint-plugin-playwright`
|
|
440
|
+
(`expect-expect`, `no-wait-for-timeout`) cobrem isso para seus
|
|
441
|
+
respectivos frameworks.
|
|
442
|
+
|
|
443
|
+
**A análise de runtime** é uma categoria à parte do linting estático:
|
|
444
|
+
|
|
445
|
+
| | Playwright retry reporter | Allure / ReportPortal | **Mjölnir forensics** |
|
|
446
|
+
| ------------------------------------------------------ | :-----------------------: | :-------------------: | :-------------------: |
|
|
447
|
+
| Lê dados reais de execução para vereditos `TRUE-FLAKE` | parcial\* | parcial (tag) | ✅ |
|
|
448
|
+
| Relatório de triagem de instabilidade do histórico | ❌ | ✅ | ✅ |
|
|
449
|
+
| Integra-se à pontuação de merecimento estática | ❌ | ❌ | ✅ |
|
|
450
|
+
|
|
451
|
+
\*O Playwright rastreia retries internamente mas não produz um relatório
|
|
452
|
+
de instabilidade autônomo com rótulos de veredito.
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## 🤖 Por que não usar apenas revisão de código com IA?
|
|
457
|
+
|
|
458
|
+
Problema diferente, camada diferente. A revisão com IA pode detectar uma
|
|
459
|
+
mudança suspeita de teste em um diff; ela não prova que o sistema de
|
|
460
|
+
verificação como um todo é confiável — e só vê o diff que você mostra.
|
|
461
|
+
|
|
462
|
+
| | Revisão de código com IA (Copilot etc.) | **Mjölnir** |
|
|
463
|
+
| -------------------------------------------- | :-------------------------------------: | :---------------------------------: |
|
|
464
|
+
| Custo por escaneio | Tokens (escala com o tamanho do diff) | **Zero** (local, instalado) |
|
|
465
|
+
| Vê toda a suíte + todas as configs de CI | Só o diff da PR que você mostra | **Tudo, toda vez** |
|
|
466
|
+
| Determinístico (mesma entrada → mesma saída) | ❌ (não determinístico) | **✅** |
|
|
467
|
+
| Pega padrões dormentes por meses | Só se estiver no contexto | **✅** (escaneia todos os arquivos) |
|
|
468
|
+
| Lembra dos findings entre execuções | ❌ (sem memória entre sessões) | **✅** (baseline + diff) |
|
|
469
|
+
| Roda sem gatilho humano | Precisa de uma PR ou prompt | **✅** (hook de CI, 3 segundos) |
|
|
470
|
+
|
|
471
|
+
**Use ambos.** A IA captura nuance, intenção e defeitos de design que
|
|
472
|
+
nenhuma regex encontra. O Mjölnir captura os padrões estruturais que a
|
|
473
|
+
IA ignora porque parecem "intencionais" — um `.only` commitado, um
|
|
474
|
+
código de saída engolido, um `continue-on-error` em um job de teste.
|
|
475
|
+
Não são bugs que precisam de raciocínio; são fatos que precisam de
|
|
476
|
+
escaneio.
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## 🤖 Integração CI
|
|
481
|
+
|
|
482
|
+
Um comando gera um workflow de PR — consultivo por padrão, nunca
|
|
483
|
+
bloqueante:
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
mjolnir ci install
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Ou conecte-o nativamente ao GitHub Code Scanning via SARIF:
|
|
490
|
+
|
|
491
|
+
```yaml
|
|
492
|
+
- run: npx mjolnir-qa@latest --format sarif > mjolnir.sarif
|
|
493
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
494
|
+
with:
|
|
495
|
+
sarif_file: mjolnir.sarif
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Configuração de editor e pipeline para SARIF:
|
|
499
|
+
[docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md).
|
|
500
|
+
|
|
501
|
+
### Cobertura de escopo alterado
|
|
502
|
+
|
|
503
|
+
`--scope changed` atribui findings às linhas adicionadas na sua branch
|
|
504
|
+
em relação ao merge-base com `main`. Ele cobre arquivos de teste
|
|
505
|
+
(`*.spec.*`, `*.test.*`) mais arquivos de workflow do GitHub e
|
|
506
|
+
configurações do Playwright no diff. Quando o merge-base não pode ser
|
|
507
|
+
resolvido — clone raso, HEAD detached, alvo sem git, branch padrão
|
|
508
|
+
diferente — ele degrada com honestidade: os findings voltam à
|
|
509
|
+
atribuição por arquivo inteiro e o relatório diz isso. Sobrescreva a ref
|
|
510
|
+
base com `--base <ref>`.
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## Configuração
|
|
515
|
+
|
|
516
|
+
O Mjölnir é zero-config. Um `mjolnir.config.json` opcional (ou
|
|
517
|
+
`.mjolnir.json`) na raiz do repo ajusta severidade, gating e escopo —
|
|
518
|
+
ele nunca muda a semântica de detecção.
|
|
519
|
+
|
|
520
|
+
| Key | Tipo | Efeito |
|
|
521
|
+
| ------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
522
|
+
| `exclude` | `string[]` | Globs de ignore adicionais (subconjunto do gitignore), além dos padrões embutidos |
|
|
523
|
+
| `gate` | `"advisory" \| "error" \| "warning"` | Quais severidades saem com código diferente de zero (padrão `error`; `advisory` nunca bloqueia) |
|
|
524
|
+
| `severityOverrides` | `{ "<RULE-ID>": severity }` | Reordena os findings de uma regra para o seu repo |
|
|
525
|
+
| `ignore` | `IgnoreEntry[]` | Suprime findings — **`reason` é obrigatório**; as entradas expiram após 90 dias (uma data `expires` explícita, ou a data de modificação do arquivo de config para entradas sem ela) |
|
|
526
|
+
| `plugins` | `string[]` | Pacotes de regras de terceiros (veja o [Modelo de confiança](#modelo-de-confiança)) |
|
|
527
|
+
|
|
528
|
+
```json
|
|
529
|
+
{
|
|
530
|
+
"gate": "error",
|
|
531
|
+
"exclude": ["legacy/**"],
|
|
532
|
+
"severityOverrides": { "QA-PW-118": "warning" },
|
|
533
|
+
"ignore": [
|
|
534
|
+
{
|
|
535
|
+
"ruleId": "QA-TEST-004",
|
|
536
|
+
"files": ["e2e/legacy-login.spec.ts"],
|
|
537
|
+
"reason": "Third-party widget needs a settle delay; tracked in JIRA-4821",
|
|
538
|
+
"expires": "2026-12-31"
|
|
539
|
+
}
|
|
540
|
+
]
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
- **`.mjolnirignore`** — um arquivo simples no estilo gitignore para
|
|
545
|
+
exclusões de caminhos, mesmo dialeto do `exclude`. Use-o para ruído
|
|
546
|
+
específico da máquina; use `exclude` quando a lista pertence ao
|
|
547
|
+
controle de versão, junto com o resto da configuração.
|
|
548
|
+
- **Overrides de CLI** — `--strict` (incluir regras de quarentena),
|
|
549
|
+
`--width <cols>` e `--ascii` / `--no-ascii` (renderização no
|
|
550
|
+
terminal), `--tone blunt` (mensagens mais secas),
|
|
551
|
+
`--max-duration <sec>` (escaneio parcial limitado).
|
|
552
|
+
- Supressão de regras e ciclo de vida de deprecação:
|
|
553
|
+
[docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md).
|
|
554
|
+
|
|
555
|
+
As entradas `ignore` também alimentam o comando autônomo
|
|
556
|
+
`mjolnir suppressions`, que lista o que está suprimido no momento e
|
|
557
|
+
quando cada entrada expira.
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## 📐 Códigos de saída e contratos
|
|
562
|
+
|
|
563
|
+
Congelados — seguros para construir lógica de CI por cima:
|
|
564
|
+
|
|
565
|
+
| Código de saída | Significado |
|
|
566
|
+
| --------------- | ----------------------------------------------------------------------------------- |
|
|
567
|
+
| `0` | Limpo — nenhum finding no nível do gate ou acima |
|
|
568
|
+
| `1` | Findings no nível do gate ou acima |
|
|
569
|
+
| `2` | Escaneio parcial (orçamento de tempo esgotado, arquivos ilegíveis) — nunca bloqueia |
|
|
570
|
+
| `10` | Erro de uso (flag inválida, alvo ausente) |
|
|
571
|
+
| `20` | Erro interno |
|
|
572
|
+
|
|
573
|
+
O relatório JSON/SARIF é `schemaVersion: 1`. Os IDs de regra
|
|
574
|
+
(`QA-<FAMILY>-NNN`) são imutáveis uma vez publicados e nunca reutilizados.
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
## Modelo de confiança
|
|
579
|
+
|
|
580
|
+
- **Local-first** — zero chamadas de rede durante o escaneio. Nunca.
|
|
581
|
+
Zero telemetria.
|
|
582
|
+
- **Nenhuma prova falsa** — preferimos dizer "desconhecido" a
|
|
583
|
+
"verificado". Um repo vazio recebe `score: null`, nunca um falso 100.
|
|
584
|
+
- **Honestidade parcial** — se a análise foi interrompida, a saída diz
|
|
585
|
+
isso. Nunca "complete" quando não está.
|
|
586
|
+
- **Firewall de FP** — a detecção roda sobre uma visão do código sem
|
|
587
|
+
comentários/strings (as regras TypeScript usam o AST do compilador):
|
|
588
|
+
um padrão dentro de um comentário de prosa ou de uma string de
|
|
589
|
+
exemplo de documentação é documentação, não um finding.
|
|
590
|
+
- **Medido, não afirmado** — apenas regras com taxa de falsos positivos
|
|
591
|
+
de código OSS real entram nos tiers de destaque (veja
|
|
592
|
+
[Quanto disso é medido](#quanto-disso-é-medido)); o rodapé do
|
|
593
|
+
escaneio e o `mjolnir rules --unmeasured` dizem qual é qual.
|
|
594
|
+
- **Confiança em plugins** — plugins são pacotes npm declarados sob
|
|
595
|
+
`"plugins"`. **Não há sandbox**: o código do plugin roda com todos os
|
|
596
|
+
privilégios do Node, o mesmo modelo de confiança dos plugins ESLint
|
|
597
|
+
ou Vitest. Prefixos de IDs de regras core são reservados e rejeitados
|
|
598
|
+
de plugins para evitar falsificação.
|
|
599
|
+
- **Regras externas locais ao workspace** (baseadas em pasta, zero
|
|
600
|
+
rede) — um diretório `mjolnir-rules/` ao lado do alvo do escaneio
|
|
601
|
+
carrega regras personalizadas: arquivos JSON declaram padrões regex
|
|
602
|
+
(nenhum código executado), módulos `.mjs`/`.js` exportam `rules`
|
|
603
|
+
(confiança total do Node, como os plugins). Regras externas carregam
|
|
604
|
+
os mesmos metadados de confiança do core; elas nunca podem entrar no
|
|
605
|
+
tier core (core exige uma taxa de FP medida do sidecar de corpus — um
|
|
606
|
+
`tier: "core"` declarado é limitado a `extended`), obedecem aos tetos
|
|
607
|
+
de tier e são verificadas contra deriva:
|
|
608
|
+
`mjolnir rules --md --external` renderiza o catálogo a partir dos
|
|
609
|
+
arquivos carregados (proveniência `external`), e o gerador de matriz
|
|
610
|
+
aceita `--external <root>`.
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
## 🏗️ Arquitetura
|
|
615
|
+
|
|
616
|
+
<details>
|
|
617
|
+
<summary>Expandir árvore</summary>
|
|
618
|
+
|
|
619
|
+
```
|
|
620
|
+
mjolnir/
|
|
621
|
+
├── src/
|
|
622
|
+
│ ├── engine/ # LanguageAdapter interface + rule runner
|
|
623
|
+
│ ├── adapters/ # typescript · python · java · csharp · github-actions
|
|
624
|
+
│ ├── rules/ # rules across 8 families + the measured-FP table
|
|
625
|
+
│ ├── playwright/ # Selector Health Score engine
|
|
626
|
+
│ ├── discovery/ # workspace, frameworks, ignore resolution
|
|
627
|
+
│ ├── scope/ # git merge-base changed-scope engine
|
|
628
|
+
│ ├── scorer/ # transparent deduction table + prioritization
|
|
629
|
+
│ ├── reporter/ # terminal · JSON · SARIF 2.1 · Mermaid
|
|
630
|
+
│ ├── forensics/ # run-data ingestion · flake verdicts · triage
|
|
631
|
+
│ ├── config/ # mjolnir.config.json + suppressions
|
|
632
|
+
│ ├── plugins/ # third-party rule loading (no sandbox)
|
|
633
|
+
│ └── commands/ # every subcommand
|
|
634
|
+
└── tests/
|
|
635
|
+
├── fixtures/ # must-fire / must-not-fire per rule
|
|
636
|
+
└── golden/ # frozen score regression locks
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
</details>
|
|
640
|
+
|
|
641
|
+
- **Regras são funções puras** — `(SourceFileContext) → Finding[]`,
|
|
642
|
+
sem I/O, sem globais. Adicionar um ecossistema = um adaptador + suas
|
|
643
|
+
regras.
|
|
644
|
+
- **TypeScript/Playwright usa o AST do compilador** (ts-morph). Python,
|
|
645
|
+
Java e C# rodam em uma camada regex compartilhada com
|
|
646
|
+
comentários/strings mascarados.
|
|
647
|
+
- Uma camada de AST tree-sitter WASM para Java e C# existe e é o
|
|
648
|
+
próximo passo de precisão — ainda não está ligada ao pipeline de
|
|
649
|
+
escaneio síncrono.
|
|
650
|
+
|
|
651
|
+
---
|
|
652
|
+
|
|
653
|
+
## 📚 Documentação
|
|
654
|
+
|
|
655
|
+
| Documento | O que contém |
|
|
656
|
+
| ------------------------------------------------------ | ---------------------------------------------------- |
|
|
657
|
+
| [docs/SCORING.md](docs/SCORING.md) | Normalização da pontuação + ponderação por evidência |
|
|
658
|
+
| [docs/FP-AUDIT.md](docs/FP-AUDIT.md) | Taxas de falsos positivos medidas + método |
|
|
659
|
+
| [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md) | Estados de regras, supressão, deprecação |
|
|
660
|
+
| [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md) | Saída SARIF + configuração de editor/CI |
|
|
661
|
+
| [docs/rules/](docs/rules/) | Catálogo gerado por regra |
|
|
662
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | Setup de dev + fluxo de contribuição |
|
|
663
|
+
| [CHANGELOG.md](CHANGELOG.md) | Histórico de releases |
|
|
664
|
+
| [SECURITY.md](SECURITY.md) | Relato de vulnerabilidades |
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## 📈 Status
|
|
669
|
+
|
|
670
|
+
**v0.5.x · beta aberto.** O schema JSON e os códigos de saída são
|
|
671
|
+
contratos congelados. TypeScript e Python têm a cobertura medida mais
|
|
672
|
+
ampla; Java e C# são mais novos — leia-os através da
|
|
673
|
+
[tabela de tiers](#tiers-de-regras-e-maturidade-por-linguagem).
|
|
674
|
+
|
|
675
|
+
---
|
|
676
|
+
|
|
677
|
+
## 🤝 Contribuir
|
|
678
|
+
|
|
679
|
+
Novas regras são a primeira contribuição mais fácil — um comando gera o
|
|
680
|
+
esqueleto da regra mais suas fixtures must-fire **e** must-not-fire (a
|
|
681
|
+
regra gerada falha nas fixtures de propósito até você implementar
|
|
682
|
+
detecção real — um stub não pode ser publicado):
|
|
683
|
+
|
|
684
|
+
```bash
|
|
685
|
+
mjolnir create-rule QA-PW-140 --title "Screenshot without diff bound"
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
Setup completo de dev, os comandos do gate permanente e as leis
|
|
689
|
+
anti-creep / firewall de fixtures estão no
|
|
690
|
+
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
691
|
+
|
|
692
|
+
---
|
|
693
|
+
|
|
694
|
+
<div align="center">
|
|
695
|
+
|
|
696
|
+
**Pare de publicar testes em que não pode confiar.**
|
|
697
|
+
|
|
698
|
+
```bash
|
|
699
|
+
npx mjolnir-qa@latest
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
**Star ⭐ · Watch 👀 · Contribute 🤝**
|
|
703
|
+
|
|
704
|
+
Construído por [Sergey Bar](https://www.linkedin.com/in/sergeybar/)
|
|
705
|
+
|
|
706
|
+
</div>
|