@educa-corp/sdd-framework 0.9.4 → 0.9.6

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 (104) hide show
  1. package/bin/build.js +11 -1
  2. package/bin/lint-trace.js +599 -2
  3. package/bin/self-check.js +195 -0
  4. package/bin/trace-schema.json +2656 -1927
  5. package/core/FRAMEWORK_VERSION +1 -1
  6. package/core/commands/dev-gen-test.md +62 -0
  7. package/core/commands/generate-bdd.md +1 -0
  8. package/core/commands/generate-code.md +39 -2
  9. package/core/commands/generate-tech-docs.md +24 -5
  10. package/core/commands/map-testids.md +164 -7
  11. package/core/commands/qc-analyze.md +163 -9
  12. package/core/commands/qc-design-test.md +294 -2
  13. package/core/commands/qc-plan.md +57 -3
  14. package/core/commands/qc-report.md +76 -60
  15. package/core/commands/qc-review.md +102 -1
  16. package/core/commands/qc-run-test.md +194 -5
  17. package/core/commands/review-tech-docs.md +20 -0
  18. package/core/commands/validate-traces.md +17 -2
  19. package/core/modules/qc-playwright/stack-profile.yaml +1 -1
  20. package/core/rules/data-protection.md +52 -0
  21. package/core/rules/workflow.md +40 -0
  22. package/core/skills/qc/_shared/self-review-principles.md +112 -0
  23. package/core/skills/qc/qa-analyst/DOC_GAP.template.md +9 -1
  24. package/core/skills/qc/qa-designer/shared/duplicate-check-procedure.md +33 -5
  25. package/core/skills/qc/qa-designer/shared/tc-metadata-format.md +24 -0
  26. package/core/skills/qc/qa-planner/test-plan.md +7 -0
  27. package/core/skills/qc/qa-runner/e2e.md +2 -2
  28. package/core/skills/qc/qa-runner/functional/gui-feature.md +9 -3
  29. package/core/skills/qc/qa-runner/functional/gui-screen.md +9 -3
  30. package/core/skills/qc/qa-runner/integration.md +1 -1
  31. package/core/skills/qc/qa-runner/non-functional.md +1 -1
  32. package/core/skills/spec/SKILL.md +1 -1
  33. package/core/steps/context-loader.md +7 -2
  34. package/core/steps/gap-verify.md +67 -0
  35. package/core/steps/qc-scope.md +67 -11
  36. package/core/steps/qc-stamp.md +142 -0
  37. package/core/steps/report-footer.md +15 -7
  38. package/core/templates/feature.template +1 -0
  39. package/core/templates/tech-design.template.md +4 -3
  40. package/docs/01-getting-started/quickstart.md +4 -3
  41. package/docs/02-concepts/architecture.md +14 -0
  42. package/docs/02-concepts/glossary.md +8 -0
  43. package/docs/02-concepts/overview.md +3 -2
  44. package/docs/02-concepts/pipeline-steps/04-bdd.md +1 -1
  45. package/docs/02-concepts/pipeline-steps/05-tech-docs.md +21 -5
  46. package/docs/02-concepts/pipeline-steps/06-code.md +12 -2
  47. package/docs/02-concepts/pipeline-steps/08-qc-automation.md +60 -12
  48. package/docs/02-concepts/pipeline-steps/README.md +4 -3
  49. package/docs/02-concepts/traceability.md +2 -2
  50. package/docs/03-guides/architect.md +2 -2
  51. package/docs/03-guides/developer.md +5 -2
  52. package/docs/03-guides/tester-qa.md +17 -5
  53. package/docs/04-reference/commands.md +7 -4
  54. package/docs/04-reference/trace-schema.md +38 -0
  55. package/docs/explain/07-generate-tech-docs.md +5 -3
  56. package/docs/explain/08-review-tech-docs.md +15 -3
  57. package/docs/explain/09-generate-code.md +30 -4
  58. package/docs/explain/10-review-code.md +1 -1
  59. package/docs/explain/11-map-testids.md +10 -7
  60. package/docs/explain/12-dev-gen-test.md +1 -1
  61. package/docs/explain/15-qc-analyze.md +14 -2
  62. package/docs/explain/16-qc-plan.md +5 -1
  63. package/docs/explain/17-qc-design-test.md +26 -3
  64. package/docs/explain/18-qc-review.md +6 -2
  65. package/docs/explain/19-qc-run-test.md +29 -6
  66. package/docs/explain/20-qc-report.md +5 -2
  67. package/docs/explain/README.md +4 -1
  68. package/docs/plans/qc-surgery/00-nhat-ky.md +497 -0
  69. package/docs/plans/qc-surgery/01-checklist.md +92 -0
  70. package/docs/plans/qc-surgery/02-lo-trinh.md +266 -0
  71. package/docs/plans/qc-surgery/buoc/0-01-testid-attr-co-cho-o.md +157 -0
  72. package/docs/plans/qc-surgery/buoc/0-02-mot-nguon-cho-testid-attr.md +135 -0
  73. package/docs/plans/qc-surgery/buoc/0-03-skill-thoi-day-do-dom.md +167 -0
  74. package/docs/plans/qc-surgery/buoc/0-04-may-canh-hop-dong.md +173 -0
  75. package/docs/plans/qc-surgery/buoc/0-05-don-nhan-cot-va-2b.md +133 -0
  76. package/docs/plans/qc-surgery/buoc/0-06-hop-dong-truoc-code.md +226 -0
  77. package/docs/plans/qc-surgery/buoc/1-01-guard-br-tag.md +156 -0
  78. package/docs/plans/qc-surgery/buoc/1-02-guard-sc-coverage.md +153 -0
  79. package/docs/plans/qc-surgery/buoc/1-03-fail-3-nhan.md +176 -0
  80. package/docs/plans/qc-surgery/buoc/1-04-self-review-dung-chung.md +175 -0
  81. package/docs/plans/qc-surgery/buoc/1-05-spec-la-du-lieu.md +164 -0
  82. package/docs/plans/qc-surgery/buoc/1-06-gap-verify-du-bo.md +162 -0
  83. package/docs/plans/qc-surgery/buoc/README.md +85 -0
  84. package/docs/plans/qc-surgery/exec-d0-b1-testid-attr-header.md +147 -0
  85. package/docs/plans/qc-surgery/exec-d0-b2-thong-nhat-nguon-testid-attr.md +152 -0
  86. package/docs/plans/qc-surgery/exec-d0-b3-sua-skill-probe-dom.md +173 -0
  87. package/docs/plans/qc-surgery/exec-d0-b4-may-canh-4-5-6.md +168 -0
  88. package/docs/plans/qc-surgery/exec-d0-b5-don-nhan-lech.md +196 -0
  89. package/docs/plans/qc-surgery/exec-d0-b6-contract-truoc-code.md +350 -0
  90. package/docs/plans/qc-surgery/exec-d1-b1-guard-br-tag.md +129 -0
  91. package/docs/plans/qc-surgery/exec-d1-b2-guard-sc-coverage.md +159 -0
  92. package/docs/plans/qc-surgery/exec-d1-b3-fail-3-bucket.md +158 -0
  93. package/docs/plans/qc-surgery/exec-d1-b4-self-review-principles.md +145 -0
  94. package/docs/plans/qc-surgery/exec-d1-b5-noi-quy-spec-la-du-lieu.md +156 -0
  95. package/docs/plans/qc-surgery/exec-d1-b6-gap-verify-mo-rong.md +179 -0
  96. package/docs/plans/qc-surgery/exec-d2-b1-tach-qc-review.md +166 -0
  97. package/docs/plans/qc-surgery/exec-d2-b2-tach-qc-run-test-atomic.md +267 -0
  98. package/docs/plans/qc-surgery/exec-d2-b3-qc-automation-assess.md +198 -0
  99. package/docs/plans/qc-surgery/exec-d3-b1-qc-report-gate-decision.md +209 -0
  100. package/docs/plans/qc-surgery/exec-d4-b1-qc-design-testdata.md +146 -0
  101. package/docs/plans/qc-surgery/exec-d4-b2-qc-smoke-test.md +179 -0
  102. package/docs/plans/qc-surgery/exec-d4-b3-qc-metrics-va-lint.md +198 -0
  103. package/docs/plans/qc-surgery/exec-d4-b4-lint-spec-injection.md +199 -0
  104. package/package.json +1 -1
