@educa-corp/sdd-framework 0.9.5 → 0.9.7

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 (113) hide show
  1. package/bin/build.js +11 -1
  2. package/bin/lint-trace.js +397 -28
  3. package/bin/self-check.js +623 -16
  4. package/bin/trace-schema.json +3187 -1981
  5. package/core/FRAMEWORK_VERSION +1 -1
  6. package/core/commands/amend-prd.md +7 -1
  7. package/core/commands/debug.md +8 -2
  8. package/core/commands/define-product.md +38 -1
  9. package/core/commands/dev-gen-test.md +70 -2
  10. package/core/commands/dev-run-test.md +8 -2
  11. package/core/commands/dev-smoke-test.md +7 -1
  12. package/core/commands/extend-prd.md +7 -1
  13. package/core/commands/fix-bug.md +11 -5
  14. package/core/commands/generate-architecture.md +9 -1
  15. package/core/commands/generate-bdd.md +45 -5
  16. package/core/commands/generate-code.md +44 -5
  17. package/core/commands/generate-design-spec.md +7 -1
  18. package/core/commands/generate-prd.md +9 -1
  19. package/core/commands/generate-spec-manifest.md +7 -1
  20. package/core/commands/generate-tech-docs.md +44 -4
  21. package/core/commands/learn.md +7 -1
  22. package/core/commands/map-testids.md +96 -13
  23. package/core/commands/propose-scenario.md +7 -1
  24. package/core/commands/qc-analyze.md +516 -426
  25. package/core/commands/qc-automation-assess.md +356 -0
  26. package/core/commands/qc-design-script.md +400 -0
  27. package/core/commands/qc-design-test.md +482 -248
  28. package/core/commands/qc-plan.md +141 -94
  29. package/core/commands/qc-report.md +9 -3
  30. package/core/commands/{qc-review.md → qc-review-script.md} +172 -132
  31. package/core/commands/qc-review-testcase.md +409 -0
  32. package/core/commands/qc-run-manualtest.md +401 -0
  33. package/core/commands/{qc-run-test.md → qc-run-script.md} +200 -232
  34. package/core/commands/refine-prd.md +7 -1
  35. package/core/commands/report-bug.md +9 -3
  36. package/core/commands/review-code.md +9 -3
  37. package/core/commands/review-context.md +11 -3
  38. package/core/commands/review-tech-docs.md +11 -3
  39. package/core/commands/setup-ai-first.md +7 -1
  40. package/core/commands/validate-traces.md +27 -6
  41. package/core/modules/qc-playwright/stack-profile.yaml +1 -1
  42. package/core/rules/workflow.md +42 -2
  43. package/core/skills/qc/_shared/self-review-principles.md +2 -2
  44. package/core/skills/qc/qa-analyst/DOC_GAP.template.md +10 -2
  45. package/core/skills/qc/qa-analyst/spec-issue-reporter.md +1 -1
  46. package/core/skills/qc/qa-automation-assess/matrix.md +120 -0
  47. package/core/skills/qc/qa-designer/e2e/journey.md +1 -1
  48. package/core/skills/qc/qa-designer/exploratory/explore-to-functional.md +1 -1
  49. package/core/skills/qc/qa-designer/functional/api.md +1 -1
  50. package/core/skills/qc/qa-designer/functional/gui-feature.md +1 -1
  51. package/core/skills/qc/qa-designer/functional/gui-screen.md +1 -1
  52. package/core/skills/qc/qa-designer/integration/api.md +1 -1
  53. package/core/skills/qc/qa-designer/integration/db.md +1 -1
  54. package/core/skills/qc/qa-designer/integration/gui.md +1 -1
  55. package/core/skills/qc/qa-designer/integration/kafka.md +1 -1
  56. package/core/skills/qc/qa-designer/non-functional.md +1 -1
  57. package/core/skills/qc/qa-designer/shared/duplicate-check-procedure.md +33 -5
  58. package/core/skills/qc/qa-designer/shared/tc-metadata-format.md +34 -5
  59. package/core/skills/qc/qa-planner/test-plan.md +7 -0
  60. package/core/skills/qc/qa-reviewer/script/e2e.md +1 -1
  61. package/core/skills/qc/qa-reviewer/script/exploratory.md +1 -1
  62. package/core/skills/qc/qa-reviewer/script/functional.md +1 -1
  63. package/core/skills/qc/qa-reviewer/script/integration.md +1 -1
  64. package/core/skills/qc/qa-reviewer/script/non-functional.md +1 -1
  65. package/core/skills/qc/qa-reviewer/shared/review-file-template.md +3 -3
  66. package/core/skills/qc/qa-reviewer/test-case/e2e.md +1 -1
  67. package/core/skills/qc/qa-reviewer/test-case/functional.md +1 -1
  68. package/core/skills/qc/qa-reviewer/test-case/integration.md +1 -1
  69. package/core/skills/qc/qa-reviewer/test-case/non-functional.md +1 -1
  70. package/core/skills/qc/qa-runner/e2e.md +2 -2
  71. package/core/skills/qc/qa-runner/functional/gui-feature.md +4 -4
  72. package/core/skills/qc/qa-runner/functional/gui-screen.md +4 -4
  73. package/core/skills/qc/qa-runner/integration.md +1 -1
  74. package/core/skills/qc/qa-runner/non-functional.md +1 -1
  75. package/core/steps/context-loader.md +1 -1
  76. package/core/steps/gate.md +7 -1
  77. package/core/steps/qc-scope.md +67 -11
  78. package/core/steps/qc-stamp.md +142 -0
  79. package/core/steps/report-footer.md +19 -10
  80. package/core/templates/tech-design.template.md +3 -3
  81. package/docs/01-getting-started/quickstart.md +4 -3
  82. package/docs/02-concepts/architecture.md +14 -0
  83. package/docs/02-concepts/glossary.md +8 -0
  84. package/docs/02-concepts/overview.md +3 -2
  85. package/docs/02-concepts/pipeline-steps/04-bdd.md +1 -1
  86. package/docs/02-concepts/pipeline-steps/05-tech-docs.md +21 -5
  87. package/docs/02-concepts/pipeline-steps/06-code.md +12 -2
  88. package/docs/02-concepts/pipeline-steps/07-dev-selftest.md +1 -1
  89. package/docs/02-concepts/pipeline-steps/08-qc-automation.md +65 -16
  90. package/docs/02-concepts/pipeline-steps/10-feedback-loop.md +3 -3
  91. package/docs/02-concepts/pipeline-steps/README.md +4 -3
  92. package/docs/02-concepts/traceability.md +2 -2
  93. package/docs/03-guides/architect.md +2 -2
  94. package/docs/03-guides/developer.md +6 -3
  95. package/docs/03-guides/tester-qa.md +23 -10
  96. package/docs/04-reference/commands.md +9 -4
  97. package/docs/04-reference/trace-schema.md +5 -5
  98. package/docs/explain/07-generate-tech-docs.md +5 -3
  99. package/docs/explain/08-review-tech-docs.md +15 -3
  100. package/docs/explain/09-generate-code.md +30 -4
  101. package/docs/explain/10-review-code.md +1 -1
  102. package/docs/explain/11-map-testids.md +72 -70
  103. package/docs/explain/12-dev-gen-test.md +1 -1
  104. package/docs/explain/15-qc-analyze.md +14 -2
  105. package/docs/explain/16-qc-plan.md +5 -1
  106. package/docs/explain/17-qc-design-test.md +30 -7
  107. package/docs/explain/18-qc-review.md +43 -17
  108. package/docs/explain/19-qc-run-test.md +38 -12
  109. package/docs/explain/20-qc-report.md +8 -5
  110. package/docs/explain/23-fix-bug.md +2 -2
  111. package/docs/explain/README.md +6 -3
  112. package/docs/plans/qc-surgery/01-checklist.md +70 -17
  113. package/package.json +1 -1
