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.fr.md ADDED
@@ -0,0 +1,714 @@
1
+ <div align="center">
2
+
3
+ <img src="assets/readme/logo.png" alt="Mjölnir — Verification Trust Engine" width="800" />
4
+
5
+ ### Vos tests vous mentent. Nous le prouvons.
6
+
7
+ **Verification Trust Engine pour la QA.** Mjölnir audite les suites de
8
+ tests et les pipelines CI, rapporte un score de fiabilité et montre
9
+ exactement où la confiance se brise.
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 | [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
+ **Vos tests sont-ils dignes de confiance ?**
25
+
26
+ [Le voir en action](#-le-voir-en-action) ·
27
+ [Démarrage rapide](#-démarrage-rapide) ·
28
+ [Ce qu'il vérifie](#-ce-que-mjölnir-vérifie) ·
29
+ [Scoring](#comment-le-score-fonctionne) ·
30
+ [CI](#-intégration-ci) · [Configuration](#configuration) ·
31
+ [Documentation](#-documentation)
32
+
33
+ </div>
34
+
35
+ ---
36
+
37
+ ## 🎬 Le voir en action
38
+
39
+ <p align="center">
40
+ <img src="assets/readme/demo.svg" alt="Le rapport --verbose complet de Mjölnir sur un dépôt de démo : WORTHINESS 75/100 NEEDS WORK, une répartition des diagnostics par catégorie, une liste FIX THIS FIRST, et chaque constat avec son ID de règle et son numéro de ligne à travers CI, Playwright, l'hygiène des tests et les règles Python" width="900" />
41
+ </p>
42
+
43
+ <sub>La sortie complète de `npx mjolnir-qa ./examples/demo-repo --verbose`,
44
+ rendue par le vrai reporter — rien de rogné. Régénérée par
45
+ `npm run docs:demo` ;
46
+ [`tests/demo-asset-reproducibility.spec.ts`](tests/demo-asset-reproducibility.spec.ts)
47
+ fait échouer la CI si elle dérive de ce que l'outil imprime.</sub>
48
+
49
+ **Ce qui vient de se passer :**
50
+
51
+ 1. Mjölnir a découvert les specs Playwright, sa configuration, le
52
+ workflow CI et un fichier de test Python — quatre langages/formats,
53
+ une seule passe.
54
+ 2. Il a trouvé des preuves qui affaiblissent la confiance dans la suite
55
+ — un `continue-on-error` qui masque un job, un `|| true` qui avale un
56
+ code de sortie, des sleeps en dur, un sélecteur fragile, des URLs de
57
+ staging codées en dur, une attente `networkidle`.
58
+ 3. Il en a fait un constat concret avec un ID de règle, un emplacement
59
+ et un correctif — et un score unique sur lequel gate une PR.
60
+
61
+ ### Un constat de près
62
+
63
+ Exécutez `mjolnir explain QA-CI-001` sur le premier constat ci-dessus
64
+ et vous obtenez :
65
+
66
+ ```text
67
+ ▚▞ QA-CI-001 — continue-on-error masks a failing verification gate
68
+
69
+ Severity: error
70
+ Confidence: high
71
+ Evidence: E2
72
+ Measured FP: not yet measured — this rule ships on assumption (see docs/FP-AUDIT.md)
73
+
74
+ WHAT WAS FOUND (real detector output, not a mockup)
75
+ Job `security-scan` runs a verification gate under `continue-on-error: true`.
76
+
77
+ WHY IT MATTERS
78
+ This job can fail every day and CI will still show green. The checkmark
79
+ on this workflow cannot be trusted.
80
+
81
+ HOW TO FIX
82
+ Remove continue-on-error, or scope it to individual non-blocking steps only.
83
+ ```
84
+
85
+ C'est l'unité de valeur : pas une broutille de style, mais un endroit
86
+ où votre CI affiche quelque chose comme réussi alors que ça ne l'est
87
+ pas.
88
+
89
+ ---
90
+
91
+ ## ⚡ Démarrage rapide
92
+
93
+ Exécutez-le sur un dépôt pour un rapport complet et un score de
94
+ fiabilité :
95
+
96
+ ```bash
97
+ npx mjolnir-qa@latest
98
+ ```
99
+
100
+ **En CI, le produit tient en une commande.** Il ne scanne que ce que la
101
+ branche a touché et sort avec un code non nul sur de nouveaux
102
+ problèmes :
103
+
104
+ ```bash
105
+ npx mjolnir-qa@latest --scope changed
106
+ ```
107
+
108
+ Déposez ça dans un check de PR — `mjolnir ci install` écrit le workflow —
109
+ et c'est fini. Tout le reste est optionnel.
110
+
111
+ | Commande | Ce qu'elle fait |
112
+ | ----------------------------------- | ---------------------------------------------------------------------- |
113
+ | `mjolnir` | Scan complet du dépôt + score de fiabilité |
114
+ | `mjolnir --scope changed` | Uniquement ce que votre branche a introduit — la forme CI |
115
+ | `mjolnir ci install` | Génère le workflow de PR consultatif |
116
+ | `mjolnir explain QA-CI-001` | Quoi / pourquoi / correctif + taux de FP mesuré d'une règle |
117
+ | `mjolnir rules --unmeasured` | Les règles qui tournent sur hypothèse, pas sur mesure |
118
+ | `mjolnir --json` / `--format sarif` | Lisible par machine / GitHub Code Scanning |
119
+ | `mjolnir --strict` | Exécute aussi les règles du tier quarantaine (risque de FP plus élevé) |
120
+
121
+ <details>
122
+ <summary><strong>Quand quelque chose est instable</strong></summary>
123
+
124
+ | Commande | Ce qu'elle fait |
125
+ | ----------------------------------- | -------------------------------------------------------------- |
126
+ | `mjolnir forensics ./test-results/` | Vraies données d'exécution → verdicts `TRUE-FLAKE`, `FLAKY.md` |
127
+ | `mjolnir triage ./test-results/` | Proposition de quarantaine issue de l'historique d'exécution |
128
+ | `mjolnir pw-report ./test-results/` | Synthèse de run Playwright — retries / flakes / plus lents |
129
+ | `mjolnir doctor:playwright` | Scan profond Playwright uniquement + Selector Health Score |
130
+
131
+ </details>
132
+
133
+ <details>
134
+ <summary><strong>Occasionnel / rapports</strong></summary>
135
+
136
+ | Commande | Ce qu'elle fait |
137
+ | ------------------------------- | ----------------------------------------------------------------- |
138
+ | `mjolnir fix --dry-run` / `fix` | Auto-corrections sûres avec preuve |
139
+ | `mjolnir baseline` / `diff` | Instantané des constats, puis rapport des seuls nouveaux/aggravés |
140
+ | `mjolnir impact --since <ref>` | Ce qui a changé depuis un commit antérieur |
141
+ | `mjolnir debt` | Registre de dette de test avec un modèle de coût |
142
+ | `mjolnir handover` | Carte d'onboarding de la suite pour un nouveau QA |
143
+ | `mjolnir stats` | Compteurs locaux de tous les correctifs vus |
144
+ | `mjolnir badge` | JSON d'endpoint shields.io + snippet |
145
+ | `mjolnir rules --md` | Catalogue complet des règles (JSON ou Markdown) |
146
+ | `mjolnir doctor` | Auto-audit de la propre base de règles de Mjölnir |
147
+ | `mjolnir create-rule <ID>` | Scafholde une nouvelle règle + ses fixtures |
148
+ | `mjolnir --format mermaid` | Diagramme d'architecture de test pour un commentaire de PR |
149
+
150
+ </details>
151
+
152
+ Installez-le globalement plutôt qu'en `npx` si vous préférez :
153
+ `npm i -g mjolnir-qa`. Requiert Node.js ≥ 22.18. Fonctionne sous
154
+ Windows, macOS et Linux.
155
+
156
+ ---
157
+
158
+ ## 👥 À qui ça s'adresse ?
159
+
160
+ - **QA / SDET** propriétaires d'une suite e2e ou d'intégration qui ont
161
+ besoin de la preuve que la suite mérite vraiment la coche verte
162
+ qu'elle produit.
163
+ - **Équipes Plateforme / DevEx** responsables de l'intégrité CI et des
164
+ release gates — celles et ceux pour qui un `continue-on-error` ne
165
+ doit jamais repeindre en vert une pipeline rouge en silence.
166
+ - **Mainteneurs OSS** qui veulent un gate de vérification bon marché,
167
+ toujours actif, qui tourne en local et en CI sans aucun appel réseau.
168
+
169
+ ---
170
+
171
+ ## 🔨 Ce que Mjölnir vérifie
172
+
173
+ | | |
174
+ | --- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
175
+ | ⚖️ | **Score de fiabilité** — un chiffre, une table de déductions transparente, aucune boîte noire |
176
+ | 🎭 | **Selector Health Score** — note vos locators Playwright, pas seulement votre taux de réussite |
177
+ | 🔬 | **Forensique d'exécution** — lit de vraies données de run Playwright/JUnit pour détecter `TRUE-FLAKE`, pas seulement des suppositions statiques |
178
+ | 🚨 | **Règles d'intégrité CI** — attrape `continue-on-error`, `\|\| true` et autres astuces à faux vert |
179
+ | 🐍 | **Les quatre bindings Playwright** — TypeScript, Python, Java, C#/.NET — plus pytest, JUnit/TestNG et workflows CI |
180
+ | 🔒 | **Local-first** — zéro appel réseau pendant le scan, zéro télémétrie, s'exécute en secondes |
181
+
182
+ ### Les règles
183
+
184
+ Chaque règle est livrée avec des fixtures must-fire **et** must-not-fire.
185
+ Une règle qui se déclenche sur sa propre fixture négative ne peut pas
186
+ sortir — c'est le pare-feu anti faux positifs.
187
+
188
+ <details>
189
+ <summary><strong>Hygiène des tests</strong></summary>
190
+
191
+ | ID | Règle | Severity |
192
+ | ----------- | ----------------------------------------------------- | -------- |
193
+ | QA-TEST-001 | Test focalisé commité (`.only`, `fit`) | error |
194
+ | QA-TEST-002 | Test sauté sans justification | error |
195
+ | QA-TEST-002 | Test sauté avec justification tracée | warning |
196
+ | QA-TEST-003 | Test sans assertion | error |
197
+ | QA-TEST-004 | Sleep en dur (`waitForTimeout`, `sleep()`, `delay()`) | warning |
198
+ | QA-TEST-006 | Abus de retry masquant l'instabilité | warning |
199
+ | QA-TEST-010 | Corps de test vide | error |
200
+
201
+ </details>
202
+
203
+ <details>
204
+ <summary><strong>Qualité des tests</strong></summary>
205
+
206
+ | ID | Règle | Severity |
207
+ | ------------ | --------------------------------- | -------- |
208
+ | QA-TQUAL-001 | Vérification par mocks uniquement | info |
209
+ | QA-TQUAL-002 | Assertion tautologique | error |
210
+ | QA-TQUAL-009 | Assertion de promise non awaitée | error |
211
+ | QA-TQUAL-011 | Tests commentés | warning |
212
+
213
+ </details>
214
+
215
+ <details>
216
+ <summary><strong>Playwright 🎭</strong></summary>
217
+
218
+ | ID | Règle | Severity |
219
+ | --------- | ------------------------------------------------ | -------- |
220
+ | QA-PW-002 | Assertion de locator non awaitée | error |
221
+ | QA-PW-003 | `page.pause()` / `test.only()` commités | error |
222
+ | QA-PW-004 | Sélecteurs CSS/XPath fragiles | warning |
223
+ | QA-PW-005 | Logique métier dans `page.evaluate()` | info |
224
+ | QA-PW-114 | Element handles historiques (`page.$`) | info |
225
+ | QA-PW-118 | Attentes `networkidle` (instables de conception) | info |
226
+ | QA-PW-123 | URLs d'environnement codées en dur | warning |
227
+
228
+ </details>
229
+
230
+ <details>
231
+ <summary><strong>Intégrité CI</strong></summary>
232
+
233
+ | ID | Règle | Severity |
234
+ | --------- | -------------------------------------------------------------------- | -------- |
235
+ | QA-CI-001 | `continue-on-error` masque les échecs | error |
236
+ | QA-CI-002 | `\|\| true` avale les codes de sortie | error |
237
+ | QA-CI-005 | Rapport consommé mais jamais généré | error |
238
+ | QA-CI-007 | Wrappers de retry autour des tests | warning |
239
+ | QA-CI-008 | Step toujours réussi masquant les échecs | error |
240
+ | QA-CI-009 | Code de sortie du test non propagé (`\|` sans pipefail, chaînes `;`) | error |
241
+ | QA-CI-010 | Tests sautés là où ils doivent bloquer (gardes skip-on-PR) | error |
242
+
243
+ </details>
244
+
245
+ <details>
246
+ <summary><strong>Python / pytest 🐍</strong></summary>
247
+
248
+ | ID | Règle | Severity |
249
+ | --------- | ----------------------------------------- | -------- |
250
+ | QA-PY-002 | Test sauté (`skip`, `xfail` non strict) | warning |
251
+ | QA-PY-003 | Fonction de test sans assertion | error |
252
+ | QA-PY-005 | `time.sleep()` dans les tests | warning |
253
+ | QA-PY-006 | Corps de test vide (`pass`) | info |
254
+ | QA-PY-010 | Dépendance au hasard/au temps sans freeze | info |
255
+ | QA-PY-012 | Assertion tautologique | error |
256
+
257
+ 20 règles Python au total (QA-PY-001…012 hygiène pytest + QA-PY-101…108 Playwright-Python).
258
+
259
+ </details>
260
+
261
+ <details>
262
+ <summary><strong>Java / JUnit · TestNG ☕</strong></summary>
263
+
264
+ | ID | Règle | Severity |
265
+ | --------- | --------------------------------------------- | -------- |
266
+ | QA-JV-101 | Test désactivé (`@Disabled`) | warning |
267
+ | QA-JV-102 | Sleep en dur (`Thread.sleep()`) | warning |
268
+ | QA-JV-103 | Méthode de test sans assertion | error |
269
+ | QA-JV-105 | Sleep en dur Playwright `waitForTimeout()` | warning |
270
+ | QA-JV-106 | Sélecteur fragile au lieu d'un role locator | warning |
271
+ | QA-JV-108 | URL d'environnement codée en dur dans le test | info |
272
+ | QA-JV-111 | Mock blanket `page.route("**")` | info |
273
+
274
+ </details>
275
+
276
+ <details>
277
+ <summary><strong>C# / .NET — NUnit · xUnit · MSTest 🟣</strong></summary>
278
+
279
+ | ID | Règle | Severity |
280
+ | --------- | --------------------------------------------- | -------- |
281
+ | QA-CS-101 | Test sauté (`[Ignore]`, `[Fact(Skip=)]`) | warning |
282
+ | QA-CS-102 | Sleep en dur (`Thread.Sleep` / `Task.Delay`) | warning |
283
+ | QA-CS-103 | Méthode de test sans assertion | error |
284
+ | QA-CS-105 | Sleep en dur `WaitForTimeoutAsync()` | warning |
285
+ | QA-CS-106 | Sélecteur fragile au lieu d'un role locator | warning |
286
+ | QA-CS-108 | URL d'environnement codée en dur dans le test | info |
287
+ | QA-CS-111 | Mock blanket `page.RouteAsync("**")` | info |
288
+
289
+ </details>
290
+
291
+ > Le catalogue live complet — chaque règle avec son tier, sa
292
+ > confidence, son risque de faux positif et sa disponibilité d'autofix —
293
+ > est généré depuis le registre :
294
+ >
295
+ > ```bash
296
+ > mjolnir rules --md
297
+ > ```
298
+ >
299
+ > Les pages par règle vivent sous [`docs/rules/`](docs/rules/).
300
+
301
+ ### Quelle part est mesurée
302
+
303
+ **74 des 99 règles portent un taux de faux positifs mesuré sur du vrai
304
+ code OSS** (≥ 10 constats classés à la main chacun ; voir
305
+ [docs/FP-AUDIT.md](docs/FP-AUDIT.md)). Les 19 autres sortent sur
306
+ l'estimation de l'auteur. Chaque pied de scan vous dit combien des
307
+ règles _déclenchées_ sont mesurées ; `mjolnir rules --unmeasured` liste
308
+ celles qui ne le sont pas ; la page `mjolnir explain` de chaque règle
309
+ énonce son statut. Nous publions le taux même quand il est laid —
310
+ QA-CS-103 s'audite à 95 % et est mis en quarantaine pour ça. Faire
311
+ grandir ce 78 est le travail continu du projet.
312
+
313
+ ### Tiers de règles et maturité par langage
314
+
315
+ Chaque règle est `core`, `extended` ou `quarantine`, attribué d'après
316
+ son taux de faux positifs **mesuré** :
317
+
318
+ | Tier | Signification | Scan par défaut | `--strict` |
319
+ | ------------ | ------------------------------------------------ | :-------------: | :--------: |
320
+ | `core` | ≤ 10 % de FP mesuré | ✅ | ✅ |
321
+ | `extended` | ≤ 30 % de FP mesuré | ✅ | ✅ |
322
+ | `quarantine` | au-dessus de 30 %, ou pas encore mesuré (n < 10) | ❌ | ✅ |
323
+
324
+ | Langage | Adaptateur | Couverture aujourd'hui |
325
+ | --------------- | --------------- | ---------------------------------------------------------- |
326
+ | TypeScript / JS | AST compilateur | la plus large, la plus mesurée — surtout `core`/`extended` |
327
+ | Python / pytest | Couche regex | large, auditée sur corpus — surtout `core`/`extended` |
328
+ | Java | Couche regex | plus récent — surtout `extended`/`quarantine` |
329
+ | C# / .NET | Couche regex | plus récent — surtout `extended`/`quarantine` |
330
+
331
+ TypeScript et Python ont la couverture mesurée la plus large. Java et
332
+ C# sont livrés, documentés, et restent hors du chiffre vedette tant
333
+ qu'une vraie suite consommatrice (pas les propres tests d'une
334
+ bibliothèque de binding) n'a pas été auditée.
335
+
336
+ ---
337
+
338
+ ## Comment le score fonctionne
339
+
340
+ <p align="center">
341
+ <img src="assets/readme/terminal-hero.svg" alt="Sortie terminal de Mjölnir — WORTHINESS 75/100 NEEDS WORK, une répartition des diagnostics par catégorie et une liste FIX THIS FIRST" width="820" />
342
+ </p>
343
+
344
+ <sub>Régénérée par `npm run docs:hero` ;
345
+ [`tests/hero-asset-reproducibility.spec.ts`](tests/hero-asset-reproducibility.spec.ts)
346
+ fait échouer la CI si elle dérive de ce que le reporter imprime
347
+ réellement.</sub>
348
+
349
+ Le score est transparent : **error −8, warning −3, info −1**, puis
350
+ normalisé par l'exposition de la suite (déductions par déclaration de
351
+ test). Les déductions pondérées par la preuve signifient que les
352
+ signaux faibles coûtent moins cher. Le terminal affiche les mêmes
353
+ chiffres actualisés que ceux du score — pas de boîte noire. Méthode
354
+ complète : [docs/SCORING.md](docs/SCORING.md).
355
+
356
+ **Verdicts**
357
+
358
+ | Score | Verdict |
359
+ | ------- | ---------------- |
360
+ | ≥ 80 | ✓ **WORTHY** |
361
+ | 50 – 79 | ⚠ **NEEDS WORK** |
362
+ | < 50 | ✖ **UNWORTHY** |
363
+
364
+ **Niveaux de preuve** — chaque constat en porte un ; il fixe le poids
365
+ du constat dans le score :
366
+
367
+ | Niveau | Signification | Impact sur le score | Exemple |
368
+ | ------ | ------------------- | --------------------- | --------------------------------------------------------- |
369
+ | E2 | Défaut déterministe | Déduction pleine | `.only` commité — structurellement prouvable |
370
+ | E1 | Motif heuristique | Déduction moitié | `sleep()` détecté par regex — signal fort, pas une preuve |
371
+ | E0 | Observation | Zéro (info seulement) | Rapporté mais ne gate jamais la CI ni ne déduit |
372
+
373
+ La plupart des règles sont **E1**. Le slogan « we prove it » renvoie à
374
+ ce système : les constats E2 sont une preuve structurelle ; les
375
+ constats E1 sont des avertissements correctement positionnés, pas des
376
+ preuves formelles.
377
+
378
+ Un dépôt vide obtient `null`, jamais un faux 100 — voir
379
+ [Modèle de confiance](#modèle-de-confiance).
380
+
381
+ ---
382
+
383
+ ## 🎭 Selector Health Score
384
+
385
+ La métrique vedette pour les suites Playwright — la résilience de vos
386
+ locators :
387
+
388
+ ```text
389
+ ▚▞ SELECTOR HEALTH — e2e/checkout.spec.ts
390
+
391
+ [█████████████████░░░] 83 / 100
392
+ role/text: 2 · testid: 1 · css-chains: 1 ⚠ · xpath: 0
393
+ ```
394
+
395
+ Les locators basés sur les rôles obtiennent la note maximale. Les
396
+ chaînes de classes CSS et le XPath effondrent le score — ils cassent à
397
+ chaque refonte du DOM sans vous dire quel comportement a régressé.
398
+
399
+ ---
400
+
401
+ ## 🔬 Preuves d'exécution
402
+
403
+ La détection statique d'instabilité, c'est deviner. Mjölnir lit de
404
+ **vraies données d'exécution** — rapports JSON Playwright et XML JUnit
405
+ de n'importe quel runner :
406
+
407
+ ```bash
408
+ mjolnir forensics ./test-results/
409
+ ```
410
+
411
+ ```text
412
+ ▚▞ FLAKINESS LEADERBOARD
413
+
414
+ 3 tests · 1 failed · 1 flaky · 1 retried
415
+
416
+ TRUE-FLAKE completes checkout with saved card (e2e/checkout.spec.ts)
417
+ ████████████████████ 6.0s · 2 attempts
418
+ FAILING declines an expired card (e2e/checkout.spec.ts)
419
+ ████░░░░░░░░░░░░░░░░ 1.1s · 1 attempt
420
+ ```
421
+
422
+ Un test qui ne passe qu'à partir de la tentative ≥ 2 n'est pas un test
423
+ qui passe — c'est un test chanceux. Il est marqué `TRUE-FLAKE` quel que
424
+ soit le vert final de la coche.
425
+
426
+ ---
427
+
428
+ ## ⚡ Mjölnir n'est pas un linter de plus
429
+
430
+ Les linters vous disent si le code suit des règles. Mjölnir vous dit si
431
+ votre vérification peut être crue.
432
+
433
+ | | ESLint / SonarQube | Outils de coverage | Revue manuelle | **Mjölnir** |
434
+ | ------------------------------------------------------------- | :----------------: | :----------------: | :------------: | :---------: |
435
+ | Intégrité des workflows CI (`continue-on-error`, `\|\| true`) | ❌ | ❌ | rarement | ✅ |
436
+ | Multi-langage (TS, Python, Java, C#) depuis un seul outil | ❌ | ❌ | ❌ | ✅ |
437
+ | Note la résilience des locators Playwright (Selector Health) | ❌ | ❌ | rarement | ✅ |
438
+ | Signale les tests sans vraies assertions | ✅ (plugin)\* | ❌ | parfois | ✅ |
439
+ | Détecte les sleeps en dur (`waitForTimeout`, `time.sleep`) | ✅ (plugin)\* | ❌ | parfois | ✅ |
440
+ | Tourne en secondes, zéro appel réseau pendant le scan | ✅ | ✅ | — | ✅ |
441
+
442
+ \*`eslint-plugin-jest` (`expect-expect`) et `eslint-plugin-playwright`
443
+ (`expect-expect`, `no-wait-for-timeout`) couvrent cela pour leurs
444
+ frameworks respectifs.
445
+
446
+ **L'analyse d'exécution** est une catégorie à part du linting
447
+ statique :
448
+
449
+ | | Playwright retry reporter | Allure / ReportPortal | **Mjölnir forensics** |
450
+ | ----------------------------------------------------------- | :-----------------------: | :-------------------: | :-------------------: |
451
+ | Lit de vraies données de run pour des verdicts `TRUE-FLAKE` | partiel\* | partiel (tag) | ✅ |
452
+ | Rapport de triage d'instabilité depuis l'historique | ❌ | ✅ | ✅ |
453
+ | S'intègre au score de fiabilité statique | ❌ | ❌ | ✅ |
454
+
455
+ \*Playwright suit les retries en interne mais ne produit pas de rapport
456
+ d'instabilité autonome avec des étiquettes de verdict.
457
+
458
+ ---
459
+
460
+ ## 🤖 Pourquoi pas simplement une revue de code par IA ?
461
+
462
+ Un problème différent, une autre couche. Une revue IA peut repérer un
463
+ changement de test suspect dans un diff ; elle ne prouve pas que le
464
+ système de vérification dans son ensemble est digne de confiance — et
465
+ elle ne voit que le diff que vous lui montrez.
466
+
467
+ | | Revue de code IA (Copilot, etc.) | **Mjölnir** |
468
+ | ------------------------------------------- | :------------------------------------: | :-------------------------------: |
469
+ | Coût par scan | Tokens (évolue avec la taille du diff) | **Zéro** (local, installé) |
470
+ | Voit toute la suite + toutes les configs CI | Seulement le diff de PR montré | **Tout, à chaque fois** |
471
+ | Déterministe (même entrée → même sortie) | ❌ (non déterministe) | **✅** |
472
+ | Détecte des motifs dormants depuis des mois | Seulement s'il est dans le contexte | **✅** (scanne tous les fichiers) |
473
+ | Se souvient des constats entre les runs | ❌ (aucune mémoire entre sessions) | **✅** (baseline + diff) |
474
+ | Tourne sans déclencheur humain | Nécessite une PR ou un prompt | **✅** (hook CI, 3 secondes) |
475
+
476
+ **Utilisez les deux.** L'IA attrape la nuance, l'intention et les
477
+ défauts de conception qu'aucune regex ne trouve. Mjölnir attrape les
478
+ motifs structurels que l'IA néglige parce qu'ils semblent
479
+ « intentionnels » — un `.only` commité, un code de sortie avalé, un
480
+ `continue-on-error` sur un job de test. Ce ne sont pas des bugs qui
481
+ demandent du raisonnement ; ce sont des faits qui demandent un scan.
482
+
483
+ ---
484
+
485
+ ## 🤖 Intégration CI
486
+
487
+ Une commande génère un workflow de PR — consultatif par défaut, jamais
488
+ bloquant :
489
+
490
+ ```bash
491
+ mjolnir ci install
492
+ ```
493
+
494
+ Ou branchez-le nativement dans GitHub Code Scanning via SARIF :
495
+
496
+ ```yaml
497
+ - run: npx mjolnir-qa@latest --format sarif > mjolnir.sarif
498
+ - uses: github/codeql-action/upload-sarif@v3
499
+ with:
500
+ sarif_file: mjolnir.sarif
501
+ ```
502
+
503
+ Configuration éditeur et pipeline pour SARIF :
504
+ [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md).
505
+
506
+ ### Couverture du périmètre modifié
507
+
508
+ `--scope changed` attribue les constats aux lignes ajoutées dans votre
509
+ branche par rapport à la base de fusion avec `main`. Il couvre les
510
+ fichiers de tests (`*.spec.*`, `*.test.*`) plus les fichiers de
511
+ workflow GitHub et les configurations Playwright du diff. Quand la base
512
+ de fusion ne peut pas être résolue — clone superficiel, HEAD détaché,
513
+ cible non-git, branche par défaut différente — il dégrade honnêtement :
514
+ les constats retombent sur une attribution au fichier entier, et le
515
+ rapport le dit. Remplacez la ref de base avec `--base <ref>`.
516
+
517
+ ---
518
+
519
+ ## Configuration
520
+
521
+ Mjölnir est zéro-config. Un `mjolnir.config.json` optionnel (ou
522
+ `.mjolnir.json`) à la racine du dépôt ajuste sévérité, gating et
523
+ périmètre — il ne change jamais la sémantique de détection.
524
+
525
+ | Clé | Type | Effet |
526
+ | ------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
527
+ | `exclude` | `string[]` | Globs d'ignore supplémentaires (sous-ensemble gitignore), au-dessus des valeurs par défaut intégrées |
528
+ | `gate` | `"advisory" \| "error" \| "warning"` | Quelles sévérités sortent avec un code non nul (défaut `error` ; `advisory` ne bloque jamais) |
529
+ | `severityOverrides` | `{ "<RULE-ID>": severity }` | Re-classe les constats d'une règle pour votre dépôt |
530
+ | `ignore` | `IgnoreEntry[]` | Supprime des constats — **`reason` est obligatoire** ; les entrées expirent après 90 jours (une date `expires` explicite, ou la date de dernière modification du fichier de config pour les entrées sans) |
531
+ | `plugins` | `string[]` | Paquets de règles tiers (voir [Modèle de confiance](#modèle-de-confiance)) |
532
+
533
+ ```json
534
+ {
535
+ "gate": "error",
536
+ "exclude": ["legacy/**"],
537
+ "severityOverrides": { "QA-PW-118": "warning" },
538
+ "ignore": [
539
+ {
540
+ "ruleId": "QA-TEST-004",
541
+ "files": ["e2e/legacy-login.spec.ts"],
542
+ "reason": "Third-party widget needs a settle delay; tracked in JIRA-4821",
543
+ "expires": "2026-12-31"
544
+ }
545
+ ]
546
+ }
547
+ ```
548
+
549
+ - **`.mjolnirignore`** — un fichier simple façon gitignore pour les
550
+ exclusions de chemins, même dialecte que `exclude`. Utilisez-le pour
551
+ le bruit propre à la machine ; utilisez `exclude` quand la liste
552
+ appartient au contrôle de version, à côté du reste de la config.
553
+ - **Surclassements CLI** — `--strict` (inclure les règles en
554
+ quarantaine), `--width <cols>` et `--ascii` / `--no-ascii` (rendu
555
+ terminal), `--tone blunt` (messages plus directs),
556
+ `--max-duration <sec>` (scan partiel borné).
557
+ - Suppression de règles et cycle de vie de dépréciation :
558
+ [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md).
559
+
560
+ Les entrées `ignore` alimentent aussi la commande autonome
561
+ `mjolnir suppressions`, qui liste ce qui est actuellement supprimé et
562
+ quand chaque entrée expire.
563
+
564
+ ---
565
+
566
+ ## 📐 Codes de sortie & contrats
567
+
568
+ Figés — sûrs pour construire de la logique CI dessus :
569
+
570
+ | Code de sortie | Signification |
571
+ | -------------- | ------------------------------------------------------------------------------ |
572
+ | `0` | Propre — aucun constat au-dessus ou à l'égal du gate |
573
+ | `1` | Constats au-dessus ou à l'égal du gate |
574
+ | `2` | Scan partiel (budget de temps atteint, fichiers illisibles) — ne bloque jamais |
575
+ | `10` | Erreur d'usage (mauvais flag, cible manquante) |
576
+ | `20` | Erreur interne |
577
+
578
+ Le rapport JSON/SARIF est `schemaVersion: 1`. Les ID de règles
579
+ (`QA-<FAMILY>-NNN`) sont immuables une fois livrés et jamais
580
+ réutilisés.
581
+
582
+ ---
583
+
584
+ ## Modèle de confiance
585
+
586
+ - **Local-first** — zéro appel réseau pendant le scan. Jamais. Zéro
587
+ télémétrie.
588
+ - **Pas de fausse preuve** — nous préférons dire « inconnu » que
589
+ « vérifié ». Un dépôt vide obtient `score: null`, jamais un faux 100.
590
+ - **Honnêteté partielle** — si l'analyse a été interrompue, la sortie le
591
+ dit. Jamais « complete » quand ça ne l'est pas.
592
+ - **Pare-feu FP** — la détection tourne sur une vue du code sans
593
+ commentaires ni chaînes (les règles TypeScript utilisent l'AST du
594
+ compilateur) : un motif dans un commentaire de prose ou une chaîne
595
+ d'exemple de doc est de la documentation, pas un constat.
596
+ - **Mesuré, pas affirmé** — seules les règles avec un taux de faux
597
+ positifs issu de vrai code OSS sortent dans les tiers vedettes (voir
598
+ [Quelle part est mesurée](#quelle-part-est-mesurée)) ; le pied de scan
599
+ et `mjolnir rules --unmeasured` vous disent lesquels.
600
+ - **Confiance plugins** — les plugins sont des paquets npm déclarés
601
+ sous `"plugins"`. Il n'y a **pas de sandbox** : le code d'un plugin
602
+ tourne avec tous les privilèges Node, le même modèle de confiance que
603
+ les plugins ESLint ou Vitest. Les préfixes d'ID de règles core sont
604
+ réservés et refusés aux plugins pour empêcher l'usurpation.
605
+ - **Règles externes locales au workspace** (par dossier, zéro réseau) —
606
+ un répertoire `mjolnir-rules/` à côté de la cible du scan charge des
607
+ règles personnalisées : des fichiers JSON déclarent des motifs regex
608
+ (aucun code exécuté), les modules `.mjs`/`.js` exportent `rules`
609
+ (confiance Node complète, comme les plugins). Les règles externes
610
+ portent les mêmes métadonnées de confiance que core ; elles ne
611
+ peuvent jamais sortir dans le tier core (core exige un taux de FP
612
+ mesuré depuis le sidecar corpus — un `tier: "core"` déclaré est
613
+ borné à `extended`), obéissent aux caps de tier et sont vérifiées
614
+ contre la dérive : `mjolnir rules --md --external` rend le catalogue
615
+ depuis les fichiers chargés (provenance `external`), et le générateur
616
+ de matrice accepte `--external <root>`.
617
+
618
+ ---
619
+
620
+ ## 🏗️ Architecture
621
+
622
+ <details>
623
+ <summary>Déplier l'arbre</summary>
624
+
625
+ ```
626
+ mjolnir/
627
+ ├── src/
628
+ │ ├── engine/ # LanguageAdapter interface + rule runner
629
+ │ ├── adapters/ # typescript · python · java · csharp · github-actions
630
+ │ ├── rules/ # rules across 8 families + the measured-FP table
631
+ │ ├── playwright/ # Selector Health Score engine
632
+ │ ├── discovery/ # workspace, frameworks, ignore resolution
633
+ │ ├── scope/ # git merge-base changed-scope engine
634
+ │ ├── scorer/ # transparent deduction table + prioritization
635
+ │ ├── reporter/ # terminal · JSON · SARIF 2.1 · Mermaid
636
+ │ ├── forensics/ # run-data ingestion · flake verdicts · triage
637
+ │ ├── config/ # mjolnir.config.json + suppressions
638
+ │ ├── plugins/ # third-party rule loading (no sandbox)
639
+ │ └── commands/ # every subcommand
640
+ └── tests/
641
+ ├── fixtures/ # must-fire / must-not-fire per rule
642
+ └── golden/ # frozen score regression locks
643
+ ```
644
+
645
+ </details>
646
+
647
+ - **Les règles sont des fonctions pures** —
648
+ `(SourceFileContext) → Finding[]`, pas d'I/O, pas de globales. Ajouter
649
+ un écosystème = un adaptateur + ses règles.
650
+ - **TypeScript/Playwright utilise l'AST du compilateur** (ts-morph).
651
+ Python, Java et C# tournent sur une couche regex partagée avec
652
+ commentaires/chaînes masqués.
653
+ - Une couche AST tree-sitter WASM pour Java et C# existe et constitue
654
+ la prochaine étape de précision — elle n'est pas encore câblée dans
655
+ le pipeline de scan synchrone.
656
+
657
+ ---
658
+
659
+ ## 📚 Documentation
660
+
661
+ | Document | Contenu |
662
+ | ------------------------------------------------------ | -------------------------------------------------- |
663
+ | [docs/SCORING.md](docs/SCORING.md) | Normalisation du score + pondération par la preuve |
664
+ | [docs/FP-AUDIT.md](docs/FP-AUDIT.md) | Taux de faux positifs mesurés + méthode |
665
+ | [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md) | États des règles, suppression, dépréciation |
666
+ | [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md) | Sortie SARIF + configuration éditeur/CI |
667
+ | [docs/rules/](docs/rules/) | Catalogue généré par règle |
668
+ | [CONTRIBUTING.md](CONTRIBUTING.md) | Setup dev + workflow de contribution |
669
+ | [CHANGELOG.md](CHANGELOG.md) | Historique des versions |
670
+ | [SECURITY.md](SECURITY.md) | Signalement de vulnérabilités |
671
+
672
+ ---
673
+
674
+ ## 📈 Statut
675
+
676
+ **v0.5.x · bêta ouverte.** Le schéma JSON et les codes de sortie sont
677
+ des contrats figés. TypeScript et Python ont la couverture mesurée la
678
+ plus large ; Java et C# sont plus récents — lisez-les via la
679
+ [table des tiers](#tiers-de-règles-et-maturité-par-langage).
680
+
681
+ ---
682
+
683
+ ## 🤝 Contribuer
684
+
685
+ Les nouvelles règles sont la première contribution la plus simple —
686
+ une commande scaffolde la règle plus ses fixtures must-fire **et**
687
+ must-not-fire (la règle générée échoue volontairement à ses fixtures
688
+ tant que vous n'implémentez pas une vraie détection — un stub ne peut
689
+ pas sortir) :
690
+
691
+ ```bash
692
+ mjolnir create-rule QA-PW-140 --title "Screenshot without diff bound"
693
+ ```
694
+
695
+ Le setup dev complet, les commandes de la barrière permanente et les
696
+ lois anti-dérive / pare-feu à fixtures sont dans
697
+ [CONTRIBUTING.md](CONTRIBUTING.md).
698
+
699
+ ---
700
+
701
+ <div align="center">
702
+
703
+ **Ne livrez plus des tests auxquels vous ne pouvez pas faire
704
+ confiance.**
705
+
706
+ ```bash
707
+ npx mjolnir-qa@latest
708
+ ```
709
+
710
+ **Star ⭐ · Watch 👀 · Contribute 🤝**
711
+
712
+ Construit par [Sergey Bar](https://www.linkedin.com/in/sergeybar/)
713
+
714
+ </div>