@@ -180,10 +180,22 @@ placeholder bên dưới sẽ rỗng và lệnh sẽ đọc/ghi sai chỗ.
180
180
  rồi mới tiếp tục phần bên dưới.
181
181
 
182
182
  Nó chốt bốn thứ mà mọi trạm QC đều cần: `TICKET-ID` · `active_platform` ·
183
- `qc_artifact_dir` · `uc_list` (kèm trạng thái BDD từng UC, và cờ `--include-draft`).
183
+ `qc_artifact_dir` · `uc_list` (kèm trạng thái BDD từng UC, và cờ `--force`).
184
184
  Bỏ qua thì artifact QC ghi vào **sai thư mục** và `qc_status` ghi vào **sai sổ trace** —
185
185
  cả hai đều xảy ra trong im lặng, không có bước nào phía sau bắt được.
186
186
 
187
+ ---
188
+
189
+ ## Stamp phiên bản nguồn
190
+
191
+ **BẮT BUỘC — đọc `.agent/steps/qc-stamp.md` và thực thi phần áp cho lệnh này**,
192
+ rồi mới tiếp tục phần bên dưới.
193
+
194
+ Nó có **hai vế**: §1 **ghi** khối `Nguồn & phiên bản` vào artifact lệnh này sinh ra ·
195
+ §2 **so** stamp của artifact lệnh này ĐỌC với version hiện tại của spec.
196
+ Bỏ vế ghi thì trạm sau không có gì để so; bỏ vế so thì stamp thành một con số không ai
197
+ đọ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**.
198
+
187
199
  > **Trạm này chạy CẢ PRD, đúng như trạm 1** *(B11)* — một `TEST_PLAN.md` cho mỗi (PRD × nền).
188
200
  > `qa-planner/test-plan.md` vốn viết *"Test Plan cho một **feature**"* và template của nó là
189
201
  > `# Test Plan – <Feature>` với metadata `Feature / Project / Module`: đây là quay về đúng
@@ -226,6 +238,45 @@ Bắt buộc:
226
238
  - **`§5 Entry criteria` chặn theo từng UC**, không chặn cả PRD: gap 🔴 Blocker ở UC3 không
227
239
  dừng việc thiết kế test cho UC1. Ghi `Ready` / `Blocked` cho **mỗi** UC.
228
240
 
241
+ ### Chạy lại — ghi đè ở đây AN TOÀN, và đây là lý do
242
+
243
+ *(Ba trạm QC khác — `/qc-analyze` · `/qc-design-test` · `/qc-run-test` — đều ở mức **chặn CỨNG** kèm
244
+ §Chạy lại. Trạm này **không**, và đó là kết luận có chủ ý, không phải chỗ sót.)*
245
+
246
+ `TEST_PLAN.md` **không có ô nào người nhập tay**: `questions-for-dev` là danh sách **gửi đi** — câu
247
+ trả lời quay về qua `DOC_GAP.md` hoặc kênh chat, **không ai điền ngược vào file này**; stamp phiên
248
+ bản và cột `Ready`/`Blocked` đều do lệnh **tự tính** từ `DOC_GAP.md`. Nên chạy lại = lập lại, không
249
+ mất gì.
250
+
251
+ *Khai máy đọc: `bin/trace-schema.json` → `artifact_writers.enrolled.qc-plan.has_human_content = false`
252
+ — và **R18 ép khai `why`** chính vì một ngoại lệ có chủ ý trông y hệt một chỗ sót.*
253
+
254
+ > **⚠️ Khai lại nếu điều này hết đúng.** Có ai bắt đầu điền tay vào `TEST_PLAN.md` — một cột quyết
255
+ > định, một ghi chú duyệt, một câu trả lời dán vào — thì lệnh này **phải** lên `checkpoint_levels.hard`
256
+ > và có §Chạy lại như `/qc-analyze`. Đánh giá "an toàn" ở trên đúng **hôm nay**, không đúng vĩnh viễn.
257
+
258
+ > **`Blocked` là mức ƯU TIÊN, KHÔNG phải lệnh cấm** *(G66)*. Nó nghĩa *"UC này chưa sẵn sàng
259
+ > để **nghiệm thu**"*, không phải *"chưa được **thiết kế**"*. `/qc-design-test` vẫn chạy được và
260
+ > **nên** chạy — TC chạm gap mang dấu `🚫 Block` trỏ về hàng gap, và cơ chế đó chỉ có nghĩa khi
261
+ > trạm 3 thực sự chạy trên UC `Blocked`.
262
+ >
263
+ > Cái bị chặn là **chạy test**, không phải **viết test**. Chờ PO trả lời mất ngày tới tuần; cấm
264
+ > thiết kế trong lúc chờ là ném đi đúng phần song song mà `/map-testids` mở ra. Và đây là cùng
265
+ > lập luận đã dùng ngay ở gạch trên — chặn theo UC chứ không theo PRD — chỉ áp thêm một bậc:
266
+ > một blocker trong UC1 cũng không dừng việc thiết kế **phần còn lại** của UC1.
267
+
268
+ ## Self-Review *(trước khi in Report)*
269
+
270
+ Theo 3 nhóm ở `{paths.qc_skills_dir}/_shared/self-review-principles.md` — **không chép lại ở đây**.
271
+
272
+ - **Bịa:** mỗi dòng rủi ro neo được vào một `BR`/`AC`/`GAP` **có thật** trong
273
+ `REQUIREMENT_ANALYSIS.md`/`DOC_GAP.md` — không phải rủi ro chung chung tự nghĩ ra kiểu *"hiệu
274
+ năng có thể chậm"*? Mỗi `questions-for-dev` suy ra từ một gap cụ thể?
275
+ - **Nhảy bước:** đã đọc **cả hai** file đầu vào (`REQUIREMENT_ANALYSIS.md` + `DOC_GAP.md`) và lọc
276
+ theo cột `UC` — không chỉ đọc file thứ nhất?
277
+ - **Số liệu:** `{risks}`/`{questions}` in ở report = đúng số dòng thật trong `TEST_PLAN.md` vừa
278
+ ghi, không phải đếm nhẩm?
279
+
229
280
  ## Report
230
281
 
