@educa-corp/sdd-framework 0.9.4 → 0.9.5

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.
Files changed (69) hide show
  1. package/bin/lint-trace.js +230 -2
  2. package/bin/self-check.js +24 -0
  3. package/bin/trace-schema.json +58 -4
  4. package/core/FRAMEWORK_VERSION +1 -1
  5. package/core/commands/generate-bdd.md +1 -0
  6. package/core/commands/generate-code.md +39 -2
  7. package/core/commands/generate-tech-docs.md +21 -2
  8. package/core/commands/map-testids.md +88 -8
  9. package/core/commands/qc-analyze.md +424 -354
  10. package/core/commands/qc-design-test.md +246 -182
  11. package/core/commands/qc-plan.md +92 -79
  12. package/core/commands/qc-report.md +76 -60
  13. package/core/commands/qc-review.md +130 -114
  14. package/core/commands/qc-run-test.md +230 -156
  15. package/core/commands/review-tech-docs.md +20 -0
  16. package/core/modules/qc-playwright/stack-profile.yaml +1 -1
  17. package/core/rules/data-protection.md +52 -0
  18. package/core/skills/qc/_shared/self-review-principles.md +112 -0
  19. package/core/skills/qc/qa-runner/e2e.md +2 -2
  20. package/core/skills/qc/qa-runner/functional/gui-feature.md +9 -3
  21. package/core/skills/qc/qa-runner/functional/gui-screen.md +9 -3
  22. package/core/skills/qc/qa-runner/integration.md +1 -1
  23. package/core/skills/qc/qa-runner/non-functional.md +1 -1
  24. package/core/skills/spec/SKILL.md +1 -1
  25. package/core/steps/context-loader.md +7 -2
  26. package/core/steps/gap-verify.md +67 -0
  27. package/core/steps/report-footer.md +3 -3
  28. package/core/templates/feature.template +1 -0
  29. package/core/templates/tech-design.template.md +1 -0
  30. package/docs/04-reference/commands.md +1 -1
  31. package/docs/04-reference/trace-schema.md +38 -0
  32. package/docs/explain/11-map-testids.md +70 -69
  33. package/docs/plans/qc-surgery/00-nhat-ky.md +497 -0
  34. package/docs/plans/qc-surgery/01-checklist.md +92 -0
  35. package/docs/plans/qc-surgery/02-lo-trinh.md +266 -0
  36. package/docs/plans/qc-surgery/buoc/0-01-testid-attr-co-cho-o.md +157 -0
  37. package/docs/plans/qc-surgery/buoc/0-02-mot-nguon-cho-testid-attr.md +135 -0
  38. package/docs/plans/qc-surgery/buoc/0-03-skill-thoi-day-do-dom.md +167 -0
  39. package/docs/plans/qc-surgery/buoc/0-04-may-canh-hop-dong.md +173 -0
  40. package/docs/plans/qc-surgery/buoc/0-05-don-nhan-cot-va-2b.md +133 -0
  41. package/docs/plans/qc-surgery/buoc/0-06-hop-dong-truoc-code.md +226 -0
  42. package/docs/plans/qc-surgery/buoc/1-01-guard-br-tag.md +156 -0
  43. package/docs/plans/qc-surgery/buoc/1-02-guard-sc-coverage.md +153 -0
  44. package/docs/plans/qc-surgery/buoc/1-03-fail-3-nhan.md +176 -0
  45. package/docs/plans/qc-surgery/buoc/1-04-self-review-dung-chung.md +175 -0
  46. package/docs/plans/qc-surgery/buoc/1-05-spec-la-du-lieu.md +164 -0
  47. package/docs/plans/qc-surgery/buoc/1-06-gap-verify-du-bo.md +162 -0
  48. package/docs/plans/qc-surgery/buoc/README.md +85 -0
  49. package/docs/plans/qc-surgery/exec-d0-b1-testid-attr-header.md +147 -0
  50. package/docs/plans/qc-surgery/exec-d0-b2-thong-nhat-nguon-testid-attr.md +152 -0
  51. package/docs/plans/qc-surgery/exec-d0-b3-sua-skill-probe-dom.md +173 -0
  52. package/docs/plans/qc-surgery/exec-d0-b4-may-canh-4-5-6.md +168 -0
  53. package/docs/plans/qc-surgery/exec-d0-b5-don-nhan-lech.md +196 -0
  54. package/docs/plans/qc-surgery/exec-d0-b6-contract-truoc-code.md +350 -0
  55. package/docs/plans/qc-surgery/exec-d1-b1-guard-br-tag.md +129 -0
  56. package/docs/plans/qc-surgery/exec-d1-b2-guard-sc-coverage.md +159 -0
  57. package/docs/plans/qc-surgery/exec-d1-b3-fail-3-bucket.md +158 -0
  58. package/docs/plans/qc-surgery/exec-d1-b4-self-review-principles.md +145 -0
  59. package/docs/plans/qc-surgery/exec-d1-b5-noi-quy-spec-la-du-lieu.md +156 -0
  60. package/docs/plans/qc-surgery/exec-d1-b6-gap-verify-mo-rong.md +179 -0
  61. package/docs/plans/qc-surgery/exec-d2-b1-tach-qc-review.md +166 -0
  62. package/docs/plans/qc-surgery/exec-d2-b2-tach-qc-run-test-atomic.md +267 -0
  63. package/docs/plans/qc-surgery/exec-d2-b3-qc-automation-assess.md +198 -0
  64. package/docs/plans/qc-surgery/exec-d3-b1-qc-report-gate-decision.md +209 -0
  65. package/docs/plans/qc-surgery/exec-d4-b1-qc-design-testdata.md +146 -0
  66. package/docs/plans/qc-surgery/exec-d4-b2-qc-smoke-test.md +179 -0
  67. package/docs/plans/qc-surgery/exec-d4-b3-qc-metrics-va-lint.md +198 -0
  68. package/docs/plans/qc-surgery/exec-d4-b4-lint-spec-injection.md +199 -0
  69. package/package.json +1 -1
