mjolnir-qa 1.0.9 → 2.0.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/README.zh.md CHANGED
@@ -1,379 +1,431 @@
1
1
  <div align="center">
2
2
 
3
- <img src="assets/readme/logo.png" alt="Mjölnir — Verification Trust Engine" width="800" />
3
+ <img src="assets/readme/hero.svg" alt="Mjölnir。测试告诉你什么通过了。Mjölnir 告诉你什么值得信任。" width="100%" />
4
4
 
5
- ### 你的测试在对你说谎。我们来证明它。
5
+ <br />
6
6
 
7
- **面向 QA 的 Verification Trust Engine。** Mjölnir 审计测试套件与 CI
8
- 流水线,给出可信度评分,并精确指出信任在哪里断裂。
7
+ Mjölnir 找出不可能失败的测试和不可能变红的流水线,<br />
8
+ 再评估结果可信到什么程度,每一分都附有证据。
9
9
 
10
- [![npm](https://img.shields.io/npm/v/mjolnir-qa.svg?style=flat-square&color=C19A34&labelColor=0A1119)](https://www.npmjs.com/package/mjolnir-qa)
11
- [![ci](https://img.shields.io/github/actions/workflow/status/Sergey-Bar/Mjolnir/ci.yml?branch=main&style=flat-square&label=ci&labelColor=0A1119)](https://github.com/Sergey-Bar/Mjolnir/actions/workflows/ci.yml)
12
- [![license](https://img.shields.io/badge/license-MIT-C19A34.svg?style=flat-square&labelColor=0A1119)](LICENSE)
13
- [![node](https://img.shields.io/badge/node-%E2%89%A5%2022.18-37ABBD.svg?style=flat-square&labelColor=0A1119)](https://nodejs.org)
14
-
15
- [English](README.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.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)
10
+ <br />
16
11
 
17
- > 🤖 Machine-assisted translation. The [English README](README.md) is canonical. Last synced: 2026-09-08.
12
+ [![npm](https://img.shields.io/npm/v/mjolnir-qa.svg?style=flat-square&color=1F6F7C&labelColor=0A1119)](https://www.npmjs.com/package/mjolnir-qa)
13
+ [![downloads](https://img.shields.io/npm/dm/mjolnir-qa.svg?style=flat-square&color=1F6F7C&labelColor=0A1119)](https://www.npmjs.com/package/mjolnir-qa)
14
+ [![ci](https://img.shields.io/github/actions/workflow/status/Sergey-Bar/Mjolnir/ci.yml?branch=main&style=flat-square&label=ci&labelColor=0A1119)](https://github.com/Sergey-Bar/Mjolnir/actions/workflows/ci.yml)
15
+ [![coverage](https://img.shields.io/codecov/c/github/Sergey-Bar/Mjolnir?style=flat-square&color=1F6F7C&labelColor=0A1119&label=coverage)](https://codecov.io/gh/Sergey-Bar/Mjolnir)
16
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/Sergey-Bar/Mjolnir/badge)](https://scorecard.dev/viewer/?uri=github.com/Sergey-Bar/Mjolnir)
17
+ [![license](https://img.shields.io/badge/license-MIT-1F6F7C.svg?style=flat-square&labelColor=0A1119)](LICENSE)
18
+ [![node](https://img.shields.io/badge/node-%E2%89%A5%2022.18-1F6F7C.svg?style=flat-square&labelColor=0A1119)](https://nodejs.org)
18
19
 
19
20
  ```bash
20
21
  npx mjolnir-qa@latest
21
22
  ```
22
23
 
23
- **你的测试值得信任吗?**
24
+ [实际效果](#实际效果) · [快速开始](#快速开始) · [能发现什么](#mjölnir-能发现什么) · [评分](#可信度评分) · [证据](#证据模型) · [运行取证](#运行时取证) · [CI](#ci-完整性) · [智能体](#ai-智能体) · [安全](#信任与安全) · [局限](#mjölnir-无法告诉你的事) · [文档](#文档)
25
+
26
+ <details>
27
+ <summary>阅读其他语言版本 — 22 种译文</summary>
28
+
29
+ [English](README.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.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)
30
+
31
+ > 🤖 Machine-assisted translation. The [English README](README.md) is canonical. Last synced: 2026-09-15.
32
+
33
+ <!-- Source hash: 3541b09e8d04 -->
24
34
 
25
- [看它如何工作](#-看它如何工作) ·
26
- [快速上手](#-快速上手) ·
27
- [它检查什么](#-mjölnir-检查什么) ·
28
- [评分](#评分如何运作) ·
29
- [CI](#-ci-集成) · [配置](#配置) ·
30
- [文档](#-文档)
35
+ </details>
31
36
 
32
37
  </div>
33
38
 
34
- ---
39
+ <br />
40
+
41
+ ## 绿色对勾是一种声明,而不是证明
42
+
43
+ 绿色对勾只说明流水线没有失败。它并不说明测试真的运行了,也不说明它们有可能失败。下面每一种情况都会显示为绿色:
44
+
45
+ - 提交进仓库的 `.only`,只运行了 3 个测试而不是 900 个
46
+ - 本应拦截的 job 上写着 `continue-on-error: true`
47
+ - 测试命令后面的 `|| true`
48
+ - 什么都不断言、或者测试体为空的测试
49
+ - 把真实失败变成侥幸通过的重试包装
50
+ - workflow 上传了、却从未生成过的报告
51
+ - 靠固定 sleep 勉强撑住的竞态条件
52
+
53
+ 它们都不会让流水线变红,而且在评审中每一个看起来都像是有意为之。所以它们才能存活下来。下面是 Mjölnir 读取一个真实案例:
54
+
55
+ <p align="center">
56
+ <img src="assets/readme/scan.svg" alt="演示仓库的 CI workflow,逐行读取。Mjölnir 在报告的行上标出每一条发现,附上它的规则、问题所在、证据等级以及实测误报率。" width="800" />
57
+ </p>
58
+
59
+ <sub>演示扫描为该 workflow 报告的每一条发现,都标在报告的行上。由 `npm run docs:readme-brand` 根据 [`demo-report.json`](assets/readme/demo-report.json) 生成,并在 CI 中锁定以防漂移。</sub>
60
+
61
+ **严格模式。** 最激进的检测——`.only`、`continue-on-error`、空测试、滥用重试——位于隔离层。它们仅在 `--strict` 下运行,且限定为 `info` 严重级别:只标记,从不拦截。默认扫描(不带 `--strict` 的 `npx mjolnir-qa@latest`)仅覆盖核心和扩展规则。需要咨询层时,加上 `--strict`。
62
+
63
+ Mjölnir 读取测试套件、CI workflow,以及(如果有的话)一次真实运行的报告。它不会运行你的测试,不会安装你的依赖,也不会执行它扫描的代码。当它没有证据时,它会直说,而不是编造信心:
64
+
65
+ | 情况 | Mjölnir 的报告 |
66
+ | ------------------------------------------ | ----------------------------------------------------- |
67
+ | 未找到测试声明 | 评分为 `null`,显示为 **UNKNOWN**。绝不编造一个 100。 |
68
+ | 没有基线或可比较的版本 | **UNKNOWN**,并写明原因。绝不假定为 0。 |
69
+ | 扫描中途终止(时间预算用尽、文件无法读取) | **PARTIAL**,退出码 `2`。绝不呈现为干净结果。 |
70
+
71
+ <p align="center">
72
+ <img src="assets/readme/how-it-works.svg" alt="Mjölnir 的工作方式。它静态读取测试套件和 CI 流水线,在有真实运行报告时也会读取该报告。它按证据等级和信任等级为每条发现加权,其中只有真实运行才能达到 L3 到 L5,最终产出发现、可信度评分,以及基于冻结退出码的 CI 门禁。在智能体循环中,AI 编写修复,Mjölnir 重新扫描来证明它。" width="880" />
73
+ </p>
74
+
75
+ <sub>为本页面设计并按 1:1 显示。由 `npm run docs:readme-brand` 生成,并在 CI 中锁定以防漂移;评分、计数和规则 ID 来自 [`script.demo.json`](assets/video/script.demo.json)、[`demo-report.json`](assets/readme/demo-report.json) 和规则注册表,从不手动输入。同一张图的海报版本:[`architecture.svg`](assets/readme/architecture.svg)。</sub>
76
+
77
+ <br />
78
+
79
+ ## 实际效果
35
80
 
36
- ## 🎬 看它如何工作
81
+ 对 [`examples/demo-repo`](examples/demo-repo) 的一次真实扫描,这是一个带 CI workflow 的小型 Playwright 套件。它的分数都扣在了这里:
37
82
 
38
83
  <p align="center">
39
- <img src="assets/readme/demo.svg" alt="Mjölnir 对演示仓库的完整 --verbose 报告:WORTHINESS 75/100 NEEDS WORK,按类目拆分的诊断,FIX THIS FIRST 清单,以及每一条发现附带的规则 ID 与行号——覆盖 CI、Playwright、测试卫生与 Python 规则" width="900" />
84
+ <img src="assets/readme/terminal-hero.svg" alt="Mjölnir 的扣分明细:WORTHINESS 75/100 NEEDS WORK、按类别的评分、按严重级别的扣分框,以及 FIX THIS FIRST 列表" width="520" />
40
85
  </p>
41
86
 
42
- <sub>`npx mjolnir-qa ./examples/demo-repo --verbose` 的完整输出,由真实
43
- reporter 渲染——无任何删减。通过 `npm run docs:demo` 重新生成;
44
- [`tests/demo-asset-reproducibility.spec.ts`](tests/demo-asset-reproducibility.spec.ts)
45
- 会在产物与工具实际打印内容发生偏差时令 CI 失败。</sub>
87
+ <sub>由 `npm run docs:hero` 根据一次真实扫描生成,并在 CI 中锁定以防漂移。同一次扫描的完整 `--verbose` 报告是 [`demo.svg`](assets/readme/demo.svg)(`npm run docs:demo`)。</sub>
46
88
 
47
- **刚才发生了什么:**
89
+ <details>
90
+ <summary><strong>观看演示</strong> — 一次扫描、它给出的修复,以及证明修复有效的重新扫描</summary>
91
+
92
+ <br />
48
93
 
49
- 1. Mjölnir 发现了 Playwright 规格文件、它的配置、CI 工作流和一个
50
- Python 测试文件——四种语言/格式,一趟扫描。
51
- 2. 它找到了削弱对测试套件信任的证据——掩盖任务失败的
52
- `continue-on-error`、吞掉退出码的 `|| true`、硬性 sleep、脆弱的
53
- 选择器、硬编码的 staging URL、`networkidle` 等待。
54
- 3. 它把每一项变成带规则 ID、位置和修复方案的切实发现——以及一个你
55
- 可以用来给 PR 设门的单一分数。
94
+ <p align="center">
95
+ <a href="assets/video/mjolnir-demo.mp4">
96
+ <img src="assets/video/mjolnir-demo-poster.png" alt="演示录像中的一帧:npx mjolnir-qa@latest 在终端窗口中扫描演示仓库" width="900" />
97
+ </a>
98
+ </p>
99
+
100
+ <sub>由 `npm run docs:video` 根据一次真实扫描逐帧渲染;从不录屏。点击画面即可打开 [`mjolnir-demo.mp4`](assets/video/mjolnir-demo.mp4)。</sub>
101
+
102
+ </details>
56
103
 
57
104
  ### 近看一条发现
58
105
 
59
- 对上面第一条发现运行 `mjolnir explain QA-CI-001`,你会得到:
106
+ 每条发现都回答四个问题:它在哪里、Mjölnir 有多确定、这条规则多常出错,以及如何修复。
107
+
108
+ <p align="center">
109
+ <img src="assets/readme/finding-anatomy.svg" alt="演示扫描的第一条发现,与终端打印的完全一致,标出了它的四个部分:位置、确定程度、规则的出错频率,以及修复方法。" width="100%" />
110
+ </p>
111
+
112
+ `mjolnir explain QA-CI-001` 会打印一条规则完整的信任档案,包括它的实测误报率,以及该误报率为它赢得的等级:
60
113
 
61
114
  ```text
62
- ▚ QA-CI-001 — continue-on-error masks a failing verification gate
115
+ ▍ QA-CI-001 — continue-on-error masks a failing verification gate
63
116
 
64
117
  Severity: error
65
118
  Confidence: high
119
+ Tier: quarantine
66
120
  Evidence: E2
67
- Measured FP: not yet measured — this rule ships on assumption (see docs/FP-AUDIT.md)
121
+ QA impact: False-green risk (FALSE-GREEN)
122
+ Measured FP: 11% (19 hand-classified corpus verdicts)
123
+ FP risk: low (author estimate)
124
+ Languages: yaml
125
+ Frameworks: github-actions, azure-pipelines
68
126
 
69
127
  WHAT WAS FOUND (real detector output, not a mockup)
70
128
  Job `security-scan` runs a verification gate under `continue-on-error: true`.
71
129
 
72
130
  WHY IT MATTERS
73
- This job can fail every day and CI will still show green. The checkmark
74
- on this workflow cannot be trusted.
131
+ This job can fail every day and CI will still show green. The checkmark on
132
+ this workflow cannot be trusted.
75
133
 
76
134
  HOW TO FIX
77
135
  Remove continue-on-error, or scope it to individual non-blocking steps only.
78
- ```
79
136
 
80
- 这就是价值的单位:不是风格上的吹毛求疵,而是你的 CI 声称某事通过了、
81
- 实则未通过的那个位置。
137
+ Example from this rule's own must-fire fixture: QA-CI-001/must-fire/masked.yml
82
138
 
83
- ---
139
+ WHAT WOULD CHANGE THE VERDICT
140
+ - a run report next to the scan target (mjolnir.report.json or test-results/)
141
+ corroborating this file lifts its findings to L3–L5
142
+ - a documented suppression (mjolnir.config.json) lowers the finding count
143
+ without claiming correctness
144
+ - quarantine findings run only under --strict and are advisory (E0) — they can
145
+ never gate CI
84
146
 
85
- ## ⚡ 快速上手
147
+ NEXT ACTION
148
+ Fix the first occurrence, then re-run: `mjolnir --scope changed`. Every
149
+ occurrence of this rule is listed in the scan output.
86
150
 
87
- 对一个仓库运行它,获得完整报告与可信度评分:
151
+ HOW TO VERIFY THE FIX
152
+ Re-run `mjolnir` on the changed file(s) — this finding should no longer
153
+ appear. `mjolnir --scope changed` scopes the check to just what you touched.
88
154
 
89
- ```bash
90
- npx mjolnir-qa@latest
155
+ Docs: mjolnir rules --md (full catalog, this rule included)
91
156
  ```
92
157
 
93
- **在 CI 中,产品就是一条命令。** 它只扫描分支改动的内容,出现新问题时
94
- 以非零码退出:
158
+ 这就是价值的基本单位:CI 报告了一次它并未赢得的通过。
159
+
160
+ <br />
161
+
162
+ ## 快速开始
95
163
 
96
164
  ```bash
97
- npx mjolnir-qa@latest --scope changed
165
+ npx mjolnir-qa@latest
98
166
  ```
99
167
 
100
- 把它放进 PR 检查——`mjolnir ci install` 会写好工作流——就完成了。
101
- 其余一切都是可选的。
168
+ 它扫描当前目录并打印 Trust Report:发现了什么、你能在多大程度上信任它、原因,以及下一步该做什么。当门禁及以上级别没有任何发现时,它以 `0` 退出。
102
169
 
103
- | 命令 | 作用 |
104
- | ----------------------------------- | ------------------------------------------------- |
105
- | `mjolnir` | 全仓扫描 + 可信度评分 |
106
- | `mjolnir --scope changed` | 只看你分支引入的内容——CI 形态 |
107
- | `mjolnir ci install` | 生成建议性的 PR 工作流 |
108
- | `mjolnir explain QA-CI-001` | 是什么 / 为什么 / 如何修复 + 单条规则的实测 FP 率 |
109
- | `mjolnir rules --unmeasured` | 列出按假设而非测量运行的规则 |
110
- | `mjolnir --json` / `--format sarif` | 机器可读 / GitHub Code Scanning |
111
- | `mjolnir --strict` | 同时运行隔离层(quarantine)规则(FP 风险更高) |
112
-
113
- <details>
114
- <summary><strong>当某个测试不稳定时</strong></summary>
170
+ 在 CI 中,只扫描分支引入的内容,这样遗留的测试套件就不会淹没你的第一个 pull request:
115
171
 
116
- | 命令 | 作用 |
117
- | ----------------------------------- | ------------------------------------------------ |
118
- | `mjolnir forensics ./test-results/` | 真实运行数据 → `TRUE-FLAKE` 判定,`FLAKY.md` |
119
- | `mjolnir triage ./test-results/` | 根据执行历史提出隔离建议 |
120
- | `mjolnir pw-report ./test-results/` | Playwright 运行摘要——重试 / 不稳定 / 最慢 |
121
- | `mjolnir doctor:playwright` | 仅 Playwright 的深度扫描 + Selector Health Score |
172
+ ```bash
173
+ npx mjolnir-qa@latest --scope changed
174
+ ```
122
175
 
123
- </details>
176
+ `mjolnir ci install` 会把它写成一个 GitHub Actions workflow,使用固定在 `v1` 主版本标签上的 [action](https://github.com/Sergey-Bar/Mjolnir#readme)(或使用 `--no-action` 改用普通 `npx`)。在你决定让它拦截之前,它始终只是建议性的。
177
+
178
+ | 命令 | 作用 |
179
+ | ----------------------------------- | --------------------------------------------- |
180
+ | `mjolnir` | Trust Report:结论、置信度、下一步行动 |
181
+ | `mjolnir --scope changed` | 只检查你的分支引入的内容(CI 用法) |
182
+ | `mjolnir ci install` | 生成建议性的 PR workflow(基于 action) |
183
+ | `mjolnir explain QA-CI-001` | 是什么、为什么、怎么修,外加实测 FP 率 |
184
+ | `mjolnir why src/a.spec.ts:42` | 解释这一行为什么被标记。从不拦截。 |
185
+ | `mjolnir forensics ./test-results/` | 来自真实运行的运行时证据 |
186
+ | `mjolnir trust-report` | 自包含的 Trust Artifact(md + json) |
187
+ | `mjolnir handoff` | 给编码智能体的修复计划 |
188
+ | `mjolnir --json` / `--format sarif` | 机器可读输出,GitHub Code Scanning |
189
+ | `mjolnir --format codequality` | GitLab Code Quality 报告(MR 组件所用的产物) |
190
+ | `mjolnir --strict` | 同时运行 quarantine 级别的规则(FP 风险更高) |
124
191
 
125
192
  <details>
126
- <summary><strong>偶尔使用 / 报告类</strong></summary>
127
-
128
- | 命令 | 作用 |
129
- | ------------------------------- | ------------------------------------- |
130
- | `mjolnir fix --dry-run` / `fix` | 带证据的安全自动修复 |
131
- | `mjolnir baseline` / `diff` | 先给发现拍快照,之后只报告新增/恶化项 |
132
- | `mjolnir impact --since <ref>` | 自某个先前的提交以来改变了什么 |
133
- | `mjolnir debt` | 带成本模型的测试债登记簿 |
134
- | `mjolnir handover` | 为新 QA 提供的套件上手地图 |
135
- | `mjolnir stats` | 本地统计所见过修复的累计计数 |
136
- | `mjolnir badge` | shields.io 端点 JSON + 代码片段 |
137
- | `mjolnir rules --md` | 完整规则目录(JSON 或 Markdown) |
138
- | `mjolnir doctor` | 对 Mjölnir 自身规则库的自审 |
139
- | `mjolnir create-rule <ID>` | 脚手架生成新规则 + 固定样例 |
140
- | `mjolnir --format mermaid` | 用于 PR 评论的测试架构图 |
193
+ <summary><strong>其他所有命令</strong> — 不稳定测试分诊、报告、治理</summary>
194
+
195
+ <br />
196
+
197
+ | 命令 | 作用 |
198
+ | ----------------------------------- | -------------------------------------------------------- |
199
+ | `mjolnir --classic` | Trust Report 之前的评分横幅样式 |
200
+ | `mjolnir explain verdict` | 解释已保存扫描的结论为何如此 |
201
+ | `mjolnir triage ./test-results/` | 引导式分诊。每一行都以下一步行动结尾。 |
202
+ | `mjolnir pw-report ./test-results/` | Playwright 运行摘要:重试、不稳定测试、最慢的测试 |
203
+ | `mjolnir doctor:playwright` | 仅针对 Playwright 的深度扫描,外加 Selector Health Score |
204
+ | `mjolnir fix --dry-run` / `fix` | 安全的自动修复,每一项都会重新扫描以证明修复生效 |
205
+ | `mjolnir baseline` / `diff` | 为发现建立快照,之后只报告新增或恶化的 |
206
+ | `mjolnir impact --since <ref>` | 某次提交引入并解决了什么 |
207
+ | `mjolnir summary` | 根据报告生成 CI 注解和 step 摘要 |
208
+ | `mjolnir pr-comment` | 限定范围的 PR 评论,Markdown 格式 |
209
+ | `mjolnir debt` | 带成本模型的测试债务登记表 |
210
+ | `mjolnir handover` | 为新 QA 工程师准备的测试套件入门地图 |
211
+ | `mjolnir init` | 检测框架,打印设置检查清单 |
212
+ | `mjolnir suppressions` | 列出被抑制的发现,用于治理 |
213
+ | `mjolnir rules --unmeasured` | 基于假设而非测量运行的规则 |
214
+ | `mjolnir rules --md` | 完整规则目录(JSON 或 Markdown) |
215
+ | `mjolnir doctor` | 对 Mjölnir 自身规则库的自我审计 |
216
+ | `mjolnir create-rule <ID>` | 为新规则及其 fixtures 生成脚手架 |
217
+ | `mjolnir stats` | 本地记录的历史修复计数 |
218
+ | `mjolnir badge` | shields.io 端点 JSON 及代码片段 |
219
+ | `mjolnir --cache` | 借助本地结论缓存进行增量重新扫描 |
220
+ | `mjolnir --format mermaid` | 用于 PR 评论的测试架构图 |
221
+
222
+ `mjolnir help <command>` 会打印其中任一命令的用法、示例和下一步。
141
223
 
142
224
  </details>
143
225
 
144
- 如果你更偏好,可以全局安装而不是 `npx`:`npm i -g mjolnir-qa`。
145
- 要求 Node.js ≥ 22.18。支持 Windows、macOS 和 Linux。
146
-
147
- ---
148
-
149
- ## 👥 这为谁而做?
150
-
151
- - **QA / SDET**——拥有 e2e 或集成测试套件,需要证据证明套件确实配得上
152
- 它产出的绿色对勾。
153
- - **平台 / DevEx 团队**——负责 CI 完整性与发布门禁;他们关心
154
- `continue-on-error` 绝不能悄悄把红色流水线涂成绿色。
155
- - **OSS 维护者**——想要一个便宜、常开、可本地和 CI 运行且零网络调用的
156
- 验证门禁。
157
-
158
- ---
226
+ 需要 Windows、macOS 或 Linux 上的 **Node.js ≥ 22.18**。想全局安装?`npm i -g mjolnir-qa`。这个最低版本来自构建工具链(tsdown 以它为目标,发布流水线也针对它做冒烟测试);运行时依赖对版本没有更高要求。
159
227
 
160
- ## 🔨 Mjölnir 检查什么
228
+ <br />
161
229
 
162
- | | |
163
- | --- | ------------------------------------------------------------------------------------------------------- |
164
- | ⚖️ | **可信度评分**——一个数字、透明的扣分表、没有黑箱 |
165
- | 🎭 | **Selector Health Score**——为你的 Playwright 定位器评级,而不只是通过率 |
166
- | 🔬 | **运行时取证**——读取真实的 Playwright/JUnit 运行数据来捕捉 `TRUE-FLAKE`,而不只是静态猜测 |
167
- | 🚨 | **CI 完整性规则**——抓出 `continue-on-error`、`\|\| true` 等假绿花招 |
168
- | 🐍 | **全部四种 Playwright 绑定**——TypeScript、Python、Java、C#/.NET——外加 pytest、JUnit/TestNG 和 CI 工作流 |
169
- | 🔒 | **本地优先**——扫描时零网络调用、零遥测、几秒内完成 |
230
+ ## Mjölnir 能发现什么
170
231
 
171
- ### 规则
232
+ <p align="center">
233
+ <img src="assets/readme/stack.svg" alt="适配你的技术栈:规则所覆盖的语言、测试框架和 CI 系统,数据来自规则注册表。" width="100%" />
234
+ </p>
172
235
 
173
- 每条规则都带有必须触发(must-fire)**和**必须不触发(must-not-fire)的
174
- 固定样例。会触发自身负样例的规则不能发布——这就是假阳性防火墙。
236
+ **79 条规则**,分为四个类别:测试卫生、测试质量、Playwright 和 CI 完整性,覆盖 TypeScript 与 JavaScript、Python、Java、C# 以及 GitHub Actions YAML。它们覆盖 Playwright 的全部四种语言绑定,以及 pytest、JUnit、TestNG、NUnit、xUnit、MSTest、Jest、Vitest 和 Mocha,并为 Cypress 和 Selenium 提供入门级覆盖。下面列出其中九条,以展示大致样貌:
175
237
 
176
- <details>
177
- <summary><strong>测试卫生</strong></summary>
178
-
179
- | ID | 规则 | Severity |
180
- | ----------- | ---------------------------------------------------- | -------- |
181
- | QA-TEST-001 | 提交了聚焦测试(`.only`、`fit`) | error |
182
- | QA-TEST-002 | 无正当理由跳过的测试 | error |
183
- | QA-TEST-002 | 有记录理由的跳过测试 | warning |
184
- | QA-TEST-003 | 无断言的测试 | error |
185
- | QA-TEST-004 | 硬性 sleep(`waitForTimeout`、`sleep()`、`delay()`) | warning |
186
- | QA-TEST-006 | 用重试掩盖不稳定 | warning |
187
- | QA-TEST-010 | 空测试主体 | error |
238
+ | ID | 规则 | 严重级别 | 等级 |
239
+ | ------------ | ------------------------------------------------ | -------- | ---------- |
240
+ | QA-CI-001 | `continue-on-error` 掩盖了失败的验证门禁 | error | quarantine |
241
+ | QA-CI-009 | 测试退出码未传递(`\|` 未启用 pipefail、`;` 链) | error | extended |
242
+ | QA-TEST-001 | 提交了聚焦测试(`.only`、`fit`) | error | quarantine |
243
+ | QA-TEST-003 | 没有断言的测试 | error | quarantine |
244
+ | QA-TQUAL-009 | 未 await 的 promise 断言 | error | quarantine |
245
+ | QA-PW-002 | 未 await 的 locator 断言 | error | core |
246
+ | QA-PW-004 | 脆弱的 CSS/XPath 选择器 | warning | quarantine |
247
+ | QA-PY-002 | 被跳过的测试(`skip`、非严格的 `xfail`) | warning | core |
248
+ | QA-CS-103 | 没有断言的测试方法 | error | core |
188
249
 
189
- </details>
250
+ 完整目录由注册表自动生成,从不手工维护:`mjolnir rules --md`、[`docs/rules/`](docs/rules/),或 [检查项指南](https://sergey-bar.github.io/Mjolnir/guide/what-it-checks)。
190
251
 
191
252
  <details>
192
- <summary><strong>测试质量</strong></summary>
193
-
194
- | ID | 规则 | Severity |
195
- | ------------ | ------------------------ | -------- |
196
- | QA-TQUAL-002 | 同义反复的断言 | error |
197
- | QA-TQUAL-009 | 未 await 的 promise 断言 | error |
198
- | QA-TQUAL-011 | 被注释掉的测试 | warning |
253
+ <summary><strong>本 README 中提到的所有规则</strong>,汇总在一张表里</summary>
254
+
255
+ <br />
256
+
257
+ > `quarantine` 规则只在 `--strict` 下运行,且从不拦截(其级别上限为 info)。表中显示的是作者设定的严重级别。
258
+
259
+ | ID | 类别 | 规则 | 严重级别 | 等级 |
260
+ | ------------ | ---------- | ------------------------------------------------ | -------- | ---------- |
261
+ | QA-TEST-001 | 卫生 | 提交了聚焦测试(`.only`、`fit`) | error | quarantine |
262
+ | QA-TEST-002 | 卫生 | 被跳过的测试。没有可追踪的原因时升级为 `error`。 | warning | quarantine |
263
+ | QA-TEST-003 | 卫生 | 没有断言的测试 | error | quarantine |
264
+ | QA-TEST-004 | 卫生 | 硬等待(`waitForTimeout`、`sleep()`、`delay()`) | warning | extended |
265
+ | QA-TEST-006 | 卫生 | 滥用重试来掩盖不稳定性 | warning | quarantine |
266
+ | QA-TEST-010 | 卫生 | 空的测试体 | error | quarantine |
267
+ | QA-TQUAL-002 | 质量 | 同义反复的断言 | error | quarantine |
268
+ | QA-TQUAL-009 | 质量 | 未 await 的 promise 断言 | error | quarantine |
269
+ | QA-TQUAL-011 | 质量 | 被注释掉的测试 | warning | extended |
270
+ | QA-PW-002 | Playwright | 未 await 的 locator 断言 | error | core |
271
+ | QA-PW-003 | Playwright | 提交了 `page.pause()` / `test.only()` | error | core |
272
+ | QA-PW-004 | Playwright | 脆弱的 CSS/XPath 选择器 | warning | quarantine |
273
+ | QA-PW-123 | Playwright | 硬编码的环境 URL | warning | quarantine |
274
+ | QA-PW-140 | Playwright | 未设置 `maxDiffPixelRatio` 的截图 | warning | core |
275
+ | QA-CI-001 | CI | `continue-on-error` 掩盖了失败的门禁 | error | quarantine |
276
+ | QA-CI-002 | CI | `\|\| true` 吞掉退出码 | error | extended |
277
+ | QA-CI-005 | CI | 报告被使用却从未生成 | error | quarantine |
278
+ | QA-CI-007 | CI | 包裹测试的重试包装 | warning | extended |
279
+ | QA-CI-008 | CI | 总是成功的 step 掩盖了失败 | error | quarantine |
280
+ | QA-CI-009 | CI | 退出码未传递(`\|` 未启用 pipefail、`;` 链) | error | extended |
281
+ | QA-CI-010 | CI | 在必须拦截的地方跳过了测试 | error | quarantine |
282
+ | QA-PY-002 | Python | 被跳过的测试(`skip`、非严格的 `xfail`) | warning | core |
283
+ | QA-PY-003 | Python | 没有断言的测试函数 | error | quarantine |
284
+ | QA-PY-005 | Python | 测试中的 `time.sleep()` | warning | extended |
285
+ | QA-PY-012 | Python | 同义反复的断言 | error | quarantine |
286
+ | QA-JV-101 | Java | 被禁用的测试(`@Disabled`) | warning | core |
287
+ | QA-JV-102 | Java | 硬等待(`Thread.sleep()`) | warning | extended |
288
+ | QA-JV-103 | Java | 没有断言的测试方法 | error | extended |
289
+ | QA-JV-105 | Java | Playwright `waitForTimeout()` 硬等待 | warning | core |
290
+ | QA-JV-106 | Java | 使用脆弱选择器而非基于角色的 locator | warning | quarantine |
291
+ | QA-CS-101 | C# | 被跳过的测试(`[Ignore]`、`[Fact(Skip=)]`) | warning | core |
292
+ | QA-CS-102 | C# | 硬等待(`Thread.Sleep` / `Task.Delay`) | warning | core |
293
+ | QA-CS-103 | C# | 没有断言的测试方法 | error | core |
294
+ | QA-CS-105 | C# | `WaitForTimeoutAsync()` 硬等待 | warning | extended |
295
+ | QA-CS-106 | C# | 使用脆弱选择器而非基于角色的 locator | warning | quarantine |
296
+
297
+ Python 还提供 QA-PY-001…012(pytest 卫生)和 QA-PY-101…108(Python 版 Playwright)。Cypress 和 Selenium 各有一套三条规则的入门集。
199
298
 
200
299
  </details>
201
300
 
202
- <details>
203
- <summary><strong>Playwright 🎭</strong></summary>
301
+ 每条规则都附带 must-fire **和** must-not-fire 两类 fixture,在自己的负向 fixture 上触发的规则不能发布。这就是误报防火墙;`mjolnir doctor` 在本仓库自己的 CI 中强制执行它。
204
302
 
205
- | ID | 规则 | Severity |
206
- | --------- | ------------------------------------- | -------- |
207
- | QA-PW-002 | 未 await 的 locator 断言 | error |
208
- | QA-PW-003 | 提交了 `page.pause()` / `test.only()` | error |
209
- | QA-PW-004 | 脆弱的 CSS/XPath 选择器 | warning |
210
- | QA-PW-123 | 硬编码的环境 URL | warning |
303
+ ### Selector Health Score
211
304
 
212
- </details>
305
+ `mjolnir doctor:playwright` 根据每个 locator 查找元素的方式为其打分:像用户那样查找(角色、标签、文本)、通过显式契约(`data-testid`),还是依赖结构上的偶然(CSS 链、XPath)。每个文件得到 0 到 100 的分数:
213
306
 
214
- <details>
215
- <summary><strong>CI 完整性</strong></summary>
216
-
217
- | ID | 规则 | Severity |
218
- | --------- | -------------------------------------------------- | -------- |
219
- | QA-CI-001 | `continue-on-error` 掩盖失败 | error |
220
- | QA-CI-002 | `\|\| true` 吞掉退出码 | error |
221
- | QA-CI-005 | 消费报告却从不生成报告 | error |
222
- | QA-CI-007 | 包在测试外面的重试包装 | warning |
223
- | QA-CI-008 | 永远成功的步骤掩盖失败 | error |
224
- | QA-CI-009 | 测试退出码未被传递(`\|` 没有 pipefail、`;` 串联) | error |
225
- | QA-CI-010 | 在必须拦截的地方跳过测试(skip-on-PR 守卫) | error |
226
-
227
- </details>
228
-
229
- <details>
230
- <summary><strong>Python / pytest 🐍</strong></summary>
231
-
232
- | ID | 规则 | Severity |
233
- | --------- | ------------------------------------ | -------- |
234
- | QA-PY-002 | 跳过的测试(`skip`、非严格 `xfail`) | warning |
235
- | QA-PY-003 | 无断言的测试函数 | error |
236
- | QA-PY-005 | 测试中的 `time.sleep()` | warning |
237
- | QA-PY-012 | 同义反复的断言 | error |
238
-
239
- 共 20 条 Python 规则(QA-PY-001…012 pytest 卫生 + QA-PY-101…108 Playwright-Python)。
307
+ ```text
308
+ ▍ SELECTOR HEALTH
240
309
 
241
- </details>
310
+ e2e/login.spec.ts
311
+ [█████████████░░░░░░░] 65 / 100
312
+ role/text: 1 · testid: 0 · plain-css: 0 · css-chains: 1 ⚠ · xpath: 0
242
313
 
243
- <details>
244
- <summary><strong>Java / JUnit · TestNG ☕</strong></summary>
314
+ e2e/checkout.spec.ts
315
+ [█████████████████░░░] 86 / 100
316
+ role/text: 3 · testid: 1 · plain-css: 0 · css-chains: 1 ⚠ · xpath: 0
317
+ ```
245
318
 
246
- | ID | 规则 | Severity |
247
- | --------- | ---------------------------------------- | -------- |
248
- | QA-JV-101 | 被禁用的测试(`@Disabled`) | warning |
249
- | QA-JV-102 | 硬性 sleep(`Thread.sleep()`) | warning |
250
- | QA-JV-103 | 无断言的测试方法 | error |
251
- | QA-JV-105 | Playwright 硬性 sleep `waitForTimeout()` | warning |
252
- | QA-JV-106 | 脆弱选择器取代 role 定位器 | warning |
319
+ 这衡量的是**韧性,而非正确性**。`.btn.btn-primary > div:nth-child(2)` 今天能通过,并会一直通过,直到有人改动标记结构。低分从不声称测试坏了,只说明它依赖于没有人承诺保留的标记结构。
253
320
 
254
- </details>
321
+ <br />
255
322
 
256
- <details>
257
- <summary><strong>C# / .NET — NUnit · xUnit · MSTest 🟣</strong></summary>
323
+ ## 可信度评分
258
324
 
259
- | ID | 规则 | Severity |
260
- | --------- | ------------------------------------------- | -------- |
261
- | QA-CS-101 | 跳过的测试(`[Ignore]`、`[Fact(Skip=)]`) | warning |
262
- | QA-CS-102 | 硬性 sleep(`Thread.Sleep` / `Task.Delay`) | warning |
263
- | QA-CS-103 | 无断言的测试方法 | error |
264
- | QA-CS-105 | 硬性 sleep `WaitForTimeoutAsync()` | warning |
265
- | QA-CS-106 | 脆弱选择器取代 role 定位器 | warning |
325
+ <p align="center">
326
+ <img src="assets/readme/score-gauge.svg" alt="0 到 100 的可信度刻度,指针扫过每一个分数:低于 50 为 UNWORTHY,50 到 79 为 NEEDS WORK,80 到 99 为 WORTHY,100 为 FORGED" width="720" />
327
+ </p>
266
328
 
267
- </details>
329
+ <sub>0 到 100 的每一个分数,都由真实的 `deriveScoreState` 定位。由 `npm run docs:gauge` 生成,并在 CI 中锁定以防漂移。</sub>
268
330
 
269
- > 完整的实时目录——每条规则的层级、置信度、假阳性风险与自动修复可用性——
270
- > 由注册表生成:
271
- >
272
- > ```bash
273
- > mjolnir rules --md
274
- > ```
275
- >
276
- > 每条规则的页面位于 [`docs/rules/`](docs/rules/)。
331
+ | 评分 | 结论 |
332
+ | --------- | --------------------------- |
333
+ | `0 – 49` | **UNWORTHY** |
334
+ | `50 – 79` | **NEEDS WORK** |
335
+ | `80 – 99` | **WORTHY** |
336
+ | `100` | **FORGED** |
337
+ | `null` | **UNKNOWN**:未找到测试声明 |
277
338
 
278
- ### 这些规则中有多少经过测量
339
+ **计算方式**。严重级别决定基础扣分(`error −8`、`warning −3`、`info −1`),证据等级再对其打折:E2 全额扣分,E1 扣一半(向下取整),E0 不扣分。总扣分按套件规模归一化,即按每个测试声明计算,而不是按文件计算。终端打印的就是评分所用的同一组折后数字;不存在隐藏的第二套模型。详情:[docs/SCORING.md](docs/SCORING.md) 和 [评分指南](https://sergey-bar.github.io/Mjolnir/guide/scoring)。
279
340
 
280
- **99 条规则中有 78 条携带在真实 OSS 代码上测得的假阳性率**(每条 ≥ 10 个
281
- 人工分类的发现;见 [docs/FP-AUDIT.md](docs/FP-AUDIT.md))。其余 21 条按
282
- 作者的估计发布。每次扫描的页脚都会告诉你,_触发过的_ 规则中有多少经过
283
- 测量;`mjolnir rules --unmeasured` 列出未测量的;每条规则的
284
- `mjolnir explain` 页面都声明其状态。即使数字难看我们也照样公布——
285
- 持续性工作。
341
+ **100 分不代表什么**。它不代表软件是正确的,不代表测试套件是充分的,也不代表产品没有缺陷。它只代表一件事:**在本次扫描和这一证据模型下,Mjölnir 评估的规则都没有产生扣分。**
286
342
 
287
- ### 规则层级与语言成熟度
343
+ <br />
288
344
 
289
- 每条规则都是 `core`、`extended` 或 `quarantine`,依据其**实测**假阳性率
290
- 分配:
345
+ ## 证据模型
291
346
 
292
- | 层级 | 含义 | 默认扫描 | `--strict` |
293
- | ------------ | ------------------------------ | :------: | :--------: |
294
- | `core` | 实测 FP ≤ 10 % | ✅ | ✅ |
295
- | `extended` | 实测 FP ≤ 30 % | ✅ | ✅ |
296
- | `quarantine` | 高于 30%,或尚未测量(n < 10) | ❌ | ✅ |
347
+ 每条发现都带有两个标签:Mjölnir 有多确定,以及这条发现被核实到了什么程度。这正是只会报告模式的工具,与可以用来把关发布的工具之间的区别。
297
348
 
298
- | 语言 | 适配器 | 当前覆盖 |
299
- | --------------- | ---------- | -------------------------------------------- |
300
- | TypeScript / JS | 编译器 AST | 最广、测量最多——主要为 `core`/`extended` |
301
- | Python / pytest | 正则层 | 广泛、经语料库审计——主要为 `core`/`extended` |
302
- | Java | 正则层 | 较新——主要为 `extended`/`quarantine` |
303
- | C# / .NET | 正则层 | 较新——主要为 `extended`/`quarantine` |
349
+ **有多确定 — 证据等级。**
304
350
 
305
- TypeScript 与 Python 拥有最广的实测覆盖。Java 与 C# 已发布、有文档,
306
- 但在真实的消费方套件(不是绑定库自己的测试)接受审计之前,不进入主打
307
- 数字。
351
+ | 等级 | 名称 | 含义 | 扣分 |
352
+ | ------ | ---------- | ------------------------------ | ---- |
353
+ | **E2** | 确定性证明 | 缺陷就存在于代码的现有写法中 | 全额 |
354
+ | **E1** | 模式证据 | 匹配到与缺陷强相关的模式 | 一半 |
355
+ | **E0** | 观察 | 值得了解。并不声称有任何问题。 | 零 |
308
356
 
309
- ---
357
+ 检测的置信度不等于证明的强度。一条规则可以确定自己匹配到了要找的东西,但它看到的仍可能只是启发式结果。E1 发现是用来阅读和判断的,绝不能盲目套用;这条边界会标注在终端、JSON 以及交给智能体的交接内容中的每条发现上。
310
358
 
311
- ## 评分如何运作
359
+ **核实到什么程度 — 信任等级**。大多数发现来自阅读你的代码。把一次真实测试运行的报告交给 Mjölnir,它就能确认代码确实运行过。
312
360
 
313
361
  <p align="center">
314
- <img src="assets/readme/terminal-hero.svg" alt="Mjölnir 终端输出——WORTHINESS 75/100 NEEDS WORK,按类目拆分的诊断与 FIX THIS FIRST 清单" width="820" />
362
+ <img src="assets/readme/trust-ladder.svg" alt="从 L0 到 L5 的信任阶梯。L0 到 L2 来自阅读代码;L3 到 L5 需要真实的运行报告,阶梯上的断口标示了这一点。" width="100%" />
315
363
  </p>
316
364
 
317
- <sub>通过 `npm run docs:hero` 重新生成;
318
- [`tests/hero-asset-reproducibility.spec.ts`](tests/hero-asset-reproducibility.spec.ts)
319
- 会在产物与 reporter 实际打印内容发生偏差时令 CI 失败。</sub>
365
+ | 等级 | 通俗解释 | 所需条件 |
366
+ | ------ | ---------------- | ---------------------------------- |
367
+ | **L0** | 已记录 | 阅读代码 |
368
+ | **L1** | 看起来像是问题 | 阅读代码:匹配到模式 |
369
+ | **L2** | 在代码中得到证明 | 阅读代码:缺陷是结构性的 |
370
+ | **L3** | 文件运行过 | 运行报告显示发现所在的文件被执行过 |
371
+ | **L4** | 测试运行过 | 运行报告显示发现所在的测试被执行过 |
372
+ | **L5** | 运行结果吻合 | 运行自身的结果证实了该缺陷类别 |
320
373
 
321
- 分数是透明的:**error −8、warning −3、info −1**,然后按套件暴露度
322
- (每条测试声明的扣分)归一化。按证据加权的扣分意味着弱信号代价更低。
323
- 终端显示的正是评分所用的同一批折后数字——没有黑箱。完整方法:
324
- [docs/SCORING.md](docs/SCORING.md)。
374
+ 静态扫描止步于 L2。只有真实的运行报告(Playwright JSON、Jest 或 Vitest JSON、JUnit XML)才能把发现提升到 L3 或更高,因此从未被观察到运行过的发现,永远不能声称它运行过。定义:[docs/TERMINOLOGY.md](docs/TERMINOLOGY.md)。
325
375
 
326
- **判定**
376
+ ### 其中有多少经过实测
327
377
 
328
- | Score | 判定 |
329
- | ------- | ---------------- |
330
- | ≥ 80 | ✓ **WORTHY** |
331
- | 50 – 79 | ⚠ **NEEDS WORK** |
332
- | < 50 | ✖ **UNWORTHY** |
378
+ **79 条规则中有 74 条的误报率是在真实开源代码上测得的**(每条至少 10 个人工分类的发现;见 [docs/FP-AUDIT.md](docs/FP-AUDIT.md))。其余 5 条基于作者的估计发布,并在 `mjolnir explain` 中逐条注明。`mjolnir rules --unmeasured` 会列出它们,每次扫描的页脚也会报告实际*触发*的规则中有多少经过实测。
333
379
 
334
- **证据等级**——每条发现携带一个;它决定该发现在分数中的权重:
380
+ 即使误报率很差也照样公开。QA-TEST-001(提交进仓库的 `.only`)在真实仓库上的审计结果很差,因此被放在 quarantine。每条规则(包括 QA-PW-141)的最新数字都在审计报告里。
335
381
 
336
- | 等级 | 含义 | 对分数的影响 | 示例 |
337
- | ---- | ---------- | ------------ | ---------------------------------------- |
338
- | E2 | 确定性缺陷 | 全额扣分 | 提交了 `.only`——结构上可证明 |
339
- | E1 | 启发式模式 | 一半扣分 | 正则匹配到 `sleep()`——信号强烈,但非证明 |
340
- | E0 | 观察 | 零(仅提示) | 只报告,从不为 CI 设门,也不扣分 |
382
+ ### 规则信任等级
341
383
 
342
- 大多数规则是 **E1**。标语「we prove it」指的就是这套系统:E2 发现是
343
- 结构性证明;E1 发现是位置恰当的警告,不是形式化证明。
384
+ 等级由实测误报率决定,而不是凭主观判断:
344
385
 
345
- 空仓库的分数是 `null`,绝不是虚假的 100——见
346
- [信任模型](#信任模型)。
386
+ | 等级 | 实测 FP | 行为 |
387
+ | -------------- | ------------------ | --------------------------------------------- |
388
+ | **core** | ≤ 10% | 默认报告,会拦截 |
389
+ | **extended** | ≤ 30% | 默认报告,置信度较低 |
390
+ | **quarantine** | > 30% 或被明确声明 | 仅在 `--strict` 下运行,上限为 info,从不拦截 |
391
+ | _未实测_ | n < 10 | 实测之前不能晋升为 core |
347
392
 
348
- ---
393
+ FP 带只能降级一个层级 — 如果规则被明确声明在 `quarantine` 中,它们永远不会将其提升出去。被明确置于 quarantine 的规则无论其测量的 FP 率如何都保持在 quarantine 中。
349
394
 
350
- ## 🎭 Selector Health Score
395
+ 晋升、降级以及各语言的成熟度:[规则生命周期](https://sergey-bar.github.io/Mjolnir/reference/rule-lifecycle)。
351
396
 
352
- Playwright 套件的头号指标——你的定位器有多抗造:
397
+ ### 为什么这不是一个 linter
353
398
 
354
- ```text
355
- ▚ SELECTOR HEALTH — e2e/checkout.spec.ts
399
+ Linter 告诉你代码是否遵循规则。Mjölnir 告诉你你的验证是否值得信任。
356
400
 
357
- [█████████████████░░░] 83 / 100
358
- role/text: 2 · testid: 1 · css-chains: 1 ⚠ · xpath: 0
359
- ```
401
+ | | Linter(ESLint、SonarQube) | 覆盖率工具 | AI 代码评审 | **Mjölnir** |
402
+ | ------------------------------------------------------ | :-------------------------: | :--------: | :---------: | :----------------: |
403
+ | 评估的是**验证体系**,而不是产品代码 | 否 | 否 | 否 | 是 |
404
+ | CI workflow 完整性(`continue-on-error`、`\|\| true`) | 否 | 否 | 仅限 diff | 是 |
405
+ | 评估 Playwright locator 的韧性(Selector Health) | 否 | 否 | 否 | 是 |
406
+ | 读取真实运行数据得出 `TRUE-FLAKE` 结论 | 否 | 否 | 否 | 是 |
407
+ | 公布每条规则的实测误报率 | 否 | 否 | 否 | 是 |
408
+ | 标记没有断言的测试 | 是\* | 否 | 有时 | 是 |
409
+ | 捕获硬等待(`waitForTimeout`、`time.sleep`) | 是\* | 否 | 有时 | 是 |
410
+ | 确定性(相同输入,相同输出) | 是 | 是 | 否 | 是 |
411
+ | 每次扫描的成本 | 免费 | 免费 | token | **零**(本地运行) |
360
412
 
361
- 基于角色的定位器拿满。CSS 类链与 XPath 会拖垮分数——它们在任何 DOM
362
- 重构时都会断,却不会告诉你是哪个行为回归了。
413
+ <sub>\*由 `eslint-plugin-jest` 和 `eslint-plugin-playwright`(`expect-expect`、`no-wait-for-timeout`)以及 SonarQube 自带的断言规则覆盖。各列描述的是验证测试套件时的默认行为;插件、付费版本和自定义规则会改变其中部分答案。这是一份定位概览,而不是基准测试。</sub>
363
414
 
364
- ---
415
+ 也请使用 AI 评审。它能捕捉到任何模式都发现不了的细微差别、意图和设计缺陷。而 Mjölnir 能捕捉到 AI 评审因为看起来是有意为之而忽略的东西:提交进仓库的 `.only`、被吞掉的退出码、测试 job 上的 `continue-on-error`。这些需要的是扫描,而不是推理。
365
416
 
366
- ## 🔬 运行时证据
417
+ <br />
367
418
 
368
- 静态不稳定性检测只是猜测。Mjölnir 读取**真实执行数据**——任何 runner
369
- 产出的 Playwright JSON 报告和 JUnit XML:
419
+ ## 运行时取证
420
+
421
+ 静态分析是对从未运行过的代码进行推理。取证读取的是实际发生的事情:来自任意运行器的 Playwright JSON、Jest JSON、Vitest JSON 和 JUnit XML。
370
422
 
371
423
  ```bash
372
424
  mjolnir forensics ./test-results/
373
425
  ```
374
426
 
375
427
  ```text
376
- ▚ FLAKINESS LEADERBOARD
428
+ ▍ FLAKINESS LEADERBOARD
377
429
 
378
430
  3 tests · 1 failed · 1 flaky · 1 retried
379
431
 
@@ -383,265 +435,184 @@ FAILING declines an expired card (e2e/checkout.spec.ts)
383
435
  ████░░░░░░░░░░░░░░░░ 1.1s · 1 attempt
384
436
  ```
385
437
 
386
- 只在第 ≥ 2 次尝试才通过的测试不是通过的测试——那是碰运气的测试。无论
387
- 最终的绿勾如何,它都会被标记为 `TRUE-FLAKE`。
438
+ `TRUE-FLAKE` 并不是说测试被重试过。它的意思是该测试**至少有一次尝试失败,随后以绿色结束**:这是一次侥幸通过,无论最终的对勾怎么显示都会被标记出来。`mjolnir triage` 会把这段历史转换成隔离建议,`mjolnir pw-report` 则汇总一次运行。正是这些运行报告,把发现提升到 L3 及以上的信任等级。
439
+
440
+ <br />
388
441
 
389
- ---
442
+ ## CI 完整性
443
+
444
+ 测试可以通过,而包裹它的流水线却不可能失败。Mjölnir 同样读取 workflow:`continue-on-error`、`|| true`、从不传递的退出码、总是成功的 step、被使用却从未生成的报告,以及恰恰在应当拦截的事件上被跳过的门禁。每条发现都会指明 job、step 和行号,并带有自己的证据等级。
445
+
446
+ 生成 PR workflow,默认是建议性的:
447
+
448
+ ```bash
449
+ mjolnir ci install
450
+ ```
451
+
452
+ 或者把 Marketplace 上的 action 加到你现有的 workflow 中:
453
+
454
+ ```yaml
455
+ - uses: Sergey-Bar/Mjolnir@v1
456
+ with:
457
+ scope: changed
458
+ fail-on: error
459
+ ```
390
460
 
391
- ## ⚡ Mjölnir 不是又一个 linter
461
+ 固定 `@v1` 以跟随主版本线,或固定一个确切的标签(`@v0.5.32`)以获得可复现的门禁。[docs/DISTRIBUTION-KIT.md](docs/DISTRIBUTION-KIT.md) 介绍了 Marketplace、Smithery 和各个 MCP 注册表。
392
462
 
393
- Linter 告诉你代码是否守规矩。Mjölnir 告诉你你的验证能不能被信任。
463
+ 要把发现推送到 GitHub Code Scanning,上传 SARIF(需要在 workflow 或 job 范围内设置 `security-events: write`):
394
464
 
395
- | | ESLint / SonarQube | 覆盖率工具 | 人工评审 | **Mjölnir** |
396
- | --------------------------------------------------- | :----------------: | :--------: | :------: | :---------: |
397
- | CI 工作流完整性(`continue-on-error`、`\|\| true`) | ❌ | ❌ | 罕见 | ✅ |
398
- | 一个工具覆盖多语言(TS、Python、Java、C#) | ❌ | ❌ | ❌ | ✅ |
399
- | 为 Playwright 定位器的韧性评级(Selector Health) | ❌ | ❌ | 罕见 | ✅ |
400
- | 标出没有真实断言的测试 | ✅(插件)\* | ❌ | 偶尔 | ✅ |
401
- | 抓出硬性 sleep(`waitForTimeout`、`time.sleep`) | ✅(插件)\* | ❌ | 偶尔 | ✅ |
402
- | 几秒内运行、扫描时零网络调用 | ✅ | ✅ | — | ✅ |
465
+ ```yaml
466
+ - run: npx mjolnir-qa@latest --format sarif > mjolnir.sarif
467
+ continue-on-error: true
468
+ - uses: github/codeql-action/upload-sarif@v3
469
+ if: ${{ !cancelled() }}
470
+ with:
471
+ sarif_file: mjolnir.sarif
472
+ ```
403
473
 
404
- \*`eslint-plugin-jest`(`expect-expect`)与
405
- `eslint-plugin-playwright`(`expect-expect`、`no-wait-for-timeout`)
406
- 为其各自框架覆盖了这些。
474
+ 在 GitLab 上,`--format codequality` 会写出 MR 组件和 diff 注解所读取的 Code Quality 报告([docs/GITLAB-CI.md](docs/GITLAB-CI.md))。编辑器和流水线设置:[docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md)。
407
475
 
408
- **运行时分析**是与静态 lint 并列的独立类别:
476
+ ### 变更范围归因
409
477
 
410
- | | Playwright retry reporter | Allure / ReportPortal | **Mjölnir forensics** |
411
- | ---------------------------------------- | :-----------------------: | :-------------------: | :-------------------: |
412
- | 读取真实运行数据以得出 `TRUE-FLAKE` 判定 | 部分\* | 部分(标签) | ✅ |
413
- | 基于执行历史的不稳定分诊报告 | ❌ | ✅ | ✅ |
414
- | 与静态可信度评分集成 | ❌ | ❌ | ✅ |
478
+ ```bash
479
+ npx mjolnir-qa@latest --scope changed
480
+ ```
415
481
 
416
- \*Playwright 内部跟踪重试,但不会产出带判定标签的独立不稳定报告。
482
+ 发现会归因到你的分支新增的行,以 **merge-base** 为基准计算。范围与完整扫描发现的文件集合相同(TS/JS spec 和适配器配置、`test_*.py`、`*Test.java`、`*Tests.cs`、`.github/workflows/*.yml`),再加上未提交和未跟踪的改动,所以在你提交之前就能使用。基准按 `main → master → origin/main → origin/master → origin/HEAD` 的顺序解析;可以用 `--base <ref>` 覆盖。
417
483
 
418
- ---
484
+ 当无法解析 merge-base 时(浅克隆、分离的 HEAD、不在 git 中的目标),发现会退回到按整个文件归因,**而且报告会明确说明这一点**。静默退回正是这个工具要捕捉的那类缺陷。
419
485
 
420
- ## 🤖 为什么不直接用 AI 代码评审?
486
+ <br />
421
487
 
422
- 问题不同、层面不同。AI 评审能在 diff 里发现可疑的测试改动;但它无法
423
- 证明整个验证系统值得信任——而且它只看到你展示给它的 diff。
488
+ ## AI 智能体
424
489
 
425
- | | AI 代码评审(Copilot 等) | **Mjölnir** |
426
- | ----------------------------- | :-----------------------: | :-------------------------------: |
427
- | 每次扫描成本 | Token(随 diff 大小增长) | **零**(本地、已安装) |
428
- | 看到整个套件 + 所有 CI 配置 | 只有你展示的 PR diff | **每次都是全部** |
429
- | 确定性(相同输入 → 相同输出) | ❌(非确定性) | **✅** |
430
- | 抓出沉睡数月的模式 | 只在其进入上下文时 | **✅**(扫描所有文件) |
431
- | 跨运行记住发现 | ❌(会话间没有记忆) | **✅**(baseline + diff) |
432
- | 无人触发也能运行 | 需要 PR 或提示词 | **✅**(CI 钩子,数秒内运行完成) |
490
+ 只有当某个东西据此采取行动时,发现才有价值。
433
491
 
434
- **两者都用。** AI 能捕捉任何正则都找不到的细微差别、意图与设计缺陷。
435
- Mjölnir 捕捉 AI 因其看起来「像是有意为之」而放过的结构模式——提交进
436
- 仓库的 `.only`、被吞掉的退出码、测试任务上的 `continue-on-error`。
437
- 这些不是需要推理的 bug;它们是需要扫描的事实。
492
+ ```text
493
+ SCAN → EVIDENCE → HANDOFF → AGENT → RE-SCAN → PROOF
494
+ ```
438
495
 
439
- ---
496
+ **AI 编写修复。Mjölnir 验证它**。证明来自重新扫描,而绝不是智能体自己报告的成功。
440
497
 
441
- ## 🤖 CI 集成
498
+ | 命令 | 智能体得到什么 |
499
+ | ----------------- | -------------------------------------------------------------------------------------------------------------------------------- |
500
+ | `mjolnir mcp` | 一个基于 stdio 的 [MCP](https://modelcontextprotocol.io) 服务器。`scan`、`explain` 和 `diff` 都成为可调用的工具。 |
501
+ | `mjolnir handoff` | 保存下来的 `--json` 报告会变成一份确定性的 Markdown 计划:检测到了什么、每条发现的证据边界、哪些东西**不能**改动,以及如何验证。 |
502
+ | `mjolnir install` | 写入你的仓库中已有的智能体配置位置(`.claude/`、`.cursor/`、`.kilo/`、`AGENTS.md`),这样智能体在声称完成之前会重新扫描。 |
442
503
 
443
- 一条命令生成 PR 工作流——默认建议性,绝不阻塞:
504
+ 添加到自带 CLI 的客户端:
444
505
 
445
506
  ```bash
446
- mjolnir ci install
507
+ claude mcp add mjolnir -- npx -y mjolnir-qa@latest mcp
447
508
  ```
448
509
 
449
- 或者通过 SARIF 原生接入 GitHub Code Scanning:
510
+ 或者添加到任何接受 `mcpServers` 配置块的客户端:
450
511
 
451
- ```yaml
452
- - run: npx mjolnir-qa@latest --format sarif > mjolnir.sarif
453
- - uses: github/codeql-action/upload-sarif@v3
454
- with:
455
- sarif_file: mjolnir.sarif
512
+ ```json
513
+ {
514
+ "mcpServers": {
515
+ "mjolnir": { "command": "npx", "args": ["-y", "mjolnir-qa@latest", "mcp"] }
516
+ }
517
+ }
456
518
  ```
457
519
 
458
- SARIF 的编辑器与流水线配置:[docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md)。
520
+ **护栏比便利更重要**。交接中的每条发现都带有它的边界。**E2** 表示 _确定性:检查位置并应用修复_。**E1** 表示 _需要确认:仅凭观察不能证明缺陷_。一个盲目修复 E1、抑制规则或修改规则来抬高分数的智能体,所做的正是这个工具要捕捉的事情,因此交接内容会在提示词中、紧挨着这条发现写明这一点。
459
521
 
460
- ### 变更范围的覆盖
522
+ <br />
461
523
 
462
- `--scope changed` 把发现归因于你的分支相对 `main` 合并基新增的行。它
463
- 覆盖测试文件(`*.spec.*`、`*.test.*`),以及 diff 中的 GitHub 工作流
464
- 文件和 Playwright 配置。当合并基无法解析——浅克隆、detached HEAD、
465
- 非 git 目标、默认分支不同——它会诚实地降级:发现回退到整文件归因,
466
- 报告会说明这一点。用 `--base <ref>` 覆盖基线引用。
524
+ ## 信任与安全
467
525
 
468
- ---
526
+ **本地优先,零遥测**。`src/` 中任何地方都不存在具备网络能力的 API(`fetch`、`http`、`https`、`net`、`dns`、`dgram`、WebSocket),一旦出现,[`privacy-network-isolation.spec.ts`](tests/contract/privacy-network-isolation.spec.ts) 就会让构建失败。它同样禁止 `eval` 和 `new Function`。扫描不受信任的代码时从不执行它:静态分析读取源代码文本,取证解析磁盘上已存在的报告文件。
469
527
 
470
- ## 配置
528
+ 两点说明:`npx` 本身会在任何代码运行之前下载软件包;而这项保证覆盖的是 `src/`,不包括第三方插件。
471
529
 
472
- Mjölnir 是零配置的。仓库根目录下可选的 `mjolnir.config.json`(或
473
- `.mjolnir.json`)可以微调严重级别、门禁与范围——它从不改变检测语义。
530
+ **插件不在沙箱中运行**。JS 插件(`mjolnir-rules/*.mjs`,或在 `"plugins"` 下列出的 npm 包)以完整的 Node 权限运行,与 ESLint 或 Vitest 插件的信任模型相同。加载它们需要**在每次扫描时**显式启用:没有 `--enable-plugins`(或 `MJOLNIR_ENABLE_PLUGINS=1`)时,它们的源码永远不会被加载,stderr 上的提示会列出被跳过的内容。JSON 规则清单不执行任何代码,核心规则 ID 前缀是保留的,因此插件无法冒充核心规则。请通过 [SECURITY.md](SECURITY.md) 报告漏洞。
474
531
 
475
- | 键 | 类型 | 作用 |
476
- | ------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
477
- | `exclude` | `string[]` | 额外的忽略 glob(gitignore 子集),叠加在内建默认之上 |
478
- | `gate` | `"advisory" \| "error" \| "warning"` | 哪些严重级别以非零码退出(默认 `error`;`advisory` 绝不阻塞) |
479
- | `severityOverrides` | `{ "<RULE-ID>": severity }` | 为你的仓库重新排列某条规则的发现 |
480
- | `ignore` | `IgnoreEntry[]` | 压制发现——**`reason` 必填**;条目 90 天后过期(显式的 `expires` 日期,或未注明时以配置文件的最后修改时间为准) |
481
- | `plugins` | `string[]` | 第三方规则包(见[信任模型](#信任模型)) |
532
+ **它会检查自己**。一个验证信任引擎,只有自身可被验证才有立足之地。每次 CI 运行都会用同一次运行产出的构建来扫描本仓库。只要出现任何 error 级别的发现,门禁就会失败;遇到**部分**扫描或**崩溃的规则**时同样失败,因为一次被截断、什么都没报告的自检,正是这个项目要捕捉的虚假绿色。`mjolnir doctor` 会在同一次运行中重新审计规则库(fixture 防火墙、等级的诚实性、core 等级上限),结果为 INCONCLUSIVE 的检查与失败的检查同样判定为失败。两份报告都会作为构建产物上传。
482
533
 
483
- ```json
484
- {
485
- "gate": "error",
486
- "exclude": ["legacy/**"],
487
- "severityOverrides": { "QA-PW-141": "warning" },
488
- "ignore": [
489
- {
490
- "ruleId": "QA-TEST-004",
491
- "files": ["e2e/legacy-login.spec.ts"],
492
- "reason": "Third-party widget needs a settle delay; tracked in JIRA-4821",
493
- "expires": "2026-12-31"
494
- }
495
- ]
496
- }
497
- ```
534
+ ### 退出码与机器契约
498
535
 
499
- - **`.mjolnirignore`**——用于路径排除的纯 gitignore 风格文件,与
500
- `exclude` 同一语法。机器级的噪音用它;当清单应当随其余配置一起进入
501
- 版本控制时用 `exclude`。
502
- - **CLI 覆盖**——`--strict`(包含隔离层规则)、`--width <cols>` 与
503
- `--ascii` / `--no-ascii`(终端渲染)、`--tone blunt`(更生硬的措辞)、
504
- `--max-duration <sec>`(限时部分扫描)。
505
- - 规则压制与弃用生命周期:[docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md)。
506
-
507
- `ignore` 条目也为独立命令 `mjolnir suppressions` 提供数据,该命令列出
508
- 当前被压制的项以及每条的过期时间。
509
-
510
- ---
511
-
512
- ## 📐 退出码与契约
513
-
514
- 冻结——可以放心在其上构建 CI 逻辑:
515
-
516
- | 退出码 | 含义 |
517
- | ------ | ---------------------------------------------- |
518
- | `0` | 干净——没有达到或超过门禁的发现 |
519
- | `1` | 存在达到或超过门禁的发现 |
520
- | `2` | 部分扫描(时间预算用尽、文件不可读)——绝不阻塞 |
521
- | `10` | 用法错误(错误的标志、缺少目标) |
522
- | `20` | 内部错误 |
523
-
524
- JSON/SARIF 报告为 `schemaVersion: 1`。规则 ID(`QA-<FAMILY>-NNN`)一经
525
- 发布即不可变,且绝不复用。
526
-
527
- ---
528
-
529
- ## 信任模型
530
-
531
- - **本地优先**——扫描期间零网络调用。任何时候都是。零遥测。
532
- - **不做虚假证明**——我们宁可说「未知」也不说「已验证」。空仓库得到
533
- `score: null`,绝不是虚假的 100。
534
- - **部分诚实**——如果分析被截断,输出会说明。绝不会在未完成时声称
535
- 「complete」。
536
- - **假阳性防火墙**——检测在去除注释/字符串的代码视图上运行
537
- (TypeScript 规则使用编译器 AST):出现在散文注释或文档示例字符串中
538
- 的模式是文档,不是发现。
539
- - **测量,而非断言**——只有具有来自真实 OSS 代码的假阳性率的规则才
540
- 进入主打层级(见[这些规则中有多少经过测量](#这些规则中有多少经过测量));
541
- 扫描页脚和 `mjolnir rules --unmeasured` 会告诉你哪条是哪条。
542
- - **插件信任与执行闸门**——插件是在 `"plugins"` 下声明的 npm 包;
543
- JS 模块位于 `mjolnir-rules/*.mjs`。**没有沙箱**:插件代码以完整
544
- Node 特权运行,与 ESLint 或 Vitest 插件相同的信任模型。正因如此,
545
- 代码执行**在每次扫描时都是选择性的**:传入 `--enable-plugins`(或
546
- 设置 `MJOLNIR_ENABLE_PLUGINS=1`),否则这些来源不会被加载——一条
547
- 醒目的 stderr 提示会准确列出被跳过的内容。扫描不可信的代码绝不会
548
- 执行它。JSON 规则清单(`mjolnir-rules/*.json`)不受影响:它们声明
549
- 正则模式,按设计不执行任何代码。核心规则 ID 前缀是保留的,插件与
550
- 外部规则若使用将被拒绝以防伪装。
551
- - **工作区本地外部规则**(基于文件夹、零网络)——扫描目标旁的
552
- `mjolnir-rules/` 目录可加载自定义规则:JSON 文件声明正则模式(不执行
553
- 代码),`.mjs`/`.js` 模块导出 `rules`(完整 Node 信任,同插件)。外部
554
- 规则携带与核心相同的信任元数据;它们绝不能进入核心层级(核心要求
555
- 来自语料库侧文件的实测 FP 率——声明的 `tier: "core"` 会被压到
556
- `extended`),遵守层级上限,并做漂移检查:`mjolnir rules --md --external`
557
- 从加载的文件渲染目录(来源 `external`),矩阵生成器接受 `--external <root>`。
558
-
559
- ---
560
-
561
- ## 🏗️ 架构
536
+ 已冻结,你可以放心地在其上构建 CI 逻辑:
562
537
 
563
- <details>
564
- <summary>展开目录树</summary>
538
+ | 退出码 | 含义 |
539
+ | ------ | -------------------------------------------------- |
540
+ | `0` | 干净:门禁及以上级别没有发现 |
541
+ | `1` | 门禁及以上级别存在发现 |
542
+ | `2` | 部分扫描(时间预算用尽、文件无法读取)。从不拦截。 |
543
+ | `10` | 用法错误(参数错误、缺少目标) |
544
+ | `20` | 内部错误 |
565
545
 
566
- ```
567
- mjolnir/
568
- ├── src/
569
- │ ├── engine/ # LanguageAdapter interface + rule runner
570
- │ ├── adapters/ # typescript · python · java · csharp · github-actions
571
- │ ├── rules/ # rules across 8 families + the measured-FP table
572
- │ ├── playwright/ # Selector Health Score engine
573
- │ ├── discovery/ # workspace, frameworks, ignore resolution
574
- │ ├── scope/ # git merge-base changed-scope engine
575
- │ ├── scorer/ # transparent deduction table + prioritization
576
- │ ├── reporter/ # terminal · JSON · SARIF 2.1 · Mermaid
577
- │ ├── forensics/ # run-data ingestion · flake verdicts · triage
578
- │ ├── config/ # mjolnir.config.json + suppressions
579
- │ ├── plugins/ # third-party rule loading (no sandbox)
580
- │ └── commands/ # every subcommand
581
- └── tests/
582
- ├── fixtures/ # must-fire / must-not-fire per rule
583
- └── golden/ # frozen score regression locks
584
- ```
546
+ `2` 被刻意区别于 `0`:一次没有完成的扫描并不是“什么都没发现”,它只是还没找完。
585
547
 
586
- </details>
548
+ 机器消费的一切(MCP 工具结果、`--json`、SARIF 2.1)都来自同一个规范结果,遵循带版本号且**只做增量扩展**的 schema(`schemaVersion: 1`、`contractVersion: 1`),因此任何消费者都无需从渲染后的文本中重建含义。参见 [机器契约](docs/machine-contract.md)。规则 ID(`QA-<FAMILY>-NNN`)一经发布即不可更改,也绝不复用。
587
549
 
588
- - **规则是纯函数**——`(SourceFileContext) → Finding[]`,无 I/O,无
589
- 全局状态。增加一个生态 = 一个适配器 + 它的规则。
590
- - **TypeScript/Playwright 使用编译器 AST**(ts-morph)。Python、Java 与
591
- C# 运行在共享的、屏蔽注释/字符串的正则层上。
592
- - 面向 Java 与 C# 的 tree-sitter WASM AST 层已存在,是下一步的精度
593
- 提升——尚未接入同步扫描管线。
550
+ <br />
594
551
 
595
- ---
552
+ ## Mjölnir 无法告诉你的事
596
553
 
597
- ## 📚 文档
554
+ - **它不会运行你的测试**。扫描干净不等于测试套件通过。
555
+ - **它无法告诉你某个断言是*错误的***。`expect(total).toBe(41)` 看起来很健康。Mjölnir 找的是*不可能失败*的测试和*不可能变红*的流水线,而不是检查了错误内容的测试。
556
+ - **它不能证明业务正确性**。这里没有任何东西能说明你的产品做到了需求的要求。
557
+ - **100 分不能证明测试套件好**。你的套件是否覆盖了真实风险是另一个问题,这个工具不回答它。
558
+ - **79 条规则中有 5 条基于估计发布**,而不是实测的误报率。每一条都会在自己的发现中注明。
559
+ - **E1 不是 E2**。启发式发现值得阅读,但不值得盲目套用。
560
+ - **空仓库的得分是 `null`,绝不是 100。**
561
+ - **名为 `*.spec.ts` 却没有测试声明的文件不算覆盖**。如果一个仓库仅有的 spec 文件里只有导入或类型(`it`/`test` 调用为零),它的得分是 `null`,而不是 100。
598
562
 
599
- | 文档 | 内容 |
600
- | ------------------------------------------------------ | --------------------------- |
601
- | [docs/SCORING.md](docs/SCORING.md) | 分数归一化 + 证据加权 |
602
- | [docs/FP-AUDIT.md](docs/FP-AUDIT.md) | 实测假阳性率 + 方法 |
603
- | [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md) | 规则状态、压制、弃用 |
604
- | [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md) | SARIF 输出 + 编辑器/CI 配置 |
605
- | [docs/rules/](docs/rules/) | 生成的逐规则目录 |
606
- | [CONTRIBUTING.md](CONTRIBUTING.md) | 开发环境 + 贡献流程 |
607
- | [CHANGELOG.md](CHANGELOG.md) | 发布历史 |
608
- | [SECURITY.md](SECURITY.md) | 漏洞报告 |
563
+ <br />
609
564
 
610
- ---
565
+ ## 文档
611
566
 
612
- ## 📈 状态
567
+ 完整文档站点位于 <https://sergey-bar.github.io/Mjolnir/>。
613
568
 
614
- **v0.5.x · 公开测试。** JSON 模式与退出码是冻结的契约。TypeScript 与
615
- Python 的实测覆盖最广;Java 与 C# 较新——请通过
616
- [层级表](#规则层级与语言成熟度)阅读。
569
+ | 文档 | 内容 |
570
+ | ------------------------------------------------------ | -------------------------------------------- |
571
+ | [docs/SCORING.md](docs/SCORING.md) | 评分归一化与证据加权 |
572
+ | [docs/TERMINOLOGY.md](docs/TERMINOLOGY.md) | 规范术语表:一个概念一个词 |
573
+ | [docs/FP-AUDIT.md](docs/FP-AUDIT.md) | 实测误报率及测量方法 |
574
+ | [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md) | 规则状态、等级、抑制与弃用 |
575
+ | [docs/VERSIONING.md](docs/VERSIONING.md) | Semver 策略、冻结的接口、弃用周期 |
576
+ | [docs/machine-contract.md](docs/machine-contract.md) | 规范的机器可读结果 |
577
+ | [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md) | SARIF 输出以及编辑器或 CI 设置 |
578
+ | [docs/GITLAB-CI.md](docs/GITLAB-CI.md) | GitLab:Code Quality 报告、MR 配置示例、门禁 |
579
+ | [docs/rules/](docs/rules/) | 自动生成的逐条规则目录 |
580
+ | [CONTRIBUTING.md](CONTRIBUTING.md) | 开发环境搭建与贡献流程 |
581
+ | [SUPPORT.md](SUPPORT.md) | 在哪里提问、报告问题和获取帮助 |
582
+ | [SECURITY.md](SECURITY.md) | 漏洞报告 |
583
+ | [CHANGELOG.md](CHANGELOG.md) | 版本历史 |
617
584
 
618
- ---
585
+ ### 状态
619
586
 
620
- ## 🤝 贡献
587
+ **版本 1**。JSON schema 和退出码是冻结的契约。TypeScript 和 Python 拥有最广的实测覆盖。Java 和 C# 较新;请参照 [成熟度表](https://sergey-bar.github.io/Mjolnir/reference/rule-lifecycle) 来理解它们。接下来的计划,不编造日期:[公开路线图](https://sergey-bar.github.io/Mjolnir/reference/roadmap)。
621
588
 
622
- 新规则是最容易迈出的第一步——一条命令即可脚手架出规则及其必须触发
623
- **和**必须不触发的固定样例(生成的规则会故意在样例上失败,直到你实现
624
- 真正的检测——占位桩无法发布):
589
+ ### 参与贡献
590
+
591
+ 新规则是最容易上手的第一份贡献。一条命令就能为规则生成脚手架,连同它的 must-fire **和** must-not-fire fixture。生成的规则在写出真正的检测逻辑之前,会故意在自己的 fixture 上失败,因为一个被发布出去的空壳,就是一条没人测量过的规则:
625
592
 
626
593
  ```bash
627
594
  mjolnir create-rule QA-PW-140 --title "Screenshot without diff bound"
628
595
  ```
629
596
 
630
- 完整的开发环境、常设门禁命令以及防蔓延 / 样例防火墙法则都在
631
- [CONTRIBUTING.md](CONTRIBUTING.md)。
597
+ 开发环境搭建、常驻门禁命令,以及 anti-creep 和 fixture 防火墙两条法则,都在 [CONTRIBUTING.md](CONTRIBUTING.md) 中。
632
598
 
633
- ---
599
+ <br />
634
600
 
635
601
  <div align="center">
636
602
 
637
- **别再发布你无法信任的测试了。**
603
+ <img src="assets/readme/closing.svg" alt="在你的仓库上运行它。" width="100%" />
638
604
 
639
605
  ```bash
640
606
  npx mjolnir-qa@latest
641
607
  ```
642
608
 
643
- **Star ⭐ · Watch 👀 · Contribute 🤝**
609
+ [阅读指南](https://sergey-bar.github.io/Mjolnir/guide/getting-started) · [文档站点](https://sergey-bar.github.io/Mjolnir/) · [npm](https://www.npmjs.com/package/mjolnir-qa)
610
+
611
+ <br />
612
+
613
+ 别问测试是否通过了。<br />
614
+ 要问证据能否证明它们值得信任。
644
615
 
645
- 由 [Sergey Bar](https://www.linkedin.com/in/sergeybar/) 构建
616
+ <sub>由 [Sergey Bar](https://www.linkedin.com/in/sergeybar/) 构建 · MIT 许可</sub>
646
617
 
647
618
  </div>