mjolnir-qa 0.4.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.vi.md ADDED
@@ -0,0 +1,680 @@
1
+ <div align="center">
2
+
3
+ <img src="assets/readme/logo.png" alt="Mjölnir — Verification Trust Engine" width="800" />
4
+
5
+ ### Các kiểm thử của bạn đang nói dối bạn. Chúng tôi chứng minh điều đó.
6
+
7
+ **Verification Trust Engine cho QA.** Mjölnir kiểm toán các suite kiểm
8
+ thử và pipeline CI, báo cáo điểm độ đáng tin và chỉ ra chính xác nơi
9
+ niềm tin gãy.
10
+
11
+ [![npm](https://img.shields.io/npm/v/mjolnir-qa.svg?style=flat-square&color=C9A227&labelColor=0B0F17)](https://www.npmjs.com/package/mjolnir-qa)
12
+ [![ci](https://img.shields.io/github/actions/workflow/status/Sergey-Bar/Mjolnir/ci.yml?branch=main&style=flat-square&label=ci&labelColor=0B0F17)](https://github.com/Sergey-Bar/Mjolnir/actions/workflows/ci.yml)
13
+ [![license](https://img.shields.io/badge/license-MIT-C9A227.svg?style=flat-square&labelColor=0B0F17)](LICENSE)
14
+ [![node](https://img.shields.io/badge/node-%E2%89%A5%2022.18-2E8C7F.svg?style=flat-square&labelColor=0B0F17)](https://nodejs.org)
15
+
16
+ [English](README.md) | [简体中文](README.zh.md) | [繁體中文](README.zht.md) | [한국어](README.ko.md) | [Deutsch](README.de.md) | [Español](README.es.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Dansk](README.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.he.md) | [العربية](README.ar.md) | [Bosanski](README.bs.md)
17
+
18
+ > 🤖 Machine-assisted translation. The [English README](README.md) is canonical. Last synced: 2026-09-04.
19
+
20
+ ```bash
21
+ npx mjolnir-qa@latest
22
+ ```
23
+
24
+ **Các kiểm thử của bạn có đáng tin không?**
25
+
26
+ [Xem nó hoạt động](#-xem-nó-hoạt-động) ·
27
+ [Khởi động nhanh](#-khởi-động-nhanh) ·
28
+ [Nó kiểm tra gì](#-mjölnir-kiểm-tra-gì) ·
29
+ [Chấm điểm](#cách-thức-chấm-điểm) ·
30
+ [CI](#-tích-hợp-ci) · [Cấu hình](#cấu-hình) ·
31
+ [Tài liệu](#-tài-liệu)
32
+
33
+ </div>
34
+
35
+ ---
36
+
37
+ ## 🎬 Xem nó hoạt động
38
+
39
+ <p align="center">
40
+ <img src="assets/readme/demo.svg" alt="Báo cáo --verbose đầy đủ của Mjölnir trên một repo demo: WORTHINESS 75/100 NEEDS WORK, phân loại chẩn đoán theo nhóm, danh sách FIX THIS FIRST và mỗi finding với rule ID cùng số dòng, trải rộng qua các quy tắc CI, Playwright, vệ sinh kiểm thử và Python" width="900" />
41
+ </p>
42
+
43
+ <sub>Toàn bộ đầu ra `npx mjolnir-qa ./examples/demo-repo --verbose`,
44
+ render từ reporter thật — không lược bỏ gì. Tạo lại bằng
45
+ `npm run docs:demo`;
46
+ [`tests/demo-asset-reproducibility.spec.ts`](tests/demo-asset-reproducibility.spec.ts)
47
+ khiến CI fail nếu sản phẩm lệch khỏi những gì công cụ in ra.</sub>
48
+
49
+ **Chuyện gì vừa xảy ra:**
50
+
51
+ 1. Mjölnir phát hiện các spec Playwright, config của nó, CI workflow và
52
+ một file kiểm thử Python — bốn ngôn ngữ/định dạng, một lượt chạy.
53
+ 2. Nó tìm thấy bằng chứng làm suy giảm niềm tin vào suite — một
54
+ `continue-on-error` che giấu job, một `|| true` nuốt exit code,
55
+ sleep cứng, selector giòn, URL staging hardcode, chờ `networkidle`.
56
+ 3. Nó biến từng cái thành finding cụ thể với rule ID, vị trí và cách
57
+ sửa — và một điểm duy nhất để gate một PR.
58
+
59
+ ### Một finding, nhìn gần
60
+
61
+ Chạy `mjolnir explain QA-CI-001` trên finding đầu tiên ở trên và bạn nhận
62
+ được:
63
+
64
+ ```text
65
+ ▚▞ QA-CI-001 — continue-on-error masks a failing verification gate
66
+
67
+ Severity: error
68
+ Confidence: high
69
+ Evidence: E2
70
+ Measured FP: not yet measured — this rule ships on assumption (see docs/FP-AUDIT.md)
71
+
72
+ WHAT WAS FOUND (real detector output, not a mockup)
73
+ Job `security-scan` runs a verification gate under `continue-on-error: true`.
74
+
75
+ WHY IT MATTERS
76
+ This job can fail every day and CI will still show green. The checkmark
77
+ on this workflow cannot be trusted.
78
+
79
+ HOW TO FIX
80
+ Remove continue-on-error, or scope it to individual non-blocking steps only.
81
+ ```
82
+
83
+ Đó là đơn vị giá trị: không phải lỗi phong cách, mà là một nơi CI của
84
+ bạn nói rằng điều gì đó đã pass khi thực tế chưa pass.
85
+
86
+ ---
87
+
88
+ ## ⚡ Khởi động nhanh
89
+
90
+ Chạy trên một repo để có báo cáo đầy đủ và điểm độ đáng tin:
91
+
92
+ ```bash
93
+ npx mjolnir-qa@latest
94
+ ```
95
+
96
+ **Trong CI, sản phẩm là một lệnh.** Nó chỉ quét những gì branch chạm tới
97
+ và thoát khác 0 khi có vấn đề mới:
98
+
99
+ ```bash
100
+ npx mjolnir-qa@latest --scope changed
101
+ ```
102
+
103
+ Thả cái đó vào một check của PR — `mjolnir ci install` ghi workflow —
104
+ và xong. Mọi thứ còn lại là tuỳ chọn.
105
+
106
+ | Lệnh | Nó làm gì |
107
+ | ----------------------------------- | ---------------------------------------------------------- |
108
+ | `mjolnir` | Quét toàn repo + điểm độ đáng tin |
109
+ | `mjolnir --scope changed` | Chỉ những gì branch bạn đưa vào — dạng CI |
110
+ | `mjolnir ci install` | Sinh workflow PR kiểu tư vấn |
111
+ | `mjolnir explain QA-CI-001` | Gì / tại sao / cách sửa + tỷ lệ FP đo được của một quy tắc |
112
+ | `mjolnir rules --unmeasured` | Các quy tắc chạy bằng giả định, không phải đo đạc |
113
+ | `mjolnir --json` / `--format sarif` | Đọc được bằng máy / GitHub Code Scanning |
114
+ | `mjolnir --strict` | Chạy thêm các quy tắc tier quarantine (rủi ro FP cao hơn) |
115
+
116
+ <details>
117
+ <summary><strong>Khi có gì đó flaky</strong></summary>
118
+
119
+ | Lệnh | Nó làm gì |
120
+ | ----------------------------------- | ------------------------------------------------------- |
121
+ | `mjolnir forensics ./test-results/` | Dữ liệu chạy thật → phán quyết `TRUE-FLAKE`, `FLAKY.md` |
122
+ | `mjolnir triage ./test-results/` | Đề xuất cách ly từ lịch sử thực thi |
123
+ | `mjolnir pw-report ./test-results/` | Tóm tắt lần chạy Playwright — retry / flake / chậm nhất |
124
+ | `mjolnir doctor:playwright` | Quét sâu riêng Playwright + Selector Health Score |
125
+
126
+ </details>
127
+
128
+ <details>
129
+ <summary><strong>Thỉnh thoảng / báo cáo</strong></summary>
130
+
131
+ | Lệnh | Nó làm gì |
132
+ | ------------------------------- | ----------------------------------------------------- |
133
+ | `mjolnir fix --dry-run` / `fix` | Sửa tự động an toàn kèm bằng chứng |
134
+ | `mjolnir baseline` / `diff` | Chụp lại các finding, rồi chỉ báo cáo cái mới/xấu hơn |
135
+ | `mjolnir impact --since <ref>` | Những gì thay đổi kể từ commit trước đó |
136
+ | `mjolnir debt` | Sổ nợ kiểm thử với mô hình chi phí |
137
+ | `mjolnir handover` | Bản đồ onboarding suite cho QA mới |
138
+ | `mjolnir stats` | Bộ đếm mọi thời đại cục bộ của các fix đã thấy |
139
+ | `mjolnir badge` | JSON endpoint shields.io + snippet |
140
+ | `mjolnir rules --md` | Danh mục quy tắc đầy đủ (JSON hoặc Markdown) |
141
+ | `mjolnir doctor` | Tự kiểm toán chính cơ sở quy tắc của Mjölnir |
142
+ | `mjolnir create-rule <ID>` | Scaffold quy tắc mới + fixtures |
143
+ | `mjolnir --format mermaid` | Sơ đồ kiến trúc kiểm thử cho comment PR |
144
+
145
+ </details>
146
+
147
+ Cài toàn cục thay vì `npx` nếu bạn thích: `npm i -g mjolnir-qa`.
148
+ Yêu cầu Node.js ≥ 22.18. Chạy trên Windows, macOS và Linux.
149
+
150
+ ---
151
+
152
+ ## 👥 Dành cho ai?
153
+
154
+ - **QA / SDET** sở hữu suite e2e hoặc tích hợp, cần bằng chứng rằng
155
+ suite thực sự xứng đáng với dấu xanh nó tạo ra.
156
+ - **Nhóm Platform / DevEx** chịu trách nhiệm về tính toàn vẹn CI và các
157
+ release gate — những người không muốn một `continue-on-error` lặng lẽ
158
+ tô đỏ thành xanh cho cả pipeline.
159
+ - **Người duy trì OSS** muốn một gate kiểm chứng rẻ, luôn bật, chạy cả
160
+ cục bộ và trong CI với 0 lệnh gọi mạng.
161
+
162
+ ---
163
+
164
+ ## 🔨 Mjölnir kiểm tra gì
165
+
166
+ | | |
167
+ | --- | -------------------------------------------------------------------------------------------------------------- |
168
+ | ⚖️ | **Điểm độ đáng tin** — một con số, bảng trừ minh bạch, không hộp đen |
169
+ | 🎭 | **Selector Health Score** — chấm locator Playwright của bạn, không chỉ tỉ lệ pass |
170
+ | 🔬 | **Pháp y runtime** — đọc dữ liệu chạy thật của Playwright/JUnit để bắt `TRUE-FLAKE`, không chỉ phỏng đoán tĩnh |
171
+ | 🚨 | **Quy tắc toàn vẹn CI** — bắt `continue-on-error`, `\|\| true` và các mẹo xanh giả khác |
172
+ | 🐍 | **Cả bốn binding Playwright** — TypeScript, Python, Java, C#/.NET — cộng pytest, JUnit/TestNG và CI workflows |
173
+ | 🔒 | **Local-first** — 0 lệnh gọi mạng khi quét, 0 telemetry, chạy trong vài giây |
174
+
175
+ ### Các quy tắc
176
+
177
+ Mọi quy tắc đều có fixture must-fire **và** must-not-fire. Quy tắc mà
178
+ bắn vào chính fixture âm của nó thì không thể ship — đó là bức tường
179
+ lửa false positive.
180
+
181
+ <details>
182
+ <summary><strong>Vệ sinh kiểm thử</strong></summary>
183
+
184
+ | ID | Quy tắc | Severity |
185
+ | ----------- | --------------------------------------------------- | -------- |
186
+ | QA-TEST-001 | Kiểm thử tập trung bị commit (`.only`, `fit`) | error |
187
+ | QA-TEST-002 | Kiểm thử bị bỏ qua mà không có lý do | error |
188
+ | QA-TEST-002 | Kiểm thử bị bỏ qua có lý do được theo dõi | warning |
189
+ | QA-TEST-003 | Kiểm thử không có assertion | error |
190
+ | QA-TEST-004 | Sleep cứng (`waitForTimeout`, `sleep()`, `delay()`) | warning |
191
+ | QA-TEST-006 | Lạm dụng retry che giấu flakiness | warning |
192
+ | QA-TEST-010 | Thân kiểm thử rỗng | error |
193
+
194
+ </details>
195
+
196
+ <details>
197
+ <summary><strong>Chất lượng kiểm thử</strong></summary>
198
+
199
+ | ID | Quy tắc | Severity |
200
+ | ------------ | --------------------------------------- | -------- |
201
+ | QA-TQUAL-001 | Xác minh chỉ bằng mock | info |
202
+ | QA-TQUAL-002 | Assertion đồng nghĩa lặp (tautological) | error |
203
+ | QA-TQUAL-009 | Assertion của promise không await | error |
204
+ | QA-TQUAL-011 | Kiểm thử bị comment | warning |
205
+
206
+ </details>
207
+
208
+ <details>
209
+ <summary><strong>Playwright 🎭</strong></summary>
210
+
211
+ | ID | Quy tắc | Severity |
212
+ | --------- | ---------------------------------------- | -------- |
213
+ | QA-PW-002 | Assertion locator không await | error |
214
+ | QA-PW-003 | `page.pause()` / `test.only()` bị commit | error |
215
+ | QA-PW-004 | Selector CSS/XPath giòn | warning |
216
+ | QA-PW-005 | Logic nghiệp vụ trong `page.evaluate()` | info |
217
+ | QA-PW-114 | Element handle kiểu cũ (`page.$`) | info |
218
+ | QA-PW-118 | Chờ `networkidle` (flaky by design) | info |
219
+ | QA-PW-123 | URL môi trường hardcode | warning |
220
+
221
+ </details>
222
+
223
+ <details>
224
+ <summary><strong>Toàn vẹn CI</strong></summary>
225
+
226
+ | ID | Quy tắc | Severity |
227
+ | --------- | ------------------------------------------------------------------------- | -------- |
228
+ | QA-CI-001 | `continue-on-error` che giấu thất bại | error |
229
+ | QA-CI-002 | `\|\| true` nuốt exit code | error |
230
+ | QA-CI-005 | Báo cáo được tiêu thụ nhưng không bao giờ sinh ra | error |
231
+ | QA-CI-007 | Wrapper retry quanh kiểm thử | warning |
232
+ | QA-CI-008 | Step luôn thành công che giấu thất bại | error |
233
+ | QA-CI-009 | Exit code của kiểm thử không được truyền (`\|` không pipefail, chuỗi `;`) | error |
234
+ | QA-CI-010 | Kiểm thử bị bỏ qua ở nơi phải chặn (guard skip-on-PR) | error |
235
+
236
+ </details>
237
+
238
+ <details>
239
+ <summary><strong>Python / pytest 🐍</strong></summary>
240
+
241
+ | ID | Quy tắc | Severity |
242
+ | --------- | ------------------------------------------------- | -------- |
243
+ | QA-PY-002 | Kiểm thử bị bỏ qua (`skip`, `xfail` không nghiêm) | warning |
244
+ | QA-PY-003 | Hàm kiểm thử không có assertion | error |
245
+ | QA-PY-005 | `time.sleep()` trong kiểm thử | warning |
246
+ | QA-PY-006 | Thân kiểm thử rỗng (`pass`) | info |
247
+ | QA-PY-010 | Phụ thuộc ngẫu nhiên/thời gian mà không freeze | info |
248
+ | QA-PY-012 | Assertion tautological | error |
249
+
250
+ Tổng cộng 20 quy tắc Python (QA-PY-001…012 vệ sinh pytest + QA-PY-101…108 Playwright-Python).
251
+
252
+ </details>
253
+
254
+ <details>
255
+ <summary><strong>Java / JUnit · TestNG ☕</strong></summary>
256
+
257
+ | ID | Quy tắc | Severity |
258
+ | --------- | ---------------------------------------- | -------- |
259
+ | QA-JV-101 | Kiểm thử bị tắt (`@Disabled`) | warning |
260
+ | QA-JV-102 | Sleep cứng (`Thread.sleep()`) | warning |
261
+ | QA-JV-103 | Phương thức kiểm thử không có assertion | error |
262
+ | QA-JV-105 | Sleep cứng Playwright `waitForTimeout()` | warning |
263
+ | QA-JV-106 | Selector giòn thay vì role locator | warning |
264
+ | QA-JV-108 | URL môi trường hardcode trong kiểm thử | info |
265
+ | QA-JV-111 | Mock phủ sóng `page.route("**")` | info |
266
+
267
+ </details>
268
+
269
+ <details>
270
+ <summary><strong>C# / .NET — NUnit · xUnit · MSTest 🟣</strong></summary>
271
+
272
+ | ID | Quy tắc | Severity |
273
+ | --------- | ------------------------------------------------ | -------- |
274
+ | QA-CS-101 | Kiểm thử bị bỏ qua (`[Ignore]`, `[Fact(Skip=)]`) | warning |
275
+ | QA-CS-102 | Sleep cứng (`Thread.Sleep` / `Task.Delay`) | warning |
276
+ | QA-CS-103 | Phương thức kiểm thử không có assertion | error |
277
+ | QA-CS-105 | Sleep cứng `WaitForTimeoutAsync()` | warning |
278
+ | QA-CS-106 | Selector giòn thay vì role locator | warning |
279
+ | QA-CS-108 | URL môi trường hardcode trong kiểm thử | info |
280
+ | QA-CS-111 | Mock phủ sóng `page.RouteAsync("**")` | info |
281
+
282
+ </details>
283
+
284
+ > Danh mục sống đầy đủ — mọi quy tắc với tier, confidence, rủi ro false
285
+ > positive và khả năng autofix — sinh từ registry:
286
+ >
287
+ > ```bash
288
+ > mjolnir rules --md
289
+ > ```
290
+ >
291
+ > Trang theo từng quy tắc nằm dưới [`docs/rules/`](docs/rules/).
292
+
293
+ ### Bao nhiêu đã được đo
294
+
295
+ **74 trong 99 quy tắc mang tỷ lệ false positive được đo trên mã OSS
296
+ thật** (≥ 10 finding được phân loại tay mỗi quy tắc; xem
297
+ [docs/FP-AUDIT.md](docs/FP-AUDIT.md)). 19 quy tắc còn lại ra mắt trên
298
+ ước lượng của tác giả. Chân mỗi bản quét cho biết bao nhiêu quy tắc
299
+ _đã bắn_ được đo; `mjolnir rules --unmeasured` liệt kê những quy tắc
300
+ chưa đo; trang `mjolnir explain` của từng quy tắc nêu trạng thái. Chúng
301
+ tôi công bố tỷ lệ kể cả khi nó xấu xí — QA-CS-103 kiểm toán ở mức 95 %
302
+ và bị cách ly vì thế. Mở rộng con số 78 đó là công việc liên tục của
303
+ dự án.
304
+
305
+ ### Tier quy tắc và độ trưởng thành theo ngôn ngữ
306
+
307
+ Mỗi quy tắc là `core`, `extended` hoặc `quarantine`, phân theo tỷ lệ
308
+ false positive **được đo**:
309
+
310
+ | Tier | Ý nghĩa | Quét mặc định | `--strict` |
311
+ | ------------ | -------------------------------- | :-----------: | :--------: |
312
+ | `core` | ≤ 10 % FP đo được | ✅ | ✅ |
313
+ | `extended` | ≤ 30 % FP đo được | ✅ | ✅ |
314
+ | `quarantine` | trên 30 %, hoặc chưa đo (n < 10) | ❌ | ✅ |
315
+
316
+ | Ngôn ngữ | Adapter | Độ phủ hiện nay |
317
+ | --------------- | ---------------- | ---------------------------------------------------------- |
318
+ | TypeScript / JS | AST bộ biên dịch | rộng nhất, đo nhiều nhất — chủ yếu `core`/`extended` |
319
+ | Python / pytest | Lớp regex | rộng, đã kiểm toán trên corpus — chủ yếu `core`/`extended` |
320
+ | Java | Lớp regex | mới hơn — chủ yếu `extended`/`quarantine` |
321
+ | C# / .NET | Lớp regex | mới hơn — chủ yếu `extended`/`quarantine` |
322
+
323
+ TypeScript và Python có độ phủ đo được rộng nhất. Java và C# đã ship,
324
+ có tài liệu và ở ngoài con số tiêu đề cho đến khi một suite người dùng
325
+ thật (không phải chính các kiểm thử của thư viện binding) được kiểm
326
+ toán.
327
+
328
+ ---
329
+
330
+ ## Cách thức chấm điểm
331
+
332
+ <p align="center">
333
+ <img src="assets/readme/terminal-hero.svg" alt="Đầu ra terminal của Mjölnir — WORTHINESS 75/100 NEEDS WORK, phân loại chẩn đoán theo nhóm và danh sách FIX THIS FIRST" width="820" />
334
+ </p>
335
+
336
+ <sub>Tạo lại bằng `npm run docs:hero`;
337
+ [`tests/hero-asset-reproducibility.spec.ts`](tests/hero-asset-reproducibility.spec.ts)
338
+ khiến CI fail nếu sản phẩm lệch khỏi những gì reporter thực sự in ra.</sub>
339
+
340
+ Điểm số minh bạch: **error −8, warning −3, info −1**, sau đó chuẩn hoá
341
+ theo độ phơi của suite (trừ trên mỗi khai báo kiểm thử). Các khoản trừ
342
+ được cân theo bằng chứng nghĩa là tín hiệu yếu tốn ít hơn. Terminal
343
+ hiện những con số đã chiết khấu chính mà điểm số dùng — không hộp đen.
344
+ Phương pháp đầy đủ: [docs/SCORING.md](docs/SCORING.md).
345
+
346
+ **Phán quyết**
347
+
348
+ | Score | Phán quyết |
349
+ | ------- | ---------------- |
350
+ | ≥ 80 | ✓ **WORTHY** |
351
+ | 50 – 79 | ⚠ **NEEDS WORK** |
352
+ | < 50 | ✖ **UNWORTHY** |
353
+
354
+ **Mức bằng chứng** — mỗi finding mang một; nó đặt trọng số của finding
355
+ trong điểm:
356
+
357
+ | Mức | Ý nghĩa | Tác động điểm | Ví dụ |
358
+ | --- | --------------------------- | ---------------- | ---------------------------------------------------------- |
359
+ | E2 | Lỗi tất yếu (deterministic) | Trừ đủ | `.only` bị commit — chứng minh được về cấu trúc |
360
+ | E1 | Mẫu heuristic | Trừ nửa | `sleep()` khớp regex — tín hiệu mạnh, chưa phải bằng chứng |
361
+ | E0 | Quan sát | Không (chỉ info) | Được báo nhưng không bao giờ gate CI hay trừ |
362
+
363
+ Đa số quy tắc là **E1**. Khẩu hiệu «we prove it» ám chỉ hệ thống này:
364
+ finding E2 là bằng chứng cấu trúc; finding E1 là cảnh báo được đặt đúng
365
+ vị trí, không phải chứng minh hình thức.
366
+
367
+ Repo rỗng chấm `null`, không bao giờ 100 giả — xem
368
+ [Mô hình niềm tin](#mô-hình-niềm-tin).
369
+
370
+ ---
371
+
372
+ ## 🎭 Selector Health Score
373
+
374
+ Chỉ số tiêu đề cho suite Playwright — locator của bạn bền bao nhiêu:
375
+
376
+ ```text
377
+ ▚▞ SELECTOR HEALTH — e2e/checkout.spec.ts
378
+
379
+ [█████████████████░░░] 83 / 100
380
+ role/text: 2 · testid: 1 · css-chains: 1 ⚠ · xpath: 0
381
+ ```
382
+
383
+ Locator dựa trên role nhận điểm tối đa. Chuỗi class CSS và XPath hạ điểm
384
+ — chúng vỡ với mọi lần refactor DOM mà không nói cho bạn biết hành vi
385
+ nào đã thoái trào.
386
+
387
+ ---
388
+
389
+ ## 🔬 Bằng chứng runtime
390
+
391
+ Phát hiện flakiness tĩnh là đoán mò. Mjölnir đọc **dữ liệu thực thi
392
+ thật** — báo cáo JSON Playwright và XML JUnit từ runner bất kỳ:
393
+
394
+ ```bash
395
+ mjolnir forensics ./test-results/
396
+ ```
397
+
398
+ ```text
399
+ ▚▞ FLAKINESS LEADERBOARD
400
+
401
+ 3 tests · 1 failed · 1 flaky · 1 retried
402
+
403
+ TRUE-FLAKE completes checkout with saved card (e2e/checkout.spec.ts)
404
+ ████████████████████ 6.0s · 2 attempts
405
+ FAILING declines an expired card (e2e/checkout.spec.ts)
406
+ ████░░░░░░░░░░░░░░░░ 1.1s · 1 attempt
407
+ ```
408
+
409
+ Một kiểm thử chỉ pass từ lần thử ≥ 2 không phải kiểm thử pass — đó là
410
+ kiểm thử may mắn. Nó bị gắn cờ `TRUE-FLAKE` bất kể dấu xanh cuối cùng.
411
+
412
+ ---
413
+
414
+ ## ⚡ Mjölnir không phải một linter nữa
415
+
416
+ Linter cho bạn biết mã có tuân thủ quy tắc không. Mjölnir cho bạn biết
417
+ sự kiểm chứng của bạn có thể được tin không.
418
+
419
+ | | ESLint / SonarQube | Công cụ coverage | Review thủ công | **Mjölnir** |
420
+ | ------------------------------------------------------- | :----------------: | :--------------: | :-------------: | :---------: |
421
+ | Toàn vẹn CI workflow (`continue-on-error`, `\|\| true`) | ❌ | ❌ | hiếm khi | ✅ |
422
+ | Đa ngôn ngữ (TS, Python, Java, C#) từ một công cụ | ❌ | ❌ | ❌ | ✅ |
423
+ | Chấm độ bền locator Playwright (Selector Health) | ❌ | ❌ | hiếm khi | ✅ |
424
+ | Gắn cờ kiểm thử không có assertion thật | ✅ (plugin)\* | ❌ | thi thoảng | ✅ |
425
+ | Bắt sleep cứng (`waitForTimeout`, `time.sleep`) | ✅ (plugin)\* | ❌ | thi thoảng | ✅ |
426
+ | Chạy trong vài giây, 0 lệnh gọi mạng khi quét | ✅ | ✅ | — | ✅ |
427
+
428
+ \*`eslint-plugin-jest` (`expect-expect`) và `eslint-plugin-playwright`
429
+ (`expect-expect`, `no-wait-for-timeout`) phủ các điểm đó cho framework
430
+ tương ứng.
431
+
432
+ **Phân tích runtime** là một hạng mục riêng ngoài linting tĩnh:
433
+
434
+ | | Playwright retry reporter | Allure / ReportPortal | **Mjölnir forensics** |
435
+ | ------------------------------------------------- | :-----------------------: | :-------------------: | :-------------------: |
436
+ | Đọc dữ liệu chạy thật cho phán quyết `TRUE-FLAKE` | một phần\* | một phần (tag) | ✅ |
437
+ | Báo cáo triage flake từ lịch sử thực thi | ❌ | ✅ | ✅ |
438
+ | Tích hợp với điểm độ đáng tin tĩnh | ❌ | ❌ | ✅ |
439
+
440
+ \*Playwright theo dõi retry bên trong nhưng không tạo báo cáo flakiness
441
+ độc lập với nhãn phán quyết.
442
+
443
+ ---
444
+
445
+ ## 🤖 Tại sao không chỉ dùng AI code review?
446
+
447
+ Vấn đề khác, tầng khác. AI review có thể phát hiện thay đổi kiểm thử
448
+ nghi ngờ trong diff; nó không chứng minh hệ thống kiểm chứng như một
449
+ toàn thể đáng tin — và nó chỉ thấy diff bạn cho nó xem.
450
+
451
+ | | AI code review (Copilot v.v.) | **Mjölnir** |
452
+ | ----------------------------------- | :--------------------------------: | :-----------------------: |
453
+ | Chi phí mỗi lần quét | Token (scale theo kích thước diff) | **Zero** (cục bộ, đã cài) |
454
+ | Thấy cả suite + mọi cấu hình CI | Chỉ diff PR bạn cho xem | **Mọi thứ, mỗi lần** |
455
+ | Tất định (cùng input → cùng output) | ❌ (không tất định) | **✅** |
456
+ | Bắt mẫu nằm im hàng tháng | Chỉ khi có trong ngữ cảnh | **✅** (quét mọi file) |
457
+ | Nhớ finding giữa các lần chạy | ❌ (không trí nhớ giữa các phiên) | **✅** (baseline + diff) |
458
+ | Chạy không cần người kích hoạt | Cần PR hoặc prompt | **✅** (hook CI, 3 giây) |
459
+
460
+ **Dùng cả hai.** AI bắt được sắc thái, ý đồ và lỗi thiết kế không regex
461
+ nào tìm ra. Mjölnir bắt các mẫu cấu trúc AI bỏ sót vì chúng trông
462
+ «có chủ ý» — một `.only` bị commit, exit code bị nuốt, một
463
+ `continue-on-error` trên job kiểm thử. Đó không phải bug cần suy luận;
464
+ đó là sự thật cần quét.
465
+
466
+ ---
467
+
468
+ ## 🤖 Tích hợp CI
469
+
470
+ Một lệnh sinh PR workflow — tư vấn mặc định, không bao giờ chặn:
471
+
472
+ ```bash
473
+ mjolnir ci install
474
+ ```
475
+
476
+ Hoặc nối native vào GitHub Code Scanning qua SARIF:
477
+
478
+ ```yaml
479
+ - run: npx mjolnir-qa@latest --format sarif > mjolnir.sarif
480
+ - uses: github/codeql-action/upload-sarif@v3
481
+ with:
482
+ sarif_file: mjolnir.sarif
483
+ ```
484
+
485
+ Cấu hình trình soạn thảo và pipeline cho SARIF:
486
+ [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md).
487
+
488
+ ### Độ phủ phạm vi thay đổi
489
+
490
+ `--scope changed` gán finding cho các dòng được thêm vào branch của bạn
491
+ so với merge-base với `main`. Nó phủ các file kiểm thử (`*.spec.*`,
492
+ `*.test.*`) cùng file workflow GitHub và cấu hình Playwright trong
493
+ diff. Khi không resolve được merge-base — shallow clone, detached HEAD,
494
+ đích không phải git, branch mặc định khác — nó thoái tr honoured: finding
495
+ quay về gán cho toàn file và báo cáo nói rõ. Ghi đè ref gốc bằng
496
+ `--base <ref>`.
497
+
498
+ ---
499
+
500
+ ## Cấu hình
501
+
502
+ Mjölnir là zero-config. Một `mjolnir.config.json` tuỳ chọn (hoặc
503
+ `.mjolnir.json`) ở gốc repo tinh chỉnh severity, gating và scope —
504
+ không bao giờ đổi ngữ nghĩa phát hiện.
505
+
506
+ | Key | Kiểu | Tác dụng |
507
+ | ------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
508
+ | `exclude` | `string[]` | Glob bỏ qua bổ sung (tập con gitignore), chồng lên mặc định sẵn có |
509
+ | `gate` | `"advisory" \| "error" \| "warning"` | Severity nào thoát khác 0 (mặc định `error`; `advisory` không bao giờ chặn) |
510
+ | `severityOverrides` | `{ "<RULE-ID>": severity }` | Xếp lại hạng finding của một quy tắc cho repo của bạn |
511
+ | `ignore` | `IgnoreEntry[]` | Nuốt finding — **`reason` bắt buộc**; mục hết hạn sau 90 ngày (ngày `expires` tường minh, hoặc thời gian sửa lần cuối của file config cho mục không ghi) |
512
+ | `plugins` | `string[]` | Gói quy tắc bên thứ ba (xem [Mô hình niềm tin](#mô-hình-niềm-tin)) |
513
+
514
+ ```json
515
+ {
516
+ "gate": "error",
517
+ "exclude": ["legacy/**"],
518
+ "severityOverrides": { "QA-PW-118": "warning" },
519
+ "ignore": [
520
+ {
521
+ "ruleId": "QA-TEST-004",
522
+ "files": ["e2e/legacy-login.spec.ts"],
523
+ "reason": "Third-party widget needs a settle delay; tracked in JIRA-4821",
524
+ "expires": "2026-12-31"
525
+ }
526
+ ]
527
+ }
528
+ ```
529
+
530
+ - **`.mjolnirignore`** — một file kiểu gitignore thuần cho loại trừ đường
531
+ dẫn, cùng ngữ pháp với `exclude`. Dùng nó cho nhiễu riêng máy; dùng
532
+ `exclude` khi danh sách thuộc version control cùng phần còn lại của
533
+ cấu hình.
534
+ - **CLI overrides** — `--strict` (gồm quy tắc cách ly), `--width <cols>`
535
+ và `--ascii` / `--no-ascii` (render terminal), `--tone blunt`
536
+ (thông điệp khô hơn), `--max-duration <sec>` (quét một phần giới hạn).
537
+ - Nuốt quy tắc và vòng đời deprecated: [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md).
538
+
539
+ Mục `ignore` cũng nuôi lệnh độc lập `mjolnir suppressions`, liệt kê thứ
540
+ đang bị nuốt và từng mục hết hạn khi nào.
541
+
542
+ ---
543
+
544
+ ## 📐 Mã thoát & hợp đồng
545
+
546
+ Đóng băng — an toàn để xây logic CI trên đó:
547
+
548
+ | Mã thoát | Ý nghĩa |
549
+ | -------- | --------------------------------------------------------------------------------- |
550
+ | `0` | Sạch — không finding ở hoặc trên gate |
551
+ | `1` | Có finding ở hoặc trên gate |
552
+ | `2` | Quét một phần (hết ngân sách thời gian, file không đọc được) — không bao giờ chặn |
553
+ | `10` | Lỗi sử dụng (flag sai, thiếu đích) |
554
+ | `20` | Lỗi nội bộ |
555
+
556
+ Báo cáo JSON/SARIF là `schemaVersion: 1`. Rule ID (`QA-<FAMILY>-NNN`)
557
+ bất biến sau khi ra mắt và không bao giờ tái sử dụng.
558
+
559
+ ---
560
+
561
+ ## Mô hình niềm tin
562
+
563
+ - **Local-first** — 0 lệnh gọi mạng trong lúc quét. Bao giờ vậy. 0
564
+ telemetry.
565
+ - **Không bằng chứng giả** — thà nói «chưa biết» hơn là «đã kiểm chứng».
566
+ Repo rỗng nhận `score: null`, không bao giờ 100 giả.
567
+ - **Trung thực một phần** — nếu phân tích bị cắt ngắn, đầu ra nói vậy.
568
+ Không bao giờ «complete» khi chưa phải.
569
+ - **Tường lửa FP** — phát hiện chạy trên cái nhìn mã không comment/
570
+ chuỗi (quy tắc TypeScript dùng AST bộ biên dịch): một mẫu trong comment
571
+ văn xuôi hoặc chuỗi ví dụ tài liệu là tài liệu, không phải finding.
572
+ - **Đã đo, không phải khẳng định** — chỉ quy tắc có tỷ lệ false positive
573
+ từ mã OSS thật mới ra tier tiêu đề (xem
574
+ [Bao nhiêu đã được đo](#bao-nhiêu-đã-được-đo)); chân bản quét và
575
+ `mjolnir rules --unmeasured` cho biết cái nào là cái nào.
576
+ - **Niềm tin plugin** — plugin là gói npm khai báo trong `"plugins"`.
577
+ **Không sandbox**: mã plugin chạy với đầy đủ đặc quyền Node, cùng mô
578
+ hình tin cậy như plugin ESLint hay Vitest. Tiền tố rule ID core được
579
+ bảo lưu và từ chối khỏi plugin để chống giả danh.
580
+ - **Quy tắc ngoài cục bộ theo workspace** (theo thư mục, 0 mạng) — một
581
+ thư mục `mjolnir-rules/` cạnh đích quét nạp quy tắc riêng: file JSON
582
+ khai báo mẫu regex (không chạy mã), module `.mjs`/`.js` export
583
+ `rules` (tin cậy Node đầy đủ, như plugin). Quy tắc ngoài mang cùng
584
+ metadata tin cậy với core; không bao giờ vào được tier core (core cần
585
+ tỷ lệ FP đo từ sidecar corpus — `tier: "core"` khai báo bị kẹp về
586
+ `extended`), tuân trần tier và được kiểm tra trôi: `mjolnir rules --md
587
+ --external` render danh mục từ các file đã nạp (nguồn gốc `external`),
588
+ và bộ sinh ma trận nhận `--external <root>`.
589
+
590
+ ---
591
+
592
+ ## 🏗️ Kiến trúc
593
+
594
+ <details>
595
+ <summary>Mở cây</summary>
596
+
597
+ ```
598
+ mjolnir/
599
+ ├── src/
600
+ │ ├── engine/ # LanguageAdapter interface + rule runner
601
+ │ ├── adapters/ # typescript · python · java · csharp · github-actions
602
+ │ ├── rules/ # rules across 8 families + the measured-FP table
603
+ │ ├── playwright/ # Selector Health Score engine
604
+ │ ├── discovery/ # workspace, frameworks, ignore resolution
605
+ │ ├── scope/ # git merge-base changed-scope engine
606
+ │ ├── scorer/ # transparent deduction table + prioritization
607
+ │ ├── reporter/ # terminal · JSON · SARIF 2.1 · Mermaid
608
+ │ ├── forensics/ # run-data ingestion · flake verdicts · triage
609
+ │ ├── config/ # mjolnir.config.json + suppressions
610
+ │ ├── plugins/ # third-party rule loading (no sandbox)
611
+ │ └── commands/ # every subcommand
612
+ └── tests/
613
+ ├── fixtures/ # must-fire / must-not-fire per rule
614
+ └── golden/ # frozen score regression locks
615
+ ```
616
+
617
+ </details>
618
+
619
+ - **Quy tắc là hàm thuần** — `(SourceFileContext) → Finding[]`, không
620
+ I/O, không biến toàn cục. Thêm một hệ sinh thái = một adapter + quy
621
+ tắc của nó.
622
+ - **TypeScript/Playwright dùng AST bộ biên dịch** (ts-morph). Python,
623
+ Java và C# chạy trên lớp regex chung có che comment/chuỗi.
624
+ - Lớp AST tree-sitter WASM cho Java và C# đã tồn tại và là bước chính
625
+ xác kế tiếp — chưa được cắm vào pipeline quét đồng bộ.
626
+
627
+ ---
628
+
629
+ ## 📚 Tài liệu
630
+
631
+ | Tài liệu | Có gì trong đó |
632
+ | ------------------------------------------------------ | ------------------------------------------ |
633
+ | [docs/SCORING.md](docs/SCORING.md) | Chuẩn hoá điểm + cân bằng chứng |
634
+ | [docs/FP-AUDIT.md](docs/FP-AUDIT.md) | Tỷ lệ false positive đo được + phương pháp |
635
+ | [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md) | Trạng thái quy tắc, nuốt, deprecation |
636
+ | [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md) | Đầu ra SARIF + cấu hình editor/CI |
637
+ | [docs/rules/](docs/rules/) | Danh mục sinh tự động theo quy tắc |
638
+ | [CONTRIBUTING.md](CONTRIBUTING.md) | Cài đặt dev + quy trình đóng góp |
639
+ | [CHANGELOG.md](CHANGELOG.md) | Lịch sử phát hành |
640
+ | [SECURITY.md](SECURITY.md) | Báo cáo lỗ hổng |
641
+
642
+ ---
643
+
644
+ ## 📈 Tình trạng
645
+
646
+ **v0.5.x · beta mở.** JSON schema và mã thoát là hợp đồng đóng băng.
647
+ TypeScript và Python có độ phủ đo được rộng nhất; Java và C# mới hơn —
648
+ đọc qua
649
+ [bảng tier](#tier-quy-tắc-và-độ-trưởng-thành-theo-ngôn-ngữ).
650
+
651
+ ---
652
+
653
+ ## 🤝 Đóng góp
654
+
655
+ Quy tắc mới là đóng góp đầu tiên dễ nhất — một lệnh scaffold quy tắc
656
+ cùng fixtures must-fire **và** must-not-fire (quy tắc sinh ra cố ý fail
657
+ fixture cho đến khi bạn cài phát hiện thật — stub không thể ship):
658
+
659
+ ```bash
660
+ mjolnir create-rule QA-PW-140 --title "Screenshot without diff bound"
661
+ ```
662
+
663
+ Cài đặt dev đầy đủ, các lệnh standing gate và luật anti-creep / tường
664
+ lửa fixture nằm trong [CONTRIBUTING.md](CONTRIBUTING.md).
665
+
666
+ ---
667
+
668
+ <div align="center">
669
+
670
+ **Ngừng ship kiểm thử mà bạn không thể tin.**
671
+
672
+ ```bash
673
+ npx mjolnir-qa@latest
674
+ ```
675
+
676
+ **Star ⭐ · Watch 👀 · Contribute 🤝**
677
+
678
+ Xây bởi [Sergey Bar](https://www.linkedin.com/in/sergeybar/)
679
+
680
+ </div>