@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
@@ -0,0 +1,162 @@
1
+ # `gap-verify` đủ bộ — bổ vào bản gốc, KHÔNG fork
2
+
3
+ | | |
4
+ |---|---|
5
+ | **Đợt** | 1 — Nền tảng |
6
+ | **Bước** | **6/6** của Đợt 1 — đóng Đợt 1 |
7
+ | **File sửa** | `steps/gap-verify.md` |
8
+ | **File test** | Không có test tự động — xem B4 |
9
+ | **Ngày xong** | 2026-09-11 · commit `8cfe9f3` |
10
+ | **Phụ thuộc** | không có |
11
+ | **Ai dùng nó** | `/qc-analyze` (gọi trực tiếp) **và** `steps/review-fanout.md` ← `/refine-prd` · `/review-context` — **3 lệnh** |
12
+
13
+ ---
14
+
15
+ # PHẦN A — Chuyện gì đang xảy ra
16
+
17
+ ## A1 · Vấn đề
18
+
19
+ Có một quy trình soát lại dùng chung: mỗi khi hệ thống phát hiện *"tài liệu thiếu chỗ này"*, quy trình đó bắt phát hiện ấy **tự chứng minh** trước khi được ghi nhận — mở lại tài liệu gốc đọc, đối chiếu, loại những cái bịa.
20
+
21
+ Quy trình đó thiếu **hai phần** so với bản gốc nó được chép ra: một bước chuẩn bị rõ ràng ở đầu, và một bảng soát nhanh ở cuối.
22
+
23
+ Trưởng phòng QC phát hiện đúng chỗ thiếu. Nhưng cách đề xuất bổ sung là **tạo một bản thứ hai** — và bản gốc đang được **ba** công đoạn khác nhau dùng chung.
24
+
25
+ ## A2 · Cách giải quyết, nói bằng một hình ảnh
26
+
27
+ > **Như sửa quy trình trong sổ tay công ty.** Thấy thiếu một bước thì **thêm vào quyển sổ
28
+ > chung**, chứ không photo ra một bản riêng cho phòng mình rồi sửa trên đó. Hai quyển sổ
29
+ > **sẽ** khác nhau — không phải nếu, mà là khi — và lúc đó không ai biết quyển nào đúng.
30
+
31
+ Bổ vào bản gốc thì hai phòng còn lại **cũng được nâng cấp**, miễn phí.
32
+
33
+ ## A3 · Xong rồi thì thấy gì khác
34
+
35
+ Quy trình soát lại giờ có một bước chuẩn bị rõ ràng ở đầu (mở đúng tài liệu nguồn ra trước, đếm xem có bao nhiêu việc phải soát) và một bảng tích ở cuối để dán ra dùng khi review.
36
+
37
+ Và hai công đoạn khác — `/refine-prd`, `/review-context` — **tự động có hai phần đó**, không phải làm gì thêm.
38
+
39
+ ## A4 · Thuật ngữ dùng ở trên
40
+
41
+ - **Fork** — tạo một bản sao riêng của một tài liệu rồi sửa trên bản sao đó.
42
+ - **Finding / gap** — một phát hiện kiểu *"tài liệu chưa nói rõ chỗ này"*.
43
+
44
+ ---
45
+
46
+ > ### ✅ Phép thử người đọc
47
+ >
48
+ > **Đã thử với:** tự soát · **ngày:** 2026-09-11 · **phải giải thích thêm chỗ nào:** chưa thử với người thật.
49
+
50
+ ---
51
+
52
+ # PHẦN B — Chi tiết kỹ thuật
53
+
54
+ ## B1 · Cách hiển nhiên là gì, và vì sao nó sai
55
+
56
+ ### Cách sai thứ nhất — fork, đúng như đề xuất
57
+
58
+ Đề xuất tạo `skills/qc/qa-analyst/gap-verify.md` là bản mở rộng, rồi cho `/qc-analyze` trỏ sang bản mới. Nghe rất hợp lý: bản mở rộng có phần chuyên biệt cho QC (`DOC_GAP.md` · `active_platform` · `GAP-UC{N}-{nnn}`), nên để nó riêng cho sạch.
59
+
60
+ Sai vì bản gốc được dùng ở **nhiều nơi hơn đề xuất tưởng**:
61
+
62
+ ```
63
+ steps/gap-verify.md
64
+ ← commands/qc-analyze.tmpl (gọi trực tiếp)
65
+ ← steps/review-fanout.md Phase 2.5
66
+ ← qc-analyze · refine-prd · review-context
67
+ ```
68
+
69
+ Fork sẽ nâng cấp **một** lệnh và **bỏ rơi hai** lệnh kia trên bản cũ. Sáu tháng sau, ai sửa một phép thử ở bản A không biết bản B tồn tại, và `/qc-analyze` với `/refine-prd` áp **hai luật khác nhau cho cùng một việc** — thẩm định phát hiện — **im lặng**, vì `self-check` không so nội dung hai file với nhau.
70
+
71
+ Đây chính là hình dạng lỗi mà cả kỷ luật `upstream_sha` + `bin/qc-base-map.json` của framework được dựng lên để chống. Fork ở đây là tự tạo lại vấn đề đó từ bên trong.
72
+
73
+ ### Cách sai thứ hai — copy nguyên phần chuyên biệt QC vào bản gốc
74
+
75
+ Đã quyết không fork thì chép nội dung bản mở rộng vào bản gốc.
76
+
77
+ Sai vì phần chuyên biệt viết cho QC: `DOC_GAP.md`, `active_platform`, định dạng `GAP-UC{N}-{nnn}`. Chép nguyên vào file dùng chung là bắt `/refine-prd` — vốn làm việc với findings YAML, không có `DOC_GAP.md` nào — đọc một quy trình nói về thứ nó không có.
78
+
79
+ Bản gốc **đã có sẵn** mục §Tham số (`FINDINGS` · `EVIDENCE_ROOT` · `VERDICT_FIELD`) đúng để làm việc này. Viết theo tham số, không theo tên cụ thể.
80
+
81
+ ### Cách sai thứ ba — bảng soát nhanh chép lại nội dung phía trên
82
+
83
+ Checklist thì phải đủ chi tiết để dùng một mình chứ.
84
+
85
+ Sai vì checklist là **bản rút gọn của nội dung phía trên** — tức một dạng **bản sao**, đúng thứ bước này vừa từ chối ở cách sai thứ nhất. Ai sửa một phép thử ở trên mà quên checklist thì hai chỗ lệch.
86
+
87
+ ## B2 · Cách làm đúng
88
+
89
+ **Bổ hai phần vào bản gốc**, viết theo tham số đã có:
90
+
91
+ **① GIAI ĐOẠN 0 — Chuẩn bị**, ba việc theo thứ tự. Đây là **bước hành động**, không phải mô tả — §Tham số nói *có những gì*, mục này nói *phải làm gì với chúng trước khi thẩm định finding đầu tiên*:
92
+
93
+ ```
94
+ 1. Đọc FINDINGS, lấy danh sách cái đang mở
95
+ 2. Dựng DOCUMENT_LIST — MỞ FILE NGUỒN THẬT từ EVIDENCE_ROOT
96
+ 3. Chốt VERDICT_FIELD, và ĐẾM tổng số finding phải thẩm định
97
+ ```
98
+
99
+ **Bước 2 là chỗ hay bị nhảy cóc nhất**, và có ghi chú riêng: agent đọc lướt danh sách finding rồi thẩm định **dựa trên trí nhớ** đọc spec từ lúc sinh finding, thay vì **mở lại** file thật. Hai việc khác nhau — verify phải là **một lượt đọc độc lập**, không phải một lượt nhớ lại. Đây là chỗ **thi hành** điều cấm ở §Nguyên tắc tối thượng (cấm dùng field evidence của finding làm bằng chứng cho chính nó).
100
+
101
+ **Bước 3 có một việc đề xuất không có:** **đếm** tổng số finding và ghi con số đó lại. Nó là **mẫu số** để biết đã đi hết chưa — thay vì bỏ dở giữa đường mà không ai thấy. Checklist ở cuối có một dòng đối chiếu lại con số này.
102
+
103
+ **② CHECKLIST NHANH**, với hai thứ chặn đúng rủi ro của chính nó:
104
+
105
+ - Mỗi dòng **trỏ về mục gốc** trong ngoặc: `(T1–T6)` · `(AP1–AP6)` · `(Q1–Q3)` · `(GIAI ĐOẠN 0/2/3)`.
106
+ - Một câu ở đầu bảng:
107
+
108
+ > ⚠️ **Bảng này là MỤC LỤC, KHÔNG phải định nghĩa.** Định nghĩa đủ nằm ở các mục phía trên.
109
+ > **Lệch nhau thì mục trên thắng.** Sửa một phép thử ở trên thì sửa dòng tương ứng ở đây —
110
+ > đừng sửa ở đây rồi tưởng đã xong.
111
+
112
+ **Và KHÔNG tạo `skills/qc/qa-analyst/gap-verify.md`.** Khi ốp Đợt 2 (copy skill từ thư mục đề xuất), phải **bỏ file đó khỏi danh sách copy** — dễ sót vì nó nằm lẫn trong `skills/qc/qa-analyst/`.
113
+
114
+ ## B3 · Nếu làm sai thì hỏng theo kiểu nào
115
+
116
+ **Kiểu hỏng: im lặng** 🔴
117
+
118
+ | | |
119
+ |---|---|
120
+ | Hỏng ở đâu | Hai bản của một cổng thẩm định 200+ dòng, một cho `/qc-analyze`, một cho `/refine-prd` |
121
+ | Ai phát hiện | **Không ai** — `self-check` canh *file được tham chiếu có tồn tại không*, không canh *hai file có cùng nội dung không* |
122
+ | Sau bao lâu | Lần đầu ai đó sửa một phép thử — có thể vài tháng |
123
+ | Vì sao đắt | Khi đó câu hỏi *"framework thẩm định phát hiện theo luật nào?"* **không còn một câu trả lời**. Và hai lệnh sẽ cho hai kết quả khác nhau trên cùng một tập dữ liệu, mà không ai giải thích được vì sao |
124
+
125
+ ## B4 · Verify bằng gì
126
+
127
+ | # | Phép thử | Kết quả |
128
+ |---|---|---|
129
+ | 1 | 🔴 Chỉ có **một** bản nguồn: `find . -name "gap-verify.md"` | ✅ `steps/` — hai bản còn lại là `.agent/` và `core/` do build sinh |
130
+ | 2 | Hai phần mới có mặt: `grep -n "^## GIAI ĐOẠN 0\|^## CHECKLIST NHANH" steps/gap-verify.md` | ✅ |
131
+ | 3 | Cả hai nơi tiêu thụ vẫn trỏ đúng | ✅ `qc-analyze.tmpl` + `review-fanout.md` |
132
+ | 4 | `build` · `self-check` · `test` · `lint-trace` | ✅ 212/212 |
133
+ | 5 | Ngân sách không đổi (`steps/` đọc lúc chạy) | ✅ 1208 KB |
134
+
135
+ **Phép thử tay chưa chạy** — phải kiểm **cả hai** nơi tiêu thụ, vì đây là bước đụng file dùng chung:
136
+
137
+ | # | Phép thử | Kết quả mong đợi |
138
+ |---|---|---|
139
+ | 6 | `/qc-analyze {TICKET-ID}` trên UC có gap | Chạy qua GIAI ĐOẠN 0 trước; phần T1–T6 không hồi quy |
140
+ | **7** | 🔴 `/refine-prd {TICKET-ID}` | Cũng chạy được, **và nhận được GIAI ĐOẠN 0 mới** |
141
+
142
+ Phép thử 7 là phép thử quyết định — **nếu fork thì `/refine-prd` sẽ không thay đổi gì**. Nó chứng minh phần lời của quyết định không-fork.
143
+
144
+ ## B5 · Bài học
145
+
146
+ - **2026-09-11** — Kế hoạch nói *"2 lệnh tiêu thụ"*; thực tế **3** (`/review-context` cũng dùng qua `review-fanout`). Con số đó làm lập luận chống fork **mạnh hơn**, không yếu đi.
147
+ → **Bài học chuyển giao được:** trước khi quyết định fork hay bổ sung một file dùng chung, **grep hết consumer, gồm cả consumer gián tiếp qua một file trung gian**. Số lượng consumer thường lớn hơn trí nhớ, và nó là dữ kiện chính của quyết định.
148
+
149
+ - **2026-09-11** — Checklist suýt tái tạo đúng vấn đề mà bước này từ chối: nó **là** một bản sao rút gọn. Chặn bằng hai thứ rẻ (trỏ về mục gốc + câu "mục trên thắng") thay vì bỏ hẳn checklist.
150
+ → **Bài học chuyển giao được:** khi một thứ **buộc phải** trùng lặp nội dung (mục lục · bảng tóm tắt · checklist), làm rõ **bản nào thắng khi lệch** ngay trong chính nó. Đó là chi phí rẻ nhất để giữ một bản sao hợp pháp không biến thành một nguồn sự thật thứ hai.
151
+
152
+ ## B6 · Copy được / không copy được
153
+
154
+ | | |
155
+ |---|---|
156
+ | ✅ **Copy được sang dự án khác** | Nguyên tắc **bổ vào bản gốc thay vì fork**, và cách kiểm trước khi quyết: grep hết consumer kể cả gián tiếp. Cách **tổng quát hoá bằng tham số** để một file dùng chung phục vụ nhiều consumer có dữ liệu khác nhau. Và cách xử lý bản-sao-hợp-pháp: trỏ về mục gốc + khai rõ bản nào thắng |
157
+ | ⚠️ **Chỉ đúng ở đây** | Nội dung T1–T6, 5 anti-pattern, 3 câu hỏi lọc — gắn với việc thẩm định gap tài liệu của pipeline này |
158
+
159
+ ## B7 · Link
160
+
161
+ - Kế hoạch trước khi làm: [`../exec-d1-b6-gap-verify-mo-rong.md`](../exec-d1-b6-gap-verify-mo-rong.md)
162
+ - Bước liên quan: [`1-04`](1-04-self-review-dung-chung.md) (`gap-verify` sâu per-finding · self-review rộng per-run — bổ sung nhau, không thay nhau)
@@ -0,0 +1,85 @@
1
+ ---
2
+ title: Nhật ký từng bước — đợt đại phẫu phần QC
3
+ updated: 2026-09-11
4
+ format: theo D:\base\sdd-re-engine\docs\build-journal\_TEMPLATE-BUOC.md
5
+ ---
6
+
7
+ # Nhật ký từng bước
8
+
9
+ > **Đây là ghi chép về việc ĐÃ LÀM.** Kế hoạch nằm ở [`../02-lo-trinh.md`](../02-lo-trinh.md),
10
+ > và bản thiết kế trước khi làm của từng bước nằm ở `../exec-d{N}-b{M}-*.md`. Bảy trong mười hai
11
+ > bước **lệch kế hoạch** — file ở đây ghi cái đã xảy ra, file `exec-*` ghi cái đã định làm.
12
+ > Đọc cả hai thì thấy vì sao đổi.
13
+
14
+ Mỗi file có **hai phần cho hai người đọc khác nhau**:
15
+
16
+ - **Phần A** — cho người không làm kỹ thuật (PO · QC · quản lý · dev mới). Đọc xong phần A là
17
+ **dừng được**.
18
+ - **Phần B** — cho người sẽ sửa code. Phần B **không** phải bản chi tiết hoá của phần A; đó là
19
+ một bài viết khác.
20
+
21
+ ---
22
+
23
+ ## Đợt 0 — Hợp đồng test-id
24
+
25
+ Bệnh nền: hợp đồng test-id giữa FE và QC **có** nhưng hỏng 4 chỗ, nên QC không dựa được và phải
26
+ tự dò selector từ DOM. Script vì vậy giòn — dev đổi tên một class là test vỡ, và **không ai báo**.
27
+
28
+ | # | Bước | Kiểu hỏng đã đóng |
29
+ |---|---|---|
30
+ | 1 | [Cho `@trace.testid_attr` một chỗ để ở](0-01-testid-attr-co-cho-o.md) | Im lặng 🔴 |
31
+ | 2 | [Một nguồn duy nhất cho tên thuộc tính](0-02-mot-nguon-cho-testid-attr.md) | Im lặng 🔴 |
32
+ | 3 | [Skill thôi dạy dò DOM trước](0-03-skill-thoi-day-do-dom.md) | Im lặng 🔴 |
33
+ | 4 | [Máy canh bảng hợp đồng (T15/T16 + R8e)](0-04-may-canh-hop-dong.md) | Im lặng 🔴 |
34
+ | 5 | [Dọn nhãn cột và tên mục đã chết](0-05-don-nhan-cot-va-2b.md) | Im lặng 🔴 |
35
+ | 6 | [Chốt hợp đồng TRƯỚC code — mở luồng song song](0-06-hop-dong-truoc-code.md) | Im lặng 🔴 + Bỏ chạy 🟠 |
36
+
37
+ ## Đợt 1 — Nền tảng
38
+
39
+ Bệnh nền: sáu trạm QC **không có phép kiểm cơ học nào**. Bỏ sót business rule · scenario chưa có
40
+ test case · fail phân loại sai — cả ba đều xảy ra **im lặng**.
41
+
42
+ | # | Bước | Kiểu hỏng đã đóng |
43
+ |---|---|---|
44
+ | 1 | [Guard BR-tag — đối chiếu tag BDD với BR sinh ra](1-01-guard-br-tag.md) | Im lặng 🔴 |
45
+ | 2 | [Guard SC coverage — mọi scenario phải có test case](1-02-guard-sc-coverage.md) | Im lặng 🔴 |
46
+ | 3 | [Fail 3 nhãn + chạy lại ×2 trước khi kết luận](1-03-fail-3-nhan.md) | Nói dối 🔴 |
47
+ | 4 | [Bộ nguyên tắc tự kiểm + ranh giới với Guard](1-04-self-review-dung-chung.md) | Nói dối 🔴 |
48
+ | 5 | [Spec là dữ liệu, không phải mệnh lệnh](1-05-spec-la-du-lieu.md) | Nói dối 🔴 |
49
+ | 6 | [`gap-verify` đủ bộ — bổ vào bản gốc, không fork](1-06-gap-verify-du-bo.md) | Im lặng 🔴 |
50
+
51
+ ---
52
+
53
+ ## Một việc không phải "bước"
54
+
55
+ **Nâng ngưỡng dung lượng lệnh 1200 → 1450 KB** (commit `bb53742`).
56
+
57
+ Có một phép thử canh tổng dung lượng các file lệnh. Nó tồn tại để bắt **một** chuyện: có ai lén
58
+ nhét một đoạn dùng chung to vào mọi lệnh (trước đây từng là ~2069 KB, đã gỡ xuống). Nó **không**
59
+ canh "framework có thêm nội dung mới hay không" — đó là tăng trưởng chính đáng.
60
+
61
+ Mốc: **1177 KB** trước đợt mổ → **1208 KB** sau Đợt 1. Bước `1-03` chạm **đúng 1200** — nếu chưa
62
+ nâng trước thì nó đã đỏ ở giữa một bước nội dung, và người ta sẽ nâng trong lúc gấp, tức **nâng
63
+ mà không ai đọc lý do**. Nên nâng trước một lần, và ghi lý do vào **chính file test** thay vì
64
+ commit message — người đọc rule sáu tháng sau sẽ mở file test, không mở git log.
65
+
66
+ 1450 vẫn cách rất xa 2069, nên rule vẫn bắt được đúng lớp lỗi nó sinh ra để bắt.
67
+
68
+ ---
69
+
70
+ ## Bốn lệnh kiểm phải xanh sau mỗi bước
71
+
72
+ ```bash
73
+ node bin/build.js # đúc .tmpl → .md → core/
74
+ node bin/self-check.js # R1–R16 — canh framework tự mâu thuẫn
75
+ node test/run.js # 212 test, gồm ngưỡng dung lượng
76
+ node bin/lint-trace.js # T1–T18 trên dữ liệu thật
77
+ ```
78
+
79
+ ## Còn nợ gì — đọc trước khi tin 12 bước này đã xong
80
+
81
+ | | Việc | Vì sao chưa làm |
82
+ |---|---|---|
83
+ | 🔴 | **Chưa ai chạy thử trên PRD thật** | Bốn lệnh trên chỉ canh **contract của framework**, không chạy `/qc-analyze` hay `/map-testids` lên một spec thật. Phép thử thật cần một dự án dùng thuộc tính **không** mặc định (`data-test`/`data-qa`) và một màn hình brownfield |
84
+ | 🟠 | Ba câu chèn (prompt injection) chưa kiểm tay | Phải làm trên **sandbox** — không commit một PRD có câu chèn vào git |
85
+ | 🟠 | **Phép thử người đọc chưa làm với người thật** | Mọi file ở đây ghi `tự soát`. Cần một người không làm kỹ thuật đọc Phần A rồi kể lại — xem mục "Phép thử người đọc" trong từng file |
@@ -0,0 +1,147 @@
1
+ ---
2
+ buoc: Đợt 0 — Bước 1
3
+ title: Thêm @trace.testid_attr vào header template tech-design
4
+ phu_thuoc: không
5
+ trang_thai: đã làm
6
+ ---
7
+
8
+ # Đợt 0 · Bước 1 — Cho `@trace.testid_attr` một chỗ để ở
9
+
10
+ ← [`01-checklist.md`](01-checklist.md) · [`02-lo-trinh.md`](02-lo-trinh.md)
11
+
12
+ > ✅ **Đã làm rồi.** File này là bản **ĐỊNH làm**. Cái **ĐÃ xảy ra** — gồm chỗ lệch
13
+ > so với kế hoạch này và lý do — ghi ở [`buoc/0-01-testid-attr-co-cho-o.md`](buoc/0-01-testid-attr-co-cho-o.md).
14
+
15
+ ## 1. Vì sao
16
+
17
+ Kể bằng một chuyện có thể xảy ra tuần sau:
18
+
19
+ Dự án FE dùng thuộc tính `data-test` (không phải `data-testid` — nhiều team chọn vậy). QC sinh
20
+ script Playwright, dùng `page.getByTestId('login-submit-btn')`. Playwright mặc định tìm
21
+ `data-testid`, nên nó **không tìm thấy gì cả**. Test đỏ với thông báo:
22
+
23
+ ```
24
+ Error: locator.click: Timeout 30000ms exceeded.
25
+ Call log: waiting for getByTestId('login-submit-btn')
26
+ ```
27
+
28
+ Thông báo này **trông y hệt một bug sản phẩm** — "nút submit không hiển thị". QC mở bug, gán
29
+ cho dev. Dev mở app, bấm thử, nút vẫn ở đó. Mất một ngày của hai người, và cuối cùng phát hiện
30
+ chỉ là sai tên thuộc tính.
31
+
32
+ Framework **đã lường trước** chuyện này: có một field tên `@trace.testid_attr` để ghi *tên
33
+ thuộc tính* mà dự án dùng, và `/qc-run-test` đọc nó để cấu hình Playwright trước khi chạy. Cơ
34
+ chế đúng.
35
+
36
+ Nhưng field đó **không có chỗ nào trong template tech-design** — nên trong thực tế không ai
37
+ điền, và mọi lần chạy đều rơi vào nhánh fallback.
38
+
39
+ ## 2. Tình trạng hiện tại
40
+
41
+ **Field được khai đầy đủ trong schema** — `bin/trace-schema.json:471-485`:
42
+
43
+ ```json
44
+ "name": "@trace.testid_attr",
45
+ "artifact": "tech-design.md",
46
+ "scope": "file",
47
+ "written_by": ["map-testids"],
48
+ "read_by": ["generate-code", "qc-run-test", "qc-design-test"]
49
+ ```
50
+
51
+ **Nhưng template không có nó** — `templates/tech-design.template.md:35-48` liệt kê 12 field, và
52
+ không có `testid_attr`:
53
+
54
+ ```
55
+ ---
56
+ @trace.id: {TICKET-ID}
57
+ @trace.domain: {domain}
58
+ @trace.prd: {TICKET-ID}
59
+ @trace.ucs: {TICKET-ID}-UC1, {TICKET-ID}-UC2{, …}
60
+ @trace.service: {service — từ header BDD @trace.service}
61
+ @trace.module: {module liên quan — vd dotnet, angular}
62
+ @trace.platforms: {system | web | app | webview | …}
63
+ @trace.bdd_versions: {MAP theo từng platform …}
64
+ @trace.api_source: {existing | —}
65
+ @trace.revision: 1
66
+ @trace.status: draft
67
+ @trace.generated_at: {YYYY-MM-DD}
68
+ ---
69
+ ```
70
+
71
+ Kiểm lại bằng grep: `grep -rn "testid_attr" templates/ steps/ rules/` → **không kết quả**.
72
+
73
+ **`/map-testids` chỉ ghi field này ở một nhánh** — `commands/map-testids.tmpl:58-64` (Step 5):
74
+
75
+ - `:61` — *"Nếu tech-doc **tồn tại** → cập nhật **bảng** §4.5.6 của platform này"* → chỉ nói
76
+ bảng, **không nói header**.
77
+ - `:62` — *"Nếu **chưa** tồn tại (pure brownfield) → ghi một file tối thiểu: header `@trace`
78
+ (gồm `@trace.testid_attr`) + §4.5.6"* → chỉ nhánh này ghi header.
79
+
80
+ Đường đi thường của dự án là: `/generate-tech-docs` tạo tech-doc trước, rồi `/map-testids` chạy
81
+ sau. Tức là **luôn** đi vào nhánh `:61` — nhánh không ghi header.
82
+
83
+ Kết quả: `/qc-run-test` rơi vào nhánh *"Không tìm thấy field"* (`commands/qc-run-test.tmpl:53`),
84
+ in cảnh báo mềm rồi đoán theo platform.
85
+
86
+ ## 3. Sẽ đổi thành gì
87
+
88
+ **a) Thêm field vào template**, đặt ngay sau `@trace.platforms` (vì nó là thuộc tính của
89
+ platform client):
90
+
91
+ ```
92
+ @trace.platforms: {system | web | app | webview | … — tuỳ thư mục BDD nào tồn tại}
93
+ @trace.testid_attr: {tên thuộc tính chứa test-id của stack client — web data-testid|data-test|data-qa · RN testID · Flutter Key/Semantics(identifier:) · iOS accessibilityIdentifier. Bỏ trống nếu doc chỉ phủ platform `system`.}
94
+ ```
95
+
96
+ **b) Sửa `/map-testids` Step 5** để **cả hai nhánh** đều ghi/làm mới header, không chỉ nhánh
97
+ brownfield. Câu ở `:61` đổi thành đại ý: *cập nhật bảng §4.5.6 của platform này, **và** đảm bảo
98
+ header `@trace` có `@trace.testid_attr` với giá trị vừa phân giải ở Step 0 (`:29`) — thiếu thì
99
+ thêm, lệch thì cảnh báo chứ không im lặng ghi đè.*
100
+
101
+ Vì sao "lệch thì cảnh báo": nếu tech-doc đang ghi `data-testid` mà `/map-testids` phân giải ra
102
+ `data-test`, thì một trong hai sai — có thể FE vừa đổi convention, có thể module khai sai. Ghi
103
+ đè im lặng là làm mất tín hiệu.
104
+
105
+ ## 4. Sửa file nào
106
+
107
+ | File | Sửa gì |
108
+ |---|---|
109
+ | `templates/tech-design.template.md` | Thêm 1 dòng `@trace.testid_attr` vào khối `@trace` (sau `:42`) |
110
+ | `commands/map-testids.tmpl` | Step 5 (`:58-64`): nhánh "tech-doc tồn tại" cũng ghi/làm mới header |
111
+
112
+ Không đụng `bin/trace-schema.json` — field đã khai đúng, không cần thêm gì.
113
+
114
+ ## 5. Kiểm thế nào để biết đã xong
115
+
116
+ ```bash
117
+ # 1. Field đã có trong template
118
+ grep -n "testid_attr" templates/tech-design.template.md # phải có 1 dòng
119
+
120
+ # 2. Build + máy tự kiểm còn xanh
121
+ node bin/build.js && node bin/self-check.js && node test/run.js
122
+ ```
123
+
124
+ Kiểm bằng tay trên một PRD web thật:
125
+
126
+ 1. Chạy `/generate-tech-docs {TICKET-ID}` → mở tech-doc, header phải có dòng
127
+ `@trace.testid_attr:` (dù còn là placeholder).
128
+ 2. Chạy `/map-testids {UC-ID}` → header phải có **giá trị thật** (vd `data-testid`), không còn
129
+ placeholder.
130
+ 3. Chạy `/qc-run-test {UC-ID}` → report **không** được in cảnh báo
131
+ `⚠️ Tech-doc thiếu @trace.testid_attr`.
132
+
133
+ Bước 3 là phép thử thật: nếu cảnh báo vẫn hiện, field vẫn chưa tới được nơi cần dùng.
134
+
135
+ ## 6. Nếu bỏ qua thì hỏng gì
136
+
137
+ Field vẫn tồn tại trên giấy, vẫn được 3 lệnh khai là đọc, và vẫn không ai điền. `/qc-run-test`
138
+ mãi rơi vào nhánh fallback `:53` → đoán tên thuộc tính theo platform. Đúng với dự án dùng
139
+ `data-testid` mặc định; **trượt 100% locator** với dự án dùng `data-test`/`data-qa`.
140
+
141
+ Và nó hỏng theo kiểu tệ nhất — `commands/qc-run-test.tmpl:55` đã nói thẳng:
142
+
143
+ > *"hỏng **im lặng theo kiểu tệ nhất**: test fail với 'element not found' — trông y hệt một bug
144
+ > sản phẩm, nên QC sẽ đi mở bug thay vì sửa selector."*
145
+
146
+ Ngoài ra Bước 4 (máy canh §4.5.6) có một check phụ thuộc field này, nên bỏ b1 thì b4 mất một
147
+ phần ba giá trị.
@@ -0,0 +1,152 @@
1
+ ---
2
+ buoc: Đợt 0 — Bước 2
3
+ title: Chốt một nguồn duy nhất cho @trace.testid_attr
4
+ phu_thuoc: d0-b1
5
+ trang_thai: đã làm
6
+ ---
7
+
8
+ # Đợt 0 · Bước 2 — Hai nửa hợp đồng đang đọc hai file khác nhau
9
+
10
+ ← [`01-checklist.md`](01-checklist.md) · [`02-lo-trinh.md`](02-lo-trinh.md)
11
+
12
+ > ✅ **Đã làm rồi.** File này là bản **ĐỊNH làm**. Cái **ĐÃ xảy ra** — gồm chỗ lệch
13
+ > so với kế hoạch này và lý do — ghi ở [`buoc/0-02-mot-nguon-cho-testid-attr.md`](buoc/0-02-mot-nguon-cho-testid-attr.md).
14
+
15
+ > **Đây là lỗi nặng nhất của Đợt 0.** Bước 1 cho field một chỗ để ở; bước này đảm bảo hai bên
16
+ > cùng đọc đúng chỗ đó.
17
+ >
18
+ > ⚠️ **Đừng nhầm hai thứ khi làm chung với [`exec-d0-b6`](exec-d0-b6-contract-truoc-code.md):**
19
+ > bước này nói về **TÊN THUỘC TÍNH** (`@trace.testid_attr`, 1 giá trị ở header) — fallback theo
20
+ > `active_module` **vẫn giữ**, kèm cảnh báo mềm. Còn b6 bỏ hẳn nhánh `/generate-code` tự sinh
21
+ > **GIÁ TRỊ id** khi §4.5.6 rỗng. Một cái là tên thuộc tính, một cái là giá trị id — bỏ nhầm là
22
+ > mất fallback đang cần.
23
+
24
+ ## 1. Vì sao
25
+
26
+ `@trace.testid_attr` là **hợp đồng giữa hai bên**:
27
+
28
+ ```
29
+ FE (/generate-code) ──đọc field──→ gắn thuộc tính này lên element
30
+ QC (/qc-run-test) ──đọc field──→ cấu hình Playwright tìm thuộc tính này
31
+ ```
32
+
33
+ Hợp đồng chỉ có nghĩa khi **cả hai đọc cùng một bản**. Hiện tại FE đọc bản ở file A, QC đọc bản
34
+ ở file B. Nếu hai bản khác nhau — hoặc một bản không tồn tại — thì FE gắn một kiểu, QC tìm một
35
+ kiểu, và không có gì báo.
36
+
37
+ Chuyện cụ thể: dự án khai `@trace.testid_attr: data-test` trong tech-doc. QC đọc được, cấu hình
38
+ đúng, tìm `data-test`. Còn `/generate-code` không đọc file đó — nó chỉ đọc header `.feature`,
39
+ không thấy gì, nên rơi về mặc định theo module là `data-testid`. FE gắn `data-testid`, QC tìm
40
+ `data-test`. **Không trùng một element nào.**
41
+
42
+ ## 2. Tình trạng hiện tại
43
+
44
+ **Nửa QC — đọc từ `tech-design.md`** (`commands/qc-run-test.tmpl:45-46`):
45
+
46
+ > *"**TÊN THUỘC TÍNH test-id: đọc `@trace.testid_attr`, KHÔNG tự suy từ platform.** Đọc
47
+ > `@trace.testid_attr` từ **header tech-doc gộp** (do `/map-testids` ghi — nó đã phân giải một
48
+ > lần cho cả feature). Đây là **nửa QC của contract FE↔QC**: `/generate-code` đọc chính field
49
+ > này để emit thuộc tính lên element."*
50
+
51
+ **Schema cũng nói `tech-design.md`** (`bin/trace-schema.json:473`):
52
+
53
+ ```json
54
+ "artifact": "tech-design.md",
55
+ "scope": "file",
56
+ "written_by": ["map-testids"]
57
+ ```
58
+
59
+ **Nửa FE — đọc từ `.feature`** (`commands/generate-code.tmpl:505`):
60
+
61
+ > *"2. **Emit qua attribute platform** — chọn attribute theo **`active_module`** (bảng dưới,
62
+ > mặc định). Chỉ override khi header `.feature` khai tường minh `@trace.testid_attr={attr}`
63
+ > (hiếm; dùng cho stack lai)."*
64
+
65
+ Ba điểm lệch trong một câu:
66
+
67
+ | | Nửa QC + schema | Nửa FE |
68
+ |---|---|---|
69
+ | Field nằm ở đâu | `tech-design.md` | header `.feature` |
70
+ | Vai trò của field | **nguồn chính** — "KHÔNG tự suy từ platform" | **override hiếm** cho stack lai |
71
+ | Nguồn mặc định | không có; thiếu field thì cảnh báo rồi mới fallback | `active_module` |
72
+
73
+ Và điều trớ trêu: `qc-run-test.tmpl:55` có sẵn một đoạn giải thích **vì sao không được suy từ
74
+ platform**:
75
+
76
+ > *"suy từ platform là **phát biểu lại một sự thật đã được ghi ở nơi khác** — đúng lớp lỗi mà
77
+ > `bin/trace-schema.json` sinh ra để chống."*
78
+
79
+ Đó chính xác là điều `/generate-code` đang làm ở nửa bên kia.
80
+
81
+ ## 3. Sẽ đổi thành gì
82
+
83
+ **Chốt một nguồn: header `tech-design.md`.** Ba lý do:
84
+
85
+ 1. Khớp `bin/trace-schema.json:473` (`artifact: tech-design.md`) — không phải sửa schema.
86
+ 2. Khớp `/map-testids`, lệnh **duy nhất** được khai là `written_by` của field.
87
+ 3. Đúng cấp phạm vi: thuộc tính test-id là **một** giá trị cho cả PRD/platform, không phải mỗi
88
+ UC một giá trị. `.feature` là cấp UC → đặt field ở đó là mời gọi 5 UC khai 5 giá trị khác
89
+ nhau cho cùng một FE.
90
+
91
+ **Sửa `commands/generate-code.tmpl:505`** thành thứ tự ưu tiên rõ ràng:
92
+
93
+ ```
94
+ 1. Header tech-doc gộp {TICKET-ID}-tech-design.md → @trace.testid_attr ← NGUỒN CHÍNH
95
+ 2. Không có field → cảnh báo mềm + fallback theo active_module
96
+ ```
97
+
98
+ Khuôn cảnh báo dùng lại đúng cái đã có ở `qc-run-test.tmpl:53`, để hai bên nói cùng một giọng:
99
+
100
+ ```
101
+ ⚠️ Tech-doc thiếu @trace.testid_attr — fallback theo module ({attr mặc định}).
102
+ Nếu FE dùng thuộc tính khác thì MỌI locator của QC sẽ trượt.
103
+ Chạy /map-testids {UC-ID} để ghi field này.
104
+ ```
105
+
106
+ **Bỏ hẳn đường đọc từ header `.feature`?** Không — nhưng hạ nó xuống dưới cùng và ghi rõ nó là
107
+ lối cũ (vd một PRD trải hai platform với hai attribute khác nhau, `.feature` của từng platform
108
+ có thể khai riêng). Điều kiện: nếu **cả hai** nơi đều có mà **lệch nhau** → cảnh báo, ưu tiên
109
+ tech-doc, **không im lặng chọn một bên**.
110
+
111
+ ## 4. Sửa file nào
112
+
113
+ | File | Sửa gì |
114
+ |---|---|
115
+ | `commands/generate-code.tmpl` | Mục §Test Selectors ý 2 (`:505`) — đổi thứ tự nguồn; thêm khối cảnh báo mềm |
116
+ | `commands/qc-run-test.tmpl` | Không đổi logic. Chỉ sửa câu ở `:46` nếu cần cho khớp câu chữ mới của `generate-code` |
117
+ | `docs/explain/09-generate-code.md` | Cập nhật ma trận phase (`:67` — dòng `Test Selectors (emit test-id)`) nếu nó mô tả nguồn cũ |
118
+
119
+ Không đụng `bin/trace-schema.json`.
120
+
121
+ ## 5. Kiểm thế nào để biết đã xong
122
+
123
+ ```bash
124
+ # 1. generate-code không còn coi .feature là nguồn chính
125
+ grep -n "testid_attr" commands/generate-code.tmpl
126
+ # → phải thấy "tech-design" đứng trước ".feature"
127
+
128
+ # 2. Build + máy tự kiểm
129
+ node bin/build.js && node bin/self-check.js && node test/run.js
130
+ ```
131
+
132
+ Kiểm bằng tay — **phép thử quyết định** của bước này. Trên một dự án dùng thuộc tính **không**
133
+ mặc định:
134
+
135
+ 1. Đặt `@trace.testid_attr: data-test` vào header tech-doc.
136
+ 2. Chạy `/generate-code {UC-ID} --phase=ui` → mở code FE vừa sinh, element phải mang
137
+ `data-test="..."`, **không** phải `data-testid="..."`.
138
+ 3. Chạy `/qc-run-test {UC-ID}` → script phải cấu hình `testIdAttribute: 'data-test'` và locator
139
+ phải khớp.
140
+
141
+ Nếu bước 2 vẫn ra `data-testid`, tức `/generate-code` chưa đọc đúng nguồn.
142
+
143
+ ## 6. Nếu bỏ qua thì hỏng gì
144
+
145
+ Hợp đồng FE↔QC vẫn là hai bản độc lập. Dự án nào dùng `data-testid` mặc định thì **may mà
146
+ trùng**, nên lỗi này ẩn — nó chỉ nổ ở dự án dùng `data-test`/`data-qa`, và nổ dưới dạng "test
147
+ đỏ hàng loạt không rõ lý do".
148
+
149
+ Nặng hơn: Bước 1 (thêm field vào template) sẽ **tạo cảm giác an toàn sai**. Field có mặt, có
150
+ giá trị, ai đọc tech-doc cũng thấy — nhưng `/generate-code` vẫn không dùng nó. Tức là làm b1 mà
151
+ bỏ b2 thì tệ hơn không làm gì: trước đây không ai tưởng field có tác dụng, sau b1 thì mọi người
152
+ tưởng có.