@@ -1,17 +1,17 @@
1
- ---
2
- version: 1.0
3
- updated: 2026-09-04
4
- ported_from: ai-automation-qc-base
5
- ---
6
-
7
- # /qc-design-test — QC Test-Case Design
8
-
9
- > Stage 3 của QC automation pipeline native (qc-analyze → qc-plan → qc-design-test → qc-review → qc-run-test → qc-report). Port từ qa-designer của team QC. Sinh test case Markdown (`.Test.md`) — Python đến sau ở /qc-run-test.
10
-
11
- ## Gate
12
-
13
- *Checkpoint: **chặn thường** — lệnh ghi file test-case. `--yes` bỏ qua được (gate Bước 3a).*
14
-
1
+ ---
2
+ version: 1.0
3
+ updated: 2026-09-04
4
+ ported_from: ai-automation-qc-base
5
+ ---
6
+
7
+ # /qc-design-test — QC Test-Case Design
8
+
9
+ > Stage 3 của QC automation pipeline native (qc-analyze → qc-plan → qc-design-test → qc-review → qc-run-test → qc-report). Port từ qa-designer của team QC. Sinh test case Markdown (`.Test.md`) — Python đến sau ở /qc-run-test.
10
+
11
+ ## Gate
12
+
13
+ *Checkpoint: **chặn thường** — lệnh ghi file test-case. `--yes` bỏ qua được (gate Bước 3a).*
14
+
15
15
  # Gate — Quy trình vào chuẩn cho mọi lệnh
16
16
 
17
17
  Mọi lệnh PHẢI chạy gate này trước khi thực thi phần logic riêng của nó.
@@ -163,183 +163,247 @@ Mỗi dòng ⚠️/🔴 phải ứng với một trạng thái **context-loader
163
163
  - "N" → dừng, hỏi người dùng muốn thay đổi gì.
164
164
  - Có `--yes` và mức *chặn thường* → coi như "Y", **nhưng vẫn IN khối CHECKPOINT** nếu có cờ
165
165
  🔴/⚠️ (không chặn ≠ không báo — người đọc log sau này vẫn cần thấy).
166
-
167
-
168
- *Lưu ý: Với lệnh này, target ở Bước 1 là một UC-ID. Đọc output của qc-analyze + qc-plan (`REQUIREMENT_ANALYSIS.md`, `DOC_GAP.md`, `TEST_PLAN.md`) từ `{qc_artifact_dir}` — **cả ba đều ở cấp PRD, lọc theo cột `UC`** để lấy phần của UC này — và file `.feature` của đúng platform đó (với `@trace.scenario` mỗi scenario). Với layer GUI, cũng đọc bảng **Test Selectors** §4.5.6 (block platform) của tech-doc gộp tại `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md` (nếu có — bảng gộp mọi UC của platform, **lọc theo cột "Serves SC" khớp SC của UC này**) — các test-id ổn định mà QC sẽ định vị theo.*
169
-
170
- ## Context
166
+
167
+
168
+ *Lưu ý: Với lệnh này, target ở Bước 1 là một UC-ID. Đọc output của qc-analyze + qc-plan (`REQUIREMENT_ANALYSIS.md`, `DOC_GAP.md`, `TEST_PLAN.md`) từ `{qc_artifact_dir}` — **cả ba đều ở cấp PRD, lọc theo cột `UC`** để lấy phần của UC này — và file `.feature` của đúng platform đó (với `@trace.scenario` mỗi scenario). Với layer GUI, cũng đọc bảng **Test Selectors** §4.5.6 (block platform) của tech-doc gộp tại `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md` (nếu có — bảng gộp mọi UC của platform, **lọc theo cột "Phục vụ SC" khớp SC của UC này**) — các test-id ổn định mà QC sẽ định vị theo.*
169
+
170
+ ## Context
171
171
  **BẮT BUỘC — đọc `.agent/steps/context-loader.md` và thực thi TOÀN BỘ quy trình trong đó**,
172
172
  rồi mới tiếp tục phần bên dưới.
173
173
 
174
174
  Bỏ qua bước này thì `{paths.*}`, `{tech_stack.*}`, `{conventions.*}`, guardrail từ
175
175
  `project-lessons`, và routing service (chế độ umbrella) đều **chưa được phân giải** — mọi
176
- placeholder bên dưới sẽ rỗng và lệnh sẽ đọc/ghi sai chỗ.
177
-
178
- ## Phạm vi QC
179
-
176
+ placeholder bên dưới sẽ rỗng và lệnh sẽ đọc/ghi sai chỗ.
177
+
178
+ ## Phạm vi QC
179
+
180
180
  **BẮT BUỘC — đọc `.agent/steps/qc-scope.md` và thực thi TOÀN BỘ quy trình trong đó**,
181
181
  rồi mới tiếp tục phần bên dưới.
182
182
 
183
183
  Nó chốt bốn thứ mà mọi trạm QC đều cần: `TICKET-ID` · `active_platform` ·
