Where
QA-CI-009 · .github/workflows/ci.yml:10The rule, and the exact file and line.
Mjölnir finds tests that cannot fail and pipelines that cannot go red, then scores how far you can trust the result.
npx mjolnir-qa@latest$ npx mjolnir-qa@latest WORTHINESS 75/100 NEEDS WORK ░░░░░░░ The hammer holds — but 19 findings weigh it down. (40 raw pts / 7 test declarations — normalized) (capped: deduction mass 40 pts — absolute ceiling 85) DETECTED [playwright] ▍ DIAGNOSTICS BY CATEGORY QA-CI ░░░░ 76 QA-PW ░░ 87 QA-TEST 97 ▍ WHERE POINTS WERE LOST ╭─────────────────────────────────────────╮ │ 4 × error − 32 │ │ 3 × warning − 7 (evidence-discounted) │ │ 12 × info − 1 (evidence-discounted) │ ╰─────────────────────────────────────────╯ ▍ FIX THIS FIRST +8 pts QA-CI-009 · .github/workflows/ci.yml:10 +8 pts QA-CI-009 · .github/workflows/ci.yml:10 +8 pts QA-CI-009 · .github/workflows/ci.yml:26
A real scan of the demo repo, replayed.
01CI integrity
Each of these lines looks deliberate in review. Mjölnir reads the workflow and flags each one with its rule and a fix.
QA-CI-009 Job `test` pipes the test command into another tool without `set -o pipefail`. E2 · deterministic · measured FP 0% · n=10
QA-CI-009 Job `deploy-smoke` sequences commands with `; ` after the test command — the test result does not fail the step. E2 · deterministic · measured FP 0% · n=10
QA-CI-010 Job `deploy-smoke` runs tests but its `if:` condition skips it on pull requests. E0 · observation · measured FP 10% · n=10
QA-CI-009 Job `deploy-smoke` pipes the test command into another tool without `set -o pipefail`. E2 · deterministic · measured FP 0% · n=10
QA-CI-001 Job `security-scan` runs a verification gate under `continue-on-error: true`. E0 · observation · measured FP 11% · n=19
continue-on-error: trueThe job fails and the workflow still reports success.
QA-CI-001 continue-on-error masks a failing verification gatenpm test || trueThe test command's exit code is thrown away.
QA-CI-002 Ignored exit code (|| true)npm test | tee results.logOnly the exit code of tee reaches CI.
QA-CI-009 Test command does not propagate exit codetest.only("checkout", ...)The focused test runs and the rest are skipped.
QA-TEST-001 Focused test committed Above: every finding the demo repo scan reported for .github/workflows/ci.yml, at the line it reported. With SARIF upload or mjolnir summary, this is how they show up in a pull request.
02Findings
This is the first finding from the demo repo scan, exactly as the terminal prints it. Each part of it answers one question.
See what it checks✗ ERROR QA-CI-009 · .github/workflows/ci.yml:10 [E2 · deterministic · measured FP 0% · n=10] Finding Job `test` pipes the test command into another tool without `set -o pipefail`. Fix Add `shell: bash` with `set -o pipefail`, or split into two steps so the test command's exit code is preserved.
QA-CI-009 · .github/workflows/ci.yml:10The rule, and the exact file and line.
E2 · deterministicE2 is proven in code and counts in full. E1 is a matching pattern and counts half. E0 is an observation and costs nothing.
measured FP 0% · n=10Measured on hand-checked findings from open-source repos.
FixThe change that closes the finding. Re-run the scan to confirm it.
03Worthiness score
The score measures the test suite, not your product. Every point it takes off is listed, and the formula has no hidden second model.
Read how the score works75
Step by step
Dividing by test declarations means adding empty spec files cannot raise the score. Three ceilings then cap it, and the scoring guide lists them.
04Evidence and trust
Most findings come from reading your code. Give Mjölnir the report of a real test run and it can confirm the code actually ran.
Read the definitions✗ ERROR QA-CI-009 · .github/workflows/ci.yml:10 [E2 · deterministic · measured FP 0% · n=10] Finding Job `test` pipes the test command into another tool without `set -o pipefail`. Fix Add `shell: bash` with `set -o pipefail`, or split into two steps so the test command's exit code is preserved.
Mjölnir read the workflow file. Nothing ran, so this finding stays at L2, proven in code.
✗ ERROR QA-PW-101 · e2e/checkout.spec.ts:6 [E2 · deterministic · measured FP 0% · n=20 · trust L3 · runtime: file executed] Finding `waitForTimeout()` hard sleep. Fix Replace with a web-first assertion (`await expect(locator).toBeVisible()`) or `locator.waitFor()`.
The Playwright report shows this spec file ran, so the finding reaches L3.
05Runtime forensics
Point it at a folder of test results. Playwright JSON, Jest and Vitest JSON, and JUnit XML from any runner all work.
Read the forensics guide▍ FLAKINESS LEADERBOARD3 tests · 1 failed · 1 flaky · 1 retriedTRUE-FLAKE completes checkout with saved card (e2e/checkout.spec.ts) ████████████████████ 6.0s · 2 attemptsFAILING declines an expired card (e2e/checkout.spec.ts) ████░░░░░░░░░░░░░░░░ 1.1s · 1 attempt
TRUE-FLAKE means the test failed at least once and then passed. Mjölnir flags it even though the final check was green.
▍ SELECTOR HEALTHe2e/login.spec.ts [█████████████░░░░░░░] 65 / 100 role/text: 1 · testid: 0 · plain-css: 0 · css-chains: 1 ⚠ · xpath: 0e2e/checkout.spec.ts [█████████████████░░░] 86 / 100 role/text: 3 · testid: 1 · plain-css: 0 · css-chains: 1 ⚠ · xpath: 0
Selector health grades how each locator finds its element. It measures resilience, not correctness.
06Measured rules
Each rate comes from at least 10 hand-classified findings in open-source repos. Rules with bad rates stay listed, in quarantine.
See all 101 rules95of101
rules measured on real code
| Rule | Catches | False positives | Sample |
|---|---|---|---|
| Core | |||
| QA-PW-002 | Unawaited Playwright assertion | 0% | n=20 |
| QA-PY-001 | Focused test committed | 0% | n=12 |
| QA-CS-103 | Test without assertions | 0% | n=11 |
| Extended | |||
| QA-JV-103 | Test without assertions | 26% | n=58 |
| Quarantine | |||
| QA-TEST-001 | Focused test committed | 60% | n=20 |
07CI and AI agents
Scan only what a branch touched, or hand the findings to an AI agent and let the re-scan prove the fix.
Connect an agent With --scope changed it scans the files the branch touched and exits non-zero on new findings.
npx mjolnir-qa@latest --scope changedOr add the GitHub Action:
- uses: Sergey-Bar/Mjolnir@v1
with:
scope: changed
fail-on: errorThe agent writes the fix and Mjölnir re-scans to prove it. Each finding in the handoff says whether it is safe to apply or needs a person to confirm.
claude mcp add mjolnir -- npx -y mjolnir-qa@latest mcpExit codes are frozen, so you can build CI logic on them.
expect(total).toBe(41) looks healthy to it.