231
282
  **Đọc `.agent/steps/report-footer.md`** và áp đúng khuôn footer trong đó (Status Badge ·
@@ -237,6 +288,9 @@ Phạm vi: {n} UC trong plan{nếu có: " · ⏸ {m} chưa xét"}
237
288
  Plan: {risks} rủi ro · {questions} câu hỏi mở cho dev · layers: {list}
238
289
  File: {paths.qc_dir}/{TICKET-ID}/{active_platform}/TEST_PLAN.md
239
290
  Sẵn sàng: {danh sách UC Ready} | Chặn: {danh sách UC Blocked + GAP-ID chặn nó}
240
- Next: /qc-design-test {UC-ID} ← thiết kế test case, chạy cho từng UC đã Ready
241
- (gửi questions-for-dev cho PO/Dev cho các UC còn Blocked)
291
+ Self-review: { sạch | ⚠️ {n} điểm cần chú ý liệt kê}
292
+ Next: /qc-design-test {UC-ID} ← thiết kế test case, từng UC một
293
+ Ưu tiên UC `Ready`. UC `Blocked` VẪN thiết kế được — TC chạm gap mang dấu
294
+ 🚫 Block và chưa chạy tới khi gap Answered.
295
+ (song song: gửi questions-for-dev cho PO/Dev để gỡ blocker)
242
296
  ```
@@ -1,14 +1,14 @@
1
- ---
2
- version: 1.0
3
- updated: 2026-06-11
4
- ported_from: ai-automation-qc-base
5
- ---
6
-
7
- # /qc-report — QC Test Report & Evidence
8
-
9
- > Stage 6 (cuối) của QC automation pipeline native (qc-analyze → qc-plan → qc-design-test → qc-review → qc-run-test → qc-report). Port từ bước report của qa-runner team QC.
10
-
11
- ## Gate
1
+ ---
2
+ version: 1.0
3
+ updated: 2026-06-11
4
+ ported_from: ai-automation-qc-base
5
+ ---
6
+
7
+ # /qc-report — QC Test Report & Evidence
8
+
9
+ > Stage 6 (cuối) của QC automation pipeline native (qc-analyze → qc-plan → qc-design-test → qc-review → qc-run-test → qc-report). Port từ bước report của qa-runner team QC.
10
+
11
+ ## Gate
12
12
  # Gate — Quy trình vào chuẩn cho mọi lệnh
13
13
 
14
14
  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ó.
@@ -160,58 +160,74 @@ Mỗi dòng ⚠️/🔴 phải ứng với một trạng thái **context-loader
160
160
  - "N" → dừng, hỏi người dùng muốn thay đổi gì.
161
161
  - Có `--yes` và mức *chặn thường* → coi như "Y", **nhưng vẫn IN khối CHECKPOINT** nếu có cờ
162
162
  🔴/⚠️ (không chặn ≠ không báo — người đọc log sau này vẫn cần thấy).
163
-
164
-
165
- *Lưu ý: Với lệnh này, target ở Bước 1 là một UC-ID. Sinh report từ lần chạy `/qc-run-test` gần nhất. Dùng module **qc-playwright** (pytest-html + Playwright Trace — không Allure, không dashboard viết tay).*
166
-
167
- ## Context
163
+
164
+
165
+ *Lưu ý: Với lệnh này, target ở Bước 1 là một UC-ID. Sinh report từ lần chạy `/qc-run-test` gần nhất. Dùng module **qc-playwright** (pytest-html + Playwright Trace — không Allure, không dashboard viết tay).*
166
+
167
+ ## Context
168
168
  **BẮT BUỘC — đọc `.agent/steps/context-loader.md` và thực thi TOÀN BỘ quy trình trong đó**,
169
169
  rồi mới tiếp tục phần bên dưới.
170
170
 
171
171
  Bỏ qua bước này thì `{paths.*}`, `{tech_stack.*}`, `{conventions.*}`, guardrail từ
172
172
  `project-lessons`, và routing service (chế độ umbrella) đều **chưa được phân giải** — mọi
173
- placeholder bên dưới sẽ rỗng và lệnh sẽ đọc/ghi sai chỗ.
174
-
175
- ---
176
-
177
- ## Role
178
-
179
- Bạn là stage **QC Report** — biến lần chạy gần nhất thành report + evidence chia sẻ được.
180
-
181
- ## Skill (`{paths.qc_skills_dir}/qa-runner/report/`)
182
-
183
- - `report.md` — tự đủ: pytest-html (`--html=reports/<feature>/report.html
184
- --self-contained-html`) + Playwright Trace (`test-results/<nodeid>/trace.zip`, xem qua
185
- `python3 -m playwright show-trace <file>`), kèm screenshot/evidence đính trên FAIL/SKIP.
186
-
187
- ## Procedure
188
-
189
- 1. Định vị artifact của lần chạy gần nhất cho `{UC-ID}` (report pytest-html + trace).
190
- 2. Sinh/làm mới `reports/<feature>/report.html` (self-contained) và đảm bảo mỗi
191
- FAIL/SKIP có trace + screenshot đính kèm.
192
- 3. Tóm tắt TOTAL / PASS / FAIL / SKIP; với mỗi FAIL gồm lệnh `show-trace` và
193
- phân loại là script-bug hay product-gap.
194
- 4. **Bàn giao product-gap về spec (có nhắc).** Với mỗi FAIL phân loại **product-gap**
195
- (defect thật, impl ≠ spec — không phải script-bug), in một
196
- `/report-bug {UC-ID} {one-line expected-vs-actual}` sẵn-chạy để QC file nó vào spec repo dùng chung.
197
- BUG_FLOW của `/report-bug` rồi định tuyến root cause (Code / BDD / PRD / Design / Env). Không bao giờ
198
- fake-pass một product-gap — nó giữ FAIL trong `qc_status` cho tới khi fix + chạy lại. **script-bug
199
- KHÔNG được file** (QC fix script và chạy lại). Liệt kê các lệnh; đừng tự tạo report.
200
-
201
- ## Report
202
-
173
+ placeholder bên dưới sẽ rỗng và lệnh sẽ đọc/ghi sai chỗ.
174
+
175
+ ---
176
+
177
+ ## Role
178
+
179
+ Bạn là stage **QC Report** — biến lần chạy gần nhất thành report + evidence chia sẻ được.
180
+
181
+ ## Skill (`{paths.qc_skills_dir}/qa-runner/report/`)
182
+
183
+ - `report.md` — tự đủ: pytest-html (`--html=reports/<feature>/report.html
184
+ --self-contained-html`) + Playwright Trace (`test-results/<nodeid>/trace.zip`, xem qua
185
+ `python3 -m playwright show-trace <file>`), kèm screenshot/evidence đính trên FAIL/SKIP.
186
+
187
+ ## Procedure
188
+
189
+ 1. Định vị artifact của lần chạy gần nhất cho `{UC-ID}` (report pytest-html + trace).
190
+ 2. Sinh/làm mới `reports/<feature>/report.html` (self-contained) và đảm bảo mỗi
191
+ FAIL/SKIP có trace + screenshot đính kèm.
192
+ 3. Tóm tắt TOTAL / PASS / FAIL / SKIP; với mỗi FAIL gồm lệnh `show-trace` và
193
+ phân loại là script-bug hay product-gap.
194
+ 4. **Bàn giao product-gap về spec (có nhắc).** Với mỗi FAIL phân loại **product-gap**
195
+ (defect thật, impl ≠ spec — không phải script-bug), in một
196
+ `/report-bug {UC-ID} {one-line expected-vs-actual}` sẵn-chạy để QC file nó vào spec repo dùng chung.
197
+ BUG_FLOW của `/report-bug` rồi định tuyến root cause (Code / BDD / PRD / Design / Env). Không bao giờ
198
+ fake-pass một product-gap — nó giữ FAIL trong `qc_status` cho tới khi fix + chạy lại. **script-bug
199
+ KHÔNG được file** (QC fix script và chạy lại). Liệt kê các lệnh; đừng tự tạo report.
200
+
201
+ ## Self-Review *(trước khi in Report)*
202
+
203
+ Theo 3 nhóm ở `{paths.qc_skills_dir}/_shared/self-review-principles.md` — **không chép lại ở đây**.
204
+
205
+ - **Bịa:** mọi con số trong báo cáo trích được về **một dòng cụ thể** của sổ trace / output
206
+ runner / bug report — không nội suy khi thiếu mẫu?
207
+ - **Nhảy bước:** đã tổng hợp trên **toàn bộ** SC trong phạm vi, không chỉ những SC có kết quả
208
+ đẹp? Đã liệt kê cả SC `not_run` và `flaky`, không im lặng bỏ khỏi bảng?
209
+ - **Số liệu:** mọi `%` là phép chia thật **và nói rõ mẫu số**? Chỗ thiếu dữ liệu ghi **"chưa đủ
210
+ dữ liệu"** thay vì điền một số cho đủ bảng?
211
+
212
+ > **Trạm này là nơi số liệu đi ra khỏi đội QC.** Một con số sai ở các trạm trước còn người trong
213
+ > đội nhìn thấy; sai ở đây là đi vào báo cáo cho Lead/PM. Nhóm 3 vì vậy là nhóm nặng nhất ở đây:
214
+ > *"một bảng đầy số sai tệ hơn một bảng có ô trống ghi rõ lý do"*.
215
+
216
+ ## Report
217
+
203
218
  **Đọc `.agent/steps/report-footer.md`** và áp đúng khuôn footer trong đó (Status Badge ·
204
- Output Artifacts · Next) cho report cuối, kèm khối bên dưới.
205
-
206
- ```
207
- /qc-report Hoàn tất — {UC-ID}
208
- Report: reports/<feature>/report.html (TOTAL {N} · PASS {p} · FAIL {f} · SKIP {s})
209
- Trace : test-results/<nodeid>/trace.zip (python3 -m playwright show-trace <file>)
210
-
211
- Product-gap cần file ({g}): ← chạy các lệnh này để PO/Dev thấy trên /sync (script-bug bị loại)
212
- /report-bug {UC-ID} {gap 1 — expected vs actual}
213
- /report-bug {UC-ID} {gap 2 …}
214
- (không có → skip)
215
-
216
- Next: /validate-traces {UC-ID} làm mới Living Docs (qc_status), rồi tạo PR
217
- ```
219
+ Output Artifacts · Next) cho report cuối, kèm khối bên dưới.
220
+
221
+ ```
222
+ /qc-report Hoàn tất — {UC-ID}
223
+ Report: reports/<feature>/report.html (TOTAL {N} · PASS {p} · FAIL {f} · SKIP {s})
224
+ Trace : test-results/<nodeid>/trace.zip (python3 -m playwright show-trace <file>)
225
+
226
+ Product-gap cần file ({g}): ← chạy các lệnh này để PO/Dev thấy trên /sync (script-bug bị loại)
227
+ /report-bug {UC-ID} {gap 1 — expected vs actual}
228
+ /report-bug {UC-ID} {gap 2 …}
229
+ (không có → skip)
230
+
231
+ Self-review: {✅ sạch | ⚠️ {n} điểm cần chú ý liệt kê}
232
+ Next: /validate-traces {UC-ID} ← làm mới Living Docs (qc_status), rồi tạo PR
233
+ ```
@@ -178,10 +178,22 @@ placeholder bên dưới sẽ rỗng và lệnh sẽ đọc/ghi sai chỗ.
178
178
  rồi mới tiếp tục phần bên dưới.
179
179
 
180
180
  Nó chốt bốn thứ mà mọi trạm QC đều cần: `TICKET-ID` · `active_platform` ·
181
- `qc_artifact_dir` · `uc_list` (kèm trạng thái BDD từng UC, và cờ `--include-draft`).
181
+ `qc_artifact_dir` · `uc_list` (kèm trạng thái BDD từng UC, và cờ `--force`).
182
182
  Bỏ qua thì artifact QC ghi vào **sai thư mục** và `qc_status` ghi vào **sai sổ trace** —
183
183
  cả hai đều xảy ra trong im lặng, không có bước nào phía sau bắt được.
184
184
 
185
+ ---
186
+
187
+ ## Stamp phiên bản nguồn
188
+
189
+ **BẮT BUỘC — đọc `.agent/steps/qc-stamp.md` và thực thi phần áp cho lệnh này**,
190
+ rồi mới tiếp tục phần bên dưới.
191
+
192
+ Nó có **hai vế**: §1 **ghi** khối `Nguồn & phiên bản` vào artifact lệnh này sinh ra ·
193
+ §2 **so** stamp của artifact lệnh này ĐỌC với version hiện tại của spec.
194
+ Bỏ vế ghi thì trạm sau không có gì để so; bỏ vế so thì stamp thành một con số không ai
195
+ đọ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**.
196
+
185
197
  > **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
186
198
  > 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
187
199
  > artifact nằm chung ở `{qc_artifact_dir}` cấp PRD, không còn một thư mục mỗi UC.
@@ -222,9 +234,63 @@ tồn tại cho UC và mới hơn → review script. Nếu mơ hồ, hỏi.
222
234
  - Soát test-case: `test-case/{functional,e2e,integration,non-functional,exploratory}.md`
223
235
  - Soát script: `script/{functional,e2e,integration,non-functional,exploratory}.md`
224
236
 
237
+ ---
238
+
239
+ ## Guard — hợp đồng test-id, chân thứ tư *(phép so GIÁ TRỊ, một chiều)*
240
+
241
+ `bin/trace-schema.json` canh **ba** chân của hợp đồng test-id: §4.5.6 ↔ `.feature` (**T15**) ·
242
+ ↔ header `@trace.testid_attr` (**T16**) · ↔ code (**T17/T18**). Chân thứ tư — §4.5.6 ↔ `*.Test.md`
243
+ — **không máy nào canh**, và nó là chân duy nhất mà giá trị id bị **chép cứng vào một artifact bền**:
244
+
245
+ ```
246
+ generate-code → bake vào code → T17/T18 canh
247
+ qc-run-test → dựng locator LÚC CHẠY → đọc bảng tươi, tự cứu
248
+ qc-design-test → chép vào .Test.md → KHÔNG AI CANH ← chỗ này
249
+ ```
250
+
251
+ **Phạm vi — đọc trước khi làm gì.** Không có block §4.5 client trong tech-doc (dự án backend-only,
252
+ hoặc chưa từng chạy `/map-testids`) → **im lặng hoàn toàn**, không kiểm gì. Cùng điều khoản phạm vi
253
+ với `testid_contract`: *"hai rule nói 'chỗ nào đã hứa thì phải giữ', KHÔNG nói 'mọi chỗ đều phải có
254
+ hợp đồng'"*.
255
+
256
+ **Phép so — MỘT chiều:**
257
+
258
+ 1. Gom mọi test-id được nhắc trong các `*.Test.md` của UC này.
259
+ 2. Gom id ở §4.5.6 (block `active_platform`), **lọc theo cột "Serves SC"** khớp SC của UC này.
260
+ 3. `TC ∖ bảng` = id TC nhắc mà hợp đồng không có → ⚠️.
261
+
262
+ | Kết quả | Làm gì |
263
+ |---|---|
264
+ | Rỗng | **im lặng, đi tiếp** |
265
+ | Có id lạ | `⚠️ Hợp đồng test-id: {k} id trong .Test.md không có ở §4.5.6: {danh sách}`<br/>kèm: `Chạy /map-testids {UC-ID} (đưa vào hợp đồng) hoặc /qc-design-test {UC-ID} (sửa TC theo hợp đồng).` |
266
+
267
+ **Chiều ngược KHÔNG kiểm.** §4.5.6 có id mà không TC nào nhắc là **bình thường** — không phải element
268
+ nào cũng cần một bước TC. Kiểm chiều đó là **ồn**, đúng bất đối xứng T17/T18 đã chọn.
269
+
270
+ **Mức là `warn`, không chặn.** Id sai làm test **ĐỎ**, không làm test **xanh giả** — nên nó **không**
271
+ thuộc lớp *"báo cáo sai"* và không đi cùng đường với `steps/qc-stamp.md` §2 (chặn `pass`). Chặn ở đây
272
+ là chặn nhầm loại.
273
+
274
+ > **Vì sao vẫn đáng cảnh báo dù test sẽ tự đỏ.** Vì nó đỏ **đội lốt thứ khác**. `testid_contract` ghi
275
+ > đúng chữ: *"test đỏ 'element not found' **trông y hệt bug sản phẩm**"* — nên `/qc-report` phân loại
276
+ > *product-gap* rồi in một `/report-bug` sẵn-chạy, và một lỗi **hợp đồng nội bộ** đi ra khỏi đội QC
277
+ > thành phiếu lỗi gửi PO. Dòng ⚠️ này là chỗ duy nhất chặn được chuyến đi đó.
278
+
225
279
  ## Review focus
226
280
 
227
281
  - **Test-case:** mọi `{UC-ID}-SC{N}` đã phủ? happy + negative + boundary? expected cụ thể? trace (`BR-xx` + `@trace.verifies` SC) có mặt? không có TC orphan?
282
+ - **`🚫 Block` còn hiệu lực không?** Mỗi TC mang `🚫 Block: [GAP-UC{N}-{nnn}]` → mở `../DOC_GAP.md`,
283
+ đọc `Trạng thái` của hàng gap đó. **Gap đã `Answered` mà TC vẫn mang dấu ⇒ FINDING.**
284
+
285
+ > **Vì sao là finding chứ không phải chuyện nhỏ** *(G66)*. Một TC mang `🚫 Block` là một TC có
286
+ > `Expected Result` dựa trên **giả định chưa ai xác nhận**. Khi gap được trả lời, quy trình
287
+ > `tc-metadata-format.md` §*Quy trình khi gap được giải quyết* đòi **cập nhật lại Expected Result**
288
+ > — giả định có thể đã sai. Bỏ qua thì TC chạy với oracle sai, fail, rồi `/qc-report` phân loại
289
+ > *product-gap* và in một `/report-bug`: một lỗ hổng **tài liệu** đi ra ngoài đội QC thành **phiếu
290
+ > lỗi sản phẩm** gửi PO.
291
+ >
292
+ > `/qc-design-test` §Guard 🚫 Block **tự gỡ** các dấu này khi chạy lại. Trạm này là **lưới bắt phía
293
+ > sau** cho ca guard đó không chạy (TC sửa tay, hoặc gap được trả lời sau lần chạy trạm 3 cuối).
228
294
  - **Script:** khớp `.Test.md` 1-1? Page Object 3 lớp + BasePage gọn? `expect()` không phải bare assert? không hard-code URL/cred/timeout, không `time.sleep`, không Allure? selector theo priority (data-testid→role→…)? 100% TC đã script (không còn Draft)?
229
295
 
230
296
  Sinh findings (mức độ + vị trí + cách sửa), chấm điểm, ra verdict.
@@ -247,6 +313,25 @@ Ghi vào **`{qc_artifact_dir}test-cases/REVIEW_<FEATURE>.md`** — đứng **c
247
313
  ⚠️ **Tên file review KHÔNG có đuôi `.Test.md`.** Đuôi đó là của file test case; gắn vào file
248
314
  review sẽ làm `/qc-run-test` nhặt nó lên như một file test case rồi cố sinh script từ một bảng điểm.
249
315
 
316
+ ### Chạy lại — ghi đè ở đây AN TOÀN, và đây là lý do
317
+
318
+ *(Ba trạm QC khác — `/qc-analyze` · `/qc-design-test` · `/qc-run-test` — đều ở mức **chặn CỨNG** kèm
319
+ §Chạy lại. Trạm này **không**, và đó là kết luận có chủ ý, không phải chỗ sót.)*
320
+
321
+ `REVIEW_<FEATURE>.md` là **biên bản của một lần soát**. Sinh lại = **soát lại**, đúng bản chất của
322
+ nó — không có trạng thái nào tích luỹ qua các lần chạy, không ô nào người điền tay vào sau.
323
+
324
+ *Khai máy đọc: `bin/trace-schema.json` → `artifact_writers.enrolled.qc-review.has_human_content = false`
325
+ — và **R18 ép khai `why`** chính vì một ngoại lệ có chủ ý trông y hệt một chỗ sót.*
326
+
327
+ > **⚠️ Khai lại nếu điều này hết đúng.** Có ai bắt đầu ghi vào biên bản thứ **không sinh lại được** —
328
+ > chữ ký duyệt, một quyết định *"chấp nhận rủi ro"*, ghi chú thảo luận với dev — thì lệnh này **phải**
329
+ > lên `checkpoint_levels.hard` và có §Chạy lại như `/qc-analyze`.
330
+ >
331
+ > *Lưu ý ngược lại: file TC mà trạm này **soát** thì **có** phần người làm tay, và `/qc-design-test`
332
+ > đã ở mức `hard` vì đúng lý do đó (G78). Trạm này chỉ an toàn ở phần nó **ghi**, không phải phần nó
333
+ > **đọc**.*
334
+
250
335
  Khuôn đầy đủ + quy tắc ghi: `shared/review-file-template.md`. Ba điều bắt buộc:
251
336
 
252
337
  - **Điểm `XX/100`** — trừ 5đ mỗi `FAIL`, 2đ mỗi `WARN`. Verdict **suy ra được**:
@@ -261,6 +346,21 @@ Khuôn đầy đủ + quy tắc ghi: `shared/review-file-template.md`. Ba điề
261
346
  > hai kết quả, và **vòng 2 không so được với vòng 1** — tức không ai biết sửa xong có tốt lên
262
347
  > không. Một cổng không để lại dấu vết đo được thì không phải cổng, nó là một lượt đọc.
263
348
 
349
+ ## Self-Review *(trước khi in Report)*
350
+
351
+ Theo 3 nhóm ở `{paths.qc_skills_dir}/_shared/self-review-principles.md` — **không chép lại ở đây**.
352
+
353
+ - **Bịa:** mỗi finding trỏ được về **dòng cụ thể** trong artifact đang soát — không phải nhận xét
354
+ chung chung? Và **cấm dùng chính field `quote` của finding làm bằng chứng cho nó** — mở lại file
355
+ đọc lại đoạn đó.
356
+ - **Nhảy bước:** đã đi hết bộ tiêu chí của **đúng vai** (test-case hay script), không trộn hai
357
+ bộ? Verdict `APPROVED` phát ra **sau** khi soát đủ, không phải vì "trông ổn"?
358
+ - **Số liệu:** số finding theo mức (critical/major/minor) = đếm thật trên file findings vừa ghi?
359
+
360
+ > **Verdict của lệnh này là cổng cho trạm sau.** Một `APPROVED` phát ra sớm không chỉ sai ở đây —
361
+ > nó mở đường cho `/qc-run-test` sinh script từ một bộ TC chưa đạt. Đây là chỗ self-review đắt
362
+ > nhất nếu bỏ qua.
363
+
264
364
  ## Report
265
365
 
266
366
  **Đọc `.agent/steps/report-footer.md`** và áp đúng khuôn footer trong đó (Status Badge ·
@@ -271,6 +371,7 @@ Output Artifacts · Next) cho report cuối, kèm khối bên dưới.
271
371
  Điểm : {XX}/100 ({fail} FAIL × −5đ · {warn} WARN × −2đ){nếu có vòng trước: " ← vòng #{N-1}: {YY}/100"}
272
372
  Verdict: {APPROVED | NEEDS_FIX} — {n} findings ({crit} chặn)
273
373
  File : {qc_artifact_dir}test-cases/REVIEW_<FEATURE>.md (thêm 1 hàng vào bảng Tổng quan)
374
+ Self-review: {✅ sạch | ⚠️ {n} điểm cần chú ý — liệt kê}
274
375
  Next (test-case APPROVED): /qc-run-test {UC-ID}
275
376
  Next (script APPROVED) : /qc-report {UC-ID} rồi tạo PR
276
377
  (NEEDS_FIX → fix artifact bị gắn cờ, rồi chạy lại /qc-review {UC-ID})
@@ -9,6 +9,11 @@ ported_from: ai-automation-qc-base
9
9
  > Stage 5 của QC automation pipeline native (qc-analyze → qc-plan → qc-design-test → qc-review → qc-run-test → qc-report). Port từ qa-runner của team QC. Sinh & chạy Python pytest-playwright từ `.Test.md` đã review, rồi ghi `qc_status` **chính thức** vào trace TSV.
10
10
 
11
11
  ## Gate
12
+
13
+ *Checkpoint: **chặn CỨNG** — ghi đè script Python đã có → XOÁ bản sửa `script-bug` mà chính lệnh này bảo QC tự sửa bằng tay. `--yes` KHÔNG bỏ qua được (gate Bước 3a).*
14
+
15
+ *Mức cứng chỉ áp khi **sinh lại** file script đã tồn tại. Chạy lại test trên script không đổi — việc thường nhất của trạm này — **không** chạm CHECKPOINT cứng. Xem §Chạy lại.*
16
+
12
17
  # Gate — Quy trình vào chuẩn cho mọi lệnh
13
18
 
14
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ó.
@@ -178,10 +183,22 @@ placeholder bên dưới sẽ rỗng và lệnh sẽ đọc/ghi sai chỗ.
178
183
  rồi mới tiếp tục phần bên dưới.
179
184
 
180
185
  Nó chốt bốn thứ mà mọi trạm QC đều cần: `TICKET-ID` · `active_platform` ·
181
- `qc_artifact_dir` · `uc_list` (kèm trạng thái BDD từng UC, và cờ `--include-draft`).
186
+ `qc_artifact_dir` · `uc_list` (kèm trạng thái BDD từng UC, và cờ `--force`).
182
187
  Bỏ qua thì artifact QC ghi vào **sai thư mục** và `qc_status` ghi vào **sai sổ trace** —
183
188
  cả hai đều xảy ra trong im lặng, không có bước nào phía sau bắt được.
184
189
 
190
+ ---
191
+
192
+ ## Stamp phiên bản nguồn
193
+
194
+ **BẮT BUỘC — đọc `.agent/steps/qc-stamp.md` và thực thi phần áp cho lệnh này**,
195
+ rồi mới tiếp tục phần bên dưới.
196
+
197
+ Nó có **hai vế**: §1 **ghi** khối `Nguồn & phiên bản` vào artifact lệnh này sinh ra ·
198
+ §2 **so** stamp của artifact lệnh này ĐỌC với version hiện tại của spec.
199
+ Bỏ vế ghi thì trạm sau không có gì để so; bỏ vế so thì stamp thành một con số không ai
200
+ đọ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**.
201
+
185
202
  > **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
186
203
  > 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
187
204
  > artifact nằm chung ở `{qc_artifact_dir}` cấp PRD, không còn một thư mục mỗi UC.
@@ -218,13 +235,111 @@ Quy tắc stack (BẮT BUỘC — từ `modules/qc-playwright/stack-profile.yaml
218
235
  - pytest-playwright fixture; mỗi test độc lập; gom theo (role, account) để auth không bao giờ xen kẽ.
219
236
  - Không hard-code URL/cred/timeout (dùng `Env.*` / `CONFIG[...]`); không `time.sleep()`; không Allure.
220
237
  - Phủ **100%** TC trong file — mỗi TC kết thúc Pass/Fail/Skip (không còn Draft).
221
- - Phân loại mỗi FAIL: script-bug (fix selector/logic) vs product-gap (giữ FAIL + evidence, không bao giờ fake-pass).
238
+ - Phân loại mỗi FAIL — **ba** nhãn, sau khi đã chạy lại (xem §Chạy lại trước khi kết luận): `script-bug` (fix selector/logic) · `product-gap` (giữ FAIL + evidence, **không bao giờ fake-pass**) · `flaky` (không nhất quán qua các lần chạy lại — chưa đủ căn cứ để kết luận hai cái kia).
222
239
 
223
240
  ## Skills — chọn layer, nạp MỘT file (`{paths.qc_skills_dir}/qa-runner/`)
224
241
 
225
242
  `functional/{gui-screen,gui-feature,api}.md`, `integration.md`, `e2e.md`,
226
243
  `non-functional.md`, `exploratory/session.md`.
227
244
 
245
+ ---
246
+
247
+ ## Guard — hợp đồng test-id, chân thứ tư *(phép so GIÁ TRỊ, một chiều)*
248
+
249
+ `bin/trace-schema.json` canh **ba** chân của hợp đồng test-id: §4.5.6 ↔ `.feature` (**T15**) ·
250
+ ↔ header `@trace.testid_attr` (**T16**) · ↔ code (**T17/T18**). Chân thứ tư — §4.5.6 ↔ `*.Test.md`
251
+ — **không máy nào canh**, và nó là chân duy nhất mà giá trị id bị **chép cứng vào một artifact bền**:
252
+
253
+ ```
254
+ generate-code → bake vào code → T17/T18 canh
255
+ qc-run-test → dựng locator LÚC CHẠY → đọc bảng tươi, tự cứu
256
+ qc-design-test → chép vào .Test.md → KHÔNG AI CANH ← chỗ này
257
+ ```
258
+
259
+ **Phạm vi — đọc trước khi làm gì.** Không có block §4.5 client trong tech-doc (dự án backend-only,
260
+ hoặc chưa từng chạy `/map-testids`) → **im lặng hoàn toàn**, không kiểm gì. Cùng điều khoản phạm vi
261
+ với `testid_contract`: *"hai rule nói 'chỗ nào đã hứa thì phải giữ', KHÔNG nói 'mọi chỗ đều phải có
262
+ hợp đồng'"*.
263
+
264
+ **Phép so — MỘT chiều:**
265
+
266
+ 1. Gom mọi test-id được nhắc trong các `*.Test.md` của UC này.
267
+ 2. Gom id ở §4.5.6 (block `active_platform`), **lọc theo cột "Serves SC"** khớp SC của UC này.
268
+ 3. `TC ∖ bảng` = id TC nhắc mà hợp đồng không có → ⚠️.
269
+
270
+ | Kết quả | Làm gì |
271
+ |---|---|
272
+ | Rỗng | **im lặng, đi tiếp** |
273
+ | Có id lạ | `⚠️ Hợp đồng test-id: {k} id trong .Test.md không có ở §4.5.6: {danh sách}`<br/>kèm: `Chạy /map-testids {UC-ID} (đưa vào hợp đồng) hoặc /qc-design-test {UC-ID} (sửa TC theo hợp đồng).` |
274
+
275
+ **Chiều ngược KHÔNG kiểm.** §4.5.6 có id mà không TC nào nhắc là **bình thường** — không phải element
276
+ nào cũng cần một bước TC. Kiểm chiều đó là **ồn**, đúng bất đối xứng T17/T18 đã chọn.
277
+
278
+ **Mức là `warn`, không chặn.** Id sai làm test **ĐỎ**, không làm test **xanh giả** — nên nó **không**
279
+ thuộc lớp *"báo cáo sai"* và không đi cùng đường với `steps/qc-stamp.md` §2 (chặn `pass`). Chặn ở đây
280
+ là chặn nhầm loại.
281
+
282
+ > **Vì sao vẫn đáng cảnh báo dù test sẽ tự đỏ.** Vì nó đỏ **đội lốt thứ khác**. `testid_contract` ghi
283
+ > đúng chữ: *"test đỏ 'element not found' **trông y hệt bug sản phẩm**"* — nên `/qc-report` phân loại
284
+ > *product-gap* rồi in một `/report-bug` sẵn-chạy, và một lỗi **hợp đồng nội bộ** đi ra khỏi đội QC
285
+ > thành phiếu lỗi gửi PO. Dòng ⚠️ này là chỗ duy nhất chặn được chuyến đi đó.
286
+
287
+ ## Output — lệnh này ghi những gì, ở đâu
288
+
289
+ | Đường dẫn | Nội dung | Người sửa tay? |
290
+ |---|---|:---:|
291
+ | `tests/<project>/test_<feature>.py` | pytest script, 1-1 với `test-cases/` | ✅ **có** — `script-bug` |
292
+ | `pages/<feature>_page.py` | Page Object (3 lớp + BasePage) | ✅ **có** — `script-bug` |
293
+ | `{paths.trace_dir}/…/{UC-ID}-{platform}.tsv` | `qc_status` · `qc_run_at` | ❌ lệnh ghi |
294
+
295
+ *(Đường dẫn theo `modules/qc-playwright/stack-profile.yaml` §layout.)*
296
+
297
+ > **Vì sao mục này tồn tại** *(G77)*. Trước đó lệnh **không có §Output** — nó không khai mình ghi
298
+ > cái gì, ở đâu; đường dẫn chỉ nằm trong `stack-profile.yaml` của module. Và một lệnh **không khai
299
+ > mình ghi gì** thì không ai nghĩ tới việc **bảo vệ thứ nó ghi**. Cột *"người sửa tay?"* là câu hỏi
300
+ > mà `bin/trace-schema.json` → `artifact_writers` giờ ép mọi lệnh phải trả lời.
301
+
302
+ ## Chạy lại — script đã tồn tại *(CHẠY, đừng sinh lại)*
303
+
304
+ **Vòng đời của trạm này là CHẠY nhiều, SINH ít.** §Phân loại FAIL ngay dưới đây khai tường minh
305
+ rằng QC **sửa tay** chính các file trên: *"`script-bug` … **QC tự sửa.** Không mở bug"*. Nên chuỗi
306
+ bình thường là **sinh → chạy → đỏ → QC sửa tay → chạy lại**, và bước cuối **không được** là bước xoá.
307
+
308
+ | Tình huống | Xử lý |
309
+ |---|---|
310
+ | Script đã có, TC không đổi | **CHẠY nó. KHÔNG sinh lại.** Không hỏi gì — đây là ca thường nhất |
311
+ | TC mới xuất hiện trong `.Test.md` | Sinh **chỉ phần thiếu**; giữ nguyên file đã có |
312
+ | Script có **thay đổi chưa commit** | **DỪNG** — xem dưới |
313
+
314
+ **Nhận diện "đã sửa tay" bằng `git status` / `git diff`** trên các đường dẫn ở §Output. Không cần ai
315
+ đánh dấu gì, và nó bắt được **mọi** lần sửa — kể cả lần người sửa quên đánh dấu.
316
+
317
+ ```
318
+ ❌ {n} file script có thay đổi chưa commit — có thể là bản sửa `script-bug` của bạn.
319
+ Sinh lại sẽ XOÁ chúng.
320
+ Files: {danh sách}
321
+ Cách đúng: commit bản sửa trước, rồi chạy lại.
322
+ Cố ý bỏ hết và sinh từ trắng: thêm --force
323
+ ```
324
+
325
+ **KHÔNG đoán.** Thấy thay đổi chưa commit thì lệnh **không tự phân biệt** *"QC vừa sửa"* với *"lần
326
+ sinh trước chưa commit"* — hai ca đó nhìn giống hệt nhau trên `git status`. **Dừng oan một lần rẻ
327
+ hơn xoá nhầm một lần.**
328
+
329
+ > **Vì sao dùng `git`, không dùng marker hay băm nội dung.** Marker (`# @qc.hand-fixed`) **dựa vào kỷ
330
+ > luật** — đúng thứ đã bác ở G64 (*"dựa vào `Next` là dựa vào kỷ luật, không phải vào máy"*). Băm nội
331
+ > dung **du nhập một lớp cơ chế mới**: `rules/workflow.md` ghi *"0 content hash trong toàn bộ
332
+ > codebase"*, và G63 đã chọn không mở mặt trận đó.
333
+ >
334
+ > **Vì sao ca "không đổi gì" phải HOÀN TOÀN im lặng.** Chạy lại test là việc làm hàng chục lần một
335
+ > ngày. Bản vá nào làm nó hỏi mỗi lần thì người dùng sẽ thêm `--force` vào alias — và lúc đó ta mất
336
+ > sạch lớp bảo vệ này.
337
+
338
+ Có `--force` → report **bắt buộc** khai:
339
+ ```
340
+ ⚠️ --force: sinh lại từ trắng — đã xoá {n} file script có thay đổi chưa commit: {danh sách}
341
+ ```
342
+
228
343
  ## Trace tag (bắt buộc)
229
344
 
230
345
  Mỗi pytest test được sinh ra mang scenario framework mà nó verify, lấy từ
@@ -235,6 +350,60 @@ Trace matrix của `.Test.md`:
235
350
  def test_TC_<FEATURE>_001_...(...): ...
236
351
  ```
237
352
 
353
+ ## Chạy lại trước khi kết luận — rồi người xác nhận *(bắt buộc cho MỌI FAIL)*
354
+
355
+ Một test đỏ **một lần** chưa nói được nó đỏ vì cái gì. Trước khi phân loại, phải loại nhiễu.
356
+
357
+ ### Bước 1 — Chạy lại riêng test đó, tối đa 2 lần
358
+
359
+ ```
360
+ đỏ → đỏ → đỏ ⇒ NHẤT QUÁN → sang Bước 2, phân loại script-bug | product-gap
361
+ đỏ → xanh ⇒ KHÔNG NHẤT QUÁN → flaky
362
+ đỏ → đỏ → xanh ⇒ KHÔNG NHẤT QUÁN → flaky
363
+ ```
364
+
365
+ **Đây KHÔNG phải `retries` trong config của test runner.** `retries` tự thử lại rồi báo
366
+ *"passed on retry"* — nó **che** sự không nhất quán. Ở đây chạy **tách biệt từng lần** để
367
+ **quan sát**, vì chính sự không nhất quán mới là thông tin cần.
368
+
369
+ ### Bước 2 — Điều tra bằng bằng chứng, KHÔNG đoán
370
+
371
+ Đọc trace/video/log: timeline, DOM snapshot, network, console **tại thời điểm fail**. Rồi chọn
372
+ một trong ba:
373
+
374
+ | Nhãn | Khi nào | Hệ quả |
375
+ |---|---|---|
376
+ | `script-bug` | Sai locator / logic test / timing / dữ liệu test | QC tự sửa. **Không** mở bug |
377
+ | `product-gap` | Hành vi thật ≠ spec (PRD/BDD/AC) — defect thật | Mở bug qua `/report-bug`. Giữ `fail` + evidence, **không bao giờ fake-pass** |
378
+ | `flaky` | Không nhất quán ở Bước 1 — **chưa đủ căn cứ** | Cách ly + **ghi nghi vấn nguyên nhân**. **Không** mở bug từ một lần chạy hên xui |
379
+
380
+ ### Bước 3 — Người xác nhận TRƯỚC khi hành động
381
+
382
+ In đề xuất kèm bằng chứng cụ thể, rồi **dừng chờ**:
383
+
384
+ ```
385
+ ❓ {UC-ID}-SC{N} FAIL — đề xuất: {script-bug | product-gap | flaky}
386
+ Bằng chứng: {trích từ trace/log — vd "locator #submit-btn không tồn tại, DOM đã đổi id"
387
+ hoặc "lần 1 đỏ, lần 2 xanh — không nhất quán"}
388
+ Xác nhận phân loại này? (script-bug / product-gap / flaky)
389
+ ```
390
+
391
+ **KHÔNG ghi `qc_status` cho scenario nào còn FAIL chưa được xác nhận phân loại.**
392
+
393
+ **Nếu QC không chắc** giữa `script-bug` và `product-gap` (bằng chứng mơ hồ, hành vi ở biên của
394
+ spec) → **mời Dev cùng xem trace**, đừng tự đoán cho xong.
395
+
396
+ > **Vì sao chỗ này đáng chặn** — trong khi framework vốn đang cố giảm số cổng chặn (G41): vì
397
+ > **hai hướng sai đều không đảo ngược rẻ**. Gắn nhầm `script-bug` cho lỗi sản phẩm thật là
398
+ > **giấu bug** — dev không bao giờ thấy nó. Gắn nhầm `product-gap` cho lỗi test là mở bug sai,
399
+ > hoặc tệ hơn là đi "sửa" một sản phẩm không có lỗi. Đây không phải cổng hỏi cho có; nó là điểm
400
+ > rẽ mà agent **không có quyền tự quyết**.
401
+ >
402
+ > **Vì sao `flaky` không phải thùng rác.** Nhãn này **buộc** ghi nghi vấn nguyên nhân (thường:
403
+ > thiếu cách ly dữ liệu · thiếu `storageState` · race condition thật) và cách ly test, không
404
+ > phải chỉ đánh dấu rồi bỏ qua. Nó là *"chưa kết luận được"*, không phải *"thôi bỏ đi"* — và
405
+ > một test nằm mãi ở `flaky` là một việc còn nợ, nhìn thấy được.
406
+
238
407
  ## Write Trace State — qc_status (kết quả QC CHÍNH THỨC)
239
408
 
240
409
  Sau khi chạy, cập nhật **sổ của platform đang test** `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{active_platform}.tsv` (`{active_platform}` đã phân giải ở Bước 1 — chính là platform của QC pass này; nếu `domain`/`prd_slug` không phân giải được từ spec target, định vị TSV bằng cách glob `{paths.trace_dir}/**/{UC-ID}-{active_platform}.tsv` — nó được tạo trước đó bởi `/generate-bdd`) — cho mỗi scenario row (khớp
@@ -242,10 +411,11 @@ Sau khi chạy, cập nhật **sổ của platform đang test** `{paths.trace_di
242
411
 
243
412
  | Cột | Giá trị |
244
413
  |--------|-------|
245
- | `qc_status` | Đọc cột `status` của row **TRƯỚC** — xem §Guard ngay dưới bảng. Row `OK`/`GAP`/`UNTRACKED`: `pass` nếu mọi QC test của SC này pass · `fail` nếu có cái fail · `skip` nếu tất cả skip/xfail · `not_run` nếu không QC test nào phủ nó. Row **`DRIFT`/`ORPHANED`**: **không bao giờ ghi `pass`** — hạ về `not_run` |
414
+ | `qc_status` | Đọc cột `status` của row **TRƯỚC** — xem §Guard ngay dưới bảng. Row `OK`/`GAP`/`UNTRACKED`: `pass` nếu mọi QC test của SC này pass · `fail` nếu có cái fail **đã xác nhận là `script-bug` hoặc `product-gap`** · `skip` nếu tất cả skip/xfail · `not_run` nếu không QC test nào phủ nó, **hoặc SC chỉ có kết quả `flaky`** (xem hàng dưới). Row **`DRIFT`/`ORPHANED`**: **không bao giờ ghi `pass`** — hạ về `not_run` |
415
+ | `qc_status` khi `flaky` | **`not_run`, KHÔNG phải `fail`.** `fail` ngụ ý *đã có kết luận*; kết quả không nhất quán qua các lần chạy lại nghĩa là **chưa có** kết luận nào. Ghi `fail` ở đây là khẳng định một điều chưa biết — và nó còn đẩy việc sang dev (xem `qc_owner`) trong khi việc đang thuộc QC |
246
416
  | `qc_run_at` | hôm nay `YYYY-MM-DD` |
247
417
  | `last_updated` | hôm nay `YYYY-MM-DD` |
248
- | `qc_owner` | **SC đang chờ ai** (view "pending" của PM/PO): `dev` nếu FAIL = product-gap (defect thật → dev fix) · `po` nếu `skip`/`not_run` vì một **`DOC_GAP` 🔴 Blocker đang open** chặn test (PO phải làm rõ PRD/BDD) · `—` nếu `pass`, hoặc FAIL = script-bug (QC tự fix — tạm thời) |
418
+ | `qc_owner` | **SC đang chờ ai** (view "pending" của PM/PO): `dev` nếu FAIL = product-gap (defect thật → dev fix) · `po` nếu `skip`/`not_run` vì một **`DOC_GAP` 🔴 Blocker đang open** chặn test (PO phải làm rõ PRD/BDD) · **`qc` nếu FAIL = flaky** (QC phải điều tra nguyên nhân, **không** phải việc của dev) · `—` nếu `pass`, hoặc FAIL = script-bug (QC tự fix — tạm thời) |
249
419
  | `qc_blocked_by` | artifact liên kết: `GAP-{id}` khi bị chặn bởi spec gap (set ở đây) · `BUG-{id}` khi `/report-bug` đã được file cho product-gap (backfill bởi `/report-bug`) · `—` ngược lại |
250
420
 
251
421
  Set `qc_owner`/`qc_blocked_by` cùng với `qc_status`. Khi `pass`, **clear** cả hai về `—` — nhưng **PHẢI chạy §Đóng bug đã verify bên dưới TRƯỚC**, vì `qc_blocked_by` chính là con trỏ tới bug và clear xong là mất đường về.
@@ -354,6 +524,21 @@ một **mirror tiện lợi cục bộ**. File `trace-report.json` đã merge (c
354
524
  bên trong từng sub-agent.
355
525
 
356
526
 
527
+ ## Self-Review *(trước khi in Report)*
528
+
529
+ Theo 3 nhóm ở `{paths.qc_skills_dir}/_shared/self-review-principles.md` — **không chép lại ở đây**.
530
+
531
+ - **Bịa:** mỗi đề xuất phân loại FAIL dựa trên bằng chứng **đọc thật** từ trace/video/log — trích
532
+ được dòng lỗi, tên locator, response code — không phải *"thường thì đây là script-bug"*?
533
+ - **Nhảy bước:** **MỌI** FAIL đã qua chạy lại ×2 trước khi đề xuất — không có cái nào bị bỏ bước
534
+ vì "chắc chắn là gì rồi"? Guard `DRIFT`/`ORPHANED` đã áp cho **từng** row trước khi ghi `pass`,
535
+ không chỉ vài row?
536
+ - **Số liệu:** `{pass}`/`{fail}`/`{skip}`/`{flaky}` = **đúng output của test runner**
537
+ (`--reporter=list` / summary), không phải đếm lại bằng mắt qua context?
538
+
539
+ > Nhóm 3 ở trạm này là chỗ dễ sai nhất: kết quả chạy nằm trong output của runner, nhưng agent đã
540
+ > đọc qua nó một lần rồi **nhớ lại** lúc viết report. Lấy lại từ nguồn, đừng nhớ.
541
+
357
542
  ## Report
358
543
 
359
544
  **Đọc `.agent/steps/report-footer.md`** và áp đúng khuôn footer trong đó (Status Badge ·
@@ -361,11 +546,15 @@ Output Artifacts · Next) cho report cuối, kèm khối bên dưới.
361
546
 
362
547
  ```
363
548
  /qc-run-test Report — {UC-ID} ({qc-playwright})
364
- QC: ✅ {pass} pass | ❌ {fail} fail | ⏭️ {skip} skip (TCs: {total})
549
+ QC: ✅ {pass} pass | ❌ {fail} fail | ⏭️ {skip} skip | 🔄 {flaky} flaky (TCs: {total})
550
+ {chỉ khi có FAIL — ngược lại bỏ}
551
+ Phân loại FAIL (người đã xác nhận): script-bug {a} · product-gap {b} · flaky {c} → cách ly
552
+ {với mỗi flaky: "🔄 {UC-ID}-SC{N} — nghi: {nguyên nhân}" }
365
553
  Trace: {paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{active_platform}.tsv updated (qc_status, qc_run_at)
366
554
  {chỉ khi có bug đổi trạng thái — ngược lại bỏ}
367
555
  🐞 Bugs: {BUG-ID} → 🟢 Closed (verified {UC-ID}-SC{N})
368
556
  ⚠️ {BUG-ID} giữ 🟢 Open — SC pass nhưng bug chưa có Resolution, kiểm tra lại test
557
+ Self-review: {✅ sạch | ⚠️ {n} điểm cần chú ý — liệt kê}
369
558
  Next: /qc-report {UC-ID} ← sinh report + evidence
370
559
  /qc-review {UC-ID} ← review script đã sinh trước khi merge
371
560
  📊 Living Docs: chạy /validate-traces (hoặc /sync) để push qc_status lên dashboard spec-module.