184
184
  `qc_artifact_dir` · `uc_list` (kèm trạng thái BDD từng UC, và cờ `--include-draft`).
185
185
  Bỏ qua thì artifact QC ghi vào **sai thư mục** và `qc_status` ghi vào **sai sổ trace** —
186
- cả hai đều xảy ra trong im lặng, không có bước nào phía sau bắt được.
187
-
188
- > **Trạm này vẫn gọi theo TỪNG UC** *(B11)* — thiết kế và chạy test **thật sự** làm tăng dần
189
- > theo UC, nên giữ khả năng làm UC1 khi UC3 chưa xong là đúng. Chỉ **chỗ đọc/ghi** đổi: mọi
190
- > artifact nằm chung ở `{qc_artifact_dir}` cấp PRD, không còn một thư mục mỗi UC.
191
- >
192
- > Nên `DOC_GAP.md` / `TEST_PLAN.md` đọc được ở đây phủ **cả PRD**: **lọc theo cột `UC`** để lấy
193
- > phần của UC đang làm. Đừng coi toàn bộ bảng gap là của UC này — sẽ chặn oan.
194
-
195
- ---
196
-
197
- ## Cờ — chọn tầng nào, tách mịn đến đâu
198
-
199
- | Cờ | Ghi file | Nhóm |
200
- |---|---|---|
201
- | *(không cờ)* | `TC_<FEATURE>.Test.md` | 1 GUI · 2 Validation · 3 Functional · 4 Integration (qua UI) · 5 NFR · 6 E2E |
202
- | `--api` | `TC_<FEATURE>_API.Test.md` | 1 Endpoint · 2 Integration API/DB/Kafka |
203
- | `--all` | **cả hai file** | như trên, hai file riêng biệt |
204
- | `--atomic-max` | — | bật chế độ tách tối đa (xem dưới) |
205
-
206
- **Câu hỏi phân file:** *"TC này verify được mà **không cần UI** không?"* → **có** = file API ·
207
- **không** = file giao diện. Đây đúng là Bước 2 của
208
- `{paths.qc_skills_dir}/qa-designer/shared/skill-decision-tree.md`.
209
-
210
- > **Vì sao hai file riêng chứ không một file rồi ghi từng nhóm** *(B12)*. `--api` chạy sau khi
211
- > file giao diện đã tồn tại là chuyện thường. Nếu chung một file, lần chạy `--api` phải **giữ
212
- > nguyên** năm nhóm kia — mà viết-lại-cả-file là đúng thứ agent hay làm. Hai file thì hai chế
213
- > độ **không bao giờ chạm nhau**, và không cần tin vào việc agent nhớ giữ phần cũ.
214
-
215
- ### `--atomic-max` — tách tới mức nhỏ nhất
216
-
217
- Mặc định đã là ATOMIC (mỗi TC đúng một kết cục), nhưng **cho phép** bullet compound khi cả hai
218
- vế nói về **cùng một** kết cục — ví dụ *"form hiển thị đủ 3 trường"* vẫn là 1 TC.
219
-
220
- `--atomic-max` **nổ danh sách completeness thành từng thành phần**: thẻ hiển thị đủ 5 thông tin
221
- → **5 TC**. Ở đội QC, một tính năng đi từ 82 lên 130 TC.
222
-
223
- **⛔ Thủ tục bắt buộc TRƯỚC khi ghi** *(theo `shared/precision-rules.md` §2.1)*:
224
-
225
- 1. **Chốt ranh giới completeness** — hai mức có quy mô rất khác nhau:
226
- | Giá trị | Nghĩa |
227
- |---|---|
228
- | `--atomic-max=keep-completeness` | giữ danh sách completeness là 1 TC (~95 TC ở ví dụ thật) |
229
- | `--atomic-max=explode` | nổ từng thành phần (~130 TC) |
230
-
231
- Không nêu giá trị → **HỎI** trước khi ghi, vì đây là quyết định quy mô của người chủ, không
232
- phải của agent. **Có `--yes`:** không hỏi — **DỪNG** với lỗi rõ ràng:
233
- ```
234
- ❌ --atomic-max cần nêu ranh giới ở chế độ headless (quy mô chênh ~35%).
235
- Chạy: /qc-design-test {UC-ID} --atomic-max=explode --yes
236
- hoặc --atomic-max=keep-completeness --yes
237
- ```
238
- *(Một cổng chỉ chặn được khi có người ngồi đó thì ở chế độ headless nó không phải cổng, nó
239
- là treo — cùng bài học với `steps/qc-scope.md` §2.)*
240
-
241
- 2. **Dựng bản đồ tách trước khi ghi** — mỗi TC cha tách ra mấy mảnh. Đừng để agent con tự suy
242
- trong lúc ghi; đó là chỗ sinh ra TC cắt cụt.
243
- 3. **GIỮ 1 TC** cho bốn loại này kể cả ở chế độ tối đa: *predicate đa-điều-kiện định-nghĩa-một-
244
- khái-niệm* · *ngưỡng* (`≥44pt`, `WCAG AA ≥4.5:1`) · *exclusivity* chọn-một · *qualifier*.
245
- 4. **Verify bằng DIFF, không tin lời đếm của agent** — ID liên tục · mỗi Expected 1 bullet ·
246
- 0 ký tự `|` · Trace matrix không còn ID chết · P0/P1 khớp tag.
247
-
248
- ---
249
-
250
- ## Role
251
-
252
- Bạn là **QC Designer** — stage 3. Sinh/bảo trì các file test-case Markdown
253
- (`.Test.md`) từ requirement đã phân tích + plan. Output feed vào qc-run-test (Python) và
254
- qc-review. Bạn **không** viết Python.
255
-
256
- ## Skills (`{paths.qc_skills_dir}/qa-designer/`)
257
-
258
- **Luật dùng chung — nạp theo tầng, không nạp hết mỗi lần:**
259
-
260
- | Khi nào | Nạp |
261
- |---|---|
262
- | **Luôn** | `shared/tc-metadata-format.md` — khuôn TC · luật ATOMIC · phân nhóm · hai file `.Test.md` · Trace · `🚫 Block` · `Test-ID attribute` |
263
- | **Luôn** | `shared/precision-rules.md` — cấm 9 cụm mơ hồ · đơn vị theo domain · toán tử · BVA 3-hay-4 giá trị · phủ đủ ô decision table · 3 mức teardown |
264
- | **Luôn** | `shared/action-keywords-glossary.md` — một hành động một từ (Click/Tap/Enter/Select…) |
265
- | **Luôn** | `shared/implicit-scenarios.md` — 7 tình huống bắt buộc nghĩ tới, áp cho mọi nhóm |
266
- | Đầu phiên, một lần | `shared/read-doc-gap-inputs.md` — đọc **đủ** mọi file trong bảng *Tài liệu đầu vào đã đọc* của `DOC_GAP.md`, không chỉ đọc bản tóm tắt gap |
267
- | Không chắc chọn tầng nào | `shared/skill-decision-tree.md` — cây quyết định UI / Integration-GUI / E2E / Integration |
268
- | Thêm TC vào file đã có | `shared/duplicate-check-procedure.md` — grep trước khi viết; và **trùng chéo UC** giờ mới thực sự xảy ra vì mọi UC dùng chung một thư mục |
269
- | `--api` / `--all` | `api/endpoint.md` · `api/auth-chain.md` · `api/http-status-codes.md` · `api/common-headers.md` · `api/crud-sequence.md` · `api/auth-sequence.md` |
270
-
271
- **Rồi chọn tầng, nạp MỘT file:**
272
-
273
- | Layer | File | Ghi vào |
274
- |---|---|---|
275
- | 1 màn hình | `functional/gui-screen.md` | file giao diện |
276
- | Feature đa-màn | `functional/gui-feature.md` | file giao diện |
277
- | API endpoint | `functional/api.md` | **file API** |
278
- | Integration qua UI | `integration/gui.md` | file giao diện |
279
- | Integration API/DB/Kafka | `integration/{api,db,kafka}.md` | **file API** |
280
- | End-to-end journey | `e2e/journey.md` | file giao diện |
281
- | Non-functional | `non-functional.md` | theo câu hỏi phân file |
282
- | Exploratory | `exploratory/{charter,explore-to-functional}.md` | file giao diện |
283
-
284
- ## Conventions
285
-
286
- *Khuôn TC, luật ATOMIC, phân nhóm, không-dùng-bảng, Trace, `🚫 Block`, `Test-ID attribute` —
287
- đều ở `shared/tc-metadata-format.md`. **Đừng lặp lại ở đây.** Dưới đây chỉ là thứ riêng của trạm:*
288
-
289
- - Markdown-first; đừng design khi requirement chưa rõ — đẩy ngược về qc-plan/qc-analyze.
290
- - **Tham chiếu test-id, không phải gợi ý hình ảnh.** Với mỗi step GUI tác động lên một element, trích test-id ổn định từ bảng §4.5.6 của tech-doc gộp (vd "click `ft001-login-submit-btn`") để qc-run-test dựng locator từ contract. Nếu một element có action không có test-id trong §4.5.6, ghi chú lại (qc-run-test sẽ fallback về locator role/text chậm hơn).
291
- - **Đọc `@trace.testid_attr` từ header tech-doc gộp** (do `/map-testids` ghi) và ghi lại một
292
- dòng `Test-ID attribute: {attr}` ở metadata đầu file `.Test.md`. §4.5.6 chỉ cho **giá trị**
293
- test-id; `@trace.testid_attr` là **tên thuộc tính** chứa chúng — `/qc-run-test` cần nó để cấu
294
- hình locator, `/qc-review` cần nó để biết selector trong script có đúng contract không.
295
- Không đọc được `@trace.testid_attr` → ghi
296
- `Test-ID attribute: — (thiếu @trace.testid_attr, chạy /map-testids)`; **đừng bỏ trống, đừng
297
- tự đoán theo platform** — đoán sai thì mọi locator trượt 100%, vì một lý do không liên quan
298
- gì tới thứ đang được test. *(Dạng dòng metadata: `shared/tc-metadata-format.md`.)*
299
- - Một TC bị block bởi gap **vẫn viết đủ** + `🚫 Block: [GAP-UC{N}-{nnn}](../DOC_GAP.md) — lý do`.
300
- Chặn theo **UC ở cột `UC` của hàng gap đó**, không chặn cả PRD.
301
-
302
- ## Trace mapping (bắt buộc)
303
-
304
- Ngoài trace `BR-xx`, mỗi TC ghi scenario framework mà nó verify — **dạng trường danh sách,
305
- không phải cột bảng** (file TC không có ký tự `|`):
306
-
307
- ```
308
- - **@trace.verifies:** {UC-ID}-SC{N}
309
- ```
310
-
311
- Lấy từ `@trace.scenario` của `.feature`. Một SC map được nhiều TC. Đây là **join key** cho phép
312
- qc-run-test ghi `qc_status` theo từng scenario vào sổ trace — **thiếu nó thì test vẫn chạy,
313
- vẫn pass/fail, nhưng kết quả không vào được sổ và không ai biết nó phủ kịch bản nào.**
314
-
315
- Cuối file: **Trace matrix** (BR ↔ TC ↔ SC) + **danh sách TC bị block** — cả hai dạng danh sách.
316
-
317
- ## Output
318
-
319
- Ghi dưới `{qc_artifact_dir}test-cases/`
320
- (= `{paths.qc_dir}/{TICKET-ID}/{active_platform}/test-cases/`)**một thư mục dùng chung cho cả
321
- PRD**. Tên file mang `<FEATURE>` nên các UC không đâm nhau; `@trace.verifies` chỗ phân biệt
322
- TC thuộc UC nào.
323
-
324
- **Đuôi bắt buộc là `.Test.md`.** `/qc-run-test` và `/qc-review` tìm `*.Test.md`; ghi ra
325
- `TC_<FEATURE>.md` ghi ra thứ **không trạm nào tìm thấy**, **không báo lỗi**.
326
-
327
- `🚫 Block` trỏ `../DOC_GAP.md` lên một cấp, file gap ở thư mục cha của `test-cases/`.
328
-
329
- ## Report
330
-
186
+ cả hai đều xảy ra trong im lặng, không có bước nào phía sau bắt được.
187
+
188
+ > **Trạm này vẫn gọi theo TỪNG UC** *(B11)* — thiết kế và chạy test **thật sự** làm tăng dần
189
+ > theo UC, nên giữ khả năng làm UC1 khi UC3 chưa xong là đúng. Chỉ **chỗ đọc/ghi** đổi: mọi
190
+ > artifact nằm chung ở `{qc_artifact_dir}` cấp PRD, không còn một thư mục mỗi UC.
191
+ >
192
+ > Nên `DOC_GAP.md` / `TEST_PLAN.md` đọc được ở đây phủ **cả PRD**: **lọc theo cột `UC`** để lấy
193
+ > phần của UC đang làm. Đừng coi toàn bộ bảng gap là của UC này — sẽ chặn oan.
194
+
195
+ ---
196
+
197
+ ## Cờ — chọn tầng nào, tách mịn đến đâu
198
+
199
+ | Cờ | Ghi file | Nhóm |
200
+ |---|---|---|
201
+ | *(không cờ)* | `TC_<FEATURE>.Test.md` | 1 GUI · 2 Validation · 3 Functional · 4 Integration (qua UI) · 5 NFR · 6 E2E |
202
+ | `--api` | `TC_<FEATURE>_API.Test.md` | 1 Endpoint · 2 Integration API/DB/Kafka |
203
+ | `--all` | **cả hai file** | như trên, hai file riêng biệt |
204
+ | `--atomic-max` | — | bật chế độ tách tối đa (xem dưới) |
205
+
206
+ **Câu hỏi phân file:** *"TC này verify được mà **không cần UI** không?"* → **có** = file API ·
207
+ **không** = file giao diện. Đây đúng là Bước 2 của
208
+ `{paths.qc_skills_dir}/qa-designer/shared/skill-decision-tree.md`.
209
+
210
+ > **Vì sao hai file riêng chứ không một file rồi ghi từng nhóm** *(B12)*. `--api` chạy sau khi
211
+ > file giao diện đã tồn tại là chuyện thường. Nếu chung một file, lần chạy `--api` phải **giữ
212
+ > nguyên** năm nhóm kia — mà viết-lại-cả-file là đúng thứ agent hay làm. Hai file thì hai chế
213
+ > độ **không bao giờ chạm nhau**, và không cần tin vào việc agent nhớ giữ phần cũ.
214
+
215
+ ### `--atomic-max` — tách tới mức nhỏ nhất
216
+
217
+ Mặc định đã là ATOMIC (mỗi TC đúng một kết cục), nhưng **cho phép** bullet compound khi cả hai
218
+ vế nói về **cùng một** kết cục — ví dụ *"form hiển thị đủ 3 trường"* vẫn là 1 TC.
219
+
220
+ `--atomic-max` **nổ danh sách completeness thành từng thành phần**: thẻ hiển thị đủ 5 thông tin
221
+ → **5 TC**. Ở đội QC, một tính năng đi từ 82 lên 130 TC.
222
+
223
+ **⛔ Thủ tục bắt buộc TRƯỚC khi ghi** *(theo `shared/precision-rules.md` §2.1)*:
224
+
225
+ 1. **Chốt ranh giới completeness** — hai mức có quy mô rất khác nhau:
226
+ | Giá trị | Nghĩa |
227
+ |---|---|
228
+ | `--atomic-max=keep-completeness` | giữ danh sách completeness là 1 TC (~95 TC ở ví dụ thật) |
229
+ | `--atomic-max=explode` | nổ từng thành phần (~130 TC) |
230
+
231
+ Không nêu giá trị → **HỎI** trước khi ghi, vì đây là quyết định quy mô của người chủ, không
232
+ phải của agent. **Có `--yes`:** không hỏi — **DỪNG** với lỗi rõ ràng:
233
+ ```
234
+ ❌ --atomic-max cần nêu ranh giới ở chế độ headless (quy mô chênh ~35%).
235
+ Chạy: /qc-design-test {UC-ID} --atomic-max=explode --yes
236
+ hoặc --atomic-max=keep-completeness --yes
237
+ ```
238
+ *(Một cổng chỉ chặn được khi có người ngồi đó thì ở chế độ headless nó không phải cổng, nó
239
+ là treo — cùng bài học với `steps/qc-scope.md` §2.)*
240
+
241
+ 2. **Dựng bản đồ tách trước khi ghi** — mỗi TC cha tách ra mấy mảnh. Đừng để agent con tự suy
242
+ trong lúc ghi; đó là chỗ sinh ra TC cắt cụt.
243
+ 3. **GIỮ 1 TC** cho bốn loại này kể cả ở chế độ tối đa: *predicate đa-điều-kiện định-nghĩa-một-
244
+ khái-niệm* · *ngưỡng* (`≥44pt`, `WCAG AA ≥4.5:1`) · *exclusivity* chọn-một · *qualifier*.
245
+ 4. **Verify bằng DIFF, không tin lời đếm của agent** — ID liên tục · mỗi Expected 1 bullet ·
246
+ 0 ký tự `|` · Trace matrix không còn ID chết · P0/P1 khớp tag.
247
+
248
+ ---
249
+
250
+ ## Role
251
+
252
+ Bạn là **QC Designer** — stage 3. Sinh/bảo trì các file test-case Markdown
253
+ (`.Test.md`) từ requirement đã phân tích + plan. Output feed vào qc-run-test (Python) và
254
+ qc-review. Bạn **không** viết Python.
255
+
256
+ ## Skills (`{paths.qc_skills_dir}/qa-designer/`)
257
+
258
+ **Luật dùng chung — nạp theo tầng, không nạp hết mỗi lần:**
259
+
260
+ | Khi nào | Nạp |
261
+ |---|---|
262
+ | **Luôn** | `shared/tc-metadata-format.md` — khuôn TC · luật ATOMIC · phân nhóm · hai file `.Test.md` · Trace · `🚫 Block` · `Test-ID attribute` |
263
+ | **Luôn** | `shared/precision-rules.md` — cấm 9 cụm mơ hồ · đơn vị theo domain · toán tử · BVA 3-hay-4 giá trị · phủ đủ ô decision table · 3 mức teardown |
264
+ | **Luôn** | `shared/action-keywords-glossary.md` — một hành động một từ (Click/Tap/Enter/Select…) |
265
+ | **Luôn** | `shared/implicit-scenarios.md` — 7 tình huống bắt buộc nghĩ tới, áp cho mọi nhóm |
266
+ | Đầu phiên, một lần | `shared/read-doc-gap-inputs.md` — đọc **đủ** mọi file trong bảng *Tài liệu đầu vào đã đọc* của `DOC_GAP.md`, không chỉ đọc bản tóm tắt gap |
267
+ | Không chắc chọn tầng nào | `shared/skill-decision-tree.md` — cây quyết định UI / Integration-GUI / E2E / Integration |
268
+ | Thêm TC vào file đã có | `shared/duplicate-check-procedure.md` — grep trước khi viết; và **trùng chéo UC** giờ mới thực sự xảy ra vì mọi UC dùng chung một thư mục |
269
+ | `--api` / `--all` | `api/endpoint.md` · `api/auth-chain.md` · `api/http-status-codes.md` · `api/common-headers.md` · `api/crud-sequence.md` · `api/auth-sequence.md` |
270
+
271
+ **Rồi chọn tầng, nạp MỘT file:**
272
+
273
+ | Layer | File | Ghi vào |
274
+ |---|---|---|
275
+ | 1 màn hình | `functional/gui-screen.md` | file giao diện |
276
+ | Feature đa-màn | `functional/gui-feature.md` | file giao diện |
277
+ | API endpoint | `functional/api.md` | **file API** |
278
+ | Integration qua UI | `integration/gui.md` | file giao diện |
279
+ | Integration API/DB/Kafka | `integration/{api,db,kafka}.md` | **file API** |
280
+ | End-to-end journey | `e2e/journey.md` | file giao diện |
281
+ | Non-functional | `non-functional.md` | theo câu hỏi phân file |
282
+ | Exploratory | `exploratory/{charter,explore-to-functional}.md` | file giao diện |
283
+
284
+ ## Conventions
285
+
286
+ *Khuôn TC, luật ATOMIC, phân nhóm, không-dùng-bảng, Trace, `🚫 Block`, `Test-ID attribute` —
287
+ đều ở `shared/tc-metadata-format.md`. **Đừng lặp lại ở đây.** Dưới đây chỉ là thứ riêng của trạm:*
288
+
289
+ - Markdown-first; đừng design khi requirement chưa rõ — đẩy ngược về qc-plan/qc-analyze.
290
+ - **Tham chiếu test-id, không phải gợi ý hình ảnh.** Với mỗi step GUI tác động lên một element, trích test-id ổn định từ bảng §4.5.6 của tech-doc gộp (vd "click `ft001-login-submit-btn`") để qc-run-test dựng locator từ contract. Nếu một element có action không có test-id trong §4.5.6, ghi chú lại (qc-run-test sẽ fallback về locator role/text chậm hơn).
291
+ - **Đọc `@trace.testid_attr` từ header tech-doc gộp** (do `/map-testids` ghi) và ghi lại một
292
+ dòng `Test-ID attribute: {attr}` ở metadata đầu file `.Test.md`. §4.5.6 chỉ cho **giá trị**
293
+ test-id; `@trace.testid_attr` là **tên thuộc tính** chứa chúng — `/qc-run-test` cần nó để cấu
294
+ hình locator, `/qc-review` cần nó để biết selector trong script có đúng contract không.
295
+ Không đọc được `@trace.testid_attr` → ghi
296
+ `Test-ID attribute: — (thiếu @trace.testid_attr, chạy /map-testids)`; **đừng bỏ trống, đừng
297
+ tự đoán theo platform** — đoán sai thì mọi locator trượt 100%, vì một lý do không liên quan
298
+ gì tới thứ đang được test. *(Dạng dòng metadata: `shared/tc-metadata-format.md`.)*
299
+ - Một TC bị block bởi gap **vẫn viết đủ** + `🚫 Block: [GAP-UC{N}-{nnn}](../DOC_GAP.md) — lý do`.
300
+ Chặn theo **UC ở cột `UC` của hàng gap đó**, không chặn cả PRD.
301
+
302
+ ## Trace mapping (bắt buộc)
303
+
304
+ Ngoài trace `BR-xx`, mỗi TC ghi scenario framework mà nó verify — **dạng trường danh sách,
305
+ không phải cột bảng** (file TC không có ký tự `|`):
306
+
307
+ ```
308
+ - **@trace.verifies:** {UC-ID}-SC{N}
309
+ ```
310
+
311
+ Lấy từ `@trace.scenario` của `.feature`. Một SC map được nhiều TC. Đây là **join key** cho phép
312
+ qc-run-test ghi `qc_status` theo từng scenario vào sổ trace — **thiếu nó thì test vẫn chạy,
313
+ vẫn pass/fail, nhưng kết quả không vào được sổ và không ai biết nó phủ kịch bản nào.**
314
+
315
+ Cuối file: **Trace matrix** (BR ↔ TC ↔ SC) + **danh sách TC bị block** — cả hai dạng danh sách.
316
+
317
+ ## Guard — SC coverage *(phép ĐẾM cơ học, chạy SAU khi ghi file, TRƯỚC CHECKPOINT)*
318
+
319
+ Dòng `Trace:` ở report cuối đã đòi con số `{K}/{total}` từ trước. Guard này là **phép tính sinh
320
+ ra con số đó**, và là **hệ quả khi `K < total`** trước đây số không có phép tính nào
321
+ đứng sau, nên ước lượng, `K < total` không dẫn tới việc gì.
322
+
323
+ ### Ba bước
324
+
325
+ 1. **Thu SC** mọi `@trace.scenario` trong `.feature` của `uc_list` (đúng `active_platform`).
326
+ **Loại** SC của UC mà `qc-scope` đã lọc ra (`⏸ chưa xét` — BDD chưa approved): chúng nằm
327
+ ngoài phạm vi lần chạy này, không phải bỏ sót.
328
+ 2. **Thu V** — mọi `@trace.verifies` trong các file `*.Test.md` **vừa ghi**.
329
+ 3. **Đếm** — mỗi SC ∈ tập ở bước 1 phải có **≥ 1** TC trỏ tới.
330
+
331
+ ### Xử lý
332
+
333
+ | Kết quả | Làm gì |
334
+ |---|---|
335
+ | Mọi SC đều có ≥1 TC | in `Guard SC coverage: khớp {K}/{K}` |
336
+ | Có SC chưa phủ | **viết bù TC ngay**, rồi in `⚠️ Guard SC coverage: {K}/{total} — đã bù {m} SC: {danh sách}` |
337
+
338
+ **Không có đường "bỏ trống có lý do".** Đây là chỗ dễ làm sai nhất, và quy ước ở §Conventions
339
+ đã trả lời: *"Một TC bị block bởi gap **vẫn viết đủ** + `🚫 Block: [GAP-UC{N}-{nnn}](../DOC_GAP.md)`"*.
340
+ Nghĩa là **gap không phải cái cớ để không viết TC** — nó là thứ được **ghi vào TC**. SC bị chặn
341
+ vẫn có TC, vẫn được đếm là đã phủ.
342
+
343
+ Nên chỉ còn đúng hai trạng thái, và cả hai đều không cho phép bỏ trống:
344
+
345
+ - SC **trong** phạm vi → phải có TC (bị gap chặn thì TC mang dấu `🚫 Block`).
346
+ - SC **ngoài** phạm vi → đã bị `qc-scope` lọc ở bước 1, không tính vào mẫu số.
347
+
348
+ Nếu bạn thấy một SC "không thể viết TC" mà không thuộc hai loại trên, thì **đó là một gap** —
349
+ mở gap trong `DOC_GAP.md`, viết TC kèm `🚫 Block` trỏ tới nó. Đừng viết một câu lý do rồi đi
350
+ tiếp: guard viết lỏng thì lần sau sẽ có một câu lý do cho qua, và phép đếm mất nghĩa.
351
+
352
+ **In dòng `Guard SC coverage:` kể cả khi sạch** — guard im lặng khi sạch là guard không ai biết
353
+ nó tồn tại, và không ai phát hiện được khi nó chết.
354
+
355
+ > **Vì sao đếm ở đây mà không đợi báo cáo cuối.** `/qc-report` (Đợt 3 của đợt mổ QC) sẽ tính
356
+ > **design coverage** bằng **đúng phép đếm này**. Khác nhau ở thời điểm: guard đếm **lúc thiết
357
+ > kế** — còn kịp viết bù; báo cáo đếm **ở cuối** — chỉ còn kịp báo. Phát hiện "design coverage
358
+ > 68%" ở phút cuối là lúc không còn thời gian viết bù 32%.
359
+ >
360
+ > **Vì sao "chưa có TC" khác "có TC nhưng chưa chạy".** Hai trạng thái này dẫn tới hai việc khác
361
+ > nhau hoàn toàn — viết test, so với chạy test. Nhưng nhìn vào sổ trace thì giống nhau: cả hai
362
+ > đều là `qc_status = not_run`. Guard này là chỗ duy nhất phân biệt được.
363
+
364
+ ## Output
365
+
366
+ Ghi dưới `{qc_artifact_dir}test-cases/`
367
+ (= `{paths.qc_dir}/{TICKET-ID}/{active_platform}/test-cases/`) — **một thư mục dùng chung cho cả
368
+ PRD**. Tên file mang `<FEATURE>` nên các UC không đâm nhau; `@trace.verifies` là chỗ phân biệt
369
+ TC thuộc UC nào.
370
+
371
+ **Đuôi bắt buộc là `.Test.md`.** `/qc-run-test` và `/qc-review` tìm `*.Test.md`; ghi ra
372
+ `TC_<FEATURE>.md` là ghi ra thứ **không trạm nào tìm thấy**, và **không có gì báo lỗi**.
373
+
374
+ `🚫 Block` trỏ `../DOC_GAP.md` — lên một cấp, vì file gap ở thư mục cha của `test-cases/`.
375
+
376
+ ## Self-Review *(trước khi in Report)*
377
+
378
+ Theo 3 nhóm ở `{paths.qc_skills_dir}/_shared/self-review-principles.md` — **không chép lại ở đây**.
379
+
380
+ - **Bịa:** mỗi TC kiểm một hành vi **spec có nêu** — không phải hành vi tôi cho là hợp lý? Mỗi
381
+ `Expected` là **giá trị cụ thể** (*"hiển thị text 'Tên lớp: Toán 6A'"*), không phải *"hiển thị
382
+ đúng"* / *"hoạt động bình thường"*?
383
+ - **Nhảy bước:** đã áp luật ATOMIC cho **mọi** TC (không chỉ vài TC đầu), và đã chạy
384
+ §duplicate-check trước khi thêm TC mới?
385
+ - **Số liệu:** `{n}` TC mỗi file = `grep -cE "^#{2,4} *TC_"` thật? Các con số phân nhóm (GUI /
386
+ Validation / Functional / …) cộng lại đúng bằng tổng TC?
387
+
388
+ > Guard SC coverage ở trên là **phép đếm cơ học**, không phải self-review — nó đối chiếu với
389
+ > `.feature`, một nguồn khác. Đừng coi self-review đã bao nó. Xem §Ranh giới trong file skill.
390
+
391
+ ## Report
392
+
331
393
  **Đọc `.agent/steps/report-footer.md`** và áp đúng khuôn footer trong đó (Status Badge ·
332
- Output Artifacts · Next) cho report cuối, kèm khối bên dưới.
333
-
334
- ```
335
- /qc-design-test Hoàn tất — {UC-ID} ({active_platform})
336
- Chế độ: {giao diện | API | cả hai}{nếu --atomic-max: " · tách tối đa ({explode|keep-completeness})"}
337
- Files: {qc_artifact_dir}test-cases/TC_<FEATURE>.Test.md — {n} TC
338
- {nếu có: "…_API.Test.md — {m} TC"}
339
- Nhóm : GUI {a} · Validation {b} · Functional {c} · Integration {d} · NFR {e} · E2E {f}
340
- {nếu file API: "Endpoint {g} · Integration API/DB/Kafka {h}"}
341
- Trùng: {k} TC bỏ vì trùng ({j} trùng chéo UC — trỏ trace về UC nguồn thay vì viết lại)
342
- Block: {blocked} TC bị chặn bởi gap — theo UC: {UC1: n · UC2: n}
343
- Trace: {N} TC map tới {K}/{total} scenario của {UC-ID}
344
- Next : /qc-review {UC-ID} review test case trước khi sinh script
345
- ```
394
+ Output Artifacts · Next) cho report cuối, kèm khối bên dưới.
395
+
396
+ ```
397
+ /qc-design-test Hoàn tất — {UC-ID} ({active_platform})
398
+ Chế độ: {giao diện | API | cả hai}{nếu --atomic-max: " · tách tối đa ({explode|keep-completeness})"}
399
+ Files: {qc_artifact_dir}test-cases/TC_<FEATURE>.Test.md — {n} TC
400
+ {nếu có: "…_API.Test.md — {m} TC"}
401
+ Nhóm : GUI {a} · Validation {b} · Functional {c} · Integration {d} · NFR {e} · E2E {f}
402
+ {nếu file API: "Endpoint {g} · Integration API/DB/Kafka {h}"}
403
+ Trùng: {k} TC bỏ vì trùng ({j} trùng chéo UC — trỏ trace về UC nguồn thay vì viết lại)
404
+ Block: {blocked} TC bị chặn bởi gap — theo UC: {UC1: n · UC2: n}
405
+ Trace: {N} TC map tới {K}/{total} scenario của {UC-ID}
406
+ Guard SC coverage: {khớp {K}/{K} | ⚠️ {K}/{total} đã {m} SC: {danh sách SC}}
407
+ Self-review: {✅ sạch | ⚠️ {n} điểm cần chú ý — liệt kê}
408
+ Next : /qc-review {UC-ID} ← review test case trước khi sinh script
409
+ ```