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.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
+ [![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](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>