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.da.md ADDED
@@ -0,0 +1,698 @@
1
+ <div align="center">
2
+
3
+ <img src="assets/readme/logo.png" alt="Mjölnir — Verification Trust Engine" width="800" />
4
+
5
+ ### Dine tests lyver for dig. Vi beviser det.
6
+
7
+ **Verification Trust Engine til QA.** Mjölnir auditor testsuiter og
8
+ CI-pipelines, rapporterer en værdighedsscore og viser præcis, hvor
9
+ tilliden brister.
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.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
+ **Er dine tests værd at stole på?**
25
+
26
+ [Se det virke](#-se-det-virke) ·
27
+ [Hurtig start](#-hurtig-start) ·
28
+ [Hvad den tjekker](#-hvad-mjölnir-tjekker) ·
29
+ [Scoring](#sådan-fungerer-scoren) ·
30
+ [CI](#-ci-integration) · [Konfiguration](#konfiguration) ·
31
+ [Dokumentation](#-dokumentation)
32
+
33
+ </div>
34
+
35
+ ---
36
+
37
+ ## 🎬 Se det virke
38
+
39
+ <p align="center">
40
+ <img src="assets/readme/demo.svg" alt="Mjölnirs fulde --verbose-rapport over et demo-repo: WORTHINESS 75/100 NEEDS WORK, en opdeling af diagnostik efter kategori, en FIX THIS FIRST-liste og hvert fund med regel-ID og linjenummer på tværs af CI-, Playwright-, testhygiejne- og Python-regler" width="900" />
41
+ </p>
42
+
43
+ <sub>Det komplette `npx mjolnir-qa ./examples/demo-repo --verbose`-output,
44
+ renderet af den rigtige reporter — intet klippet væk. Regenereres med
45
+ `npm run docs:demo`;
46
+ [`tests/demo-asset-reproducibility.spec.ts`](tests/demo-asset-reproducibility.spec.ts)
47
+ får CI til at fejle, hvis artefaktet afviger fra, hvad værktøjet
48
+ printer.</sub>
49
+
50
+ **Hvad der lige er sket:**
51
+
52
+ 1. Mjölnir opdagede Playwright-specs, dens konfiguration,
53
+ CI-workflowet og en Python-testfil — fire sprog/formater, ét
54
+ gennemløb.
55
+ 2. Den fandt beviser, der svækker tilliden til suiten — en
56
+ `continue-on-error`, der maskerer et job, en `|| true`, der synker
57
+ en exit-kode, hårde sleeps, en skrøbelig selector, hårdkodede
58
+ staging-URL'er, en `networkidle`-venten.
59
+ 3. Den gjorde hver af dem til et konkret fund med regel-ID, placering
60
+ og fix — og til én score, du kan gate en PR på.
61
+
62
+ ### Ét fund på nært hold
63
+
64
+ Kør `mjolnir explain QA-CI-001` på det første fund ovenfor, og du får:
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
+ Det er værdiens enhed: ikke en stilprik, men et sted, hvor dit CI
86
+ fortæller dig, at noget bestod, selvom det ikke gjorde.
87
+
88
+ ---
89
+
90
+ ## ⚡ Hurtig start
91
+
92
+ Kør den mod et repo for en fuld rapport og en værdighedsscore:
93
+
94
+ ```bash
95
+ npx mjolnir-qa@latest
96
+ ```
97
+
98
+ **I CI er produktet én kommando.** Den scanner kun det, branchen rørte,
99
+ og afslutter med ikke-nul ved nye problemer:
100
+
101
+ ```bash
102
+ npx mjolnir-qa@latest --scope changed
103
+ ```
104
+
105
+ Smid det ind som et PR-check — `mjolnir ci install` skriver workflowet —
106
+ og du er færdig. Alt andet er valgfrit.
107
+
108
+ | Kommando | Hvad den gør |
109
+ | ----------------------------------- | -------------------------------------------------- |
110
+ | `mjolnir` | Scanning af hele repoet + værdighedsscore |
111
+ | `mjolnir --scope changed` | Kun det, din branch introducerede — CI-formen |
112
+ | `mjolnir ci install` | Genererer den vejledende PR-workflow |
113
+ | `mjolnir explain QA-CI-001` | Hvad / hvorfor / fix + målt FP-rate for én regel |
114
+ | `mjolnir rules --unmeasured` | Reglerne, der kører på antagelse, ikke måling |
115
+ | `mjolnir --json` / `--format sarif` | Maskinlæsbart / GitHub Code Scanning |
116
+ | `mjolnir --strict` | Kør også quarantine-tier-regler (højere FP-risiko) |
117
+
118
+ <details>
119
+ <summary><strong>Når noget er flaky</strong></summary>
120
+
121
+ | Kommando | Hvad den gør |
122
+ | ----------------------------------- | --------------------------------------------------------- |
123
+ | `mjolnir forensics ./test-results/` | Ægte kørselsdata → `TRUE-FLAKE`-domme, `FLAKY.md` |
124
+ | `mjolnir triage ./test-results/` | Karantæneforslag fra eksekveringshistorikken |
125
+ | `mjolnir pw-report ./test-results/` | Playwright-runoversigt — retries / flakes / de langsomste |
126
+ | `mjolnir doctor:playwright` | Deep scan kun Playwright + Selector Health Score |
127
+
128
+ </details>
129
+
130
+ <details>
131
+ <summary><strong>Lejlighedsvis / rapporter</strong></summary>
132
+
133
+ | Kommando | Hvad den gør |
134
+ | ------------------------------- | ------------------------------------------------------- |
135
+ | `mjolnir fix --dry-run` / `fix` | Sikre autofixes med bevis |
136
+ | `mjolnir baseline` / `diff` | Snapshot af fund, rapportér derefter kun nye/forværrede |
137
+ | `mjolnir impact --since <ref>` | Hvad der ændrede sig siden et tidligere commit |
138
+ | `mjolnir debt` | Testgældsregister med en kostmodel |
139
+ | `mjolnir handover` | Onboarding-kort over suiten til ny QA |
140
+ | `mjolnir stats` | Lokale all-time-tællere af sete fixes |
141
+ | `mjolnir badge` | shields.io-endpoint-JSON + snippet |
142
+ | `mjolnir rules --md` | Fuldt regelkatalog (JSON eller Markdown) |
143
+ | `mjolnir doctor` | Selvundersøgelse af Mjölnirs egen regelbase |
144
+ | `mjolnir create-rule <ID>` | Scaffold en ny regel + fixtures |
145
+ | `mjolnir --format mermaid` | Testarkitekturdiagram til en PR-kommentar |
146
+
147
+ </details>
148
+
149
+ Installér globalt i stedet for `npx`, hvis du foretrækker det:
150
+ `npm i -g mjolnir-qa`. Kræver Node.js ≥ 22.18. Virker på Windows, macOS
151
+ og Linux.
152
+
153
+ ---
154
+
155
+ ## 👥 Hvem er det til?
156
+
157
+ - **QA / SDET**, der ejer en e2e- eller integrationsuite og har brug
158
+ for beviser for, at suiten faktisk fortjener den grønne check, den
159
+ producerer.
160
+ - **Platform-/DevEx-teams**, der har ansvaret for CI-integritet og
161
+ release gates — folkene, for hvem en `continue-on-error` aldrig må
162
+ male en rød pipeline grøn i stilhed.
163
+ - **OSS-maintainere**, der vil have en billig, altid aktiveret
164
+ verifikationsgate, der kører lokalt og i CI uden netværkskald.
165
+
166
+ ---
167
+
168
+ ## 🔨 Hvad Mjölnir tjekker
169
+
170
+ | | |
171
+ | --- | ------------------------------------------------------------------------------------------------------------------- |
172
+ | ⚖️ | **Værdighedsscore** — ét tal, transparent fradragstabel, ingen black box |
173
+ | 🎭 | **Selector Health Score** — bedømmer dine Playwright-locators, ikke kun din pass rate |
174
+ | 🔬 | **Runtime-forundersøgelse** — læser ægte Playwright/JUnit-kørselsdata og fanger `TRUE-FLAKE`, ikke kun statiske gæt |
175
+ | 🚨 | **CI-integritetsregler** — fanger `continue-on-error`, `\|\| true` og andre falsk-grønne tricks |
176
+ | 🐍 | **Alle fire Playwright-bindings** — TypeScript, Python, Java, C#/.NET — plus pytest, JUnit/TestNG og CI-workflows |
177
+ | 🔒 | **Local-first** — nul netværkskald under scanning, nul telemetri, kører på sekunder |
178
+
179
+ ### Reglerne
180
+
181
+ Hver regel leveres med både must-fire- **og** must-not-fire-fixtures.
182
+ En regel, der udløses på sin egen negative fixture, kan ikke skibes —
183
+ det er false-positive-firewallen.
184
+
185
+ <details>
186
+ <summary><strong>Testhygiejne</strong></summary>
187
+
188
+ | ID | Regel | Severity |
189
+ | ----------- | ---------------------------------------------------- | -------- |
190
+ | QA-TEST-001 | Committet fokuseret test (`.only`, `fit`) | error |
191
+ | QA-TEST-002 | Sprunget test uden begrundelse | error |
192
+ | QA-TEST-002 | Sprunget test med registreret begrundelse | warning |
193
+ | QA-TEST-003 | Test uden assertions | error |
194
+ | QA-TEST-004 | Hårdt sleep (`waitForTimeout`, `sleep()`, `delay()`) | warning |
195
+ | QA-TEST-006 | Retry-misbrug, der skjuler flakiness | warning |
196
+ | QA-TEST-010 | Tomt testlegeme | error |
197
+
198
+ </details>
199
+
200
+ <details>
201
+ <summary><strong>Testkvalitet</strong></summary>
202
+
203
+ | ID | Regel | Severity |
204
+ | ------------ | ------------------------------- | -------- |
205
+ | QA-TQUAL-001 | Kun-mock-verifikation | info |
206
+ | QA-TQUAL-002 | Tautologisk assertion | error |
207
+ | QA-TQUAL-009 | Assertion på promise uden await | error |
208
+ | QA-TQUAL-011 | Udkommenterede tests | warning |
209
+
210
+ </details>
211
+
212
+ <details>
213
+ <summary><strong>Playwright 🎭</strong></summary>
214
+
215
+ | ID | Regel | Severity |
216
+ | --------- | ---------------------------------------- | -------- |
217
+ | QA-PW-002 | Locator-assertion uden await | error |
218
+ | QA-PW-003 | `page.pause()` / `test.only()` committet | error |
219
+ | QA-PW-004 | Skrøbelige CSS/XPath-selectors | warning |
220
+ | QA-PW-005 | Forretningslogik i `page.evaluate()` | info |
221
+ | QA-PW-114 | Legacy element handles (`page.$`) | info |
222
+ | QA-PW-118 | `networkidle`-venten (flaky by design) | info |
223
+ | QA-PW-123 | Hårdkodede miljø-URL'er | warning |
224
+
225
+ </details>
226
+
227
+ <details>
228
+ <summary><strong>CI-integritet</strong></summary>
229
+
230
+ | ID | Regel | Severity |
231
+ | --------- | ----------------------------------------------------------------- | -------- |
232
+ | QA-CI-001 | `continue-on-error` maskerer fejl | error |
233
+ | QA-CI-002 | `\|\| true` synker exit-koder | error |
234
+ | QA-CI-005 | Rapport forbruges, men genereres aldrig | error |
235
+ | QA-CI-007 | Retry-wrappers omkring tests | warning |
236
+ | QA-CI-008 | Altid-succesfuldt step maskerer fejl | error |
237
+ | QA-CI-009 | Testens exit-kode propageres ikke (`\|` uden pipefail, `;`-kæder) | error |
238
+ | QA-CI-010 | Tests sprunget over, hvor de skal blokere (skip-on-PR-guards) | error |
239
+
240
+ </details>
241
+
242
+ <details>
243
+ <summary><strong>Python / pytest 🐍</strong></summary>
244
+
245
+ | ID | Regel | Severity |
246
+ | --------- | ------------------------------------------- | -------- |
247
+ | QA-PY-002 | Sprunget test (`skip`, ikke-strikt `xfail`) | warning |
248
+ | QA-PY-003 | Testfunktion uden assertions | error |
249
+ | QA-PY-005 | `time.sleep()` i tests | warning |
250
+ | QA-PY-006 | Tomt testlegeme (`pass`) | info |
251
+ | QA-PY-010 | Tilfældigheds-/tidsafhængighed uden freeze | info |
252
+ | QA-PY-012 | Tautologisk assertion | error |
253
+
254
+ 20 Python-regler i alt (QA-PY-001…012 pytest-hygiejne + QA-PY-101…108 Playwright-Python).
255
+
256
+ </details>
257
+
258
+ <details>
259
+ <summary><strong>Java / JUnit · TestNG ☕</strong></summary>
260
+
261
+ | ID | Regel | Severity |
262
+ | --------- | -------------------------------------------- | -------- |
263
+ | QA-JV-101 | Deaktiveret test (`@Disabled`) | warning |
264
+ | QA-JV-102 | Hårdt sleep (`Thread.sleep()`) | warning |
265
+ | QA-JV-103 | Testmetode uden assertions | error |
266
+ | QA-JV-105 | Playwright hårdt sleep `waitForTimeout()` | warning |
267
+ | QA-JV-106 | Skrøbelig selector i stedet for role-locator | warning |
268
+ | QA-JV-108 | Hårdkodet miljø-URL i test | info |
269
+ | QA-JV-111 | Blanket-mock `page.route("**")` | info |
270
+
271
+ </details>
272
+
273
+ <details>
274
+ <summary><strong>C# / .NET — NUnit · xUnit · MSTest 🟣</strong></summary>
275
+
276
+ | ID | Regel | Severity |
277
+ | --------- | -------------------------------------------- | -------- |
278
+ | QA-CS-101 | Sprunget test (`[Ignore]`, `[Fact(Skip=)]`) | warning |
279
+ | QA-CS-102 | Hårdt sleep (`Thread.Sleep` / `Task.Delay`) | warning |
280
+ | QA-CS-103 | Testmetode uden assertions | error |
281
+ | QA-CS-105 | Hårdt sleep `WaitForTimeoutAsync()` | warning |
282
+ | QA-CS-106 | Skrøbelig selector i stedet for role-locator | warning |
283
+ | QA-CS-108 | Hårdkodet miljø-URL i test | info |
284
+ | QA-CS-111 | Blanket-mock `page.RouteAsync("**")` | info |
285
+
286
+ </details>
287
+
288
+ > Det fulde, levende katalog — hver regel med tier, confidence,
289
+ > false-positive-risiko og autofix-tilgængelighed — genereres fra
290
+ > registreren:
291
+ >
292
+ > ```bash
293
+ > mjolnir rules --md
294
+ > ```
295
+ >
296
+ > Sider pr. regel ligger under [`docs/rules/`](docs/rules/).
297
+
298
+ ### Hvor meget er målt
299
+
300
+ **74 af 99 regler bærer en false-positive-rate målt mod rigtig OSS-kode**
301
+ (≥ 10 håndklassificerede fund hver; se
302
+ [docs/FP-AUDIT.md](docs/FP-AUDIT.md)). De andre 19 skiber på forfatterens
303
+ estimat. Hver scan-fodnote fortæller, hvor mange af de _udløste_ regler,
304
+ der er målt; `mjolnir rules --unmeasured` lister de uregistrerede; hver
305
+ regels `mjolnir explain`-side angiver dens status. Vi offentliggør
306
+ raten, selv når den er grim — QA-CS-103 auditeres til 95 % og er sat i
307
+ karantæne for det. At få de 78 til at vokse er projektets fortsatte
308
+ arbejde.
309
+
310
+ ### Regel-tiers og sproglig modenhed
311
+
312
+ Hver regel er `core`, `extended` eller `quarantine`, tildelt ud fra sin
313
+ **målte** false-positive-rate:
314
+
315
+ | Tier | Betydning | Standardscan | `--strict` |
316
+ | ------------ | ----------------------------------------- | :----------: | :--------: |
317
+ | `core` | ≤ 10 % målt FP | ✅ | ✅ |
318
+ | `extended` | ≤ 30 % målt FP | ✅ | ✅ |
319
+ | `quarantine` | over 30 %, eller endnu ikke målt (n < 10) | ❌ | ✅ |
320
+
321
+ | Sprog | Adapter | Dækning i dag |
322
+ | --------------- | ------------ | ------------------------------------------------ |
323
+ | TypeScript / JS | Compiler-AST | bredeste, mest målte — mest `core`/`extended` |
324
+ | Python / pytest | Regex-lag | bredt, corpus-auditeret — mest `core`/`extended` |
325
+ | Java | Regex-lag | nyere — mest `extended`/`quarantine` |
326
+ | C# / .NET | Regex-lag | nyere — mest `extended`/`quarantine` |
327
+
328
+ TypeScript og Python har den bredeste målte dækning. Java og C# er
329
+ skibet, dokumenteret og holdes uden for overskriftstallet, indtil en
330
+ rigtig forbrugersuite (ikke et binding-biblioteks egne tests) er blevet
331
+ auditeret.
332
+
333
+ ---
334
+
335
+ ## Sådan fungerer scoren
336
+
337
+ <p align="center">
338
+ <img src="assets/readme/terminal-hero.svg" alt="Mjölnir-terminaloutput — WORTHINESS 75/100 NEEDS WORK, en opdeling af diagnostik efter kategori og en FIX THIS FIRST-liste" width="820" />
339
+ </p>
340
+
341
+ <sub>Regenereres med `npm run docs:hero`;
342
+ [`tests/hero-asset-reproducibility.spec.ts`](tests/hero-asset-reproducibility.spec.ts)
343
+ får CI til at fejle, hvis artefaktet afviger fra, hvad reporteren
344
+ faktisk printer.</sub>
345
+
346
+ Scoren er transparent: **error −8, warning −3, info −1**, derefter
347
+ normaliseret efter suitens eksponering (fradrag pr. testdeklaration).
348
+ Bevisvægtede fradrag betyder, at svage signaler koster mindre.
349
+ Terminalen viser de samme diskonterede tal, som scoren bruger — ingen
350
+ black box. Fuld metode: [docs/SCORING.md](docs/SCORING.md).
351
+
352
+ **Domme**
353
+
354
+ | Score | Domme |
355
+ | ------- | ---------------- |
356
+ | ≥ 80 | ✓ **WORTHY** |
357
+ | 50 – 79 | ⚠ **NEEDS WORK** |
358
+ | < 50 | ✖ **UNWORTHY** |
359
+
360
+ **Bevisniveauer** — hvert fund bærer ét; det sætter fundets vægt i
361
+ scoren:
362
+
363
+ | Niveau | Betydning | Score-effekt | Eksempel |
364
+ | ------ | --------------------- | -------------- | -------------------------------------------------- |
365
+ | E2 | Deterministisk defekt | Fuldt fradrag | Committet `.only` — strukturelt beviseligt |
366
+ | E1 | Heuristisk mønster | Halvt fradrag | Regex-fundet `sleep()` — stærkt signal, ikke bevis |
367
+ | E0 | Iagttagelse | Nul (kun info) | Rapporteret, men gater aldrig CI eller trækker fra |
368
+
369
+ De fleste regler er **E1**. Slagordet „we prove it" henviser til dette
370
+ system: E2-fund er strukturelt bevis; E1-fund er korrekt positionerede
371
+ advarsler, ikke formelle beviser.
372
+
373
+ Et tomt repo scorer `null`, aldrig en falsk 100 — se
374
+ [Tillidsmodellen](#tillidsmodellen).
375
+
376
+ ---
377
+
378
+ ## 🎭 Selector Health Score
379
+
380
+ Hovedmetrikken for Playwright-suiter — hvor robuste dine locators er:
381
+
382
+ ```text
383
+ ▚▞ SELECTOR HEALTH — e2e/checkout.spec.ts
384
+
385
+ [█████████████████░░░] 83 / 100
386
+ role/text: 2 · testid: 1 · css-chains: 1 ⚠ · xpath: 0
387
+ ```
388
+
389
+ Rollebaserede locators får fuld score. CSS-klassekæder og XPath synker
390
+ scoren — de brækker ved enhver DOM-refaktor uden at fortælle dig,
391
+ hvilken adfærd der er regresseret.
392
+
393
+ ---
394
+
395
+ ## 🔬 Runtime-beviser
396
+
397
+ Statisk flakiness-detektion er gætteri. Mjölnir læser **ægte
398
+ eksekveringsdata** — Playwright JSON-rapporter og JUnit-XML fra enhver
399
+ runner:
400
+
401
+ ```bash
402
+ mjolnir forensics ./test-results/
403
+ ```
404
+
405
+ ```text
406
+ ▚▞ FLAKINESS LEADERBOARD
407
+
408
+ 3 tests · 1 failed · 1 flaky · 1 retried
409
+
410
+ TRUE-FLAKE completes checkout with saved card (e2e/checkout.spec.ts)
411
+ ████████████████████ 6.0s · 2 attempts
412
+ FAILING declines an expired card (e2e/checkout.spec.ts)
413
+ ████░░░░░░░░░░░░░░░░ 1.1s · 1 attempt
414
+ ```
415
+
416
+ En test, der kun består fra forsøg ≥ 2, er ikke en bestået test — det
417
+ er en heldig test. Den markeres `TRUE-FLAKE` uanset den endelige grønne
418
+ check.
419
+
420
+ ---
421
+
422
+ ## ⚡ Mjölnir er ikke endnu en linter
423
+
424
+ Lintere fortæller dig, om koden følger regler. Mjölnir fortæller dig,
425
+ om din verifikation kan stoles på.
426
+
427
+ | | ESLint / SonarQube | Coverage-værktøjer | Manuelt review | **Mjölnir** |
428
+ | --------------------------------------------------------- | :----------------: | :----------------: | :------------: | :---------: |
429
+ | CI-workflow-integritet (`continue-on-error`, `\|\| true`) | ❌ | ❌ | sjældent | ✅ |
430
+ | På tværs af sprog (TS, Python, Java, C#) fra ét værktøj | ❌ | ❌ | ❌ | ✅ |
431
+ | Bedømmer Playwright-locators robusthed (Selector Health) | ❌ | ❌ | sjældent | ✅ |
432
+ | Markerer tests uden rigtige assertions | ✅ (plugin)\* | ❌ | nogle gange | ✅ |
433
+ | Fanger hårde sleeps (`waitForTimeout`, `time.sleep`) | ✅ (plugin)\* | ❌ | nogle gange | ✅ |
434
+ | Kører på sekunder, nul netværkskald under scanning | ✅ | ✅ | — | ✅ |
435
+
436
+ \*`eslint-plugin-jest` (`expect-expect`) og `eslint-plugin-playwright`
437
+ (`expect-expect`, `no-wait-for-timeout`) dækker dette for deres
438
+ respektive frameworks.
439
+
440
+ **Runtime-analyse** er en separat kategori ud over statisk lintning:
441
+
442
+ | | Playwright retry reporter | Allure / ReportPortal | **Mjölnir forensics** |
443
+ | ------------------------------------------------ | :-----------------------: | :-------------------: | :-------------------: |
444
+ | Læser ægte kørselsdata til `TRUE-FLAKE`-domme | delvist\* | delvist (tag) | ✅ |
445
+ | Flaky-triage-rapport fra eksekveringshistorikken | ❌ | ✅ | ✅ |
446
+ | Integrerer med den statiske værdighedsscore | ❌ | ❌ | ✅ |
447
+
448
+ \*Playwright sporer retries internt, men producerer ikke en selvstændig
449
+ flakiness-rapport med domme-etiketter.
450
+
451
+ ---
452
+
453
+ ## 🤖 Hvorfor ikke bare bruge AI-kodereview?
454
+
455
+ Andet problem, andet lag. AI-review kan spotte en mistænkelig
456
+ testændring i en diff; det beviser ikke, at verifikationssystemet som
457
+ helhed er troværdigt — og det ser kun den diff, du viser det.
458
+
459
+ | | AI-kodereview (Copilot m.fl.) | **Mjölnir** |
460
+ | ------------------------------------------- | :-----------------------------------------: | :--------------------------: |
461
+ | Omkostning pr. scan | Tokens (skalerer med diff-størrelse) | **Nul** (lokal, installeret) |
462
+ | Ser hele suiten + alle CI-konfigs | Kun den PR-diff, du viser | **Alt, hver gang** |
463
+ | Deterministisk (samme input → samme output) | ❌ (ikke-deterministisk) | **✅** |
464
+ | Fanger mønstre, der har sovet i måneder | Kun hvis det er i konteksten | **✅** (scanner alle filer) |
465
+ | Husker fund mellem kørsler | ❌ (ingen hukommelse på tværs af sessioner) | **✅** (baseline + diff) |
466
+ | Kører uden menneskelig udløser | Kræver en PR eller prompt | **✅** (CI-hook, 3 sekunder) |
467
+
468
+ **Brug begge.** AI fanger nuance, intention og designfejl, ingen regex
469
+ kan finde. Mjölnir fanger de strukturelle mønstre, AI overser, fordi de
470
+ ser „intentionelle" ud — et committet `.only`, en opslugt exit-kode,
471
+ en `continue-on-error` på et testjob. Det er ikke bugs, der kræver
472
+ resonnement; det er fakta, der kræver scanning.
473
+
474
+ ---
475
+
476
+ ## 🤖 CI-integration
477
+
478
+ Én kommando genererer en PR-workflow — vejledende som standard, aldrig
479
+ blokerende:
480
+
481
+ ```bash
482
+ mjolnir ci install
483
+ ```
484
+
485
+ Eller kobl den native ind i GitHub Code Scanning via SARIF:
486
+
487
+ ```yaml
488
+ - run: npx mjolnir-qa@latest --format sarif > mjolnir.sarif
489
+ - uses: github/codeql-action/upload-sarif@v3
490
+ with:
491
+ sarif_file: mjolnir.sarif
492
+ ```
493
+
494
+ Editor- og pipeline-opsætning til SARIF:
495
+ [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md).
496
+
497
+ ### Changed-scope-dækning
498
+
499
+ `--scope changed` tilskriver fund de linjer, din branch tilføjede i
500
+ forhold til merge-base med `main`. Den dækker testfiler (`*.spec.*`,
501
+ `*.test.*`) plus GitHub-workflowfiler og Playwright-konfigurationer i
502
+ diffen. Når merge-base ikke kan resolve — shallow clone, detached HEAD,
503
+ ikke-git-mål, anden default-branch — degraderer den ærligt: fund
504
+ falder tilbage til hel-fil-attribuering, og rapporten siger det.
505
+ Overskriv base-ref'en med `--base <ref>`.
506
+
507
+ ---
508
+
509
+ ## Konfiguration
510
+
511
+ Mjölnir er zero-config. En valgfri `mjolnir.config.json` (eller
512
+ `.mjolnir.json`) i roden af repoet finjusterer severity, gating og
513
+ scope — den ændrer aldrig detektionssemantikken.
514
+
515
+ | Key | Type | Effekt |
516
+ | ------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
517
+ | `exclude` | `string[]` | Ekstra ignore-globs (gitignore-undersæt), oven på de indbyggede defaults |
518
+ | `gate` | `"advisory" \| "error" \| "warning"` | Hvilke severities der afslutter med ikke-nul (default `error`; `advisory` blokerer aldrig) |
519
+ | `severityOverrides` | `{ "<RULE-ID>": severity }` | Omrangordner en regels fund for dit repo |
520
+ | `ignore` | `IgnoreEntry[]` | Undertrykker fund — **`reason` er påkrævet**; indgange udløber efter 90 dage (en eksplicit `expires`-dato, eller config-filens last-modified-tid for indgange uden) |
521
+ | `plugins` | `string[]` | Regelpakker fra tredjepart (se [Tillidsmodellen](#tillidsmodellen)) |
522
+
523
+ ```json
524
+ {
525
+ "gate": "error",
526
+ "exclude": ["legacy/**"],
527
+ "severityOverrides": { "QA-PW-118": "warning" },
528
+ "ignore": [
529
+ {
530
+ "ruleId": "QA-TEST-004",
531
+ "files": ["e2e/legacy-login.spec.ts"],
532
+ "reason": "Third-party widget needs a settle delay; tracked in JIRA-4821",
533
+ "expires": "2026-12-31"
534
+ }
535
+ ]
536
+ }
537
+ ```
538
+
539
+ - **`.mjolnirignore`** — en enkel gitignore-agtig fil til
540
+ sti-udelukkelser, samme dialekt som `exclude`. Brug den til
541
+ maskinspecifikt støj; brug `exclude`, når listen hører til i
542
+ versionsstyring sammen med resten af konfigurationen.
543
+ - **CLI-overrides** — `--strict` (inkluder karantæneregler),
544
+ `--width <cols>` og `--ascii` / `--no-ascii` (terminalrendering),
545
+ `--tone blunt` (knugere beskeder), `--max-duration <sec>` (begrænset
546
+ delvis scanning).
547
+ - Regelundertrykkelse og deprecation-levetid:
548
+ [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md).
549
+
550
+ `ignore`-indgange driver også den selvstændige kommando
551
+ `mjolnir suppressions`, som lister, hvad der i øjeblikket er
552
+ undertrykket, og hvornår hver indgang udløber.
553
+
554
+ ---
555
+
556
+ ## 📐 Exit-koder & kontrakter
557
+
558
+ Frosne — trygge at bygge CI-logik på:
559
+
560
+ | Exit-kode | Betydning |
561
+ | --------- | -------------------------------------------------------------------- |
562
+ | `0` | Rent — ingen fund på eller over gaten |
563
+ | `1` | Fund på eller over gaten |
564
+ | `2` | Delvis scanning (tidsbudget ramt, ulæselige filer) — blokerer aldrig |
565
+ | `10` | Brugsfejl (ugyldigt flag, manglende mål) |
566
+ | `20` | Intern fejl |
567
+
568
+ JSON/SARIF-rapporten er `schemaVersion: 1`. Regel-ID'er
569
+ (`QA-<FAMILY>-NNN`) er uforanderlige, når de først er skibet, og
570
+ genbruges aldrig.
571
+
572
+ ---
573
+
574
+ ## Tillidsmodellen
575
+
576
+ - **Local-first** — nul netværkskald under scanning. Ever. Nul
577
+ telemetri.
578
+ - **Ingen falsk bevis** — vi siger hellere „ukendt" end „verificeret".
579
+ Et tomt repo får `score: null`, aldrig en falsk 100.
580
+ - **Delvis ærlighed** — hvis analysen blev afkortet, siger outputtet
581
+ det. Aldrig „complete", når det ikke er.
582
+ - **FP-firewall** — detektion kører på et kommentar-/string-frit view
583
+ af koden (TypeScript-regler bruger compiler-AST): et mønster inde i
584
+ en prosakommentar eller en doc-eksempelstreng er dokumentation, ikke
585
+ et fund.
586
+ - **Målt, ikke påstået** — kun regler med en false-positive-rate fra
587
+ rigtig OSS-kode skiber i overskriftstierne (se
588
+ [Hvor meget er målt](#hvor-meget-er-målt)); scan-fodnoten og
589
+ `mjolnir rules --unmeasured` fortæller dig, hvilke der er hvad.
590
+ - **Plugin-tillid** — plugins er npm-pakker deklareret under
591
+ `"plugins"`. Der er **ingen sandbox**: plugin-kode kører med fulde
592
+ Node-privilegier, samme tillidsmodel som ESLint- eller
593
+ Vitest-plugins. Core regel-ID-præfikser er reserverede og afvises
594
+ fra plugins mod spoofing.
595
+ - **Workspace-lokale eksterne regler** (mappebaserede, nul netværk) —
596
+ en `mjolnir-rules/`-mappe ved siden af scan-målet loader brugerdefinerede
597
+ regler: JSON-filer deklarerer regex-mønstre (ingen kode eksekveres),
598
+ `.mjs`/`.js`-moduler eksporterer `rules` (fuld Node-tillid, som
599
+ plugins). Eksterne regler bærer samme trust-metadata som core; de kan
600
+ aldrig skibe i core-tieren (core kræver en målt FP-rate fra
601
+ corpus-sidecaren — en deklareret `tier: "core"` klemmes til
602
+ `extended`), adlyder tier-grænser og tjekkes for drift:
603
+ `mjolnir rules --md --external` renderer kataloget fra de loadede
604
+ filer (proveniens `external`), og matrixgeneratoren accepterer
605
+ `--external <root>`.
606
+
607
+ ---
608
+
609
+ ## 🏗️ Arkitektur
610
+
611
+ <details>
612
+ <summary>Udvid træet</summary>
613
+
614
+ ```
615
+ mjolnir/
616
+ ├── src/
617
+ │ ├── engine/ # LanguageAdapter interface + rule runner
618
+ │ ├── adapters/ # typescript · python · java · csharp · github-actions
619
+ │ ├── rules/ # rules across 8 families + the measured-FP table
620
+ │ ├── playwright/ # Selector Health Score engine
621
+ │ ├── discovery/ # workspace, frameworks, ignore resolution
622
+ │ ├── scope/ # git merge-base changed-scope engine
623
+ │ ├── scorer/ # transparent deduction table + prioritization
624
+ │ ├── reporter/ # terminal · JSON · SARIF 2.1 · Mermaid
625
+ │ ├── forensics/ # run-data ingestion · flake verdicts · triage
626
+ │ ├── config/ # mjolnir.config.json + suppressions
627
+ │ ├── plugins/ # third-party rule loading (no sandbox)
628
+ │ └── commands/ # every subcommand
629
+ └── tests/
630
+ ├── fixtures/ # must-fire / must-not-fire per rule
631
+ └── golden/ # frozen score regression locks
632
+ ```
633
+
634
+ </details>
635
+
636
+ - **Regler er rene funktioner** — `(SourceFileContext) → Finding[]`,
637
+ ingen I/O, ingen globals. Nyt økosystem = én adapter + dens regler.
638
+ - **TypeScript/Playwright bruger compiler-AST** (ts-morph). Python,
639
+ Java og C# kører på et delt regex-lag med maskerede kommentare/strenge.
640
+ - Et tree-sitter WASM AST-lag til Java og C# findes og er næste
641
+ præcisionsskridt — det er endnu ikke koblet på den synkrone
642
+ scan-pipeline.
643
+
644
+ ---
645
+
646
+ ## 📚 Dokumentation
647
+
648
+ | Dokument | Hvad der er i det |
649
+ | ------------------------------------------------------ | ------------------------------------------- |
650
+ | [docs/SCORING.md](docs/SCORING.md) | Score-normalisering + bevisvægtning |
651
+ | [docs/FP-AUDIT.md](docs/FP-AUDIT.md) | Målte false-positive-rater + metode |
652
+ | [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md) | Regeltilstande, undertrykkelse, deprecation |
653
+ | [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md) | SARIF-output + editor/CI-opsætning |
654
+ | [docs/rules/](docs/rules/) | Genereret katalog pr. regel |
655
+ | [CONTRIBUTING.md](CONTRIBUTING.md) | Dev-opsætning + bidrag workflow |
656
+ | [CHANGELOG.md](CHANGELOG.md) | Releasehistorik |
657
+ | [SECURITY.md](SECURITY.md) | Sårbarhedsrapportering |
658
+
659
+ ---
660
+
661
+ ## 📈 Status
662
+
663
+ **v0.5.x · åben beta.** JSON-skemaet og exit-koderne er frosne
664
+ kontrakter. TypeScript og Python har den bredeste målte dækning; Java
665
+ og C# er nyere — læs dem gennem
666
+ [tiers-tabellen](#regel-tiers-og-sproglig-modenhed).
667
+
668
+ ---
669
+
670
+ ## 🤝 Bidrag
671
+
672
+ Nye regler er den nemmeste første bidrag — én kommando scaffolder
673
+ reglen plus dens must-fire- **og** must-not-fire-fixtures (den
674
+ genererede regel fejler bevidst dens fixtures, indtil du implementerer
675
+ rigtig detektion — en stub kan ikke skibes):
676
+
677
+ ```bash
678
+ mjolnir create-rule QA-PW-140 --title "Screenshot without diff bound"
679
+ ```
680
+
681
+ Fuld dev-opsætning, standing-gate-kommandoerne og anti-creep- /
682
+ fixture-firewall-lovene er i [CONTRIBUTING.md](CONTRIBUTING.md).
683
+
684
+ ---
685
+
686
+ <div align="center">
687
+
688
+ **Stop med at skibe tests, du ikke kan stole på.**
689
+
690
+ ```bash
691
+ npx mjolnir-qa@latest
692
+ ```
693
+
694
+ **Star ⭐ · Watch 👀 · Contribute 🤝**
695
+
696
+ Bygget af [Sergey Bar](https://www.linkedin.com/in/sergeybar/)
697
+
698
+ </div>