mjolnir-qa 1.0.9 → 2.0.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.ko.md CHANGED
@@ -1,389 +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
- 파이프라인을 감사하고, 신뢰도 점수를 보고하며, 신뢰가 정확히 어디서
9
- 깨지는지 보여줍니다.
7
+ Mjölnir는 실패할 수 없는 테스트와 빨간색이 될 수 없는 파이프라인을 찾아낸 뒤,<br />
8
+ 결과를 어디까지 믿을 수 있는지 모든 항목에 증거를 붙여 점수로 매깁니다.
10
9
 
11
- [![npm](https://img.shields.io/npm/v/mjolnir-qa.svg?style=flat-square&color=C19A34&labelColor=0A1119)](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=0A1119)](https://github.com/Sergey-Bar/Mjolnir/actions/workflows/ci.yml)
13
- [![license](https://img.shields.io/badge/license-MIT-C19A34.svg?style=flat-square&labelColor=0A1119)](LICENSE)
14
- [![node](https://img.shields.io/badge/node-%E2%89%A5%2022.18-37ABBD.svg?style=flat-square&labelColor=0A1119)](https://nodejs.org)
15
-
16
- [English](README.md) | [简体中文](README.zh.md) | [繁體中文](README.zht.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 />
17
11
 
18
- > 🤖 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)
19
19
 
20
20
  ```bash
21
21
  npx mjolnir-qa@latest
22
22
  ```
23
23
 
24
- **당신의 테스트는 신뢰할 만한가요?**
24
+ [실제 동작 보기](#실제-동작-보기) · [빠른 시작](#빠른-시작) · [무엇을 찾는가](#mjölnir가-찾는-것) · [점수](#신뢰도-점수) · [증거](#증거-모델) · [실행 포렌식](#런타임-포렌식) · [CI](#ci-무결성) · [에이전트](#ai-에이전트) · [보안](#신뢰와-보안) · [한계](#mjölnir가-알려줄-수-없는-것) · [문서](#문서)
25
+
26
+ <details>
27
+ <summary>다른 언어로 읽기 — 22개 번역</summary>
28
+
29
+ [English](README.md) | [简体中文](README.zh.md) | [繁體中文](README.zht.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 -->
25
34
 
26
- [작동 모습 보기](#-작동-모습-보기) ·
27
- [빠른 시작](#-빠른-시작) ·
28
- [무엇을 검사하는가](#-mjölnir가-검사하는-것) ·
29
- [점수 산정](#점수-산정은-어떻게-작동하는가) ·
30
- [CI](#-ci-통합) · [설정](#설정) ·
31
- [문서](#-문서)
35
+ </details>
32
36
 
33
37
  </div>
34
38
 
35
- ---
39
+ <br />
40
+
41
+ ## 초록색 체크는 주장일 뿐, 증명이 아닙니다
42
+
43
+ 초록색 체크는 파이프라인이 실패하지 않았다는 뜻입니다. 테스트가 실행되었다거나, 테스트가 실패할 수 있었다는 뜻은 아닙니다. 다음은 모두 초록색으로 통과합니다.
44
+
45
+ - 900개가 아니라 3개의 테스트만 실행한, 커밋된 `.only`
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
+ ## 실제 동작 보기
36
80
 
37
- ## 🎬 작동 모습 보기
81
+ CI workflow가 있는 작은 Playwright 스위트인 [`examples/demo-repo`](examples/demo-repo)를 실제로 스캔한 결과입니다. 점수는 여기서 깎였습니다.
38
82
 
39
83
  <p align="center">
40
- <img src="assets/readme/demo.svg" alt="데모 리포지터리에 대한 Mjölnir의 전체 --verbose 보고서: WORTHINESS 75/100 NEEDS WORK, 범주별 진단 내역, FIX THIS FIRST 목록, 그리고 CI·Playwright·테스트 위생·Python 규칙 전반에 걸친 규칙 ID와 줄 번호가 붙은 각 발견" width="900" />
84
+ <img src="assets/readme/terminal-hero.svg" alt="Mjölnir의 감점 내역: WORTHINESS 80/100 WORTHY, 카테고리별 점수, 심각도별 감점 상자, 그리고 FIX THIS FIRST 목록" width="520" />
41
85
  </p>
42
86
 
43
- <sub>`npx mjolnir-qa ./examples/demo-repo --verbose`의 전체 출력을 실제
44
- reporter로 렌더링한 것 — 아무것도 잘라내지 않았습니다. `npm run
45
- docs:demo`로 재생성합니다.
46
- [`tests/demo-asset-reproducibility.spec.ts`](tests/demo-asset-reproducibility.spec.ts)
47
- 은(는) 산출물이 도구가 실제로 출력하는 것과 어긋나면 CI를 떨어뜨립니다.</sub>
87
+ <sub>`npm run docs:hero`가 실제 스캔으로 생성하며 CI에서 변경되지 않도록 고정됩니다. 같은 스캔의 전체 `--verbose` 리포트는 [`demo.svg`](assets/readme/demo.svg)입니다 (`npm run docs:demo`).</sub>
48
88
 
49
- **방금 무슨 일이 일어났나:**
89
+ <details>
90
+ <summary><strong>영상으로 보기</strong> — 스캔, 스캔이 출력하는 수정, 그리고 그 수정을 증명하는 재스캔</summary>
50
91
 
51
- 1. Mjölnir는 Playwright 스펙, 그 설정, CI 워크플로, Python 테스트
52
- 파일을 발견했습니다 — 네 가지 언어/포맷, 한 번의 패스로.
53
- 2. 스위트에 대한 신뢰를 약화시키는 증거를 찾았습니다 — 작업을 가리는
54
- `continue-on-error`, 종료 코드를 삼키는 `|| true`, 하드 sleep, 취약한
55
- 선택자, 하드코딩된 스테이징 URL, `networkidle` 대기.
56
- 3. 각각을 규칙 ID·위치·수정 방법을 갖춘 구체적인 발견으로 — 그리고 PR에
57
- 게이트를 걸 수 있는 단 하나의 점수로 바꾸었습니다.
92
+ <br />
58
93
 
59
- ### 발견 하나, 가까이서
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>
60
99
 
61
- 위의 첫 번째 발견에 대해 `mjolnir explain QA-CI-001`을 실행하면:
100
+ <sub>`npm run docs:video`가 실제 스캔으로부터 프레임 단위로 렌더링했으며, 화면 녹화가 아닙니다. 프레임을 선택하면 [`mjolnir-demo.mp4`](assets/video/mjolnir-demo.mp4)가 열립니다.</sub>
101
+
102
+ </details>
103
+
104
+ ### 발견 사항 하나 자세히 보기
105
+
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`은 규칙의 신뢰 기록 전체를 출력합니다. 측정된 오탐률과 그 비율로 얻은 등급도 포함됩니다.
62
113
 
63
114
  ```text
64
- ▚ QA-CI-001 — continue-on-error masks a failing verification gate
115
+ ▍ QA-CI-001 — continue-on-error masks a failing verification gate
65
116
 
66
117
  Severity: error
67
118
  Confidence: high
119
+ Tier: quarantine
68
120
  Evidence: E2
69
- 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
70
126
 
71
127
  WHAT WAS FOUND (real detector output, not a mockup)
72
128
  Job `security-scan` runs a verification gate under `continue-on-error: true`.
73
129
 
74
130
  WHY IT MATTERS
75
- This job can fail every day and CI will still show green. The checkmark
76
- 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.
77
133
 
78
134
  HOW TO FIX
79
135
  Remove continue-on-error, or scope it to individual non-blocking steps only.
80
- ```
81
136
 
82
- 이것이 가치의 단위입니다. 스타일 지적이 아니라, 당신의 CI가 무언가
83
- 통과했다고 말하지만 실제로는 통과하지 않은 바로 그 지점입니다.
137
+ Example from this rule's own must-fire fixture: QA-CI-001/must-fire/masked.yml
84
138
 
85
- ---
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
86
146
 
87
- ## ⚡ 빠른 시작
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.
88
150
 
89
- 완전한 보고서와 신뢰도 점수를 위해 리포지터리에 대해 실행합니다:
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.
90
154
 
91
- ```bash
92
- npx mjolnir-qa@latest
155
+ Docs: mjolnir rules --md (full catalog, this rule included)
93
156
  ```
94
157
 
95
- **CI에서 제품은 한 개의 명령입니다.** 브랜치가 건드린 것만 스캔하고 새
96
- 문제가 있으면 0이 아닌 코드로 종료합니다:
158
+ 이것이 가치의 단위입니다. CI가 얻지 못한 통과를 보고하는 한 지점.
159
+
160
+ <br />
161
+
162
+ ## 빠른 시작
97
163
 
98
164
  ```bash
99
- npx mjolnir-qa@latest --scope changed
165
+ npx mjolnir-qa@latest
100
166
  ```
101
167
 
102
- 그것을 PR 검사에 넣으세요 — `mjolnir ci install`이 워크플로를 작성합니다 —
103
- 끝입니다. 나머지는 모두 선택 사항입니다.
168
+ 현재 디렉터리를 스캔하고 Trust Report를 출력합니다. 무엇을 찾았는지, 어디까지 믿을 수 있는지, 그 이유, 그리고 다음에 할 일. 게이트 이상에서 아무것도 발견되지 않으면 `0`으로 종료합니다.
104
169
 
105
- | 명령 | 무엇을 하는가 |
106
- | ----------------------------------- | --------------------------------------------------- |
107
- | `mjolnir` | 전체 리포지터리 스캔 + 신뢰도 점수 |
108
- | `mjolnir --scope changed` | 당신의 브랜치가 도입한 것만 — CI 형태 |
109
- | `mjolnir ci install` | 자문형(advisory) PR 워크플로 생성 |
110
- | `mjolnir explain QA-CI-001` | 무엇 / 왜 / 수정 방법 + 규칙 하나의 측정된 FP 비율 |
111
- | `mjolnir rules --unmeasured` | 측정이 아니라 가정으로 작동 중인 규칙들 |
112
- | `mjolnir --json` / `--format sarif` | 기계 판독 가능 / GitHub Code Scanning |
113
- | `mjolnir --strict` | 격리(quarantine) 계층 규칙도 실행 (FP 위험 더 높음) |
114
-
115
- <details>
116
- <summary><strong>뭔가 flaky할 때</strong></summary>
170
+ CI에서는 브랜치가 도입한 부분만 스캔하세요. 그래야 레거시 스위트가 첫 pull request를 뒤덮지 않습니다.
117
171
 
118
- | 명령 | 무엇을 하는가 |
119
- | ----------------------------------- | ------------------------------------------------------ |
120
- | `mjolnir forensics ./test-results/` | 실제 실행 데이터 → `TRUE-FLAKE` 판정, `FLAKY.md` |
121
- | `mjolnir triage ./test-results/` | 실행 이력으로부터의 격리 제안 |
122
- | `mjolnir pw-report ./test-results/` | Playwright 실행 요약 — 재시도 / flake / 가장 느린 것들 |
123
- | `mjolnir doctor:playwright` | Playwright 전용 심층 스캔 + Selector Health Score |
172
+ ```bash
173
+ npx mjolnir-qa@latest --scope changed
174
+ ```
124
175
 
125
- </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 위험이 더 높음) |
126
191
 
127
192
  <details>
128
- <summary><strong>가끔 / 보고서</strong></summary>
129
-
130
- | 명령 | 무엇을 하는가 |
131
- | ------------------------------- | ----------------------------------------------------------- |
132
- | `mjolnir fix --dry-run` / `fix` | 증거가 붙은 안전한 자동 수정 |
133
- | `mjolnir baseline` / `diff` | 발견 스냅샷을 찍고, 이후에는 새로 생기거나 악화된 것만 보고 |
134
- | `mjolnir impact --since <ref>` | 이전 커밋 이후 무엇이 바뀌었는가 |
135
- | `mjolnir debt` | 비용 모델을 갖춘 테스트 부채 대장 |
136
- | `mjolnir handover` | 새 QA를 위한 스위트 온보딩 지도 |
137
- | `mjolnir stats` | 지금까지 본 수정의 로컬 누적 카운터 |
138
- | `mjolnir badge` | shields.io 엔드포인트 JSON + 스니펫 |
139
- | `mjolnir rules --md` | 전체 규칙 카탈로그 (JSON 또는 Markdown) |
140
- | `mjolnir doctor` | Mjölnir 자체 규칙 베이스의 자체 감사 |
141
- | `mjolnir create-rule <ID>` | 새 규칙 + 픽스처 스캐폴딩 |
142
- | `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>` | 새 규칙과 그 fixture의 뼈대 생성 |
217
+ | `mjolnir stats` | 지금까지 본 수정의 로컬 누적 카운터 |
218
+ | `mjolnir badge` | shields.io 엔드포인트 JSON과 스니펫 |
219
+ | `mjolnir --cache` | 로컬 판정 캐시를 이용한 증분 재스캔 |
220
+ | `mjolnir --format mermaid` | PR 댓글용 테스트 아키텍처 다이어그램 |
221
+
222
+ `mjolnir help <command>`는 어떤 명령이든 사용법, 예시, 다음 단계를 출력합니다.
143
223
 
144
224
  </details>
145
225
 
146
- 선호한다면 `npx` 대신 전역 설치: `npm i -g mjolnir-qa`. Node.js ≥ 22.18
147
- 필요. Windows, macOS, Linux에서 작동합니다.
148
-
149
- ---
150
-
151
- ## 👥 누구를 위한 것인가?
152
-
153
- - **QA / SDET** — e2e 또는 통합 스위트를 소유하고 있으며, 스위트가
154
- 만들어내는 녹색 체크마크를 정말로 값지게 여길 만하다는 증거가 필요한
155
- 사람들.
156
- - **플랫폼 / DevEx 팀** — CI 무결성과 릴리스 게이트를 책임지는 사람들.
157
- `continue-on-error`가 붉은 파이프라인을 조용히 녹색으로 칠하지 않기를
158
- 바라는 사람들.
159
- - **OSS 메인테이너** — 로컬과 CI에서, 네트워크 호출 없이 돌아가는
160
- 값싸고 늘 켜져 있는 검증 게이트를 원하는 사람들.
161
-
162
- ---
226
+ Windows, macOS 또는 Linux에서 **Node.js ≥ 22.18**이 필요합니다. 전역 설치를 원하시나요? `npm i -g mjolnir-qa`. 이 최소 버전은 빌드 도구 체인에서 옵니다 (tsdown이 이를 대상으로 하고 릴리스 파이프라인이 이에 대해 스모크 테스트를 합니다). 런타임 의존성은 그 이상을 요구하지 않습니다.
163
227
 
164
- ## 🔨 Mjölnir가 검사하는 것
228
+ <br />
165
229
 
166
- | | |
167
- | --- | ------------------------------------------------------------------------------------------------------------------ |
168
- | ⚖️ | **신뢰도 점수** — 하나의 숫자, 투명한 감점 표, 블랙박스 없음 |
169
- | 🎭 | **Selector Health Score** — 통과율만이 아니라 당신의 Playwright 로케이터를 평가한다 |
170
- | 🔬 | **런타임 포렌식** — 실제 Playwright/JUnit 실행 데이터를 읽어 `TRUE-FLAKE`를 잡아낸다, 정적 추측만이 아니라 |
171
- | 🚨 | **CI 무결성 규칙** — `continue-on-error`, `\|\| true`, 그 밖의 가짜 녹색 트릭을 잡아낸다 |
172
- | 🐍 | **네 가지 Playwright 바인딩 모두** — TypeScript, Python, Java, C#/.NET — 게다가 pytest, JUnit/TestNG와 CI 워크플로 |
173
- | 🔒 | **로컬 퍼스트** — 스캔 중 네트워크 호출 제로, 텔레메트리 제로, 몇 초 만에 실행 |
230
+ ## Mjölnir가 찾는 것
174
231
 
175
- ### 규칙들
232
+ <p align="center">
233
+ <img src="assets/readme/stack.svg" alt="사용 중인 스택과 함께 동작합니다: 규칙이 다루는 언어, 테스트 프레임워크, CI 시스템 (규칙 레지스트리 기준)." width="100%" />
234
+ </p>
176
235
 
177
- 모든 규칙은 must-fire **와** must-not-fire 픽스처를 갖추고 출시됩니다.
178
- 자기 자신의 부정 픽스처에서 발화하는 규칙은 출시될 수 없습니다 — 그것이
179
- 거짓 양성 방화벽입니다.
236
+ 네 가지 계열(테스트 위생, 테스트 품질, Playwright, CI 무결성)에 걸친 **79개 규칙**이 TypeScript와 JavaScript, Python, Java, C#, GitHub Actions YAML을 다룹니다. Playwright의 네 가지 바인딩 모두와 pytest, JUnit, TestNG, NUnit, xUnit, MSTest, Jest, Vitest, Mocha를 지원하며, Cypress와 Selenium은 입문 수준으로 지원합니다. 형태를 보여주기 위해 그중 아홉 개를 소개합니다.
180
237
 
181
- <details>
182
- <summary><strong>테스트 위생</strong></summary>
183
-
184
- | ID | 규칙 | Severity |
185
- | ----------- | --------------------------------------------------- | -------- |
186
- | QA-TEST-001 | 커밋된 포커스 테스트 (`.only`, `fit`) | error |
187
- | QA-TEST-002 | 정당화 없이 건너뛴 테스트 | error |
188
- | QA-TEST-002 | 기록된 정당화와 함께 건너뛴 테스트 | warning |
189
- | QA-TEST-003 | 어설션이 없는 테스트 | error |
190
- | QA-TEST-004 | 하드 sleep (`waitForTimeout`, `sleep()`, `delay()`) | warning |
191
- | QA-TEST-006 | flakiness를 숨기는 재시도 남용 | warning |
192
- | 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 |
193
249
 
194
- </details>
250
+ 전체 카탈로그는 레지스트리에서 생성되며 손으로 관리하지 않습니다: `mjolnir rules --md`, [`docs/rules/`](docs/rules/), 또는 [검사 항목 가이드](https://sergey-bar.github.io/Mjolnir/guide/what-it-checks).
195
251
 
196
252
  <details>
197
- <summary><strong>테스트 품질</strong></summary>
198
-
199
- | ID | 규칙 | Severity |
200
- | ------------ | ----------------------------- | -------- |
201
- | QA-TQUAL-002 | 동어반복적 어설션 | error |
202
- | QA-TQUAL-009 | await되지 않은 promise 어설션 | error |
203
- | 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 | 위생 | 고정 sleep (`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 | 고정 sleep (`Thread.sleep()`) | warning | extended |
288
+ | QA-JV-103 | Java | 단언이 없는 테스트 메서드 | error | extended |
289
+ | QA-JV-105 | Java | Playwright `waitForTimeout()` 고정 sleep | 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# | 고정 sleep (`Thread.Sleep` / `Task.Delay`) | warning | core |
293
+ | QA-CS-103 | C# | 단언이 없는 테스트 메서드 | error | core |
294
+ | QA-CS-105 | C# | `WaitForTimeoutAsync()` 고정 sleep | 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에는 각각 세 개 규칙으로 된 입문 세트가 있습니다.
204
298
 
205
299
  </details>
206
300
 
207
- <details>
208
- <summary><strong>Playwright 🎭</strong></summary>
301
+ 모든 규칙은 must-fire **그리고** must-not-fire fixture와 함께 배포되며, 자신의 음성 fixture에서 발동하는 규칙은 배포될 수 없습니다. 이것이 오탐 방화벽입니다. `mjolnir doctor`가 이 저장소의 CI에서 이를 강제합니다.
209
302
 
210
- | ID | 규칙 | Severity |
211
- | --------- | ------------------------------------- | -------- |
212
- | QA-PW-002 | await되지 않은 로케이터 어설션 | error |
213
- | QA-PW-003 | 커밋된 `page.pause()` / `test.only()` | error |
214
- | QA-PW-004 | 취약한 CSS/XPath 선택자 | warning |
215
- | QA-PW-123 | 하드코딩된 환경 URL | warning |
303
+ ### Selector Health Score
216
304
 
217
- </details>
305
+ `mjolnir doctor:playwright`는 각 locator가 요소를 찾는 방식으로 등급을 매깁니다. 사용자처럼 찾는지 (역할, 레이블, 텍스트), 명시적 계약을 통하는지 (`data-testid`), 아니면 구조적 우연에 기대는지 (CSS 체인, XPath). 파일마다 0에서 100 사이의 점수를 받습니다.
218
306
 
219
- <details>
220
- <summary><strong>CI 무결성</strong></summary>
221
-
222
- | ID | 규칝 | Severity |
223
- | --------- | --------------------------------------------------------------- | -------- |
224
- | QA-CI-001 | `continue-on-error`가 실패를 가린다 | error |
225
- | QA-CI-002 | `\|\| true`가 종료 코드를 삼킨다 | error |
226
- | QA-CI-005 | 보고서는 소비되는데 생성되지는 않는다 | error |
227
- | QA-CI-007 | 테스트를 감싸는 재시도 래퍼 | warning |
228
- | QA-CI-008 | 항상 성공하는 단계가 실패를 가린다 | error |
229
- | QA-CI-009 | 테스트 종료 코드가 전달되지 않음 (pipefail 없는 `\|`, `;` 연쇄) | error |
230
- | QA-CI-010 | 반드시 막아야 할 곳에서 테스트를 건너뜀 (skip-on-PR 가드) | error |
231
-
232
- </details>
233
-
234
- <details>
235
- <summary><strong>Python / pytest 🐍</strong></summary>
236
-
237
- | ID | 규칙 | Severity |
238
- | --------- | -------------------------------------- | -------- |
239
- | QA-PY-002 | 건너뛴 테스트 (`skip`, 비엄격 `xfail`) | warning |
240
- | QA-PY-003 | 어설션이 없는 테스트 함수 | error |
241
- | QA-PY-005 | 테스트 안의 `time.sleep()` | warning |
242
- | QA-PY-012 | 동어반복적 어설션 | error |
243
-
244
- 총 20개의 Python 규칙 (QA-PY-001…012 pytest 위생 + QA-PY-101…108 Playwright-Python).
307
+ ```text
308
+ ▍ SELECTOR HEALTH
245
309
 
246
- </details>
310
+ e2e/login.spec.ts
311
+ [█████████████░░░░░░░] 65 / 100
312
+ role/text: 1 · testid: 0 · plain-css: 0 · css-chains: 1 ⚠ · xpath: 0
247
313
 
248
- <details>
249
- <summary><strong>Java / JUnit · TestNG ☕</strong></summary>
314
+ e2e/checkout.spec.ts
315
+ [██████████████████░░] 88 / 100
316
+ role/text: 4 · testid: 1 · plain-css: 0 · css-chains: 1 ⚠ · xpath: 0
317
+ ```
250
318
 
251
- | ID | 규칙 | Severity |
252
- | --------- | ---------------------------------------- | -------- |
253
- | QA-JV-101 | 비활성화된 테스트 (`@Disabled`) | warning |
254
- | QA-JV-102 | 하드 sleep (`Thread.sleep()`) | warning |
255
- | QA-JV-103 | 어설션이 없는 테스트 메서드 | error |
256
- | QA-JV-105 | Playwright 하드 sleep `waitForTimeout()` | warning |
257
- | QA-JV-106 | role 로케이터 대신 취약한 선택자 | warning |
319
+ 이 점수는 **정확성이 아니라 복원력**을 측정합니다. `.btn.btn-primary > div:nth-child(2)`는 오늘 통과하고, 누군가 마크업을 건드리기 전까지 계속 통과합니다. 낮은 점수는 테스트가 망가졌다고 주장하지 않으며, 아무도 유지하겠다고 약속하지 않은 마크업에 의존한다는 뜻일 뿐입니다.
258
320
 
259
- </details>
321
+ <br />
260
322
 
261
- <details>
262
- <summary><strong>C# / .NET — NUnit · xUnit · MSTest 🟣</strong></summary>
323
+ ## 신뢰도 점수
263
324
 
264
- | ID | 규칙 | Severity |
265
- | --------- | ------------------------------------------- | -------- |
266
- | QA-CS-101 | 건너뛴 테스트 (`[Ignore]`, `[Fact(Skip=)]`) | warning |
267
- | QA-CS-102 | 하드 sleep (`Thread.Sleep` / `Task.Delay`) | warning |
268
- | QA-CS-103 | 어설션이 없는 테스트 메서드 | error |
269
- | QA-CS-105 | 하드 sleep `WaitForTimeoutAsync()` | warning |
270
- | 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>
271
328
 
272
- </details>
329
+ <sub>0부터 100까지의 모든 점수를 실제 `deriveScoreState`로 배치했습니다. `npm run docs:gauge`로 생성되며 CI에서 변경되지 않도록 고정됩니다.</sub>
273
330
 
274
- > 완전한 실시간 카탈로그 — 각 규칙의 계층, 신뢰도, 거짓 양성 위험, 자동
275
- > 수정 가능 여부가 함께 — 는 레지스트리에서 생성됩니다:
276
- >
277
- > ```bash
278
- > mjolnir rules --md
279
- > ```
280
- >
281
- > 규칙별 페이지는 [`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**: 테스트 선언을 찾을 수 없음 |
282
338
 
283
- ### 이 중 얼마나가 측정되었나
339
+ **계산 방식.** 심각도가 기본 감점을 정하고 (`error −8`, `warning −3`, `info −1`) 증거 수준이 이를 할인합니다. E2는 전액, E1은 절반 (내림), E0는 감점 없음. 합계는 스위트 규모로 정규화되며, 파일당이 아니라 테스트 선언당 감점입니다. 터미널은 점수에 쓰인 것과 같은 할인된 숫자를 출력하며, 숨겨진 두 번째 모델은 없습니다. 자세한 내용: [docs/SCORING.md](docs/SCORING.md)와 [점수 가이드](https://sergey-bar.github.io/Mjolnir/guide/scoring).
284
340
 
285
- **99개 규칙 중 78개가 실제 OSS 코드에 대해 측정된 거짓 양성 비율을
286
- 갖습니다** (각 규칙당 손으로 분류된 발견 ≥ 10건;
287
- [docs/FP-AUDIT.md](docs/FP-AUDIT.md) 참조). 나머지 21개는 저자의 추정으로
288
- 출시됩니다. 모든 스캔의 바닥글은 _발화한_ 규칙 중 몇 개가 측정되었는지
289
- 말해줍니다; `mjolnir rules --unmeasured`는 측정되지 않은 것들을 나열합니다;
290
- 각 규칙의 `mjolnir explain` 페이지는 그 상태를 명시합니다. 수치가 흉해도
291
- 계층에 있습니다. 그 숫자를 늘려가는 것이 프로젝트의 지속적인 작업입니다.
341
+ **100점이 의미하지 않는 것.** 소프트웨어가 올바르다는 뜻도, 스위트가 충분하다는 뜻도, 제품에 결함이 없다는 뜻도 아닙니다. 의미하는 것은 하나뿐입니다. **이 스캔과 이 증거 모델에서 Mjölnir가 평가한 규칙 중 어느 것도 감점을 만들지 않았다는 것.**
292
342
 
293
- ### 규칙 계층과 언어 성숙도
343
+ <br />
294
344
 
295
- 모든 규칙은 **측정된** 거짓 양성 비율에 따라 `core`, `extended`,
296
- `quarantine`로 배정됩니다:
345
+ ## 증거 모델
297
346
 
298
- | 계층 | 의미 | 기본 스캔 | `--strict` |
299
- | ------------ | ------------------------------------ | :-------: | :--------: |
300
- | `core` | 측정 FP ≤ 10 % | ✅ | ✅ |
301
- | `extended` | 측정 FP ≤ 30 % | ✅ | ✅ |
302
- | `quarantine` | 30 % 초과, 또는 아직 미측정 (n < 10) | ❌ | ✅ |
347
+ 모든 발견 사항에는 두 개의 라벨이 붙습니다. Mjölnir가 얼마나 확신하는지, 그리고 그 발견 사항이 어디까지 확인되었는지. 이것이 패턴을 보고하는 도구와 릴리스 게이트로 삼을 수 있는 도구의 차이입니다.
303
348
 
304
- | 언어 | 어댑터 | 현재 커버리지 |
305
- | --------------- | ------------ | --------------------------------------------------- |
306
- | TypeScript / JS | 컴파일러 AST | 가장 넓고 가장 많이 측정됨 — 주로 `core`/`extended` |
307
- | Python / pytest | 정규식 계층 | 넓음, 코퍼스 감사됨 — 주로 `core`/`extended` |
308
- | Java | 정규식 계층 | 더 최신 — 주로 `extended`/`quarantine` |
309
- | C# / .NET | 정규식 계층 | 더 최신 — 주로 `extended`/`quarantine` |
349
+ **얼마나 확실한가 — 증거 수준.**
310
350
 
311
- TypeScript와 Python이 가장 넓은 측정된 커버리지를 갖습니다. Java와 C#은
312
- 출시되고 문서화되어 있지만, 실제 소비자 스위트(바인딩 라이브러리 자체의
313
- 테스트가 아닌)가 감사될 때까지 헤드라인 숫자에서는 제외됩니다.
351
+ | 수준 | 이름 | 의미 | 감점 |
352
+ | ------ | ------------- | ---------------------------------------------------- | ---- |
353
+ | **E2** | 결정론적 증명 | 작성된 코드 그대로에 결함이 존재함 | 전액 |
354
+ | **E1** | 패턴 증거 | 결함과 강하게 연결된 패턴이 일치함 | 절반 |
355
+ | **E0** | 관찰 | 알아둘 가치가 있음. 무언가 잘못되었다는 주장은 아님. | 없음 |
314
356
 
315
- ---
357
+ 탐지의 확신도는 증명의 강도가 아닙니다. 규칙은 찾던 것과 일치했다고 확신하면서도 여전히 휴리스틱을 보고 있을 수 있습니다. E1 발견 사항은 읽고 판단하기 위한 것이지 맹목적으로 적용하기 위한 것이 아니며, 그 경계는 터미널, JSON, 에이전트 인계 자료의 발견 사항에 모두 표시됩니다.
316
358
 
317
- ## 점수 산정은 어떻게 작동하는가
359
+ **어디까지 확인했는가 — 신뢰 수준.** 대부분의 발견 사항은 코드를 읽어서 나옵니다. 실제 테스트 실행 리포트를 Mjölnir에 주면 코드가 실제로 실행되었는지 확인할 수 있습니다.
318
360
 
319
361
  <p align="center">
320
- <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%" />
321
363
  </p>
322
364
 
323
- <sub>`npm run docs:hero`로 재생성됩니다.
324
- [`tests/hero-asset-reproducibility.spec.ts`](tests/hero-asset-reproducibility.spec.ts)
325
- 은(는) 산출물이 reporter가 실제로 출력하는 것과 어긋나면 CI를 떨어뜨립니다.</sub>
365
+ | 수준 | 쉽게 말하면 | 필요한 것 |
366
+ | ------ | ------------------ | ------------------------------------------------------ |
367
+ | **L0** | 기록됨 | 코드 읽기 |
368
+ | **L1** | 문제로 보임 | 코드 읽기: 패턴이 일치함 |
369
+ | **L2** | 코드에서 증명됨 | 코드 읽기: 결함이 구조적임 |
370
+ | **L3** | 파일이 실행됨 | 실행 리포트가 발견 사항의 파일이 실행되었음을 보여줌 |
371
+ | **L4** | 테스트가 실행됨 | 실행 리포트가 발견 사항의 테스트가 실행되었음을 보여줌 |
372
+ | **L5** | 실행 결과가 일치함 | 실행 자체의 결과가 결함 유형을 확인함 |
326
373
 
327
- 점수는 투명합니다: **error −8, warning −3, info −1**, 그다음 스위트
328
- 노출도(테스트 선언당 감점)로 정규화합니다. 증거로 가중된 감점은 약한
329
- 신호일수록 덜 잃는다는 뜻입니다. 터미널은 점수가 사용하는 동일한
330
- 할인된 숫자를 보여줍니다 — 블랙박스가 없습니다. 전체 방법론:
331
- [docs/SCORING.md](docs/SCORING.md).
374
+ 정적 스캔은 L2에서 멈춥니다. 실제 실행 리포트 (Playwright JSON, Jest 또는 Vitest JSON, JUnit XML)만이 발견 사항을 L3 이상으로 올릴 수 있으므로, 실행되는 모습이 한 번도 확인되지 않은 발견 사항은 실행되었다고 주장할 수 없습니다. 정의: [docs/TERMINOLOGY.md](docs/TERMINOLOGY.md).
332
375
 
333
- **판정**
376
+ ### 이 중 얼마나 측정되었는가
334
377
 
335
- | Score | 판정 |
336
- | ------- | ---------------- |
337
- | ≥ 80 | ✓ **WORTHY** |
338
- | 50 – 79 | ⚠ **NEEDS WORK** |
339
- | < 50 | ✖ **UNWORTHY** |
378
+ **79개 규칙 중 74개는 실제 OSS 코드로 측정한 오탐률을 갖고 있습니다** (규칙마다 손으로 분류한 발견 사항 10개 이상. [docs/FP-AUDIT.md](docs/FP-AUDIT.md) 참고). 나머지 5개는 작성자의 추정치로 배포되며, `mjolnir explain`에서 규칙별로 그 사실을 밝힙니다. `mjolnir rules --unmeasured`가 이를 나열하고, 모든 스캔의 하단에는 실제로 _발동한_ 규칙 중 몇 개가 측정되었는지 보고합니다.
340
379
 
341
- **증거 수준** — 모든 발견은 하나를 갖습니다; 그것이 점수 안에서 발견의
342
- 가중치를 정합니다:
380
+ 비율은 나쁠 때도 공개합니다. QA-TEST-001 (커밋된 `.only`)은 실제 저장소 감사 결과가 나빠서 quarantine에 있습니다. QA-PW-141을 포함한 모든 규칙의 최신 수치는 감사 문서에 있습니다.
343
381
 
344
- | 수준 | 의미 | 점수에 미치는 영향 | 예시 |
345
- | ---- | ------------- | ------------------ | ------------------------------------------------ |
346
- | E2 | 결정론적 결함 | 전액 감점 | 커밋된 `.only` — 구조적으로 증명 가능 |
347
- | E1 | 휴리스틱 패턴 | 반액 감점 | 정규식에 걸린 `sleep()` — 강한 신호, 증명은 아님 |
348
- | E0 | 관찰 | 제로 (info만) | 보고되지만 CI를 게이트하거나 감점하지는 않음 |
382
+ ### 규칙 신뢰 등급
349
383
 
350
- 대부분의 규칙은 **E1**입니다. "we prove it"라는 슬로건은 이 체계를
351
- 가리킵니다: E2 발견은 구조적 증명이고; E1 발견은 올바르게 자리 잡은
352
- 경고이지 형식적 증명이 아닙니다.
384
+ 등급은 의견이 아니라 측정된 오탐률을 따릅니다.
353
385
 
354
- 빈 리포지터리는 `null`을 점수로 받습니다. 가짜 100은 결코 아닙니다 —
355
- [신뢰 모델](#신뢰-모델) 참조.
386
+ | 등급 | 측정된 FP | 동작 |
387
+ | -------------- | ------------------------------- | -------------------------------------------- |
388
+ | **core** | ≤ 10% | 기본 리포트, 차단함 |
389
+ | **extended** | ≤ 30% | 기본 리포트, 낮은 확신도 |
390
+ | **quarantine** | > 30% 또는 명시적으로 선언된 것 | `--strict`에서만, info로 상한, 차단하지 않음 |
391
+ | _측정 안 됨_ | n < 10 | 측정되기 전까지 core로 승격할 수 없음 |
356
392
 
357
- ---
393
+ FP 밴드는 티어를 강등할 수만 있습니다 — 명시적으로 `quarantine`에 선언된 규칙을 거기서 승격시키지는 않습니다. 명시적으로 quarantine에 놓인 규칙은 측정된 FP율과 관계없이 quarantine에 머뭅니다.
358
394
 
359
- ## 🎭 Selector Health Score
395
+ 승격, 강등, 언어별 성숙도: [규칙 생명 주기](https://sergey-bar.github.io/Mjolnir/reference/rule-lifecycle).
360
396
 
361
- Playwright 스위트의 대표 지표 — 당신의 로케이터는 얼마나 튼튼한가:
397
+ ### 왜 이것은 린터가 아닌가
362
398
 
363
- ```text
364
- ▚ SELECTOR HEALTH — e2e/checkout.spec.ts
399
+ 린터는 코드가 규칙을 따르는지 알려줍니다. Mjölnir는 검증을 믿을 수 있는지 알려줍니다.
365
400
 
366
- [█████████████████░░░] 83 / 100
367
- role/text: 2 · testid: 1 · css-chains: 1 ⚠ · xpath: 0
368
- ```
401
+ | | 린터 (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
+ | 고정 sleep 탐지 (`waitForTimeout`, `time.sleep`) | 예\* | 아니요 | 가끔 | 예 |
410
+ | 결정론적 (같은 입력, 같은 출력) | 예 | 예 | 아니요 | 예 |
411
+ | 스캔당 비용 | 무료 | 무료 | 토큰 | **0** (로컬) |
369
412
 
370
- 역할 기반 로케이터는 만점입니다. CSS 클래스 체인과 XPath는 점수를
371
- 가라앉힙니다 — 어떤 동작이 퇴행했는지 알려주지 않은 채 어떤 DOM
372
- 리팩터링에서든 부서지기 때문입니다.
413
+ <sub>\*`eslint-plugin-jest`와 `eslint-plugin-playwright` (`expect-expect`, `no-wait-for-timeout`), 그리고 SonarQube 자체의 단언 규칙이 다룹니다. 각 열은 테스트 스위트 검증의 기본 동작을 설명하며, 플러그인, 유료 요금제, 사용자 정의 규칙에 따라 일부 답이 달라집니다. 이것은 포지셔닝 요약이지 벤치마크가 아닙니다.</sub>
373
414
 
374
- ---
415
+ AI 리뷰도 함께 쓰세요. AI 리뷰는 어떤 패턴으로도 찾을 수 없는 뉘앙스, 의도, 설계 결함을 잡아냅니다. Mjölnir는 AI 리뷰가 의도된 것처럼 보여서 놓치는 것을 잡습니다: 커밋된 `.only`, 삼켜진 종료 코드, 테스트 job에 붙은 `continue-on-error`. 이런 것에는 추론이 아니라 스캔이 필요합니다.
375
416
 
376
- ## 🔬 런타임 증거
417
+ <br />
377
418
 
378
- 정적 flakiness 탐지는 추측입니다. Mjölnir는 **실제 실행 데이터**를
379
- 읽습니다 — 어떤 러너의 Playwright JSON 보고서와 JUnit XML이든:
419
+ ## 런타임 포렌식
420
+
421
+ 정적 분석은 한 번도 실행되지 않은 코드에 대해 추론합니다. 포렌식은 실제로 일어난 일을 읽습니다: 어떤 러너든 Playwright JSON, Jest JSON, Vitest JSON, JUnit XML.
380
422
 
381
423
  ```bash
382
424
  mjolnir forensics ./test-results/
383
425
  ```
384
426
 
385
427
  ```text
386
- ▚ FLAKINESS LEADERBOARD
428
+ ▍ FLAKINESS LEADERBOARD
387
429
 
388
430
  3 tests · 1 failed · 1 flaky · 1 retried
389
431
 
@@ -393,286 +435,184 @@ FAILING declines an expired card (e2e/checkout.spec.ts)
393
435
  ████░░░░░░░░░░░░░░░░ 1.1s · 1 attempt
394
436
  ```
395
437
 
396
- 2회차 이상 시도에서만 통과하는 테스트는 통과하는 테스트가 아닙니다 —
397
- 운이 좋은 테스트입니다. 최종 녹색 체크마크와 무관하게 `TRUE-FLAKE`로
398
- 표시됩니다.
438
+ `TRUE-FLAKE`는 테스트가 재시도되었다는 뜻이 아닙니다. 테스트가 **적어도 한 번의 시도에서 실패한 뒤 초록색으로 끝났다**는 뜻입니다. 운 좋은 통과이며, 최종 체크가 무엇을 말하든 표시됩니다. `mjolnir triage`는 그 이력을 격리 제안으로 바꾸고, `mjolnir pw-report`는 실행을 요약합니다. 발견 사항을 신뢰 수준 L3 이상으로 올리는 것도 바로 이 실행 리포트입니다.
439
+
440
+ <br />
399
441
 
400
- ---
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
+ 또는 이미 있는 workflow에 Marketplace action을 추가합니다:
453
+
454
+ ```yaml
455
+ - uses: Sergey-Bar/Mjolnir@v1
456
+ with:
457
+ scope: changed
458
+ fail-on: error
459
+ ```
401
460
 
402
- ## ⚡ Mjölnir는 또 하나의 린터가 아닙니다
461
+ 메이저 라인을 따라가려면 `@v1`을, 재현 가능한 게이트를 원하면 정확한 태그 (`@v0.5.32`)를 고정하세요. [docs/DISTRIBUTION-KIT.md](docs/DISTRIBUTION-KIT.md)는 Marketplace, Smithery, MCP 레지스트리를 다룹니다.
403
462
 
404
- 린터는 코드가 규칙을 따르는지 말해줍니다. Mjölnir는 당신의 검증이
405
- 신뢰될 수 있는지 말해줍니다.
463
+ 발견 사항을 GitHub Code Scanning에 올리려면 SARIF를 업로드하세요 (workflow 또는 job 범위에서 `security-events: write` 필요):
406
464
 
407
- | | ESLint / SonarQube | 커버리지 도구 | 수동 리뷰 | **Mjölnir** |
408
- | ----------------------------------------------------- | :----------------: | :-----------: | :-------: | :---------: |
409
- | CI 워크플로 무결성 (`continue-on-error`, `\|\| true`) | ❌ | ❌ | 드묾 | ✅ |
410
- | 한 도구로 다중 언어 (TS, Python, Java, C#) | ❌ | ❌ | ❌ | ✅ |
411
- | Playwright 로케이터 회복탄력성 평가 (Selector Health) | ❌ | ❌ | 드묾 | ✅ |
412
- | 실제 어설션 없는 테스트 표시 | ✅ (플러그인)\* | ❌ | 때때로 | ✅ |
413
- | 하드 sleep 탐지 (`waitForTimeout`, `time.sleep`) | ✅ (플러그인)\* | ❌ | 때때로 | ✅ |
414
- | 몇 초 만에 실행, 스캔 중 네트워크 호출 제로 | ✅ | ✅ | — | ✅ |
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
+ ```
415
473
 
416
- \*`eslint-plugin-jest` (`expect-expect`)와 `eslint-plugin-playwright`
417
- (`expect-expect`, `no-wait-for-timeout`)가 각 프레임워크에 대해 이를
418
- 커버합니다.
474
+ GitLab에서는 `--format codequality`가 MR 위젯과 diff 주석이 읽는 Code Quality 리포트를 작성합니다 ([docs/GITLAB-CI.md](docs/GITLAB-CI.md)). 에디터와 파이프라인 설정: [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md).
419
475
 
420
- **런타임 분석**은 정적 린팅과 별개의 범주입니다:
476
+ ### 변경 범위 귀속
421
477
 
422
- | | Playwright retry reporter | Allure / ReportPortal | **Mjölnir forensics** |
423
- | ------------------------------------------------ | :-----------------------: | :-------------------: | :-------------------: |
424
- | `TRUE-FLAKE` 판정을 위해 실제 실행 데이터를 읽음 | 부분적\* | 부분적 (tag) | ✅ |
425
- | 실행 이력으로부터의 flaky 트리아지 보고서 | ❌ | ✅ | ✅ |
426
- | 정적 신뢰도 점수와 통합 | ❌ | ❌ | ✅ |
478
+ ```bash
479
+ npx mjolnir-qa@latest --scope changed
480
+ ```
427
481
 
428
- \*Playwright는 재시도를 내부적으로 추적하지만 판정 레이블이 붙은 독립적인
429
- flakiness 보고서를 만들지는 않습니다.
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>`로 바꿀 수 있습니다.
430
483
 
431
- ---
484
+ merge-base를 결정할 수 없으면 (얕은 클론, detached HEAD, git 밖의 대상) 발견 사항은 파일 전체 귀속으로 대체되며 **리포트가 그 사실을 알립니다.** 조용한 대체는 바로 이 도구가 잡아내려고 존재하는 종류의 결함이기 때문입니다.
432
485
 
433
- ## 🤖 왜 그냥 AI 코드 리뷰를 쓰지 않는가?
486
+ <br />
434
487
 
435
- 다른 문제, 다른 계층입니다. AI 리뷰는 diff 속의 의심스러운 테스트 변경을
436
- 발견할 수 있습니다; 그러나 검증 시스템 전체가 신뢰할 만하다는 증명은
437
- 하지 못합니다 — 그리고 보여주는 diff만 볼 뿐입니다.
488
+ ## AI 에이전트
438
489
 
439
- | | AI 코드 리뷰 (Copilot 등) | **Mjölnir** |
440
- | -------------------------------- | :----------------------------: | :---------------------------: |
441
- | 스캔당 비용 | 토큰 (diff 크기에 비례해 증가) | **제로** (로컬, 설치됨) |
442
- | 전체 스위트 + 모든 CI 설정을 봄 | 당신이 보여준 PR diff만 | **매번 모든 것** |
443
- | 결정론적 (같은 입력 → 같은 출력) | ❌ (비결정론적) | **✅** |
444
- | 수개월 잠들어 있던 패턴을 잡음 | 컨텍스트에 있을 때만 | **✅** (모든 파일을 스캔) |
445
- | 실행 사이에 발견을 기억 | ❌ (세션 간 기억 없음) | **✅** (baseline + diff) |
446
- | 사람의 트리거 없이 실행 | PR이나 프롬프트가 필요 | **✅** (CI 훅, 몇 초 내 실행) |
490
+ 발견 사항은 무언가가 그에 따라 행동할 때만 가치가 있습니다.
447
491
 
448
- **둘 다 사용하세요.** AI는 어떤 정규식도 찾을 수 없는 뉘앙스, 의도,
449
- 설계 결함을 잡아냅니다. Mjölnir는 "의도적인" 것처럼 보여서 AI가 놓치는
450
- 구조적 패턴을 잡아냅니다 — 커밋된 `.only`, 삼켜진 종료 코드, 테스트
451
- 작업의 `continue-on-error`. 이것들은 추론이 필요한 버그가 아니라, 스캔이
452
- 필요한 사실입니다.
492
+ ```text
493
+ SCAN → EVIDENCE → HANDOFF → AGENT → RE-SCAN → PROOF
494
+ ```
453
495
 
454
- ---
496
+ **AI가 수정을 작성하고, Mjölnir가 그것을 검증합니다.** 증명은 재스캔에서 나오며, 에이전트 스스로의 성공 보고에서 나오지 않습니다.
455
497
 
456
- ## 🤖 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`)에 작성해, 에이전트가 완료했다고 말하기 전에 다시 스캔하게 합니다. |
457
503
 
458
- 한 명령이 PR 워크플로를 생성합니다 — 기본적으로 자문형이며 결코 막지
459
- 않습니다:
504
+ 자체 CLI가 있는 클라이언트에 추가하기:
460
505
 
461
506
  ```bash
462
- mjolnir ci install
507
+ claude mcp add mjolnir -- npx -y mjolnir-qa@latest mcp
463
508
  ```
464
509
 
465
- 또는 SARIF를 통해 GitHub Code Scanning에 네이티브로 연결합니다:
510
+ 또는 `mcpServers` 블록을 받는 모든 클라이언트에:
466
511
 
467
- ```yaml
468
- - run: npx mjolnir-qa@latest --format sarif > mjolnir.sarif
469
- - uses: github/codeql-action/upload-sarif@v3
470
- with:
471
- sarif_file: mjolnir.sarif
512
+ ```json
513
+ {
514
+ "mcpServers": {
515
+ "mjolnir": { "command": "npx", "args": ["-y", "mjolnir-qa@latest", "mcp"] }
516
+ }
517
+ }
472
518
  ```
473
519
 
474
- SARIF의 에디터·파이프라인 설정:
475
- [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md).
520
+ **편의보다 가드레일이 더 중요합니다.** 인계 자료의 모든 발견 사항에는 경계가 붙어 있습니다. **E2**는 _결정론적: 위치를 확인하고 수정을 적용하세요_ 라고 말합니다. **E1**은 _확인 필요: 관찰만으로는 결함이 증명되지 않습니다_ 라고 말합니다. E1을 맹목적으로 고치거나, 규칙을 억제하거나, 점수를 올리려고 규칙을 수정하는 에이전트는 바로 이 도구가 잡아내려는 일을 하는 것이므로, 인계 자료는 프롬프트 안에서 발견 사항 바로 옆에 그렇게 적어 둡니다.
476
521
 
477
- ### 변경 범위 커버리지
522
+ <br />
478
523
 
479
- `--scope changed`는 `main`과의 병합 기준점(merge-base)에 대비해 당신의
480
- 브랜치가 추가한 줄에 발견을 귀속시킵니다. 테스트 파일(`*.spec.*`,
481
- `*.test.*`)과 diff 속의 GitHub 워크플로 파일, Playwright 설정을
482
- 커버합니다. 병합 기준점을 해석할 수 없을 때 — 얕은 클론, detached HEAD,
483
- git이 아닌 대상, 다른 기본 브랜치 — 정직하게 저하됩니다: 발견은 파일
484
- 전체 귀속으로 되돌아가고 보고서는 그렇게 말합니다. 기준 참조는
485
- `--base <ref>`로 재정의할 수 있습니다.
524
+ ## 신뢰와 보안
486
525
 
487
- ---
526
+ **로컬 우선, 텔레메트리 없음.** 네트워크를 쓸 수 있는 API (`fetch`, `http`, `https`, `net`, `dns`, `dgram`, WebSocket)는 `src/` 어디에도 없으며, 하나라도 나타나면 [`privacy-network-isolation.spec.ts`](tests/contract/privacy-network-isolation.spec.ts)가 빌드를 실패시킵니다. `eval`과 `new Function`도 금지합니다. 신뢰할 수 없는 코드를 스캔해도 그 코드를 실행하지 않습니다. 정적 분석은 소스 텍스트를 읽고, 포렌식은 이미 디스크에 있는 리포트 파일을 파싱합니다.
488
527
 
489
- ## 설정
528
+ 주의할 점 두 가지: `npx` 자체는 무엇이든 실행되기 전에 패키지를 내려받으며, 이 보장은 `src/`에 적용될 뿐 서드파티 플러그인에는 적용되지 않습니다.
490
529
 
491
- Mjölnir는 제로 설정입니다. 리포지터리 루트의 선택적 `mjolnir.config.json`
492
- (또는 `.mjolnir.json`)이 심각도, 게이팅, 범위를 조정합니다 — 탐지
493
- 의미론은 절대 바꾸지 않습니다.
530
+ **플러그인은 샌드박스에서 실행되지 않습니다.** JS 플러그인 (`mjolnir-rules/*.mjs`, 또는 `"plugins"` 아래 나열된 npm 패키지)은 Node의 모든 권한으로 실행되며, ESLint나 Vitest 플러그인과 같은 신뢰 모델입니다. 로드는 **스캔마다** 명시적으로 켜야 합니다. `--enable-plugins` (또는 `MJOLNIR_ENABLE_PLUGINS=1`)가 없으면 소스가 절대 로드되지 않고, stderr의 알림이 건너뛴 항목을 나열합니다. JSON 규칙 매니페스트는 코드를 실행하지 않으며, 코어 규칙 ID 접두사는 예약되어 있어 플러그인이 코어 규칙을 사칭할 수 없습니다. 취약점은 [SECURITY.md](SECURITY.md)로 신고해 주세요.
494
531
 
495
- | 키 | 유형 | 효과 |
496
- | ------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
497
- | `exclude` | `string[]` | 추가 무시 glob (gitignore 부분 집합), 내장 기본값 위에 얹음 |
498
- | `gate` | `"advisory" \| "error" \| "warning"` | 어떤 심각도가 0이 아닌 코드로 종료하는가 (기본 `error`; `advisory`는 결코 막지 않음) |
499
- | `severityOverrides` | `{ "<RULE-ID>": severity }` | 당신의 리포지터리에 맞춰 규칙의 발견을 재정렬 |
500
- | `ignore` | `IgnoreEntry[]` | 발견 억제 — **`reason` 필수**; 항목은 90일 후 만료됩니다 (명시적 `expires` 날짜, 또는 미기재 시 설정 파일의 마지막 수정 시각) |
501
- | `plugins` | `string[]` | 서드파티 규칙 패키지 ([신뢰 모델](#신뢰-모델) 참조) |
532
+ **자기 자신에게도 실행됩니다.** 검증 신뢰 엔진은 스스로 검증 가능하지 않으면 설 자리가 없습니다. 모든 CI 실행은 같은 실행에서 만든 빌드로 이 저장소를 스캔합니다. 게이트는 error 심각도의 발견 사항이 하나라도 있으면 실패하고, **부분** 스캔이나 **충돌한 규칙**이 있어도 실패합니다. 아무것도 보고하지 않는 잘린 자체 스캔이야말로 이 프로젝트가 잡아내려는 가짜 초록색이기 때문입니다. `mjolnir doctor`는 같은 실행에서 규칙 기반을 다시 감사하며 (fixture 방화벽, 등급의 정직성, core 등급 상한), 결과가 INCONCLUSIVE인 검사는 실패한 검사와 똑같이 실패합니다. 두 리포트 모두 빌드 아티팩트로 업로드됩니다.
502
533
 
503
- ```json
504
- {
505
- "gate": "error",
506
- "exclude": ["legacy/**"],
507
- "severityOverrides": { "QA-PW-141": "warning" },
508
- "ignore": [
509
- {
510
- "ruleId": "QA-TEST-004",
511
- "files": ["e2e/legacy-login.spec.ts"],
512
- "reason": "Third-party widget needs a settle delay; tracked in JIRA-4821",
513
- "expires": "2026-12-31"
514
- }
515
- ]
516
- }
517
- ```
534
+ ### 종료 코드와 기계 계약
518
535
 
519
- - **`.mjolnirignore`** — 경로 제외를 위한 단순 gitignore 스타일 파일,
520
- `exclude`와 같은 방언입니다. 기계 단위 노이즈에는 이것을; 목록이 나머지
521
- 설정과 함께 버전 관리에 들어가야 한다면 `exclude`를.
522
- - **CLI 오버라이드** — `--strict` (격리 계층 규칙 포함), `--width <cols>`
523
- 및 `--ascii` / `--no-ascii` (터미널 렌더링), `--tone blunt`
524
- (더 뻣뻣한 메시지), `--max-duration <sec>` (시간 제한 부분 스캔).
525
- - 규칙 억제와 사용 중단 수명 주기: [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md).
526
-
527
- `ignore` 항목은 독립 명령 `mjolnir suppressions`도 구동합니다. 이 명령은
528
- 현재 억제된 항목과 각 항목의 만료 시점을 나열합니다.
529
-
530
- ---
531
-
532
- ## 📐 종료 코드와 계약
533
-
534
- 동결됨 — 그 위에 CI 로직을 세워도 안전합니다:
535
-
536
- | 종료 코드 | 의미 |
537
- | --------- | -------------------------------------------------------------- |
538
- | `0` | 깨끗함 — 게이트 이상의 발견 없음 |
539
- | `1` | 게이트 이상의 발견이 있음 |
540
- | `2` | 부분 스캔 (시간 예산 소진, 읽을 수 없는 파일) — 결코 막지 않음 |
541
- | `10` | 사용 오류 (잘못된 플래그, 대상 누락) |
542
- | `20` | 내부 오류 |
543
-
544
- JSON/SARIF 보고서는 `schemaVersion: 1`입니다. 규칙 ID (`QA-<FAMILY>-NNN`)
545
- 는 출시 후 불변이며 결코 재사용되지 않습니다.
546
-
547
- ---
548
-
549
- ## 신뢰 모델
550
-
551
- - **로컬 퍼스트** — 스캔 중 네트워크 호출 제로. 언제나. 텔레메트리
552
- 제로.
553
- - **가짜 증명 없음** — "검증됨"보다 "알 수 없음"이라 말하는 쪽을
554
- 택합니다. 빈 리포지터리는 `score: null`을 받습니다, 가짜 100은 절대
555
- 아닙니다.
556
- - **부분적 정직함** — 분석이 중단되면 출력이 그렇게 말합니다. 그렇지
557
- 않은데 "complete"라고 하는 일은 없습니다.
558
- - **FP 방화벽** — 탐지는 주석/문자열이 제거된 코드 뷰에서 작동합니다
559
- (TypeScript 규칙은 컴파일러 AST를 사용): 산문 주석 안이나 문서 예제
560
- 문자열 안의 패턴은 문서이지 발견이 아닙니다.
561
- - **측정됨, 단언됨이 아님** — 실제 OSS 코드에서 온 거짓 양성 비율을 가진
562
- 규칙만이 헤드라인 계층에 출시됩니다 ([이 중 얼마나가 측정되었나](#이-중-얼마나가-측정되었나)
563
- 참조); 스캔 바닥글과 `mjolnir rules --unmeasured`가 어느 것이 어느 것인지
564
- 알려줍니다.
565
- - **플러그인 신뢰 및 실행 게이트** — 플러그인은 `"plugins"` 아래 선언된
566
- npm 패키지입니다; JS 모듈은 `mjolnir-rules/*.mjs`에 위치합니다.
567
- **샌드박스가 없습니다**: 플러그인 코드는 완전한 Node 권한으로
568
- 실행되며, ESLint나 Vitest 플러그인과 같은 신뢰 모델입니다. 그렇기
569
- 때문에 코드 실행은 **매 스캔마다 옵트인**입니다: `--enable-plugins`를
570
- 전달하거나(`MJOLNIR_ENABLE_PLUGINS=1` 설정) 그렇지 않으면 해당 소스는
571
- 로드되지 않습니다 — 시끄러운 stderr 알림이 건너뛴 항목을 정확히
572
- 나열합니다. 신뢰할 수 없는 코드를 스캔하는 것이 그 코드를 실행하지는
573
- 않습니다. JSON 규칙 매니페스트(`mjolnir-rules/*.json`)는 영향을 받지
574
- 않습니다: 정규식 패턴을 선언하며 설계상 코드를 실행하지 않습니다.
575
- 코어 규칙 ID 접두사는 예약되어 있고 사칭 방지를 위해 플러그인과 외부
576
- 규칙으로부터 거부됩니다.
577
- - **워크스페이스 로컬 외부 규칙** (폴더 기반, 네트워크 제로) — 스캔 대상
578
- 옆의 `mjolnir-rules/` 디렉터리가 사용자 정의 규칙을 로드합니다: JSON
579
- 파일은 정규식 패턴을 선언하고 (코드는 실행되지 않음), `.mjs`/`.js`
580
- 모듈은 `rules`를 내보냅니다 (플러그인과 같은 완전한 Node 신뢰). 외부
581
- 규칙은 코어와 동일한 신뢰 메타데이터를 가집니다; 절대 코어 계층으로
582
- 출시될 수 없습니다 (코어는 코퍼스 사이드카로부터의 측정된 FP 비율을
583
- 요구합니다 — 선언된 `tier: "core"`는 `extended`로 클램프됩니다), 계층
584
- 한도를 준수하며, 드리프트 검사를 받습니다: `mjolnir rules --md --external`은
585
- 로드된 파일로부터 카탈로그를 렌더링하고 (출처 `external`), 매트릭스
586
- 생성기는 `--external <root>`를 받습니다.
587
-
588
- ---
589
-
590
- ## 🏗️ 아키텍처
536
+ 고정되어 있으므로 이를 바탕으로 CI 로직을 만들 수 있습니다.
591
537
 
592
- <details>
593
- <summary>트리 펼치기</summary>
538
+ | 종료 코드 | 의미 |
539
+ | --------- | ------------------------------------------------------------- |
540
+ | `0` | 깨끗함: 게이트 이상의 발견 사항 없음 |
541
+ | `1` | 게이트 이상의 발견 사항 있음 |
542
+ | `2` | 부분 스캔 (시간 예산 초과, 읽을 수 없는 파일). 차단하지 않음. |
543
+ | `10` | 사용 오류 (잘못된 플래그, 대상 누락) |
544
+ | `20` | 내부 오류 |
594
545
 
595
- ```
596
- mjolnir/
597
- ├── src/
598
- │ ├── engine/ # LanguageAdapter interface + rule runner
599
- │ ├── adapters/ # typescript · python · java · csharp · github-actions
600
- │ ├── rules/ # rules across 8 families + the measured-FP table
601
- │ ├── playwright/ # Selector Health Score engine
602
- │ ├── discovery/ # workspace, frameworks, ignore resolution
603
- │ ├── scope/ # git merge-base changed-scope engine
604
- │ ├── scorer/ # transparent deduction table + prioritization
605
- │ ├── reporter/ # terminal · JSON · SARIF 2.1 · Mermaid
606
- │ ├── forensics/ # run-data ingestion · flake verdicts · triage
607
- │ ├── config/ # mjolnir.config.json + suppressions
608
- │ ├── plugins/ # third-party rule loading (no sandbox)
609
- │ └── commands/ # every subcommand
610
- └── tests/
611
- ├── fixtures/ # must-fire / must-not-fire per rule
612
- └── golden/ # frozen score regression locks
613
- ```
546
+ `2`는 의도적으로 `0`과 구분됩니다. 끝나지 않은 스캔은 아무것도 찾지 못한 것이 아니라, 아직 다 찾지 못한 것입니다.
614
547
 
615
- </details>
548
+ 기계가 소비하는 모든 것 (MCP 도구 결과, `--json`, SARIF 2.1)은 버전이 있고 **추가만 허용되는** 스키마 (`schemaVersion: 1`, `contractVersion: 1`)를 따르는 하나의 표준 결과에서 나오므로, 어떤 소비자도 렌더링된 텍스트에서 의미를 다시 조립할 필요가 없습니다. [기계 계약](docs/machine-contract.md)을 참고하세요. 규칙 ID (`QA-<FAMILY>-NNN`)는 배포된 뒤에는 바뀌지 않으며 재사용되지 않습니다.
616
549
 
617
- - **규칙은 순수 함수입니다** — `(SourceFileContext) → Finding[]`, I/O
618
- 없음, 전역 없음. 새로운 에코시스템 = 하나의 어댑터 + 그 규칙들.
619
- - **TypeScript/Playwright는 컴파일러 AST를 사용합니다** (ts-morph).
620
- Python, Java, C#는 주석/문자열을 마스킹한 공유 정규식 계층 위에서
621
- 실행됩니다.
622
- - Java와 C#을 위한 tree-sitter WASM AST 계층이 존재하며 다음 정밀도
623
- 단계입니다 — 아직 동기 스캔 파이프라인에 연결되지는 않았습니다.
550
+ <br />
624
551
 
625
- ---
552
+ ## Mjölnir가 알려줄 수 없는 것
626
553
 
627
- ## 📚 문서
554
+ - **테스트를 실행하지 않습니다.** 깨끗한 스캔이 통과하는 스위트를 뜻하지는 않습니다.
555
+ - **단언이 _틀렸다_ 는 것은 알려줄 수 없습니다.** `expect(total).toBe(41)`은 건강해 보입니다. Mjölnir가 찾는 것은 _실패할 수 없는_ 테스트와 _빨간색이 될 수 없는_ 파이프라인이지, 엉뚱한 것을 확인하는 테스트가 아닙니다.
556
+ - **비즈니스 정확성을 증명하지 않습니다.** 여기 있는 어떤 것도 제품이 요구 사항대로 동작한다고 말하지 않습니다.
557
+ - **100점이 좋은 스위트의 증거는 아닙니다.** 스위트가 실제 위험을 다루는지는 다른 질문이며, 이 도구는 그 질문에 답하지 않습니다.
558
+ - **79개 규칙 중 5개는 추정치로 배포됩니다**. 측정된 비율이 아닙니다. 각 규칙은 자신의 발견 사항에 그 사실을 밝힙니다.
559
+ - **E1은 E2가 아닙니다.** 휴리스틱 발견 사항은 읽을 가치가 있지만 맹목적으로 적용할 가치는 없습니다.
560
+ - **빈 저장소는 `null`점을 받으며, 절대 100점이 아닙니다.**
561
+ - **테스트 선언이 없는 `*.spec.ts` 파일은 커버리지로 치지 않습니다.** spec 파일에 import나 타입만 있는 (`it`/`test` 호출이 0개인) 저장소는 100점이 아니라 `null`점을 받습니다.
628
562
 
629
- | 문서 | 내용 |
630
- | ------------------------------------------------------ | ------------------------------ |
631
- | [docs/SCORING.md](docs/SCORING.md) | 점수 정규화 + 증거 가중치 |
632
- | [docs/FP-AUDIT.md](docs/FP-AUDIT.md) | 측정된 거짓 양성 비율 + 방법론 |
633
- | [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md) | 규칙 상태, 억제, 사용 중단 |
634
- | [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md) | SARIF 출력 + 에디터/CI 설정 |
635
- | [docs/rules/](docs/rules/) | 생성된 규칙별 카탈로그 |
636
- | [CONTRIBUTING.md](CONTRIBUTING.md) | 개발 환경 + 기여 흐름 |
637
- | [CHANGELOG.md](CHANGELOG.md) | 릴리스 이력 |
638
- | [SECURITY.md](SECURITY.md) | 취약점 보고 |
563
+ <br />
639
564
 
640
- ---
565
+ ## 문서
641
566
 
642
- ## 📈 상태
567
+ 전체 문서 사이트는 <https://sergey-bar.github.io/Mjolnir/>에 있습니다.
643
568
 
644
- **v0.5.x · 오픈 베타.** JSON 스키마와 종료 코드는 동결된 계약입니다.
645
- TypeScript와 Python이 가장 넓은 측정된 커버리지를 갖고; Java와 C#은 더
646
- 새롭습니다 —
647
- [계층 표](#규칙-계층과-언어-성숙도)를 통해 읽어 주세요.
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) | 릴리스 이력 |
648
584
 
649
- ---
585
+ ### 상태
650
586
 
651
- ## 🤝 기여
587
+ **버전 1.** JSON 스키마와 종료 코드는 고정된 계약입니다. TypeScript와 Python이 가장 넓은 측정 범위를 갖습니다. Java와 C#은 비교적 새로우니 [성숙도 표](https://sergey-bar.github.io/Mjolnir/reference/rule-lifecycle)와 함께 보세요. 앞으로의 계획 (지어낸 날짜 없음): [공개 로드맵](https://sergey-bar.github.io/Mjolnir/reference/roadmap).
652
588
 
653
- 새 규칙이 가장 쉬운 첫 기여입니다 — 한 명령으로 규칙과 그 must-fire **와**
654
- must-not-fire 픽스처를 스캐폴드합니다 (생성된 규칙은 실제 탐지를 구현할
655
- 때까지 의도적으로 픽스처에서 실패합니다 — 스텁은 출시될 수 없습니다):
589
+ ### 기여하기
590
+
591
+ 새 규칙은 가장 쉬운 첫 기여입니다. 명령 하나로 must-fire **그리고** must-not-fire fixture와 함께 규칙의 뼈대가 만들어집니다. 생성된 규칙은 실제 탐지 로직이 작성될 때까지 일부러 자신의 fixture에서 실패합니다. 배포된 빈 껍데기는 아무도 측정하지 않은 규칙이기 때문입니다.
656
592
 
657
593
  ```bash
658
594
  mjolnir create-rule QA-PW-140 --title "Screenshot without diff bound"
659
595
  ```
660
596
 
661
- 완전한 개발 환경, 상시 게이트 명령들, 안티 크립 / 픽스처 방화벽 법칙은
662
- [CONTRIBUTING.md](CONTRIBUTING.md)에 있습니다.
597
+ 개발 환경 설정, 상시 게이트 명령, anti-creep 법칙과 fixture 방화벽 법칙은 [CONTRIBUTING.md](CONTRIBUTING.md)에 있습니다.
663
598
 
664
- ---
599
+ <br />
665
600
 
666
601
  <div align="center">
667
602
 
668
- **신뢰할 수 없는 테스트를 출시하지 마세요.**
603
+ <img src="assets/readme/closing.svg" alt="여러분의 저장소에서 실행해 보세요." width="100%" />
669
604
 
670
605
  ```bash
671
606
  npx mjolnir-qa@latest
672
607
  ```
673
608
 
674
- **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
+ 그 테스트가 믿을 만하다는 것을 증거가 증명하는지 물으세요.
675
615
 
676
- [Sergey Bar](https://www.linkedin.com/in/sergeybar/) 제작
616
+ <sub>제작: [Sergey Bar](https://www.linkedin.com/in/sergeybar/) · MIT 라이선스</sub>
677
617
 
678
618
  </div>