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 +124 -0
- package/LICENSE +21 -0
- package/README.md +395 -0
- package/dist/cli.d.mts +234 -0
- package/dist/cli.mjs +10372 -0
- package/package.json +92 -0
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)
|
|
19
|
+
[](https://nodejs.org)
|
|
20
|
+
[](#-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>
|