@@ -1,17 +1,19 @@
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-testcase → qc-design-script → qc-review-script → qc-run-script → qc-report). Port từ qa-designer của team QC. Sinh test case Markdown (`.Test.md`) — Python đến sau ở /qc-design-script.
10
+
11
+ ## Gate
12
+
13
+ *Checkpoint: **chặn CỨNG** — ghi đè `*.Test.md` đã qua cổng HITL `/qc-review-testcase` → mất `Status` đã duyệt, `Expected Result` đã sửa theo finding, dấu 🚫 Block vừa gỡ; và ĐÁNH SỐ LẠI `TC_<FEATURE>_NNN` mà `REVIEW_<FEATURE>.md` đang trỏ vào. `--yes` KHÔNG bỏ qua được (gate Bước 3a).*
14
+
15
+ *Mức cứng chỉ áp khi file `.Test.md` **đã tồn tại**. Lần thiết kế đầu không ghi đè gì, đi thẳng. Xem §Chạy lại.*
16
+
15
17
  # Gate — Quy trình vào chuẩn cho mọi lệnh
16
18
 
17
19
  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ó.
@@ -91,7 +93,7 @@ Lưu toàn bộ context đã nạp vào bộ nhớ để dùng xuyên suốt phi
91
93
 
92
94
  | Mức | Lệnh nào | `--yes` bỏ qua được? |
93
95
  |---|---|:---:|
94
- | **Không chặn** | Lệnh read-only: `/review-code` · `/validate-traces` · `/debug` · `/review-context` · `/review-tech-docs` | — (vốn không có) |
96
+ | **Không chặn** | `/review-code` · `/validate-traces` · `/debug` **KHÔNG phải vì read-only**: cả ba đều CÓ ghi file. Chúng không chặn vì thao tác ghi của chúng hoặc nằm sau một câu hỏi `(Y/N)`, hoặc nằm sau một cờ, hoặc là `append`/dựng-lại-được | — (vốn không có) |
95
97
  | **Chặn thường** | Mọi lệnh sinh/sửa artifact | ✅ |
96
98
  | **Chặn CỨNG** | Ghi đè file đã tồn tại · `--resume` áp findings · migrate · prune | ❌ **không bao giờ** |
97
99
 
@@ -99,6 +101,12 @@ Lưu toàn bộ context đã nạp vào bộ nhớ để dùng xuyên suốt phi
99
101
  `--` khỏi phần resolve target, nên cờ này không ảnh hưởng việc tìm file.) Mở đường chạy
100
102
  headless: `claude -p "/generate-code UC1 --yes"`.
101
103
 
104
+ > **Tên lệnh trong bảng trên có máy canh — `R19`.** Mỗi hàng bảng vừa nêu một mức vừa nêu
105
+ > tên lệnh sẽ bị đối chiếu với `gate.checkpoint_levels`; lệch là build đỏ. Lý do có rule này:
106
+ > ngày 2026-09-16 hai lệnh đổi mức, schema và `commands/*.tmpl` đều sửa, **build vẫn xanh**,
107
+ > mà bảng này lẫn `rules/workflow.md` đều còn liệt chúng ở mức cũ. `R11` chỉ canh
108
+ > `commands/*.tmpl` ↔ schema — *biết có máy canh không bằng biết máy canh **đến đâu***.
109
+
102
110
  > **KHÔNG tự suy mức từ bảng này.** Mỗi lệnh **tự khai** mức của nó ở một dòng `*Checkpoint: …*`
103
111
  > ngay dưới `## Gate` của chính nó — đọc dòng đó, đừng suy diễn. Bảng trên chỉ giải thích ba mức
104
112
  > **nghĩa là gì**.
@@ -163,247 +171,473 @@ Mỗi dòng ⚠️/🔴 phải ứng với một trạng thái **context-loader
163
171
  - "N" → dừng, hỏi người dùng muốn thay đổi gì.
164
172
  - 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
