mjolnir-qa 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,124 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Rule behavior changes (new rules, FP-rate changes against the corpus,
9
+ severity changes) are first-class entries here — rule IDs are immutable
10
+ once shipped, so this file is the record of what changed between versions.
11
+
12
+ ## [0.4.0] — 2026-08-27
13
+
14
+ ### Changed
15
+
16
+ - **BREAKING: Rebranded from QA Doctor to Mjölnir.** Package name is now
17
+ `mjolnir-qa` (bin: `mjolnir`). Config file: `mjolnir.config.json`.
18
+ Data directory: `.mjolnir/`. Badge: `mjolnir-badge.json`.
19
+ - Score label: "SCORE" → "WORTHINESS".
20
+ - Verdicts: "HEALTHY" → "WORTHY", "CRITICAL" → "UNWORTHY".
21
+ - Environment variable: `QA_DOCTOR_ASCII` → `MJOLNIR_ASCII`.
22
+ - SARIF tool.driver.name: "Mjölnir".
23
+ - Repository: `github.com/Sergey-Bar/Mjolnir`.
24
+ - CLI: all help text, error messages, usage strings reference `mjolnir`.
25
+ - Generated workflows: `mjolnir.yml`, `npx mjolnir-qa@latest`.
26
+
27
+ ### Added
28
+
29
+ - `--format mermaid` — test-architecture diagram (Sprint 9).
30
+ - `--tone blunt` — opt-in blunter messages (Sprint 9).
31
+ - Milestones — first flawless scan / first debt reduction announced once.
32
+ - New MJÖLNIR ASCII art logo (minimal Nordic hammer).
33
+
34
+ ## [Unreleased]
35
+
36
+ ### Fixed (adversarial-audit hardening wave)
37
+
38
+ - **QA-TEST-003**: the assertion-detection regex contained a literal tab
39
+ character (`\t`) instead of `to`, so `.toThrow()`, `.rejects.toThrow()`
40
+ and `.resolves.*` were never recognized as assertions — tests whose only
41
+ assertion was `await expect(p).rejects.toThrow()` were flagged as
42
+ "no assertions" with error severity. Fixed; must-not-fire fixture now
43
+ covers `rejects`/`resolves` forms.
44
+ - **workflow-parser**: the YAML alias-bomb guard ran AFTER parsing, i.e.
45
+ after a billion-laughs document had already expanded. Alias counting now
46
+ happens before parse, and the parser additionally enforces
47
+ `maxAliasCount` during parse.
48
+ - **CRLF/BOM robustness**: files are normalized once at read time (BOM
49
+ stripped, CRLF → LF). Previously `$`-anchored Python rules missed every
50
+ assertion line on Windows checkouts.
51
+ - **Terminal deduction table** now uses the same evidence-discounted math
52
+ as the score (`deductionFor`), so displayed numbers reconcile with the
53
+ reported score when E0/E1 findings exist.
54
+ - **JSON/SARIF truncation removed**: results were silently capped at 50
55
+ findings, including machine consumers. The full finding set is now in
56
+ JSON/SARIF; only terminal display is capped (with an honest count).
57
+ - **`qa-doctor fix` path containment**: plugin-supplied finding paths can
58
+ no longer write outside the scan root (`../` traversal refused).
59
+ - **Symlinks are no longer followed** during test-file discovery in any
60
+ adapter — prevents scanning outside the repo and link cycles.
61
+ - Plugin reserved-prefix blocklist extended to all core families
62
+ (`QA-JV`, `QA-CS`, `QA-PLUGIN`).
63
+ - `doctor:playwright` bad-usage exit code unified to 10 (was 2).
64
+
65
+ ### Changed (adversarial-audit hardening wave)
66
+
67
+ - QA-TQUAL-002 and QA-PW-004 detection now runs on a comment-stripped /
68
+ comment-and-string-free AST view of the file: patterns inside prose
69
+ comments or string literals no longer fire (FP firewall).
70
+ - QA-TQUAL-011 (commented-out test) now scans actual comment ranges via
71
+ the compiler scanner instead of raw-text regex — `it(` inside a string
72
+ literal is no longer flagged, and without an available AST the rule
73
+ conservatively reports nothing rather than flooding false positives.
74
+ - `--scope changed` now includes GitHub workflow files and Playwright
75
+ configs changed in the diff, not just `*.spec/test.*` files.
76
+
77
+ ### Added
78
+
79
+ - Upgrade-Plan-v3 Phase 0.1: expanded the false-positive corpus with two
80
+ additional Python repos (`pytest-dev/pytest`, `psf/requests`) so all
81
+ QA-PY-001..012 rules are exercised against real code via
82
+ `npm run corpus:audit`.
83
+ - Added `CHANGELOG.md` (this file) per Upgrade-Plan-v3 critical item #3:
84
+ user-visible rule behavior changes get a first-class entry from now on.
85
+ - Upgrade-Plan-v3 Phase 1: five new TypeScript/Playwright rules —
86
+ QA-PW-141 (retries without flake-triage loop), QA-PW-142 (blanket
87
+ `page.route()` mocking), QA-PW-143 (no screenshot/video on failure),
88
+ QA-PW-144 (single-browser project matrix), QA-PW-145 (no accessibility
89
+ assertions; absence-based, `falsePositiveRisk: high`).
90
+ - Upgrade-Plan-v3 Phase 2: eight new Playwright-Python rules on the
91
+ Python adapter — QA-PY-101 (sync/async API mix), QA-PY-102 (`time.sleep`
92
+ in Playwright tests), QA-PY-103 (`wait_for_timeout` as sync),
93
+ QA-PY-104 (brittle selectors), QA-PY-105 (UI-driving test without
94
+ assertions), QA-PY-106 (shared page/context across tests), QA-PY-107
95
+ (`networkidle` wait), QA-PY-108 (hardcoded environment URLs).
96
+
97
+ - Upgrade-Plan-v3 Phase 6: Plugin API — declare third-party rule packages
98
+ in `qa-doctor.config.json` (`"plugins": [...]`). Security model: no
99
+ sandbox (same trust as ESLint/Vitest plugins); reserved core rule-ID
100
+ prefixes rejected; load failures degrade honestly as QA-PLUGIN-000
101
+ warnings without affecting exit codes. Plus cross-file duplicate-test-name
102
+ detection (`src/engine/cross-file.ts`).
103
+
104
+ - Upgrade-Plan-v3 Phase 0.2: new `@qa-doctor/playwright-reporter` package
105
+ (`packages/playwright-reporter/`) — official Playwright JSON reporter
106
+ wrapper for QA Doctor's forensics pipeline; default output
107
+ `qa-doctor.report.json` is the CLI's auto-discovery convention.
108
+
109
+ - Upgrade-Plan-v3 Phase 3: ts-morph AST precision layer behind the `ast`
110
+ seam (`src/engine/ts-ast.ts`). QA-PW-002 and QA-PW-005 migrated from
111
+ regex to syntax-tree detection (legacy regex kept as fallback). No
112
+ scoring changes — golden lock byte-identical.
113
+
114
+ - Upgrade-Plan-v3 Phases 4+5: new language adapters — Java
115
+ (`src/adapters/java.ts`, rules QA-JV-101..105) and C#/.NET
116
+ (`src/adapters/csharp.ts`, rules QA-CS-101..104). Core Playwright rule
117
+ families ported: disabled/skipped tests, hard sleeps, missing assertions,
118
+ shared browser state, wait-timeout misuse. Rule-ID registry now accepts
119
+ QA-JV/QA-CS families.
120
+
121
+ ## [0.3.x] — prior releases
122
+
123
+ See git history; per-rule Trust Metadata `introduced` fields record the
124
+ first released version of each rule.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sergey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,395 @@
1
+ <div align="center">
2
+
3
+ <pre>
4
+ ╔═══════════╗
5
+ ║ ║
6
+ ╠═══════════╣ M J Ö L N I R
7
+ ║ ║ ║
8
+ ╚═════╩═════╝ VERIFICATION TRUST ENGINE
9
+
10
+
11
+ </pre>
12
+
13
+ ### Your tests are lying to you. We prove it.
14
+
15
+ **Verification Trust Engine for QA.** Audits test suites and CI pipelines,
16
+ reports a worthiness score, and shows exactly where trust breaks.
17
+
18
+ [![license](https://img.shields.io/badge/license-MIT-B45309.svg?style=flat-square&labelColor=0D0D0D)](LICENSE)
19
+ [![node](https://img.shields.io/badge/node-%E2%89%A5%2022.18-146A8C.svg?style=flat-square&labelColor=0D0D0D)](https://nodejs.org)
20
+ [![status](https://img.shields.io/badge/status-●_ONLINE-15803D.svg?style=flat-square&labelColor=0D0D0D)](#-quickstart)
21
+
22
+ ```bash
23
+ npx mjolnir-qa@latest
24
+ ```
25
+
26
+ **Are your tests worthy of trust?**
27
+
28
+ [Quickstart](#-quickstart) · [Rules](#-the-rules) · [Selector Health](#-selector-health-score) · [Runtime Evidence](#-runtime-evidence) · [CI Integration](#-ci-integration) · [Contributing](#-contributing)
29
+
30
+ </div>
31
+
32
+ ---
33
+
34
+ ## 🔨 What is Mjölnir?
35
+
36
+ | | |
37
+ | --- | -------------------------------------------------------------------------------------------------------------- |
38
+ | ⚖️ | **Worthiness Score** — one number, transparent deduction table, no black box |
39
+ | 🎭 | **Selector Health Score** — grades your Playwright locators, not just your pass rate |
40
+ | 🔬 | **Runtime forensics** — reads real Playwright/JUnit run data to catch `TRUE-FLAKE`, not just static guesses |
41
+ | 🚨 | **CI-integrity rules** — catches `continue-on-error`, `\|\| true`, and other false-green tricks |
42
+ | 🐍 | **Multi-language** — TypeScript/Playwright, Python/pytest, Java, and C#/.NET today, one adapter away from more |
43
+ | 🔒 | **Local-first** — zero network calls while scanning, zero telemetry, runs in seconds |
44
+
45
+ ---
46
+
47
+ ## ⚡ Mjölnir is not another linter
48
+
49
+ Linters tell you whether code follows rules.
50
+ Mjölnir tells you whether your verification can be trusted.
51
+
52
+ | | ESLint / SonarQube | Coverage tools | Manual review | **Mjölnir** |
53
+ | ---------------------------------------------------------- | :----------------: | :------------: | :-----------: | :---------: |
54
+ | Catches syntax & style bugs | ✅ | ❌ | ✅ (slow) | — |
55
+ | Flags tests with no real assertions | ❌ | ❌ | sometimes | ✅ |
56
+ | Catches CI false-greens (`\|\| true`, `continue-on-error`) | ❌ | ❌ | rarely | ✅ |
57
+ | Reads **real** run data for `TRUE-FLAKE` verdicts | ❌ | ❌ | ❌ | ✅ |
58
+ | Grades Playwright locator resilience | ❌ | ❌ | rarely | ✅ |
59
+ | Runs in seconds, zero network calls | ✅ | ✅ | — | ✅ |
60
+
61
+ ---
62
+
63
+ ## ⚡ Quickstart
64
+
65
+ ```bash
66
+ npx mjolnir-qa@latest
67
+ ```
68
+
69
+ That's it. Zero configuration — Mjölnir detects your frameworks, finds your
70
+ tests, and reports.
71
+
72
+ | Command | What it does |
73
+ | ------------------------------------------------------ | ------------------------------------------------------------------- |
74
+ | `npx mjolnir-qa@latest --scope changed` | Only findings introduced by **your** changes — perfect for PRs |
75
+ | `npx mjolnir-qa@latest --json` | Machine-readable output |
76
+ | `npx mjolnir-qa@latest --format sarif > mjolnir.sarif` | GitHub Code Scanning integration |
77
+ | `npx mjolnir-qa@latest --format mermaid` | Test-architecture diagram — paste into a GitHub comment or a slide |
78
+ | `mjolnir doctor:playwright` | Playwright-only deep scan + Selector Health Score |
79
+ | `mjolnir forensics ./test-results/` | Runtime evidence — retries, true flakes, `FLAKY.md` artifact |
80
+ | `mjolnir triage ./test-results/` | The flaky-triage meeting, in 10 minutes instead of 45 |
81
+ | `mjolnir fix --dry-run` / `fix` | Safe auto-fixes with proof (dry-run first) |
82
+ | `mjolnir debt` | Test debt register — presentable to management |
83
+ | `mjolnir handover` | New-QA-onboarding map of the suite |
84
+ | `mjolnir pw-report ./test-results/` | Playwright run summary — retries / flakes / slowest |
85
+ | `mjolnir badge` | Evidentiary badge (shields.io endpoint JSON) |
86
+ | `mjolnir doctor` | Self-audit — prove Mjölnir's own rule base is worthy |
87
+ | `mjolnir rules` / `rules --md` | Rule catalog with trust metadata (JSON or markdown) |
88
+ | `mjolnir explain <RULE-ID>` | What/why/fix for one rule, with a real example from its own fixture |
89
+ | `mjolnir impact [--since <ref>]` | What changed since a prior commit — fixes and new debt |
90
+
91
+ ---
92
+
93
+ ## ⚖️ Worthiness Score
94
+
95
+ ```text
96
+ 🔨 MJÖLNIR
97
+
98
+ WORTHINESS 80/100 — WORTHY
99
+ ████████████████████████░░░░░░
100
+
101
+ DETECTED [playwright]
102
+
103
+ ▚ FIX THIS FIRST
104
+ +8 pts QA-CI-001 · .github/workflows/ci.yml:48
105
+ +3 pts QA-TEST-004 · e2e/checkout.spec.ts:6
106
+ ```
107
+
108
+ The score is transparent: error −8, warning −3, info −1. Evidence-weighted
109
+ deductions mean weak signals cost less. The terminal shows the same discounted
110
+ numbers the score uses — no black box.
111
+
112
+ **Verdicts:**
113
+
114
+ | Score | Verdict |
115
+ | ------- | ---------------- |
116
+ | ≥ 80 | ✓ **WORTHY** |
117
+ | 50 – 79 | ⚠ **NEEDS WORK** |
118
+ | < 50 | ✖ **UNWORTHY** |
119
+
120
+ ---
121
+
122
+ ## 📋 The Rules
123
+
124
+ Every rule ships with must-fire **and** must-not-fire fixtures. A rule that
125
+ fires on its own negative fixture cannot ship. That's the false-positive
126
+ firewall.
127
+
128
+ <details>
129
+ <summary><strong>Test Hygiene</strong></summary>
130
+
131
+ | ID | Rule | Severity |
132
+ | ----------- | --------------------------------------------------- | -------- |
133
+ | QA-TEST-001 | Focused test committed (`.only`, `fit`) | error |
134
+ | QA-TEST-002 | Skipped test without justification | error |
135
+ | QA-TEST-002 | Skipped test with tracked justification | warning |
136
+ | QA-TEST-003 | Test with no assertions | error |
137
+ | QA-TEST-004 | Hard sleep (`waitForTimeout`, `sleep()`, `delay()`) | warning |
138
+ | QA-TEST-006 | Retry abuse hiding flakiness | warning |
139
+ | QA-TEST-010 | Empty test body | error |
140
+
141
+ </details>
142
+
143
+ <details>
144
+ <summary><strong>Test Quality</strong></summary>
145
+
146
+ | ID | Rule | Severity |
147
+ | ------------ | --------------------------- | -------- |
148
+ | QA-TQUAL-001 | Mock-only verification | warning |
149
+ | QA-TQUAL-002 | Tautological assertion | error |
150
+ | QA-TQUAL-009 | Unawaited promise assertion | error |
151
+ | QA-TQUAL-011 | Commented-out tests | warning |
152
+
153
+ </details>
154
+
155
+ <details>
156
+ <summary><strong>Playwright 🎭</strong></summary>
157
+
158
+ | ID | Rule | Severity |
159
+ | --------- | ---------------------------------------- | -------- |
160
+ | QA-PW-002 | Unawaited locator assertion | error |
161
+ | QA-PW-003 | `page.pause()` / `test.only()` committed | error |
162
+ | QA-PW-004 | Brittle CSS/XPath selectors | warning |
163
+ | QA-PW-005 | Business logic inside `page.evaluate()` | warning |
164
+ | QA-PW-114 | Legacy element handles (`page.$`) | warning |
165
+ | QA-PW-118 | `networkidle` waits (flaky by design) | warning |
166
+ | QA-PW-123 | Hardcoded environment URLs | warning |
167
+
168
+ </details>
169
+
170
+ <details>
171
+ <summary><strong>CI Integrity</strong></summary>
172
+
173
+ | ID | Rule | Severity |
174
+ | --------- | ----------------------------------------------------------------- | -------- |
175
+ | QA-CI-001 | `continue-on-error` masks failures | error |
176
+ | QA-CI-002 | `\|\| true` swallows exit codes | error |
177
+ | QA-CI-005 | Report consumed but never generated | error |
178
+ | QA-CI-007 | Retry wrappers around tests | warning |
179
+ | QA-CI-008 | Always-success step masks failures | error |
180
+ | QA-CI-009 | Test exit code not propagated (`\|` without pipefail, `;` chains) | error |
181
+ | QA-CI-010 | Tests skipped where they must block (skip-on-PR guards) | error |
182
+
183
+ </details>
184
+
185
+ <details>
186
+ <summary><strong>Python / pytest 🐍</strong></summary>
187
+
188
+ | ID | Rule | Severity |
189
+ | --------- | ----------------------------------------- | -------- |
190
+ | QA-PY-002 | Skipped test (`skip`, non-strict `xfail`) | warning |
191
+ | QA-PY-003 | Test function with no assertions | error |
192
+ | QA-PY-005 | `time.sleep()` in tests | warning |
193
+ | QA-PY-006 | Empty test body (`pass`) | error |
194
+ | QA-PY-010 | Random/time dependence without freeze | warning |
195
+ | QA-PY-012 | Tautological assertion | error |
196
+
197
+ </details>
198
+
199
+ > The full live catalog — every rule with confidence, false-positive risk,
200
+ > and autofix availability — is generated from the registry:
201
+ >
202
+ > ```bash
203
+ > mjolnir rules --md
204
+ > ```
205
+
206
+ ---
207
+
208
+ ## 🎭 Selector Health Score
209
+
210
+ The headline metric for Playwright suites — how resilient your locators are:
211
+
212
+ ```text
213
+ ▚▞ SELECTOR HEALTH — e2e/checkout.spec.ts
214
+
215
+ [█████████████████░░░] 83 / 100
216
+ role/text: 2 · testid: 1 · css-chains: 1 ⚠ · xpath: 0
217
+ ```
218
+
219
+ Role-based locators score full credit. CSS class chains and XPath tank the
220
+ score — they break on any DOM refactor without telling you which behavior
221
+ regressed.
222
+
223
+ ---
224
+
225
+ ## 🔬 Runtime Evidence
226
+
227
+ Static flakiness detection is guessing. Mjölnir reads **real execution
228
+ data** — Playwright JSON reports and JUnit XML from any runner:
229
+
230
+ ```bash
231
+ mjolnir forensics ./test-results/
232
+ ```
233
+
234
+ ```text
235
+ ▚▞ FLAKINESS LEADERBOARD
236
+
237
+ 3 tests · 1 failed · 1 flaky · 1 retried
238
+
239
+ TRUE-FLAKE completes checkout with saved card (e2e/checkout.spec.ts)
240
+ ████████████████████ 6.0s · 2 attempts
241
+ FAILING declines an expired card (e2e/checkout.spec.ts)
242
+ ████░░░░░░░░░░░░░░░░ 1.1s · 1 attempt
243
+ ```
244
+
245
+ A test that passes only on attempt ≥ 2 is not a passing test — it's a lucky
246
+ test. It gets flagged `TRUE-FLAKE` regardless of the final green checkmark.
247
+
248
+ ---
249
+
250
+ ## 🤖 CI Integration
251
+
252
+ One command generates a PR workflow — advisory by default, never blocking:
253
+
254
+ ```bash
255
+ mjolnir ci install
256
+ ```
257
+
258
+ Or wire it into GitHub Code Scanning natively via SARIF:
259
+
260
+ ```yaml
261
+ - run: npx mjolnir-qa@latest --format sarif > mjolnir.sarif
262
+ - uses: github/codeql-action/upload-sarif@v3
263
+ with:
264
+ sarif_file: mjolnir.sarif
265
+ ```
266
+
267
+ ---
268
+
269
+ ## 🛡️ Trust Model
270
+
271
+ - **Local-first** — zero network calls during scanning. Ever.
272
+ - **No false proof** — we'd rather say "unknown" than "verified". An empty
273
+ repo gets `score: null`, never a fake 100.
274
+ - **Transparent scoring** — public deduction constants: error −8, warning −3,
275
+ info −1. No black box. Verdict thresholds: **≥80 WORTHY**, **50–79 NEEDS
276
+ WORK**, **<50 UNWORTHY**.
277
+ - **Evidence-weighted deductions** — every finding carries an evidence level:
278
+ **E2** (deterministic defect, full deduction), **E1** (heuristic pattern,
279
+ half deduction), **E0** (observation — reported but costs nothing and never
280
+ gates CI).
281
+ - **Partial honesty** — if analysis was cut short, the output says so.
282
+ Never "complete" when it isn't.
283
+ - **QA-native language** — findings speak your vocabulary:
284
+ `FALSE-GREEN`, `FLAKY-RISK`, `BLOCKS-RELEASE`, `HYGIENE`.
285
+ - **FP firewall** — detection runs on a comment/string-free view of the code
286
+ (TypeScript rules use the compiler AST): a pattern inside a prose comment
287
+ or a doc-example string is documentation, not a finding.
288
+
289
+ ---
290
+
291
+ ## 🔌 Plugin Trust Model
292
+
293
+ Plugins are npm packages you declare in `mjolnir.config.json`. There is **no
294
+ sandbox**: plugin code executes with full Node privileges against the scanned
295
+ tree — the same trust model as ESLint or Vitest plugins. Only install plugins
296
+ you'd trust as devDependencies. Core rule-ID prefixes (`QA-TEST`, `QA-TQUAL`,
297
+ `QA-PW`, `QA-CI`, `QA-PY`, `QA-ENV`, `QA-JV`, `QA-CS`) are reserved and
298
+ rejected from plugins to prevent spoofing.
299
+
300
+ ---
301
+
302
+ ## 🔍 Changed-Scope Coverage
303
+
304
+ `--scope changed` attributes findings to lines added in your branch vs the
305
+ merge-base with `main`. It covers test files (`*.spec.*`, `*.test.*`) plus
306
+ GitHub workflow files and Playwright configs changed in the diff. On shallow
307
+ clones or non-git targets it degrades honestly: findings fall back to
308
+ full-file attribution and the report says so.
309
+
310
+ ---
311
+
312
+ ## 🏗️ Architecture
313
+
314
+ <details>
315
+ <summary>Expand tree</summary>
316
+
317
+ ```
318
+ mjolnir/
319
+ ├── src/
320
+ │ ├── engine/ # LanguageAdapter interface + rule runner
321
+ │ ├── adapters/ # typescript · python · java · csharp · github-actions
322
+ │ ├── rules/ # deterministic rules across 8 families
323
+ │ ├── playwright/ # Selector Health Score engine
324
+ │ ├── discovery/ # workspace, frameworks, safe-YAML parser
325
+ │ ├── scope/ # git merge-base changed-scope engine
326
+ │ ├── scorer/ # transparent deduction table
327
+ │ ├── reporter/ # terminal · JSON · SARIF 2.1 · Mermaid
328
+ │ ├── forensics/ # run-data ingestion · flake verdicts · triage
329
+ │ ├── commands/ # fix · badge · debt · handover · init · create-rule · doctor · rules-catalog · explain · impact · baseline · diff · pr-comment · stats
330
+ │ └── integrations/ # CI workflow generator
331
+ └── tests/
332
+ ├── fixtures/ # must-fire / must-not-fire per rule
333
+ └── golden/ # frozen score regression locks
334
+ ```
335
+
336
+ </details>
337
+
338
+ Multi-language by design: adding a language = one adapter + its rules.
339
+ TypeScript/Playwright and Python are the most mature; Java and C#/.NET
340
+ adapters ship a regex-based core rule family with tree-sitter WASM AST
341
+ precision as the next step.
342
+
343
+ ---
344
+
345
+ ## 📦 Install
346
+
347
+ ```bash
348
+ # Run directly (recommended):
349
+ npx mjolnir-qa@latest
350
+
351
+ # Or install globally:
352
+ npm i -g mjolnir-qa
353
+ ```
354
+
355
+ Requires Node.js ≥ 22.18. Works on Windows, macOS, and Linux.
356
+
357
+ ---
358
+
359
+ ## 🤝 Contributing
360
+
361
+ New rules are the easiest first contribution — one command scaffolds
362
+ everything (anti-creep law enforced by the fixture harness):
363
+
364
+ ```bash
365
+ mjolnir create-rule QA-PW-140 --title "Screenshot without diff bound"
366
+ ```
367
+
368
+ That generates the rule file plus must-fire AND must-not-fire fixture
369
+ skeletons. The generated rule intentionally fails its fixtures until you
370
+ implement real detection — a stub cannot ship.
371
+
372
+ Manual path:
373
+
374
+ 1. Fork + clone, `npm install`
375
+ 2. Copy any rule folder under `src/rules/` as a template
376
+ 3. Add must-fire AND must-not-fire fixtures under `tests/fixtures/<YOUR-ID>/`
377
+ 4. `npx vitest run` — both directions green? Ship the PR.
378
+
379
+ See [SECURITY.md](SECURITY.md) for vulnerability reporting.
380
+
381
+ ---
382
+
383
+ <div align="center">
384
+
385
+ **Are your tests worthy of trust?**
386
+
387
+ ```bash
388
+ npx mjolnir-qa@latest
389
+ ```
390
+
391
+ **Star ⭐ · Watch 👀 · Contribute 🤝**
392
+
393
+ Built by [Sergey Bar](https://www.linkedin.com/in/sergeybar/)
394
+
395
+ </div>