173
  🔴/⚠️ (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 "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
174
+
175
+
176
+ *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.*
177
+
178
+ ## Context
171
179
  **BẮT BUỘC — đọc `.agent/steps/context-loader.md` và thực thi TOÀN BỘ quy trình trong đó**,
172
180
  rồi mới tiếp tục phần bên dưới.
173
181
 
174
182
  Bỏ qua bước này thì `{paths.*}`, `{tech_stack.*}`, `{conventions.*}`, guardrail từ
175
183
  `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
-
184
+ placeholder bên dưới sẽ rỗng và lệnh sẽ đọc/ghi sai chỗ.
185
+
186
+ ## Phạm vi QC
187
+
180
188
  **BẮT BUỘC — đọc `.agent/steps/qc-scope.md` và thực thi TOÀN BỘ quy trình trong đó**,
181
189
  rồi mới tiếp tục phần bên dưới.
182
190
 
183
191
  Nó chốt bốn thứ mà mọi trạm QC đều cần: `TICKET-ID` · `active_platform` ·
184
- `qc_artifact_dir` · `uc_list` (kèm trạng thái BDD từng UC, và cờ `--include-draft`).
192
+ `qc_artifact_dir` · `uc_list` (kèm trạng thái BDD từng UC, và cờ `--force`).
185
193
  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 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 viết-lại-cả-file đú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 đã 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 dụ *"form hiển thị đủ 3 trường"* vẫn 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, đây quyết định quy 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ớichế độ headless (quy 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 người ngồi đó thì chế độ headless không phải cổng, nó
239
- 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; đó 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 **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; **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 đẩ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` **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
- 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 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 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 có số mà không có phép tính nào
321
- đứng sau, nên nó là ước lượng, và `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 **≥ 1** TC trỏ tới.
330
-
331
- ### Xử
332
-
333
- | Kết quả | Làm |
334
- |---|---|
335
- | Mọi SC đều ≥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 **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 TC, vẫn được đếm đã phủ.
342
-
343
- Nên chỉ còn đúng hai trạng thái, cả hai đều không cho phép bỏ trống:
344
-
345
- - SC **trong** phạm vi phải 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, 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
- tồn tại, không ai phát hiện được khi chết.
354
-
355
- > **Vì sao đếm đây 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úc không còn thời gian viết 32%.
359
- >
360
- > **Vì sao "chưa 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ànviết test, so với chạy test. Nhưng nhìn vào sổ trace thì giống nhau: cả hai
362
- > đều `qc_status = not_run`. Guard này 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` 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` 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` **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), đã 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
-
194
+ cả hai đều xảy ra trong im lặng, không có bước nào phía sau bắt được.
195
+
196
+ ---
197
+
198
+ ## Stamp phiên bản nguồn
199
+
200
+ **BẮT BUỘC đọc `.agent/steps/qc-stamp.md` thực thi phần áp cho lệnh này**,
201
+ rồi mới tiếp tục phần bên dưới.
202
+
203
+ Nó có **hai vế**: §1 **ghi** khối `Nguồn & phiên bản` vào artifact lệnh này sinh ra ·
204
+ §2 **so** stamp của artifact lệnh này ĐỌC với version hiện tại của spec.
205
+ Bỏ vế ghi thì trạm sau không để so; bỏ vế so thì stamp thành một con số không ai
206
+ đọc — và một bộ TC lỗi thời sẽ chạy xanh rồi ghi `pass` **hợp lệ theo mọi phép kiểm**.
207
+
208
+ > **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
209
+ > theo UC, nên giữ khả năng làm UC1 khi UC3 chưa xong đúng. Chỉ **chỗ đọc/ghi** đổi: mọi
210
+ > artifact nằm chung ở `{qc_artifact_dir}` cấp PRD, không còn một thư mục mỗi UC.
211
+ >
212
+ > Nên `DOC_GAP.md` / `TEST_PLAN.md` đọc được đây phủ **cả PRD**: **lọc theo cột `UC`** để lấy
213
+ > 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.
214
+
215
+ ---
216
+
217
+ ## Guard — tiền đề *(chạy NGAY SAU §Phạm vi QC, TRƯỚC mọi việc khác của lệnh)*
218
+
219
+ Trạm này **trạm 3**. **tiêu thụ** output của trạm 1 trạm 2 không tự sinh lại được.
220
+ Kiểm sự tồn tại của cả ba file dưới `{qc_artifact_dir}`:
221
+
222
+ | File | Do lệnh nào sinh | Trạm này dùng để |
223
+ |---|---|---|
224
+ | `REQUIREMENT_ANALYSIS.md` | `/qc-analyze` | neo `BR-xx`/`AC-xx` cho trường `Trace:` của mỗi TC |
225
+ | `DOC_GAP.md` | `/qc-analyze` | gắn `🚫 Block` cho TC bị gap chặn |
226
+ | `TEST_PLAN.md` | `/qc-plan` | độ sâu theo rủi ro (P0/P1) · layer trong scope · entry criteria |
227
+
228
+ **Thiếu bất kỳ file nào DỪNG.** Không hỏi Y/N, không cảnh báo rồi đi tiếp:
229
+
230
+ ```
231
+ {TICKET-ID} ({active_platform}): thiếu đầu vào của trạm 3.
232
+ Không có: {danh sách file thiếu}
233
+ Chạy trước: /qc-analyze {TICKET-ID} {active_platform}
234
+ rồi: /qc-plan {TICKET-ID} {active_platform}
235
+ ```
236
+
237
+ **Đủ cả ba im lặng, đi tiếp.** Guard này là **điều kiện vào cửa**, không phải phép đo: "đủ file"
238
+ không phải một phát hiện, nên không in dòng nào. *(Khác `Guard BR-tag` và `Guard SC coverage` —
239
+ hai cái đó phép ĐẾM, con số của chúng giá trị ngay cả khi sạch, nên chúng luôn in.)*
240
+
241
+ > **Vì sao DỪNG chứ không cảnh báo rồi chạy.** Guard duy nhất còn lại của trạm này —
242
+ > `Guard SC coverage` đối chiếu TC với `.feature`, mà `.feature` spec repo thì **luôn mặt**,
243
+ > không mất theo khi trạm 1–2 chưa chạy. Nên nó vẫn in `khớp K/K` lần chạy thiếu đầu vào
244
+ > **không phân biệt được** với một lần chạy đúng.
245
+ >
246
+ > Thiếu `DOC_GAP.md` ca đắt nhất: TC bị gap chặn **trông y hệt** TC bình thường (không dấu
247
+ > `🚫 Block` nào), nên `/qc-run-script` chạy nó, thấy đỏ, và `/qc-report` phân loại thành *product-gap*
248
+ > rồi in một `/report-bug` sẵn-chạy. Lỗi đi **ra khỏi đội QC** thành bug gửi PO — cho một hành vi
249
+ > spec chưa bao giờ định nghĩa. Đây **báo cáo sai**, cùng loại với việc giữ một `pass` đã hết
250
+ > hiệu lực (`rules/workflow.md` §*"Làm mất hiệu lực ghi đè"*).
251
+ >
252
+ > **Vì sao DỪNG chứ không hỏi Y/N.** Một cổng chỉ chặn được khi có người ngồi đó thì ở chế độ
253
+ > headless không phải cổng, **treo**cùng bài học với `steps/qc-scope.md` §2.
254
+ >
255
+ > **Vì sao `--yes` KHÔNG bỏ qua được.** `--yes` nghĩa *"tôi không ngồi đây để trả lời"*, không phải
256
+ > *"tôi chấp nhận thiết kế test không có phân tích gap"*. Gộp hai nghĩa là để một lần chạy headless
257
+ > âm thầm sinh ra một bộ test case thiếu mọi dấu chặn.
258
+ >
259
+ > **Vì sao cả ba file đều bắt buộc, không miễn trừ `TEST_PLAN.md`.** Không có luồng hợp lệ nào có
260
+ > file 1–2 thiếu file 3: `Next` của `/qc-analyze` chính là `/qc-plan`. Miễn trừ nó là mở một
261
+ > đường tắt không ai xin.
262
+ >
263
+ > **Chỉ kiểm SỰ TỒN TẠI, không kiểm nội dung hay độ tươi.** Ba file này có còn khớp spec hiện tại
264
+ > không là một câu hỏi khác, và nó cần một cơ chế khác (đóng dấu version nguồn). Trộn hai việc vào
265
+ > một guard thì guard này không bao giờ landing được.
266
+
267
+ ---
268
+
269
+ ## Cờ — chọn tầng nào, tách mịn đến đâu
270
+
271
+ | Cờ | Ghi file | Nhóm |
272
+ |---|---|---|
273
+ | *(không cờ)* | `TC_<FEATURE>.Test.md` | 1 GUI · 2 Validation · 3 Functional · 4 Integration (qua UI) · 5 NFR · 6 E2E |
274
+ | `--api` | `TC_<FEATURE>_API.Test.md` | 1 Endpoint · 2 Integration API/DB/Kafka |
275
+ | `--all` | **cả hai file** | như trên, hai file riêng biệt |
276
+ | `--atomic-max` | — | bật chế độ tách tối đa (xem dưới) |
277
+
278
+ **Câu hỏi phân file:** *"TC này verify được mà **không cần UI** không?"* → **có** = file API ·
279
+ **không** = file giao diện. Đây đúng là Bước 2 của
280
+ `{paths.qc_skills_dir}/qa-designer/shared/skill-decision-tree.md`.
281
+
282
+ > **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
283
+ > 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ữ
284
+ > nguyên** năm nhóm kia — mà viết-lại-cả-file đúng thứ agent hay làm. Hai file thì hai chế
285
+ > độ **không bao giờ chạm nhau**, không cần tin vào việc agent nhớ giữ phần cũ.
286
+
287
+ ### `--atomic-max` tách tới mức nhỏ nhất
288
+
289
+ Mặc định đã ATOMIC (mỗi TC đúng một kết cục), nhưng **cho phép** bullet compound khi cả hai
290
+ 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.
291
+
292
+ `--atomic-max` **nổ danh sách completeness thành từng thành phần**: thẻ hiển thị đủ 5 thông tin
293
+ → **5 TC**. Ở đội QC, một tính năng đi từ 82 lên 130 TC.
294
+
295
+ **⛔ Thủ tục bắt buộc TRƯỚC khi ghi** *(theo `shared/precision-rules.md` §2.1)*:
296
+
297
+ 1. **Chốt ranh giới completeness** hai mức quy rất khác nhau:
298
+ | Giá trị | Nghĩa |
299
+ |---|---|
300
+ | `--atomic-max=keep-completeness` | giữ danh sách completeness 1 TC (~95 TC ví dụ thật) |
301
+ | `--atomic-max=explode` | nổ từng thành phần (~130 TC) |
302
+
303
+ 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
304
+ phải của agent. **Có `--yes`:** không hỏi **DỪNG** với lỗi rõ ràng:
305
+ ```
306
+ --atomic-max cần nêu ranh giới ở chế độ headless (quy chênh ~35%).
307
+ Chạy: /qc-design-test {UC-ID} --atomic-max=explode --yes
308
+ hoặc --atomic-max=keep-completeness --yes
309
+ ```
310
+ *(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ó
311
+ là treo — cùng bài học với `steps/qc-scope.md` §2.)*
312
+
313
+ 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
314
+ trong lúc ghi; đó là chỗ sinh ra TC cắt cụt.
315
+ 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-
316
+ khái-niệm* · *ngưỡng* (`≥44pt`, `WCAG AA ≥4.5:1`) · *exclusivity* chọn-một · *qualifier*.
317
+ 4. **Verify bằng DIFF, không tin lời đếm của agent** — ID liên tục · mỗi Expected 1 bullet ·
318
+ 0 ký tự `|` · Trace matrix không còn ID chết · P0/P1 khớp tag.
319
+
320
+ ---
321
+
322
+ ## Role
323
+
324
+ Bạn là **QC Designer** — stage 3. Sinh/bảo trì các file test-case Markdown
325
+ (`.Test.md`) từ requirement đã phân tích + plan. Output feed vào qc-design-script (Python)
326
+ qc-review. Bạn **không** viết Python.
327
+
328
+ ## Skills (`{paths.qc_skills_dir}/qa-designer/`)
329
+
330
+ **Luật dùng chung — nạp theo tầng, không nạp hết mỗi lần:**
331
+
332
+ | Khi nào | Nạp |
333
+ |---|---|
334
+ | **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` |
335
+ | **Luôn** | `shared/precision-rules.md` cấm 9 cụm hồ · đơn vị theo domain · toán tử · BVA 3-hay-4 giá trị · phủ đủ ô decision table · 3 mức teardown |
336
+ | **Luôn** | `shared/action-keywords-glossary.md` một hành động một từ (Click/Tap/Enter/Select…) |
337
+ | **Luôn** | `shared/implicit-scenarios.md` 7 tình huống bắt buộc nghĩ tới, áp cho mọi nhóm |
338
+ | Đầ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 |
339
+ | 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 |
340
+ | 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 |
341
+ | `--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` |
342
+
343
+ **Rồi chọn tầng, nạp MỘT file:**
344
+
345
+ | Layer | File | Ghi vào |
346
+ |---|---|---|
347
+ | 1 màn hình | `functional/gui-screen.md` | file giao diện |
348
+ | Feature đa-màn | `functional/gui-feature.md` | file giao diện |
349
+ | API endpoint | `functional/api.md` | **file API** |
350
+ | Integration qua UI | `integration/gui.md` | file giao diện |
351
+ | Integration API/DB/Kafka | `integration/{api,db,kafka}.md` | **file API** |
352
+ | End-to-end journey | `e2e/journey.md` | file giao diện |
353
+ | Non-functional | `non-functional.md` | theo câu hỏi phân file |
354
+ | Exploratory | `exploratory/{charter,explore-to-functional}.md` | file giao diện |
355
+
356
+ ## Conventions
357
+
358
+ *Khuôn TC, luật ATOMIC, phân nhóm, không-dùng-bảng, Trace, `🚫 Block`, `Test-ID attribute`
359
+ đều ở `shared/tc-metadata-format.md`. **Đừng lặp lại ở đây.** Dưới đây chỉ là thứ riêng của trạm:*
360
+
361
+ - Markdown-first; đừng design khi requirement chưa đẩy ngược về qc-plan/qc-analyze.
362
+ - **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-design-script 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-design-script sẽ fallback về locator role/text chậm hơn).
363
+ - **Đọc `@trace.testid_attr` từ header tech-doc gộp** (do `/map-testids` ghi) ghi lại một
364
+ dòng `Test-ID attribute: {attr}` metadata đầu file `.Test.md`. §4.5.6 chỉ cho **giá trị**
365
+ test-id; `@trace.testid_attr` **tên thuộc tính** chứa chúng`/qc-design-script` cần để cấu
366
+ hình locator, `/qc-review-script` cần để biết selector trong script đúng contract không.
367
+ Không đọc được `@trace.testid_attr` → ghi
368
+ `Test-ID attribute: (thiếu @trace.testid_attr, chạy /map-testids)`; **đừng bỏ trống, đừng
369
+ tự đoán theo platform**đoán sai thì mọi locator trượt 100%, một do không liên quan
370
+ tới thứ đang được test. *(Dạng dòng metadata: `shared/tc-metadata-format.md`.)*
371
+ - Một TC bị block bởi gap **vẫn viết đủ** + `🚫 Block: [GAP-UC{N}-{nnn}](../DOC_GAP.md) — lý do`.
372
+ Chặn theo **UC ở cột `UC` của hàng gap đó**, không chặn cả PRD.
373
+
374
+ ## Trace mapping (bắt buộc)
375
+
376
+ Ngoài trace `BR-xx`, mỗi TC ghi scenario framework verify **dạng trường danh sách,
377
+ không phải cột bảng** (file TC không ký tự `|`):
378
+
379
+ ```
380
+ - **@trace.verifies:** {UC-ID}-SC{N}
381
+ ```
382
+
383
+ Lấy từ `@trace.scenario` của `.feature`. Một SC map được nhiều TC. Đây là **join key** cho phép
384
+ qc-run-script ghi `qc_status` theo từng scenario vào sổ trace — **thiếu nó thì test vẫn chạy,
385
+ 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.**
386
+
387
+ Cuối file: **Trace matrix** (BR ↔ TC ↔ SC) + **danh sách TC bị block** — cả hai dạng danh sách.
388
+
389
+ ## Guard SC coverage *(phép ĐẾM học, chạy SAU khi ghi file, TRƯỚC CHECKPOINT)*
390
+
391
+ Dòng `Trace:` report cuối đã đòi con số `{K}/{total}` từ trước. Guard này **phép tính sinh
392
+ ra con số đó**, và là **hệ quả khi `K < total`** — trước đây có số mà không có phép tính nào
393
+ đứng sau, nên ước lượng, `K < total` không dẫn tới việc gì.
394
+
395
+ ### Ba bước
396
+
397
+ 1. **Thu SC** mọi `@trace.scenario` trong `.feature` của **UC đang chạy** (`{UC-ID}`, đúng
398
+ `active_platform`) — **KHÔNG phải `uc_list`.**
399
+
400
+ `uc_list` là mọi UC approved của **cả PRD**; trạm này chạy **per-UC** (§Phạm vi QC), nên lấy
401
+ `uc_list` làm mẫu số là ép viết TC cho UC **không ai yêu cầu** — bằng dữ liệu đã lọc bỏ phần
402
+ của chúng ở Gate (*"lọc theo cột `UC` để lấy phần của UC này"*), tức là **viết mù**.
403
+
404
+ *Trạm 1 `/qc-analyze` dùng `uc_list` là **đúng** — nó cấp PRD, và mâu thuẫn chéo UC chỉ lộ ra
405
+ khi đọc cùng lúc. **Đừng copy mẫu số giữa hai trạm khác tầng.** Phép kiểm một câu: mẫu số của
406
+ một guard phải khớp **phạm vi mà lệnh đó được gọi**.*
407
+
408
+ **Không cần lọc `⏸ chưa xét` ở đây nữa:** `qc-scope` §4b đã chặn UC target chưa approved từ
409
+ trước khi tới bước này. Chạy được tới đây nghĩa là target hoặc đã `approved`, hoặc người dùng
410
+ đã nêu `--force` tường minh (và artifact đang mang dấu *"dựa trên BDD nháp"*).
411
+ 2. **Thu V** — mọi `@trace.verifies` trong **MỌI** `*.Test.md` dưới `{qc_artifact_dir}test-cases/`
412
+ — **không chỉ file vừa ghi** — rồi lọc lấy giá trị bắt đầu bằng `{UC-ID}-SC`.
413
+
414
+ *Thư mục `test-cases/` dùng chung cho **cả PRD** nên phải lọc theo UC. Và lệnh này có **ba chế
415
+ độ ghi** (§Cờ), hai trong ba chỉ ghi **một nửa**: `--api` chạy sau khi file giao diện đã tồn tại
416
+ là **chuyện thường** (§B12 ngay dưới bảng Cờ). Chỉ đếm file "vừa ghi" thì lần chạy `--api` thấy
417
+ độ phủ ≈ 0, rồi luật "viết bù TC ngay" **nhân bản toàn bộ TC giao diện vào file API** — phá đúng
418
+ ranh giới hai-file mà §B12 dựng lên.*
419
+
420
+ **Tiền tố lọc phải gồm cả `-SC`.** `{UC-ID}-SC` chứ **không** phải `{UC-ID}`:
421
+ `FT-001-UC1-SC` không khớp `FT-001-UC11-SC3`, còn `FT-001-UC1` thì có. Một PRD tới UC thứ 11
422
+ là đủ để phép lọc sai **trong im lặng**.
423
+
424
+ *Quét cả thư mục chứ không đoán theo tên file: tên file mang `<FEATURE>`, **không** mang UC —
425
+ `@trace.verifies` là chỗ duy nhất nói TC thuộc UC nào (§Output).*
426
+ 3. **Đếm** — mỗi SC ∈ tập ở bước 1 phải có **≥ 1** TC trỏ tới.
427
+
428
+ ### Xử lý
429
+
430
+ | Kết quả | Làm gì |
431
+ |---|---|
432
+ | Mọi SC đều có ≥1 TC | in `Guard SC coverage: khớp {K}/{K}` |
433
+ | Có SC chưa phủ | **viết bù TC ngay** — vào file đúng theo §Câu hỏi phân file, xem luật định tuyến dưới đây — rồi in `⚠️ Guard SC coverage: {K}/{total} — đã bù {m} SC: {danh sách}` |
434
+
435
+ **TC bù đi vào file nào — theo §Câu hỏi phân file, KHÔNG mặc định vào file của chế độ đang chạy.**
436
+
437
+ Hỏi từng SC chưa phủ: *"TC cho SC này verify được mà **không cần UI** không?"*
438
+
439
+ | Trả lời | Chế độ đang chạy | Làm gì |
440
+ |---|---|---|
441
+ | **Có** (không cần UI) | `--api` hoặc `--all` | bù vào **file API** — xong |
442
+ | **Không** (cần UI) | *(không cờ)* hoặc `--all` | bù vào **file giao diện** — xong |
443
+ | **Không** (cần UI) | **`--api`** | **KHÔNG bù vào file API.** SC vẫn tính là **chưa phủ**; in thêm dòng chỉ đường (dưới) |
444
+
445
+ ```
446
+ ⚠️ {n} SC cần UI, chưa có TC ở file giao diện — KHÔNG bù vào file API (§Câu hỏi phân file).
447
+ Chạy: /qc-design-test {UC-ID} ← không cờ, để bù đúng chỗ
448
+ SC: {danh sách}
449
+ ```
450
+
451
+ > **Đây KHÔNG phải "bỏ trống có lý do".** Phân biệt bằng một câu: đường thoát làm SC **được tính là
452
+ > đã phủ**; luật này để SC **vẫn nằm trong số chưa phủ** — `{K}/{total}` không đổi, dòng ⚠️ vẫn in,
453
+ > guard vẫn chưa sạch. Nó chỉ đổi **chỗ ghi TC**, không đổi **phép đếm**.
454
+ >
455
+ > Và nó **không** cho agent một câu-lý-do-cho-qua: điều kiện là một phép so cơ học (*"cần UI không?"*
456
+ > — đúng Bước 2 của `shared/skill-decision-tree.md`), không phải một nhận định tự do. Ghi *"SC này
457
+ > phức tạp"*, *"tương tự SC2"* thì **không** rơi vào ô nào của bảng trên.
458
+ >
459
+ > **Vì sao không cứ bù vào file đang mở cho gọn.** File API được `/qc-design-script` đọc bằng lane API.
460
+ > Một TC cần UI nằm ở đó **không chạy nổi** — và nó sẽ đỏ dưới dạng *script-bug* hoặc *product-gap*,
461
+ > không ai truy ngược về một quyết định ghi file ở phase thiết kế.
462
+
463
+ **Không có đường "bỏ trống có lý do".** Đây là chỗ dễ làm sai nhất, và quy ước ở §Conventions
464
+ đã trả lời: *"Một TC bị block bởi gap **vẫn viết đủ** + `🚫 Block: [GAP-UC{N}-{nnn}](../DOC_GAP.md)`"*.
465
+ 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
466
+ vẫn có TC, vẫn được đếm là đã phủ.
467
+
468
+ Nên chỉ còn đúng hai trạng thái, và cả hai đều không cho phép bỏ trống:
469
+
470
+ - SC **của UC đang chạy** → phải có TC (bị gap chặn thì TC mang dấu `🚫 Block`). Đây là **toàn bộ**
471
+ mẫu số — bước 1 không lấy SC của UC nào khác.
472
+ - SC **của UC khác** → không thuộc lần chạy này, không tính vào mẫu số. *(Muốn phủ chúng thì chạy
473
+ `/qc-design-test` cho UC đó — đừng viết TC của chúng ở đây.)*
474
+
475
+ 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** —
476
+ 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
477
+ 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.
478
+
479
+ *(Ca "SC cần UI mà đang chạy `--api`" **không** phải trạng thái thứ ba: SC đó vẫn ở nhóm thứ nhất,
480
+ vẫn tính vào mẫu số, vẫn chưa phủ. Chỉ **chỗ ghi TC** bị hoãn — xem luật định tuyến ở §Xử lý.)*
481
+
482
+ **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
483
+ nó tồn tại, và không ai phát hiện được khi nó chết.
484
+
485
+ > **Vì sao đếm ở đây mà không đợi báo cáo cuối.** `/qc-report` **sẽ** tính **design coverage** bằng
486
+ > **đúng phép đếm này** — **Đợt 3, CHƯA TRIỂN KHAI** (`docs/plans/qc-surgery/01-checklist.md`, phụ
487
+ > thuộc cứng vào `d2-b2`). Khác nhau ở thời điểm: guard đếm **lúc thiết kế** — còn kịp viết bù; báo
488
+ > cáo đếm **ở cuối** — chỉ còn kịp báo. Phát hiện "design coverage 68%" ở phút cuối là lúc không còn
489
+ > thời gian viết bù 32%.
490
+ >
491
+ > **Tới khi Đợt 3 có, guard này là lớp đếm DUY NHẤT — đừng dựa vào một lớp thứ hai chưa tồn tại.**
492
+ > *(Bản trước viết "sẽ tính" ở thì hiện tại, không nêu trạng thái. Một lời hứa không có trạng thái sẽ
493
+ > được đọc như một sự thật — và tệ nhất là khi nó được dùng để biện minh cho việc không đếm ở chỗ khác.)*
494
+ >
495
+ > **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
496
+ > 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
497
+ > đều là `qc_status = not_run`. Guard này là chỗ duy nhất phân biệt được.
498
+
499
+ ## Chạy lại — `.Test.md` đã tồn tại *(GIỮ, không sinh lại từ trắng)*
500
+
501
+ File `.Test.md` đi qua **cổng HITL `/qc-review-testcase`**. Bốn thứ trong đó **không sinh lại được**:
502
+
503
+ | Mất gì | Vì sao |
504
+ |---|---|
505
+ | `Status` đổi sau review | **kết luận của người**, không suy từ spec |
506
+ | `Expected Result` sửa theo finding | finding nằm ở `REVIEW_<FEATURE>.md` — **lệnh này không đọc file đó** |
507
+ | Dấu `🚫 Block` vừa gỡ | phụ thuộc ô `Câu trả lời` của `DOC_GAP.md` (xem §Guard vòng đời 🚫 Block) |
508
+ | Đánh số `TC_<FEATURE>_NNN` | **`REVIEW_<FEATURE>.md` đang trỏ vào các số đó**, và nó nằm cùng thư mục |
509
+
510
+ **Đọc file cũ TRƯỚC khi ghi.** Với mỗi TC:
511
+
512
+ | Tình huống | Xử lý |
513
+ |---|---|
514
+ | TC cũ, spec **không đổi** | **Giữ nguyên** — số · `Status` · `Expected Result` đã sửa |
515
+ | TC cũ, spec **đổi** | Cập nhật **nội dung**; giữ **số** và `Status`, thêm ⚠️ để `/qc-review-testcase` soát lại |
516
+ | TC **mới** | Cấp số **tiếp theo** — không dồn, không tái dùng |
517
+ | TC không còn ứng với SC nào | **KHÔNG xoá** → `Status: Obsolete`, ghi lý do |
518
+
519
+ > **`Obsolete` chứ không xoá** — cùng luật `Stale` của `/qc-analyze` §Chạy lại: `REVIEW_<FEATURE>.md`
520
+ > đang trỏ vào số đó, xoá hàng là biến biên bản soát thành liên kết chết.
521
+ >
522
+ > **§duplicate-check KHÔNG phải lưới cho ca này.** Nó chống **viết trùng** (*"grep trước khi viết"*),
523
+ > không chống **ghi đè**. Hai chuyện khác nhau, và tên nghe giống nhau đủ để tưởng đã có người lo.
524
+
525
+ ### Lập lại từ trắng — phải nói ra
526
+
527
+ ```
528
+ ❌ {n} file .Test.md đã tồn tại ({k} TC có Status khác Draft — đã qua review).
529
+ Mặc định: GIỮ số + Status + Expected đã sửa, chỉ cập nhật nội dung theo spec mới.
530
+ Muốn bỏ hẳn và thiết kế lại từ trắng: thêm --force
531
+ ⚠️ --force sẽ XOÁ {k} kết quả review và làm REVIEW_<FEATURE>.md trỏ sai số TC.
532
+ ```
533
+
534
+ ---
535
+
536
+ ## Guard — vòng đời `🚫 Block` *(phép so cơ học, chạy SAU khi ghi file, TRƯỚC CHECKPOINT)*
537
+
538
+ `🚫 Block` có bước **mở** (§Conventions: TC chạm gap thì mang dấu) và một quy trình **đóng** viết
539
+ sẵn ở `{paths.qc_skills_dir}/qa-designer/shared/tc-metadata-format.md` §*Quy trình khi gap được giải
540
+ quyết (Answered)*. Bước đóng đó **chưa lệnh nào gọi** — nên dấu chặn chỉ dán vào, không bao giờ gỡ ra.
541
+
542
+ Một TC mang `🚫 Block` là một TC có **Expected Result dựa trên giả định chưa ai xác nhận**. Nó nằm
543
+ trong thư mục dùng chung, được `Guard SC coverage` đếm là **đã phủ**, và `/qc-design-script` sẽ nhặt lên.
544
+
545
+ > **Vì sao stamp phiên bản (§Stamp phiên bản nguồn) KHÔNG phủ ca này.** Gap chuyển `Open → Answered`
546
+ > là một **ô trong `DOC_GAP.md`** — nó **không bump version nào cả**. Mọi drift detector của framework
547
+ > so **nhãn version**; thay đổi trạng thái nằm *bên trong* một tài liệu đi vòng qua tất cả.
548
+
549
+ ### Bốn bước, thuần so chuỗi
550
+
551
+ 1. **Thu B** — mọi `GAP-ID` xuất hiện trong dòng `🚫 Block:` của các `*.Test.md` của UC này.
552
+ 2. **Thu A** — mọi `GAP-ID` có `Trạng thái = Answered` trong `DOC_GAP.md` (lọc theo cột `UC`).
553
+ 3. **So** — `A ∩ B` = gap đã trả lời mà TC vẫn mang dấu chặn.
554
+ 4. **Xử lý:**
555
+
556
+ | Kết quả | Làm gì |
557
+ |---|---|
558
+ | `A ∩ B` rỗng | in `Guard 🚫 Block: {n} dấu đang mở, 0 cần gỡ` |
559
+ | Có phần tử, **ô `Câu trả lời` ĐÃ điền** | **GỠ NGAY** theo 4 bước của skill: bỏ dấu `🚫 Block` · cập nhật `Expected Result` theo câu trả lời thật · nếu answer đổi scope thì tách/thêm TC. Rồi in:<br/>`🔓 Guard 🚫 Block: gỡ {k} dấu (gap đã Answered): {danh sách GAP-ID}` |
560
+ | Có phần tử, **ô `Câu trả lời` TRỐNG** | **KHÔNG gỡ.** In:<br/>`⚠️ Guard 🚫 Block: {GAP-ID} = Answered nhưng ô "Câu trả lời" trống — không gỡ được, hỏi lại PO.` |
561
+
562
+ **Guard TỰ SỬA, không chỉ tự báo** — cùng luật `Guard BR-tag` của `/qc-analyze` đã chốt: *"In cảnh
563
+ báo rồi để người đi lấp là thêm một dòng nữa để bỏ qua."*
564
+
565
+ **Nhưng ô `Câu trả lời` trống thì DỪNG, không đoán.** Gỡ dấu chặn mà không có câu trả lời thật là
566
+ **biến một giả định thành sự thật trong im lặng** — và `Expected Result` sai sẽ fail lúc chạy, rồi
567
+ `/qc-report` phân loại *product-gap* và in một `/report-bug`. Lúc đó một lỗ hổng **tài liệu** đã đi ra
568
+ ngoài đội QC thành **phiếu lỗi sản phẩm** gửi PO.
569
+
570
+ **In dòng `Guard 🚫 Block:` kể cả khi sạch** — cùng lý do với hai guard kia.
571
+
572
+ ---
573
+
574
+ ## UC đang Blocked — nói ra, KHÔNG chặn
575
+
576
+ `TEST_PLAN.md` §5 ghi `Ready`/`Blocked` cho mỗi UC (guard tiền đề đã ép file này phải có). Đọc trạng
577
+ thái của UC đang chạy. **`Blocked` là mức ƯU TIÊN, không phải lệnh cấm** — vẫn thiết kế bình thường,
578
+ chỉ in thêm một dòng ở report:
579
+
580
+ ```
581
+ ⚠️ {UC-ID} đang Blocked bởi {n} gap 🔴 — TC chạm chúng mang dấu 🚫 Block, chưa chạy được.
582
+ Gỡ chặn: trả lời {danh sách GAP-ID} với PO → chạy lại lệnh này để tự gỡ dấu.
583
+ ```
584
+
585
+ > **Vì sao không chặn** *(G66)*. `/qc-analyze` nói *"chưa sẵn sàng"* nghĩa là **chưa sẵn sàng để
586
+ > nghiệm thu**, không phải *"chưa được thiết kế"* — và §Conventions của chính lệnh này đã trả lời:
587
+ > *"TC bị block bởi gap **vẫn viết đủ**"*. Chặn ở đây là giết cơ chế `🚫 Block`, thứ được dựng kỹ tới
588
+ > mức có mã gap mang UC, liên kết ngược `../DOC_GAP.md`, và một dòng riêng trong report.
589
+ >
590
+ > Cái bị chặn là **chạy test**, không phải **viết test**.
591
+
592
+ ## Output
593
+
594
+ Ghi dưới `{qc_artifact_dir}test-cases/`
595
+ (= `{paths.qc_dir}/{TICKET-ID}/{active_platform}/test-cases/`) — **một thư mục dùng chung cho cả
596
+ PRD**. Tên file mang `<FEATURE>` nên các UC không đâm nhau; `@trace.verifies` là chỗ phân biệt
597
+ TC thuộc UC nào.
598
+
599
+ **Đuôi bắt buộc là `.Test.md`.** `/qc-design-script` và `/qc-review-testcase` tìm `*.Test.md`; ghi ra
600
+ `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**.
601
+
602
+ `🚫 Block` trỏ `../DOC_GAP.md` — lên một cấp, vì file gap ở thư mục cha của `test-cases/`.
603
+
604
+ ## Self-Review *(trước khi in Report)*
605
+
606
+ Theo 3 nhóm ở `{paths.qc_skills_dir}/_shared/self-review-principles.md` — **không chép lại ở đây**.
607
+
608
+ - **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
609
+ `Expected` là **giá trị cụ thể** (*"hiển thị text 'Tên lớp: Toán 6A'"*), không phải *"hiển thị
610
+ đúng"* / *"hoạt động bình thường"*?
611
+ - **Nhảy bước:** đã áp luật ATOMIC cho **mọi** TC (không chỉ vài TC đầu), và đã chạy
612
+ §duplicate-check trước khi thêm TC mới?
613
+ - **Số liệu:** `{n}` TC mỗi file = `grep -cE "^#{2,4} *TC_"` thật? Các con số phân nhóm (GUI /
614
+ Validation / Functional / …) cộng lại đúng bằng tổng TC?
615
+
616
+ > Guard SC coverage ở trên là **phép đếm cơ học**, không phải self-review — nó đối chiếu với
617
+ > `.feature`, một nguồn khác. Đừng coi self-review đã bao nó. Xem §Ranh giới trong file skill.
618
+
619
+ ## Report
620
+
393
621
  **Đọc `.agent/steps/report-footer.md`** và áp đúng khuôn footer trong đó (Status Badge ·
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} — đã bù {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
- ```
622
+ Output Artifacts · Next) cho report cuối, kèm khối bên dưới.
623
+
624
+ ```
625
+ /qc-design-test Hoàn tất — {UC-ID} ({active_platform})
626
+ Chế độ: {giao diện | API | cả hai}{nếu --atomic-max: " · tách tối đa ({explode|keep-completeness})"}
627
+ Files: {qc_artifact_dir}test-cases/TC_<FEATURE>.Test.md — {n} TC
628
+ {nếu có: "…_API.Test.md — {m} TC"}
629
+ Nhóm : GUI {a} · Validation {b} · Functional {c} · Integration {d} · NFR {e} · E2E {f}
630
+ {nếu file API: "Endpoint {g} · Integration API/DB/Kafka {h}"}
631
+ 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)
632
+ Block: {blocked} TC bị chặn bởi gap — theo UC: {UC1: n · UC2: n}
633
+ {nếu UC đang Blocked: "⚠️ {UC-ID} đang Blocked bởi {n} gap 🔴 — trả lời {GAP-ID} với PO
634
+ rồi chạy lại lệnh này để tự gỡ dấu."}
635
+ Guard 🚫 Block: {{n} dấu đang mở, 0 cần gỡ | 🔓 gỡ {k} dấu (gap đã Answered): {danh sách}
636
+ | ⚠️ {GAP-ID} = Answered nhưng ô "Câu trả lời" trống — không gỡ được, hỏi lại PO}
637
+ Trace: {N} TC map tới {K}/{total} scenario của {UC-ID}
638
+ Guard SC coverage: {khớp {K}/{K} | ⚠️ {K}/{total} — đã bù {m} SC: {danh sách SC}}
639
+ {nếu có SC cần UI mà đang chạy --api: "⚠️ {n} SC cần UI chưa có TC ở file giao diện —
640
+ KHÔNG bù vào file API. Chạy: /qc-design-test {UC-ID} (không cờ). SC: {danh sách}"}
641
+ Self-review: {✅ sạch | ⚠️ {n} điểm cần chú ý — liệt kê}
642
+ Next : /qc-review-testcase {UC-ID} ← review test case trước khi sinh script
643
+ ```