@educa-corp/sdd-framework 0.9.7 → 0.9.8

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/qc-base-map.json +13 -11
  2. package/bin/self-check.js +49 -4
  3. package/bin/trace-schema.json +3226 -3187
  4. package/core/FRAMEWORK_VERSION +1 -1
  5. package/core/commands/qc-analyze.md +2 -2
  6. package/core/commands/qc-automation-assess.md +3 -3
  7. package/core/commands/qc-design-script.md +60 -30
  8. package/core/commands/qc-design-test.md +79 -7
  9. package/core/commands/qc-plan.md +1 -1
  10. package/core/commands/qc-report.md +85 -76
  11. package/core/commands/qc-review-script.md +25 -16
  12. package/core/commands/qc-review-testcase.md +8 -7
  13. package/core/commands/qc-run-manualtest.md +1 -1
  14. package/core/commands/qc-run-script.md +15 -8
  15. package/core/modules/qc-playwright-ts/module.yaml +13 -0
  16. package/core/modules/qc-playwright-ts/stack-profile.yaml +99 -0
  17. package/core/modules/qc-wdio-appium/module.yaml +20 -0
  18. package/core/modules/qc-wdio-appium/stack-profile.yaml +107 -0
  19. package/core/skills/qc/qa-analyst/data-flow.md +1 -1
  20. package/core/skills/qc/qa-automation-assess/matrix.md +6 -3
  21. package/core/skills/qc/{qa-runner → qa-designer}/exploratory/session.md +8 -2
  22. package/core/skills/qc/qa-designer/functional/api.md +1 -1
  23. package/core/skills/qc/qa-designer/functional/job.md +128 -0
  24. package/core/skills/qc/qa-designer/integration/api.md +1 -1
  25. package/core/skills/qc/qa-designer/integration/db.md +1 -1
  26. package/core/skills/qc/qa-designer/integration/{kafka.md → queue.md} +20 -4
  27. package/core/skills/qc/qa-designer/shared/skill-decision-tree.md +17 -0
  28. package/core/skills/qc/qa-designer/shared/tc-metadata-format.md +17 -0
  29. package/core/skills/qc/qa-reviewer/script/_shared/review-rules.md +121 -0
  30. package/core/skills/qc/qa-reviewer/script/api/auth.md +49 -0
  31. package/core/skills/qc/qa-reviewer/script/api/endpoint.md +89 -0
  32. package/core/skills/qc/qa-reviewer/script/api/security.md +46 -0
  33. package/core/skills/qc/qa-reviewer/script/exploratory.md +2 -2
  34. package/core/skills/qc/qa-reviewer/script/mobile/e2e.md +41 -0
  35. package/core/skills/qc/qa-reviewer/script/mobile/functional.md +90 -0
  36. package/core/skills/qc/qa-reviewer/script/mobile/integration.md +41 -0
  37. package/core/skills/qc/qa-reviewer/script/mobile/non-functional.md +43 -0
  38. package/core/skills/qc/qa-reviewer/script/web/e2e.md +46 -0
  39. package/core/skills/qc/qa-reviewer/script/web/functional.md +111 -0
  40. package/core/skills/qc/qa-reviewer/script/web/integration.md +46 -0
  41. package/core/skills/qc/qa-reviewer/script/web/non-functional.md +49 -0
  42. package/core/skills/qc/qa-reviewer/shared/read-doc-gap-inputs.md +1 -1
  43. package/core/skills/qc/qa-reviewer/shared/review-file-template.md +26 -7
  44. package/core/skills/qc/qa-reviewer/test-case/e2e.md +1 -1
  45. package/core/skills/qc/qa-reviewer/test-case/exploratory.md +1 -1
  46. package/core/skills/qc/qa-reviewer/test-case/functional.md +1 -1
  47. package/core/skills/qc/qa-reviewer/test-case/integration.md +1 -1
  48. package/core/skills/qc/qa-reviewer/test-case/non-functional.md +1 -1
  49. package/core/skills/qc/qa-script-designer/_shared/api-conventions.md +94 -0
  50. package/core/skills/qc/qa-script-designer/_shared/file-naming-and-folders.md +109 -0
  51. package/core/skills/qc/qa-script-designer/_shared/mobile-conventions.md +196 -0
  52. package/core/skills/qc/qa-script-designer/_shared/web-conventions.md +257 -0
  53. package/core/skills/qc/qa-script-designer/api/auth.md +43 -0
  54. package/core/skills/qc/qa-script-designer/api/endpoint.md +61 -0
  55. package/core/skills/qc/qa-script-designer/api/security.md +41 -0
  56. package/core/skills/qc/qa-script-designer/mobile/e2e.md +35 -0
  57. package/core/skills/qc/qa-script-designer/mobile/functional/feature.md +32 -0
  58. package/core/skills/qc/qa-script-designer/mobile/functional/screen.md +42 -0
  59. package/core/skills/qc/qa-script-designer/mobile/integration.md +39 -0
  60. package/core/skills/qc/qa-script-designer/mobile/non-functional.md +39 -0
  61. package/core/skills/qc/qa-script-designer/web/e2e.md +36 -0
  62. package/core/skills/qc/qa-script-designer/web/functional/api.md +39 -0
  63. package/core/skills/qc/qa-script-designer/web/functional/gui-feature.md +34 -0
  64. package/core/skills/qc/qa-script-designer/web/functional/gui-screen.md +42 -0
  65. package/core/skills/qc/qa-script-designer/web/integration.md +43 -0
  66. package/core/skills/qc/qa-script-designer/web/non-functional.md +42 -0
  67. package/core/skills/qc/qa-script-runner/mobile/run.md +38 -0
  68. package/core/skills/qc/qa-script-runner/report.md +41 -0
  69. package/core/skills/qc/qa-script-runner/web/run.md +48 -0
  70. package/core/steps/qc-scope.md +43 -0
  71. package/core/steps/report-footer.md +2 -2
  72. package/docs/02-concepts/pipeline-steps/07-dev-selftest.md +1 -1
  73. package/docs/02-concepts/pipeline-steps/08-qc-automation.md +10 -10
  74. package/docs/02-concepts/pipeline-steps/10-feedback-loop.md +1 -1
  75. package/docs/02-concepts/traceability.md +1 -1
  76. package/docs/03-guides/developer.md +1 -1
  77. package/docs/03-guides/tester-qa.md +40 -12
  78. package/docs/04-reference/commands.md +1 -1
  79. package/docs/04-reference/modules.md +2 -1
  80. package/docs/explain/17-qc-design-test.md +2 -2
  81. package/docs/explain/19-qc-run-test.md +4 -4
  82. package/docs/explain/20-qc-report.md +1 -1
  83. package/docs/explain/23-fix-bug.md +2 -2
  84. package/docs/plans/qc-surgery/01-checklist.md +18 -6
  85. package/docs/plans/qc-surgery/PLAN_v2.md +295 -0
  86. package/docs/plans/qc-surgery/exec-S-ap-stack-typescript.md +420 -0
  87. package/docs/plans/qc-surgery/exec-S0-guard-cam-stack-cu.md +400 -0
  88. package/docs/plans/qc-surgery/exec-S1-hai-module-thay-qc-playwright.md +267 -0
  89. package/docs/plans/qc-surgery/exec-S2-qa-runner-thanh-script-designer-runner.md +340 -0
  90. package/docs/plans/qc-surgery/exec-S3-viet-lai-tieu-chi-review-script.md +322 -0
  91. package/docs/plans/qc-surgery/exec-S5-an-theo-don-dau-vet-stack-cu.md +292 -0
  92. package/package.json +1 -1
  93. package/core/modules/qc-playwright/stack-profile.yaml +0 -66
  94. package/core/skills/qc/qa-reviewer/script/e2e.md +0 -95
  95. package/core/skills/qc/qa-reviewer/script/functional.md +0 -109
  96. package/core/skills/qc/qa-reviewer/script/integration.md +0 -99
  97. package/core/skills/qc/qa-reviewer/script/non-functional.md +0 -134
  98. package/core/skills/qc/qa-runner/e2e.md +0 -49
  99. package/core/skills/qc/qa-runner/functional/api.md +0 -35
  100. package/core/skills/qc/qa-runner/functional/gui-feature.md +0 -57
  101. package/core/skills/qc/qa-runner/functional/gui-screen.md +0 -61
  102. package/core/skills/qc/qa-runner/integration.md +0 -47
  103. package/core/skills/qc/qa-runner/non-functional.md +0 -49
  104. package/core/skills/qc/qa-runner/report/report.md +0 -37
@@ -0,0 +1,322 @@
1
+ ---
2
+ buoc: Bước S · việc S3
3
+ title: Xoá 5 file review script phẳng, viết mới theo lane — và chốt bố cục thư mục test
4
+ phu_thuoc: S0 (đèn) · S1 (module + §2b) · S5 (dọn ăn theo)
5
+ trang_thai: ĐÃ TRIỂN KHAI 2026-09-17 · R5 đỏ 14→9 · R4 sạch · verdict 4 mức cho lane QC
6
+ ---
7
+
8
+ # S3 — Viết lại tiêu chí review script, và chốt nơi script sẽ nằm
9
+
10
+ ← [`exec-S-ap-stack-typescript.md`](exec-S-ap-stack-typescript.md) · [`exec-S2-…`](exec-S2-qa-runner-thanh-script-designer-runner.md) · [`PLAN_v2.md`](PLAN_v2.md)
11
+
12
+ | | |
13
+ |---|---|
14
+ | **Lớp** | Bước S · việc **4/6** theo `S-ORDER` *(S0→S1→S5→**S3**→S2→S4)* |
15
+ | **File code** | `skills/qc/qa-reviewer/script/` — **xoá 5, viết mới** `script/{web,mobile,api}/*` |
16
+ | **File test** | không thêm |
17
+ | **Phụ thuộc** | S0 ✅ · S1 ✅ · S5 ✅ |
18
+ | **Ai dùng nó** | `/qc-review-script` |
19
+
20
+ ---
21
+
22
+ ## 0 · Đo lại — 2026-09-17
23
+
24
+ | Đo | Kết quả |
25
+ |---|---|
26
+ | `qa-reviewer/script/` hiện tại | **5 file · 22,033 B · 490 dòng**, phẳng theo tầng |
27
+ | Mật độ nhiễm | `non-functional` **11,2 %** · `functional` **9,2 %** · `e2e` 4,2 % · `integration` 4,0 % · `exploratory` 1,9 % |
28
+ | Proposal `qa-reviewer/script/` | **10 file** — `web/*` 5 *(có `web/functional/api.md`)* · `mobile/*` 4 · `exploratory.md` |
29
+ | `AGT-006` *(Draft)* | **R01–R07** · §3.2 Severity · §3.3 **Verdict Rules** · §4 Checklist 7 nhóm |
30
+ | Verdict framework đang dùng | **2 giá trị** — `APPROVED` (17 chỗ) · `NEEDS_FIX` (16 chỗ), **không có trong `trace-schema.json`** ⇒ là văn xuôi, không phải contract field |
31
+ | Verdict `AGT-006` | **4 giá trị** — `APPROVED` · `APPROVED_WITH_SUGGESTIONS` · `REVISION_REQUIRED` · `REJECTED` |
32
+
33
+ ### 0.1 · 🔴 `{paths.qc_automation_dir}` — key KHÔNG TỒN TẠI, và proposal dùng nó ở **29 file**
34
+
35
+ ```
36
+ framework khai (templates/project-context.yaml): qc_dir · qc_skills_dir
37
+ proposal dùng: qc_automation_dir ← không có ở đâu cả
38
+ ```
39
+
40
+ **File đích của S2 + S3 mang key này:**
41
+
42
+ | Cụm | Dính |
43
+ |---|---|
44
+ | `qa-script-designer/` | **12/14** |
45
+ | `qa-script-runner/` | **4/4** |
46
+ | `qa-reviewer/script/` | **8/10** |
47
+ | | **24/28** |
48
+
49
+ `R4` của `self-check` bắt **đúng** lớp lỗi này — *"`{paths.X}` dùng mà X không có trong
50
+ `project-context.yaml` → ERROR"* — và từ S0, `R4` **đã quét `skills/`**. Nghĩa là: port
51
+ nguyên 24 file đó làm `R4` đỏ ngay, không cần ai phát hiện bằng mắt.
52
+
53
+ ### 0.2 · 🔴 Hai bố cục thư mục test đang chọi nhau
54
+
55
+ ```
56
+ Approved (Automation-Standards §2.3 — đã vào stack-profile ở S1)
57
+ automation/pages/<feature>.page.ts
58
+ automation/tests/<feature>/<feature>-happy-path.spec.ts
59
+
60
+ proposal (24/28 file đích)
61
+ {qc_automation_dir}/web/pages/{domain}/{prd-slug}/<feature>.page.ts
62
+ {qc_automation_dir}/web/tests/{domain}/{prd-slug}/<feature>.spec.ts
63
+ ```
64
+
65
+ Khác **ba** chỗ, không chỉ tên biến: có/không segment `{domain}/{prd-slug}` · có/không segment
66
+ `web/` *(module đã nói nền rồi)* · tên file `<feature>.spec.ts` vs `<feature>-<scenario>.spec.ts`.
67
+
68
+ > 🔑 **Chỗ này KHÔNG phải "đề xuất sai chuẩn".** `Automation-Standards §2.3` viết
69
+ > *"`tests/` mirror `test-suites/`"* — nhưng framework **không có** `test-suites/`; nó có
70
+ > `{qc_dir}/{TICKET-ID}/{platform}/test-cases/*.Test.md`. Áp **nguyên tắc** của chuẩn
71
+ > *(tests/ soi gương theo nơi test-case sống)* vào layout của framework thì ra **đúng hình dạng
72
+ > mà proposal viết**. Hai bên không mâu thuẫn về nguyên tắc — chúng mâu thuẫn vì chuẩn giả định
73
+ > một bố cục artifact mà framework không dùng.
74
+
75
+ ### 0.3 · 🟢 `S-ORDER` được xác nhận đúng ngay ở bước đầu áp dụng
76
+
77
+ Quyết định *"viết tiêu chí review **trước** bộ sinh"* vừa trả cổ tức: **S3 lộ ra một quyết định
78
+ mà S2 phụ thuộc** *(§0.1 + §0.2 — nơi script sẽ nằm)*. Làm S2 trước thì bộ sinh đã **nướng sẵn
79
+ một bố cục**, và S3 chỉ còn cách chép theo — mất đúng cái đối chứng độc lập mà `G3` nói tới.
80
+
81
+ ---
82
+
83
+ ## 1 · BA CÂU CHỜ CHỐT
84
+
85
+ ### 1.1 · Bố cục thư mục test — chốt hình dạng nào?
86
+
87
+ | Phương án | Hình dạng | Cái giá |
88
+ |---|---|---|
89
+ | **A · Approved thuần** | `automation/tests/<feature>/<feature>-happy-path.spec.ts` | Tên feature phải **độc nhất toàn repo QC**. Hai PRD cùng có feature `login` là va nhau, **im lặng ghi đè** |
90
+ | **B · proposal thuần** | `{qc_automation_dir}/web/tests/{domain}/{prd-slug}/<feature>.spec.ts` | Cần **khai key mới** *(§1.2)*; segment `web/` **lặp lại điều module đã nói**; mất quy ước `<feature>-<scenario>` của chuẩn |
91
+ | **C · gốc theo chuẩn, phân tách theo PRD** ⭐ | `automation/tests/{TICKET-ID}/<feature>-<scenario>.spec.ts`<br/>`automation/pages/<feature>.page.ts` *(phẳng)* | Lệch chữ so với chuẩn, nhưng **giữ nguyên tắc** của chuẩn |
92
+
93
+ **Khuyến nghị: C.** Ba lý do đo được:
94
+
95
+ 1. **Gốc `automation/` · `api-automation/` · `mobile-automation/` là của chuẩn Approved** và đã
96
+ nằm trong `stack-profile.yaml` từ S1 — giữ.
97
+ 2. **Segment PRD là bất biến của framework**, không phải sở thích: `qc_artifact_dir` =
98
+ `{qc_dir}/{TICKET-ID}/{platform}/`, sổ trace = `{trace_dir}/{domain}/{prd-slug}/…`. `tests/`
99
+ không mang PRD thì nó là thư mục duy nhất trong cả dây chuyền **không biết mình thuộc PRD nào**.
100
+ 3. **`pages/` để phẳng** — Page Object là **tài sản dùng lại**: một màn `login` phục vụ nhiều PRD.
101
+ Nhét nó vào `{TICKET-ID}/` là ép nhân bản, đúng thứ Page Object Pattern sinh ra để tránh.
102
+
103
+ > Điểm bất đối xứng ở (3) là **có chủ ý và phải ghi rõ**: `tests/` chia theo PRD, `pages/` thì
104
+ > không. Lý do: một spec **thuộc về** một PRD; một Page Object **được dùng bởi** nhiều PRD.
105
+
106
+ **Nếu chốt A** → phải thêm luật *"tên feature độc nhất toàn repo"* và một chỗ canh nó, nếu không
107
+ lỗi va tên là **ghi đè im lặng**.
108
+
109
+ ---
110
+
111
+ ### 1.2 · Khai `{paths.qc_automation_dir}` hay không dùng key?
112
+
113
+ | Phương án | |
114
+ |---|---|
115
+ | **A · KHÔNG khai key** ⭐ | Gốc lấy từ `§layout` của module *(đã có từ S1)*; skill viết đường dẫn **tương đối với gốc đó**. Chuẩn Approved cũng **không tham số hoá** `automation/` |
116
+ | B · khai `qc_automation_dir` vào `project-context.yaml` | Dự án đổi chỗ được. Cái giá: một key mới phải qua `R4` · template · docs · `sync`, và **hai nguồn cho một sự thật** — `§layout` của module đã nói gốc ở đâu |
117
+
118
+ **Khuyến nghị: A.** Đây là cùng lập luận đã dùng ở S1 §1.1 khi bỏ `tech_stack.qc_module`: hỏi
119
+ lại một câu đã có đáp án thì hai đáp án sẽ lệch nhau, và lệch **im lặng**.
120
+
121
+ **Việc phải làm khi port:** thay `{paths.qc_automation_dir}/web/` → gốc `§layout.web` ở **24
122
+ file**. `R4` là lưới an toàn — không port sót file nào mà không bị bắt.
123
+
124
+ ---
125
+
126
+ ### 1.3 · Verdict — giữ 2 giá trị hay lấy 4 của `AGT-006`?
127
+
128
+ ```
129
+ framework (17 + 16 chỗ) APPROVED · NEEDS_FIX
130
+ AGT-006 §3.3 APPROVED · APPROVED_WITH_SUGGESTIONS · REVISION_REQUIRED · REJECTED
131
+ ```
132
+
133
+ | Phương án | Cái giá |
134
+ |---|---|
135
+ | **A · giữ 2 verdict, lấy SEVERITY của AGT-006** ⭐ | `BLOCKER`·`MAJOR`·`MINOR`·`SUGGESTION` vào phần **finding**; verdict quy về 2: `BLOCKER∨MAJOR → NEEDS_FIX`, còn lại `→ APPROVED` kèm ghi chú |
136
+ | B · lấy 4 verdict cho **riêng** lane script | `/qc-review-script` và `/qc-review-testcase` **nói hai thứ tiếng** — cặp lệnh mà `E2` vừa tách ra để rõ vai, nay lệch từ vựng |
137
+ | C · lấy 4 verdict cho **cả hai** lane | Đụng `qa-reviewer/test-case/*` — mà `S-SCOPE` đã chốt **không đổi** tầng đó |
138
+
139
+ **✅ CHỐT (09-17): C — 4 verdict cho CẢ HAI lệnh soát của lane QC.** Khác khuyến nghị ban đầu của tôi (A), và anh đúng: xem §1.3b.
140
+
141
+ > ### 1.3b · Vì sao khuyến nghị A của tôi sai
142
+ >
143
+ > Tôi báo giá 4 verdict là *"sinh ra ba quyết định mới cho ba cửa chặn"* — `/qc-automation-assess`,
144
+ > `/qc-design-script`, `/qc-run-script` phải nói rõ chúng nhận mức nào. **Không đúng.** Đọc kỹ
145
+ > nguồn thì:
146
+ >
147
+ > - **Verdict là HÀM của severity**, không phải một phán quyết riêng. `AGT-006` §3.3 suy nó từ
148
+ > số đếm `BLOCKER`/`MAJOR`. Nên **lấy severity là đã có 4 verdict** — chúng chỉ là tên của
149
+ > bốn ô đếm. Phương án A của tôi *(giữ 2 verdict + lấy severity)* thực ra **đã chứa sẵn 4 mức**,
150
+ > chỉ là không đặt tên cho hai mức giữa.
151
+ > - **Ngưỡng qua cửa đã khai sẵn**: `AGT-005:132` và `AGT-010:130` — *"Code Review verdict
152
+ > ≥ APPROVED_WITH_SUGGESTIONS"*. Ba cửa chặn không phải quyết gì mới.
153
+ >
154
+ > Nguyên nhân tôi sai: ước lượng chi phí từ **số chỗ phải sửa chữ** mà chưa đọc **luật suy ra
155
+ > verdict**. Câu hỏi *"tài liệu để bao nhiêu verdict"* mới lộ ra.
156
+
157
+ **Phần vẫn đúng của khuyến nghị A:** severity của `AGT-006` là thứ **cả hai nguồn đều thiếu** *(đo: 0/10 file
158
+ proposal, 0/13 file framework)* và nó làm finding kiểm chứng được — giữ. Nhưng verdict là **từ
159
+ vựng chung của cặp review**; đổi một nửa là tạo drift ngay trong thứ vừa tách.
160
+
161
+ > Ghi kèm: verdict **chưa phải contract field** — `trace-schema.json` không có nó. Nếu sau này
162
+ > `/qc-report` (Đợt 3) đọc verdict để tính gate thì lúc ấy mới khai vào schema, **và lúc ấy mới
163
+ > là lúc bàn lại có cần 4 mức không**.
164
+
165
+ ---
166
+
167
+ # PHẦN A — Chuyện gì đang xảy ra
168
+
169
+ ## A1 · Vấn đề
170
+
171
+ Bộ tiêu chí dùng để soát bài kiểm tra tự động đang viết cho một cách làm đã bỏ — có chỗ tới hơn
172
+ một phần mười số dòng là của công cụ cũ. Nó cũng chỉ có **một** bản dùng chung cho cả web lẫn
173
+ điện thoại, trong khi hai nền soát những thứ khác hẳn nhau. Và có một câu chưa ai trả lời dứt
174
+ khoát: **bài kiểm tra sẽ được đặt ở thư mục nào** — hai tài liệu nguồn nói hai kiểu, chênh nhau
175
+ ở ba chỗ.
176
+
177
+ ## A2 · Cách giải quyết, nói bằng một hình ảnh
178
+
179
+ Như viết lại bảng chấm điểm trước khi mở lớp dạy nấu. Bảng cũ chấm theo công thức đã bỏ, nên
180
+ sửa từng dòng thì vẫn là bảng cũ. Viết bảng mới trước, rồi mới dạy — người dạy biết mình sẽ bị
181
+ chấm theo gì, và bảng chấm không bị nắn theo món mà người dạy lỡ nấu ra.
182
+
183
+ ## A3 · Xong rồi thì thấy gì khác
184
+
185
+ Năm file phẳng thành ba nhánh `web` · `mobile` · `api`, mỗi nhánh soát đúng thứ nền đó có. Mỗi
186
+ lỗi tìm được mang một mức nặng-nhẹ rõ ràng thay vì chỉ "đạt / phải sửa". Và **có một câu trả
187
+ lời duy nhất** cho câu hỏi bài kiểm tra nằm ở đâu. Số file đèn còn bắt tụt từ **14 xuống 9**.
188
+
189
+ ## A4 · Thuật ngữ dùng ở trên
190
+
191
+ - **review script** — soát bài kiểm tra tự động đã sinh ra, trước khi cho chạy thật.
192
+ - **lane / nhánh** — `web` · `mobile` · `api`; mỗi lần soát chỉ nạp một nhánh.
193
+ - **verdict** — kết luận của một lượt soát. Nay có hai giá trị: `APPROVED`, `NEEDS_FIX`.
194
+ - **severity** — mức nặng của một lỗi tìm được: `BLOCKER` · `MAJOR` · `MINOR` · `SUGGESTION`.
195
+ - **Page Object** — nơi gom thao tác của một màn hình vào một chỗ, để nhiều bài kiểm tra dùng lại.
196
+ - **`{TICKET-ID}`** — mã PRD; framework dùng nó để tách artifact của các PRD khác nhau.
197
+
198
+ ---
199
+
200
+ > ### ✅ Phép thử đọc to
201
+ > **Đã thử với:** tự đọc to · **ngày:** 2026-09-17 · **phải giải thích thêm:** *"hai tài liệu
202
+ > nguồn nói hai kiểu"* — người nghe hỏi *"vậy cái nào đúng?"*. Câu trả lời là **cả hai đều đúng
203
+ > trong bối cảnh của nó**, và đó là §0.2. Đã thêm *"chênh nhau ở ba chỗ"* để thấy đây là chuyện
204
+ > đo được, không phải chuyện quan điểm.
205
+
206
+ ---
207
+
208
+ # PHẦN B — Chi tiết kỹ thuật
209
+
210
+ ## B1 · Cách hiển nhiên là gì, và vì sao nó sai
211
+
212
+ **Cách hiển nhiên: port 10 file `script/{web,mobile}/*` của proposal, xong.** Chúng đã
213
+ TypeScript, đã tách lane, đã có `web/functional/api.md`.
214
+
215
+ **Sai ở ba chỗ, cả ba đo được:**
216
+
217
+ **1 · Kéo theo một key không tồn tại vào 8/10 file** *(§0.1)*. `R4` sẽ đỏ — may là có lưới. Nhưng
218
+ người sửa vội sẽ **khai đại `qc_automation_dir` vào `project-context.yaml`** cho đèn xanh, và thế
219
+ là framework có thêm một key **trùng nghĩa với `§layout`** của module: hai nguồn cho một sự thật,
220
+ đúng thứ S1 vừa từ chối khi bỏ `tech_stack.qc_module`.
221
+
222
+ **2 · Chốt bố cục thư mục bằng cách… không để ý mình đang chốt.** Port xong là đã chọn phương án
223
+ B của §1.1 — một quyết định ảnh hưởng tới mọi script sinh ra về sau, đi vào repo qua đường
224
+ *"copy cho nhanh"*.
225
+
226
+ **3 · Bỏ mất thứ cả hai nguồn đều thiếu.** Đo: `Severity`/`BLOCKER` xuất hiện **0/10** file
227
+ proposal và **0/13** file `qa-reviewer` hiện tại. `AGT-006` §3.2–§3.3 có cả severity lẫn verdict
228
+ rules, cộng `R05 — Positive Observations Required` *(mỗi review phải ghi ≥2–3 điểm tốt)* và
229
+ `R07 — Traceability First` *(thiếu header artifact ID ⇒ BLOCKER ngay, không review tiếp)*. Port
230
+ proposal thuần là bỏ cả ba.
231
+
232
+ > **Cách hiển nhiên thứ hai — "sửa 5 file cũ cho nhanh".** `S3′` đã bác: hai file nhiễm **11,2 %**
233
+ > và **9,2 %**; tái bố cục dưới cái tên *"sửa"* dẫn thẳng vào bẫy B1 của
234
+ > [`exec-S`](exec-S-ap-stack-typescript.md) — **đúng từ vựng TypeScript, đúng cấu trúc Python**.
235
+
236
+ ## B2 · Cách làm đúng
237
+
238
+ **Ba nguồn, ba vai — không trộn:**
239
+
240
+ ```
241
+ AGT-006 → LUẬT SOÁT: R01–R07 · severity · verdict rules · checklist 7 nhóm
242
+ qc-base-new §10 → TIÊU CHÍ CODE theo nền: Automation §9–§11 · Mobile §10–§11 · API §10–§11
243
+ proposal → KHUÔN FILE: Khi nào trigger / Khi KHÔNG trigger / Phase 1 Clarify /
244
+ Phase 2 Review / Checklist chi tiết / Output
245
+ ```
246
+
247
+ ```
248
+ ① xoá qa-reviewer/script/{e2e,exploratory,functional,integration,non-functional}.md (5 file)
249
+ ② viết script/web/{functional,integration,e2e,non-functional}.md ← Automation-Standards
250
+ ③ viết script/mobile/{functional,integration,e2e,non-functional}.md ← Mobile-Standards
251
+ ④ viết script/api/{endpoint,auth,security}.md ← API-Standards + AGT-010
252
+ ⑤ viết script/_shared/review-rules.md ← AGT-006 R01–R07 + severity + verdict (§1.3 A)
253
+ ⑥ dời exploratory.md → giữ ở gốc script/ (không thuộc nền nào)
254
+ ⑦ sửa qc-review-script.tmpl §Skills — trỏ lane theo §2b
255
+ ```
256
+
257
+ **Đường dẫn trong mọi tiêu chí lấy từ §1.1 C:**
258
+
259
+ ```
260
+ automation/tests/{TICKET-ID}/<feature>-<scenario>.spec.ts spec — chia theo PRD
261
+ automation/pages/<feature>.page.ts page — PHẲNG, dùng lại
262
+ api-automation/tests/{TICKET-ID}/… · api/<resource>.api.ts
263
+ mobile-automation/tests/{TICKET-ID}/… · screens/<feature>.screen.ts
264
+ ```
265
+
266
+ **Con số có lý do — `_shared/review-rules.md` tách riêng:** bốn nhánh × 4 file = **16 file** sẽ
267
+ cùng cần R01–R07 + bảng severity + verdict rules. Viết vào từng file là **16 bản của một luật**;
268
+ đó đúng là thứ `review-check-groups.md` *(13,8 KB, dùng chung cho test-case review)* đã tồn tại
269
+ để tránh.
270
+
271
+ ## B3 · Nếu làm sai thì hỏng theo kiểu nào
272
+
273
+ **Nói dối 🔴.** Đây là trạm **cấp phép**: nó nói *"script này dùng được"*. Một tiêu chí viết cho
274
+ stack cũ vẫn chạy trót lọt và vẫn in ra `APPROVED` — nhưng nó vừa duyệt một thứ nó không kiểm.
275
+ Cụ thể: tiêu chí `non-functional` hiện kiểm `@pytest.mark.parametrize` trên browser; script
276
+ TypeScript dùng `projects` trong `playwright.config.ts`, nên mục đó **không khớp gì cả** và
277
+ reviewer sẽ… bỏ qua, rồi `APPROVED`. Ai phát hiện: không ai — đó là định nghĩa của Nói dối.
278
+
279
+ Và `R07` của `AGT-006` tồn tại đúng vì lý do này: *thiếu header artifact ID ⇒ BLOCKER ngay,
280
+ không review tiếp* — chặn cửa **trước** khi một lượt review vô nghĩa kịp in ra verdict.
281
+
282
+ ## B4 · Verify bằng gì
283
+
284
+ | # | Phép thử | Kết quả mong đợi |
285
+ |:-:|---|---|
286
+ | 1 | `ls skills/qc/qa-reviewer/script/*.md` | chỉ còn **`exploratory.md`** ở gốc |
287
+ | 2 | `find skills/qc/qa-reviewer/script -type f \| wc -l` | **16** = 4 web + 4 mobile + 3 api + 1 `_shared` + 1 exploratory + 3 *(api gộp `_shared`?)* → **chốt con số lúc làm, ghi ra** |
288
+ | 3 | `self-check` | R5 đỏ **9** — bớt **đúng 5**, không hơn *(S3 không đụng `qa-runner` hay `commands/`)* |
289
+ | 4 | **`R4`** | **không đỏ** — 0 chỗ còn `{paths.qc_automation_dir}` |
290
+ | 5 | `grep -rn 'qc_automation_dir' skills/` | **0** |
291
+ | 6 | `grep -rlE 'BLOCKER\|MAJOR\|MINOR\|SUGGESTION' skills/qc/qa-reviewer/script/_shared/` | **1** — severity có đúng **một** bản |
292
+ | 7 | `grep -rc 'NEEDS_FIX\|APPROVED' skills/qc/qa-reviewer/script/` vs `test-case/` | **cùng từ vựng 2 verdict** — không có `REVISION_REQUIRED`/`REJECTED` |
293
+ | 8 | `grep -rn 'R0[1-7]' skills/qc/qa-reviewer/script/_shared/review-rules.md` | **7 rule đủ** |
294
+ | 9 | **Phá:** thêm `pytest` vào `script/api/endpoint.md` | **ĐỎ** — lane mới nằm trong tầm đèn |
295
+ | 10 | 4 lệnh kiểm | `build` ✅ · `244/244` ✅ · `lint-trace` ✅ · `self-check` đỏ 9 |
296
+
297
+ > **#4 là phép thử đáng giá nhất của S3.** Nó không kiểm thứ S3 làm — nó kiểm **thứ S3 suýt kéo
298
+ > vào**: 24 file đích mang một key không tồn tại. Và nó là lưới **cho cả S2**, vì S2 port 16 file
299
+ > nữa cùng loại.
300
+
301
+ ## B5 · Bài học
302
+
303
+ - **2026-09-17** — Khai với anh rằng 4 verdict *"sinh ra ba quyết định mới cho ba cửa chặn"*. **Sai.** Đọc kỹ `AGT-006` §3.3 thì verdict là **hàm của severity** — đếm BLOCKER/MAJOR là ra, không có chỗ nào để người quyết; và ngưỡng qua cửa **đã khai sẵn** ở `AGT-005:132` + `AGT-010:130` *(`≥ APPROVED_WITH_SUGGESTIONS`)*. Nguyên nhân thật: ước lượng chi phí từ **số chỗ phải sửa chữ** mà không đọc **luật suy ra verdict**. Anh hỏi *"tài liệu để bao nhiêu verdict"* mới lộ. **Bài học: khi báo giá một thay đổi, đọc luật của thứ mình định thay trước khi đếm số dòng.**
304
+ - **2026-09-17** — Framework đã có **hai** bộ nhãn cho một việc: `FAIL`/`WARN` *(để trừ điểm)* và nay thêm `BLOCKER`/`MAJOR`/`MINOR`/`SUGGESTION` *(để suy verdict)*. Suýt để chúng sống song song không liên hệ. Sửa: ghi **phép ánh xạ tường minh** vào `review-file-template.md` — `FAIL ≡ BLOCKER ∪ MAJOR` · `WARN ≡ MINOR ∪ SUGGESTION`. Hai bộ nhãn không nối nhau là hai bộ nhãn sẽ lệch nhau.
305
+ - **2026-09-17** — Chèn một hàng vào bảng Markdown bằng phép thay chuỗi làm **vỡ bảng**: dòng mới rơi xuống dưới một dòng trống nên nó thành đoạn văn, không còn là hàng. `build` vẫn xanh, `self-check` vẫn xanh — không máy nào canh cấu trúc bảng. Chỉ thấy khi đọc lại file. **Phép thay chuỗi trên Markdown có cấu trúc thì phải đọc lại vùng vừa sửa, đừng tin exit code.**
306
+ - **2026-09-17** — `qc-review-script.tmpl` còn hai dòng luật của stack cũ mà đèn **không bắt được** vì chúng không chứa chữ bị cấm: *"Không Allure"* *(nay sai với mobile)* và *"Selector theo thứ tự ưu tiên (data-testid → role)"* *(thứ tự của web, không đúng Appium, và api không có locator)*. Đèn canh **tên công nghệ**, không canh **luật đã lỗi thời**. Đây là giới hạn đã biết của S0, nay có ca thật.
307
+
308
+ ## B6 · Copy được / không copy được
309
+
310
+ | | |
311
+ |---|---|
312
+ | ✅ **Copy được** | Viết **tiêu chí chấm trước, bộ sinh sau** — nó lộ ra quyết định còn thiếu trước khi bộ sinh kịp nướng sẵn một câu trả lời (§0.3); khi hai tài liệu nguồn chọi nhau, hỏi **"nguyên tắc của chúng có chọi nhau không"** trước khi chọn bên (§0.2); một luật dùng chung cho n file thì viết **một bản**, đừng chép n lần |
313
+ | ⚠️ **Chỉ đúng ở đây** | Bất đối xứng `tests/` chia theo PRD còn `pages/` phẳng; từ vựng 2 verdict `APPROVED`/`NEEDS_FIX`; tên key `qc_automation_dir` của bộ đề xuất |
314
+
315
+ ## B7 · Link
316
+
317
+ - [`exec-S-ap-stack-typescript.md`](exec-S-ap-stack-typescript.md) §8 `S-ORDER` · §8.1 `S3′` *(xoá, viết mới — không vá)*
318
+ - [`exec-S2-…`](exec-S2-qa-runner-thanh-script-designer-runner.md) §0.2 — bộ đo *"công của Đợt 0–2 có được giữ không"*, dùng lại cho S3
319
+ - `upstream/qc-base-new/AGT-006-code_reviewer.md` §3.1 R01–R07 · §3.2 Severity · §3.3 Verdict · §4 Checklist
320
+ - `upstream/qc-base-new/Automation-Standards.md` §9–§11 · `Mobile-…` §10–§11 · `API-…` §10–§11
321
+ - `modules/qc-*/stack-profile.yaml` §`layout` §`naming` — nguồn đường dẫn, **không tra lại chuẩn**
322
+ - `bin/self-check.js:256` — `R4`, lưới an toàn cho `{paths.X}`
@@ -0,0 +1,292 @@
1
+ ---
2
+ buoc: Bước S · việc S5
3
+ title: Dọn dấu vết stack cũ ở các chỗ ăn theo — lệnh, schema, provenance, docs
4
+ phu_thuoc: S1 xong (tên file/report đã chốt ở stack-profile)
5
+ trang_thai: ĐÃ TRIỂN KHAI 2026-09-17 · 10/10 phép thử đạt · R5 đỏ 18→14
6
+ ---
7
+
8
+ # S5 — Bốn chỗ ăn theo, và một thứ đèn không nhìn thấy
9
+
10
+ ← [`exec-S-ap-stack-typescript.md`](exec-S-ap-stack-typescript.md) · [`exec-S1-…`](exec-S1-hai-module-thay-qc-playwright.md) · [`PLAN_v2.md`](PLAN_v2.md)
11
+
12
+ | | |
13
+ |---|---|
14
+ | **Lớp** | Bước S · việc **3/6 theo thứ tự mới** `S-ORDER` *(S0→S1→**S5**→S3→S2→S4)* |
15
+ | **File code** | 4 lệnh · `bin/trace-schema.json` · `bin/qc-base-map.json` · 5 `docs/` |
16
+ | **File test** | không thêm |
17
+ | **Phụ thuộc** | S1 ✅ — mọi thứ S5 cần **đã được `stack-profile.yaml` quyết** *(tên file, reporter)* |
18
+ | **Ai dùng nó** | `/qc-analyze` · `/qc-design-test` · `/qc-report` · `/qc-automation-assess` |
19
+
20
+ ---
21
+
22
+ ## 0 · Đo lại — 2026-09-17
23
+
24
+ ### 0.1 · Trong tầm đèn — 4 file · 11 dòng
25
+
26
+ | File | Dòng | Nội dung |
27
+ |---|:-:|---|
28
+ | `commands/qc-report.tmpl` | 5 | `pytest-html (--html=…)` · `python3 -m playwright show-trace` ×2 · *"report pytest-html + trace"* |
29
+ | `commands/qc-design-test.tmpl` | 3 | *"Python đến sau ở /qc-design-script"* · *"Output feed vào qc-design-script (Python) và qc-review"* · *"Bạn không viết Python"* |
30
+ | `skills/qc/qa-automation-assess/matrix.md` | 2 | ⚠️ **ghi chú TODO do chính S1 để lại** — xem §0.2 |
31
+ | `commands/qc-analyze.tmpl` | 1 | *"viết test case chi tiết hay Python (đó là qc-design-test / qc-design-script)"* |
32
+
33
+ **Mọi dòng đều đã có đáp án sẵn từ S1** — không phải quyết gì thêm:
34
+
35
+ ```
36
+ pytest-html → Playwright HTML Report (web·api) · Allure v2.x (mobile)
37
+ python3 -m playwright show-trace → npx playwright show-trace
38
+ "Python" → "script" / "TypeScript"
39
+ ```
40
+
41
+ ### 0.2 · 🔴 Hai trong mười một dòng đỏ là do **chính tôi** tạo ra ở S1
42
+
43
+ `matrix.md:106-107` — S1 đã thay xong nội dung stack cũ, nhưng để lại một ghi chú TODO **trích
44
+ nguyên tên cũ**:
45
+
46
+ ```
47
+ > *(Nội dung mô tả stack cũ — Python + pytest-playwright, `test_<feature>.py`,
48
+ > `<feature>_page.py` — thuộc việc S5 của Bước S, chưa sửa ở bước này.)*
49
+ ```
50
+
51
+ Nội dung thật **đã sạch**; chỉ ghi chú là đỏ. Nên S5 với file này = **xoá hai dòng ghi chú**.
52
+
53
+ > **Bài học ngay tại chỗ:** một ghi chú TODO **trích dẫn** pattern bị cấm làm đèn đếm chính phần
54
+ > bookkeeping của mình thành nợ. Ghi chú đúng cách là nói *"nội dung stack cũ, S5 sửa"* —
55
+ > **không liệt kê lại tên**. Đã làm sai một lần ở S1, ghi vào B5 để lần sau không lặp.
56
+
57
+ ### 0.3 · Ngoài tầm đèn nhưng thuộc S5
58
+
59
+ | Nơi | Lượng | Nội dung |
60
+ |---|:-:|---|
61
+ | `bin/trace-schema.json` | **4 dòng** | `:1477` *"ghi đè script Python đã có"* · `:2420` `tests/…/test_<feature>.py` · `:2423` `pages/<feature>_page.py` · `:2497` *"pytest-html self-contained"* |
62
+ | `bin/qc-base-map.json` | **5 entry** `undecided` | 3 × `qa-runner` *(agent + script-writer + test-executor)* · **2 × `qa-gate/styled-report-*.py`** |
63
+ | `docs/` | **5 file · 13 dòng** | `08-qc-automation` 5 · `19-qc-run-test` 4 · `tester-qa` 2 · `04-reference/modules` 1 · `17-qc-design-test` 1 |
64
+
65
+ > `bin/` nằm ngoài `scope` của đèn **có chủ ý**: 6 entry `forbidden_patterns` của S0 mang chính
66
+ > chữ `pytest`/`Python` trong `reason` của chúng. Nếu một ngày ai nới scope sang `bin/`, đèn sẽ
67
+ > **báo động chính định nghĩa của nó**. Ghi ra đây để đừng nới.
68
+
69
+ ### 0.4 · 🔴 Thứ đèn KHÔNG nhìn thấy — dây chuyền QC khai sai ở **6 lệnh**
70
+
71
+ Không phải Python, nên `R5` im. Đây là **drift từ Đợt 2** mà chưa ai đánh số lại:
72
+
73
+ | Lệnh | Tự khai | Đúng phải là |
74
+ |---|---|---|
75
+ | `qc-analyze` · `qc-plan` · `qc-design-test` · `qc-review-testcase` | Stage 1 · 2 · 3 · 4 | ✅ đúng |
76
+ | `qc-automation-assess` | **— (không khai)** | **5** |
77
+ | `qc-design-script` | Stage **5** | **6** |
78
+ | `qc-review-script` | **— (không khai)** | **7** |
79
+ | `qc-run-script` | Stage **7** | **8** |
80
+ | `qc-run-manualtest` | Stage **7b** | **8b** |
81
+ | `qc-report` | Stage **6 (cuối)** | **9** |
82
+
83
+ **Chỗ tự mâu thuẫn:** `qc-report` khai *"Stage 6 **(cuối)**"* trong khi hai lệnh khác khai
84
+ **Stage 7 / 7b** — trạm tự xưng cuối cùng đứng **trước** hai trạm sau nó.
85
+
86
+ > ⚠️ **Đính chính phép đo.** Bản trình bày đầu khai *"`qc-run-script` và `qc-run-manualtest`
87
+ > **cùng** khai Stage 7"*. Sai: regex `Stage [0-9]+` khớp phần `7` của chuỗi `Stage 7b`, nên hai
88
+ > giá trị khác nhau bị đếm thành một. Thật ra cặp `7`/`7b` **nhất quán với nhau** — chúng chỉ
89
+ > lệch so với sơ đồ 10 trạm *(đúng phải là `8`/`8b`)*. **Bài học:** một regex `[0-9]+` không có
90
+ > ranh giới token thì `7b` đọc thành `7` — đúng lớp lỗi mà hàm `mentions()` của `self-check.js`
91
+ > đã dựng `(?![A-Za-z0-9_])` để chống, và tôi vừa mắc lại bằng tay.
92
+
93
+ **Và chuỗi dây chuyền in trong 5 lệnh liệt kê 8 trạm**, thiếu đúng hai lệnh Đợt 2 vừa thêm:
94
+ `qc-automation-assess` *(d2-b3)* và `qc-run-manualtest` *(d2-b2)*.
95
+
96
+ ```
97
+ đang in: analyze → plan → design-test → review-testcase → design-script
98
+ → review-script → run-script → report (8 trạm)
99
+ thật sự: … → review-testcase → automation-assess → design-script
100
+ → review-script → run-script → run-manualtest → report (10 lệnh)
101
+ ```
102
+
103
+ Số đúng **đã có sẵn** ở [`PLAN_v2 §2`](PLAN_v2.md) — chưa bao giờ được chép ngược vào lệnh.
104
+
105
+ ---
106
+
107
+ ## 1 · HAI CÂU CHỜ CHỐT
108
+
109
+ ### 1.1 · Đánh số lại dây chuyền — làm trong S5 hay tách ra?
110
+
111
+ | Phương án | |
112
+ |---|---|
113
+ | **A · gộp vào S5** ⭐ | S5 **đã mở** 3 trong 6 file đó *(`qc-analyze` · `qc-design-test` · `qc-report`)*. Sửa thêm 3 file nữa là một lượt. Mở lại cùng file ở hai bước khác nhau là hai lần đọc, hai lần review, hai cơ hội sai lệch |
114
+ | B · tách thành nợ độc lập | Giữ S5 đúng nghĩa *"ăn theo việc đổi stack"*. Nhưng nó là **drift từ Đợt 2**, không phải từ stack — để lại thì nằm đó thêm một đợt nữa |
115
+
116
+ **Khuyến nghị: A**, kèm một điều kiện — **ghi rõ trong commit** rằng đây là drift Đợt 2, không
117
+ phải hệ quả của đổi stack. Lý do gộp không phải *"tiện tay"* mà là: một lệnh khai sai số trạm
118
+ làm người đọc **hiểu sai thứ tự chạy**, và ba trong sáu file đó đang mở sẵn.
119
+
120
+ > **Vì sao không dựng rule canh việc này:** số trạm là **một dữ kiện, không phải một contract
121
+ > field** — chưa có nơi nào máy đọc nó. Dựng rule cho một thứ chưa ai tiêu thụ là đúng lớp lỗi
122
+ > `R2` cảnh báo *(có producer, không consumer)*. Nếu sau này `/qc-metrics` (d4-b3) cần số trạm
123
+ > thì lúc đó mới khai vào schema.
124
+
125
+ ---
126
+
127
+ ### 1.2 · Hai entry `styled-report-*.py` trong `qc-base-map` — chốt trạng thái nào?
128
+
129
+ Hai file Python **sinh báo cáo** của kho QC cũ, `state: undecided`, `targets: []`, **chưa bao
130
+ giờ port**, ghi chú: *"script chạy được, không phải prompt — framework QC hiện 100% markdown-first"*.
131
+
132
+ Đây chính là chỗ câu hỏi *"Python làm tốt hơn ở tầng sinh doc"* đáp xuống thành một quyết định
133
+ cụ thể.
134
+
135
+ | Phương án | |
136
+ |---|---|
137
+ | **A · `skipped`, lý do từ chuẩn mới** ⭐ | `Automation-Standards §1` + `OQ-01` *(đóng 2026-06-02)* chốt reporter là **Playwright HTML Report**; `Mobile §1` chốt **Allure v2.x**. Báo cáo do **công cụ sinh**, không do script tự viết — nên hai file này không còn đích để port |
138
+ | B · giữ `undecided` | `R16` không đỏ, nhưng entry `undecided` tồn tại để **chờ quyết**; giữ mãi là biến nó thành rác |
139
+ | C · port thành công cụ Node | việc mới, ngoài Bước S, và mâu thuẫn với chuẩn vừa chốt |
140
+
141
+ **Khuyến nghị: A.** Cùng lượt chốt nốt **3 entry `qa-runner`** → `replaced`, `targets` trỏ
142
+ `skills/qc/qa-script-designer/` và `qa-script-runner/` *(sau S2)*. Tức **để nguyên 3 entry đó ở
143
+ S5, xử ở S2** — S5 chỉ đóng 2 entry `styled-report`.
144
+
145
+ ---
146
+
147
+ # PHẦN A — Chuyện gì đang xảy ra
148
+
149
+ ## A1 · Vấn đề
150
+
151
+ Đội đã đổi bộ công cụ, nhưng vài chỗ trong bộ khung vẫn kể lại chuyện cũ: hướng dẫn bảo mở báo
152
+ cáo bằng một câu lệnh không còn chạy, và bảng khai *"lệnh này ghi ra file gì"* vẫn ghi đuôi file
153
+ của bộ cũ. Những dòng này không làm hỏng gì ngay — chúng chỉ làm người đọc tin vào một thứ không
154
+ còn đúng, rồi mất buổi sáng để phát hiện. Và có một chỗ lệch nữa nặng hơn: **sáu lệnh tự khai
155
+ mình là trạm số mấy trong dây chuyền, và bốn trong số đó khai sai** — có hai lệnh cùng nhận một
156
+ số, còn lệnh tự xưng *"trạm cuối"* thì đứng trước hai trạm khác.
157
+
158
+ ## A2 · Cách giải quyết, nói bằng một hình ảnh
159
+
160
+ Như sửa bảng chỉ dẫn treo dọc một dây chuyền sau khi chèn thêm hai công đoạn. Máy móc chạy đúng
161
+ rồi — chỉ có bảng là còn đánh số theo sơ đồ cũ, và người mới vào đọc bảng chứ không đọc máy.
162
+
163
+ ## A3 · Xong rồi thì thấy gì khác
164
+
165
+ Số file đèn còn bắt tụt từ **18 xuống 14**. Mở bất kỳ lệnh QC nào cũng thấy nó khai đúng mình
166
+ là trạm mấy trong mười, và không có hai lệnh nào trùng số. Hướng dẫn mở báo cáo ghi đúng câu
167
+ lệnh chạy được.
168
+
169
+ ## A4 · Thuật ngữ dùng ở trên
170
+
171
+ - **trạm / stage** — một bước trong dây chuyền QC; hiện có 10 lệnh, đánh số 1 → 9 *(có một trạm `8b`)*.
172
+ - **`trace-schema.json`** — bản khai máy đọc: lệnh nào ghi ra file gì, ở đâu. Sai ở đây thì máy canh sai.
173
+ - **provenance / `qc-base-map.json`** — sổ ghi mỗi file của kho QC gốc đã đi về đâu trong bộ khung.
174
+ - **`undecided`** — trạng thái *"chưa quyết lấy hay bỏ"*; để mãi thì nó thành rác chứ không thành quyết định.
175
+ - **reporter** — công cụ tự sinh báo cáo sau khi chạy test. Nay là Playwright HTML Report *(web · dịch vụ)* và Allure *(điện thoại)*.
176
+
177
+ ---
178
+
179
+ > ### ✅ Phép thử đọc to
180
+ > **Đã thử với:** tự đọc to · **ngày:** 2026-09-17 · **phải giải thích thêm:** *"trạm cuối đứng
181
+ > trước hai trạm khác"* nghe như nói đùa — đã thêm *"có hai lệnh cùng nhận một số"* ngay trước
182
+ > để người nghe hiểu đây là lỗi đánh số, không phải nghịch lý.
183
+
184
+ ---
185
+
186
+ # PHẦN B — Chi tiết kỹ thuật
187
+
188
+ ## B1 · Cách hiển nhiên là gì, và vì sao nó sai
189
+
190
+ **Cách hiển nhiên: `sed` thay `pytest-html` → `Playwright HTML Report`, `python3 -m playwright`
191
+ → `npx playwright`, `Python` → `TypeScript`. Mười một dòng, ba lệnh `sed`.**
192
+
193
+ **Sai ở ba chỗ:**
194
+
195
+ **1 · `Python` → `TypeScript` sai nghĩa ở 3/4 chỗ.** Đọc kỹ từng dòng:
196
+
197
+ | Dòng | Thay máy móc | Đúng phải là |
198
+ |---|---|---|
199
+ | `qc-analyze:113` *"viết test case chi tiết hay Python"* | *"…hay TypeScript"* | *"…hay **script**"* — câu này nói về **loại việc**, không về ngôn ngữ |
200
+ | `qc-design-test:152` *"Bạn **không** viết Python"* | *"không viết TypeScript"* | *"không viết **script**"* — ranh giới vai trò, không phải ranh giới ngôn ngữ |
201
+ | `qc-design-test:9` *"Python đến sau ở /qc-design-script"* | *"TypeScript đến sau"* | *"**script** đến sau"* |
202
+
203
+ Ba dòng này dùng tên ngôn ngữ để **trỏ một giai đoạn**. Thay tên ngôn ngữ giữ nguyên lỗi: sáu
204
+ tháng nữa đổi stack lần nữa thì lại phải sửa đúng ba dòng đó.
205
+
206
+ **2 · `pytest-html` → một tên không đủ.** Reporter giờ **khác nhau theo nền**: Playwright HTML
207
+ Report *(web · api)* và **Allure v2.x** *(mobile)* — và mobile **bắt buộc**, ngược luật
208
+ `No Allure` cũ. Một phép thay chuỗi cho ra một tên duy nhất, tức khai sai cho nền mobile.
209
+
210
+ **3 · `sed` không thấy `qc-design-test:152` còn trỏ `/qc-review`** — lệnh **đã gỡ ở Đợt 2 `E3`**.
211
+ Nó ngồi cùng dòng với chữ `Python`, nên chỉ ai **đọc** dòng đó mới thấy. Đèn bắt được file, không
212
+ bắt được bug thứ hai đi kèm.
213
+
214
+ > **Và cả ba đều không đụng tới §0.4** — dây chuyền khai sai ở 6 lệnh không chứa chữ nào bị cấm.
215
+ > `sed` xong, đèn xanh cho 4 file, **lỗi nặng hơn vẫn nguyên**.
216
+
217
+ ## B2 · Cách làm đúng
218
+
219
+ ```
220
+ ① 4 file trong tầm đèn — sửa theo NGHĨA, không theo chuỗi
221
+ qc-analyze:113 Python → script
222
+ qc-design-test:9,151,152 Python → script · GỠ "và qc-review" (E3)
223
+ qc-report:14,27,29,33,67 pytest-html → reporter THEO NỀN (§2b)
224
+ python3 -m playwright → npx playwright
225
+ matrix.md:106-107 XOÁ ghi chú TODO của S1 (§0.2)
226
+
227
+ ② bin/trace-schema.json — 4 dòng
228
+ :2420 test_<feature>.py → <feature>-<scenario>.spec.ts (naming của S1)
229
+ :2423 <feature>_page.py → <feature>.page.ts
230
+ :1477 "script Python" → "script"
231
+ :2497 "pytest-html" → "Playwright HTML Report / Allure theo nền"
232
+
233
+ ③ bin/qc-base-map.json — 2 entry styled-report-*.py → skipped, lý do từ §1.2
234
+ (3 entry qa-runner để S2 xử — chúng cần targets trỏ thư mục S2 mới tạo)
235
+
236
+ ④ 5 file docs/ — 13 dòng, cùng luật ①
237
+
238
+ ⑤ §0.4 — đánh số lại 6 lệnh + chuỗi dây chuyền 10 trạm (nếu chốt §1.1 A)
239
+ ```
240
+
241
+ **Nguồn duy nhất cho ①②④:** `modules/qc-*/stack-profile.yaml` §`naming` và §`reporting` — đã
242
+ viết ở S1. **Không tra lại chuẩn** ở bước này; nếu hai bên lệch thì `stack-profile` đúng, vì nó
243
+ là thứ lệnh thật sự đọc.
244
+
245
+ ## B3 · Nếu làm sai thì hỏng theo kiểu nào
246
+
247
+ **Ồn 🟠 cho ①②④ — Im lặng 🔴 cho ⑤.**
248
+
249
+ Mười một dòng kia sai thì có người đọc, gõ theo, thấy lệnh báo `command not found`, mất mười
250
+ phút rồi tự hiểu. Khó chịu, không nguy hiểm.
251
+
252
+ `⑤` khác hẳn: số trạm sai **không báo gì cả**. Một người mới đọc `/qc-report` thấy *"Stage 6
253
+ (cuối)"* sẽ tin dây chuyền có 6 trạm và **bỏ qua `/qc-automation-assess`** — đúng trạm quyết
254
+ định TC nào máy chạy, TC nào làm tay. Hệ quả là TC `Automatable: N` không ai chạy, và
255
+ `/qc-report` chấm PRD FAIL vĩnh viễn. Đó là lỗ mà `G1`/`d2-b3` sinh ra để bịt, quay lại qua
256
+ đường **tài liệu**.
257
+
258
+ ## B4 · Verify bằng gì
259
+
260
+ | # | Phép thử | Kết quả mong đợi |
261
+ |:-:|---|---|
262
+ | 1 | `self-check` | R5 đỏ **14 file** — bớt **đúng 4**, không hơn *(S5 không đụng `qa-runner` hay `qa-reviewer/script`)* |
263
+ | 2 | `grep -rn 'pytest\|python' commands/qc-*.tmpl` | **0** |
264
+ | 3 | `grep -rn 'qc-review\b' commands/*.tmpl \| grep -v 'qc-review-'` | chỉ còn **1** — dòng kể lịch sử ở `qc-run-script.tmpl:39` |
265
+ | 4 | `grep -rn 'Python\|TypeScript' commands/qc-analyze.tmpl commands/qc-design-test.tmpl` | **0** — đã thay bằng *"script"*, không đổi tên ngôn ngữ này lấy tên ngôn ngữ khác |
266
+ | 5 | `grep -c 'Allure' commands/qc-report.tmpl` | **≥1** — reporter khai theo nền, không một tên |
267
+ | 6 | **Đếm Stage:** mỗi lệnh khai đúng 1 số, **không trùng**, đủ 1–9 + 8b | 10/10 lệnh, `qc-report` = **9**, không còn *"(cuối)"* ở số 6 |
268
+ | 7 | Chuỗi dây chuyền in trong lệnh | **10 trạm**, có `automation-assess` và `run-manualtest` |
269
+ | 8 | `grep -rn 'test_<feature>.py\|_page.py' bin/trace-schema.json` | **0** |
270
+ | 9 | `node bin/self-check.js` → `R16` | không đỏ — `qc-base-map` vẫn hợp lệ sau khi đổi 2 entry |
271
+ | 10 | 4 lệnh kiểm | `build` ✅ · `244/244` ✅ · `lint-trace` ✅ · `self-check` đỏ 14 |
272
+
273
+ ## B5 · Bài học
274
+
275
+ - **2026-09-17** *(ghi trước khi làm, vì lỗi đã xảy ra ở S1)* — Ghi chú TODO ở `matrix.md:106-107` **trích nguyên** `Python + pytest-playwright`, `test_<feature>.py`, `<feature>_page.py`. Nội dung thật đã sạch, nhưng đèn đếm **chính phần bookkeeping** thành nợ: 2/11 dòng đỏ của S5 là do S1 tự tạo. Nguyên nhân thật: viết ghi chú theo phản xạ *"nêu rõ cái gì chưa sửa"* mà quên rằng "nêu rõ" ở đây nghĩa là **viết lại đúng chuỗi bị cấm**. Sửa: ghi chú TODO trong vùng có guard **mô tả**, không **trích dẫn**.
276
+ - **2026-09-17** — Đổi con trỏ skill của `/qc-report` sang `qa-script-runner/` — thư mục **S2 mới tạo, chưa tồn tại**. `R16` đỏ ngay: *"trỏ vào `{paths.qc_skills_dir}/qa-script-runner/` — KHÔNG TỒN TẠI"*. Nguyên nhân thật: **lấn sang việc của S2**; S5 là "đổi chữ theo stack", còn đấu dây skill là của bước tạo ra thư mục. Sửa: trả lại `qa-runner/report/` kèm ghi chú S2 đổi. **Rule của chính framework bắt được chỗ tôi vượt phạm vi** — đó là điều đáng giữ hơn cả việc sửa xong.
277
+ - **2026-09-17** — Khai *"`qc-run-script` và `qc-run-manualtest` cùng Stage 7"*. Sai: regex `Stage [0-9]+` nuốt `7b` thành `7`. Cặp `7`/`7b` vốn nhất quán; chúng chỉ lệch so với sơ đồ 10 trạm. Nguyên nhân thật: dùng regex **không có ranh giới token** để đếm — đúng lớp lỗi mà `self-check.js` `mentions()` đã chống bằng `(?![A-Za-z0-9_])`. Sửa: mọi phép đếm bằng regex trên **mã định danh** phải có ranh giới token, kể cả khi chỉ chạy một lần để lấy số.
278
+ - **2026-09-17** — Phép thay chuỗi cho `qc-review-testcase.tmpl` **trượt im lặng**: chuỗi dây chuyền của nó **xuống dòng giữa chừng** và liệt kê 6 trạm chứ không 8, nên không khớp mẫu. Chỉ phát hiện vì phép thử #7 đếm `5` file mà ra `4`. **Phép thử đếm cứu được một lần trượt mà `sed` báo thành công.**
279
+
280
+ ## B6 · Copy được / không copy được
281
+
282
+ | | |
283
+ |---|---|
284
+ | ✅ **Copy được** | Sửa theo **nghĩa** chứ không theo chuỗi — một cái tên công nghệ dùng để **trỏ một giai đoạn** thì phải thay bằng tên giai đoạn, nếu không lần đổi stack sau lại sửa đúng chỗ đó; ghi chú TODO trong vùng có guard thì **mô tả, đừng trích dẫn**; nhân lúc đã mở file thì dọn nốt drift cùng chỗ, **nhưng nói rõ trong commit đó là drift khác nguồn** |
285
+ | ⚠️ **Chỉ đúng ở đây** | Sơ đồ 10 trạm và cách đánh số `8b`; việc `bin/` cố ý nằm ngoài scope đèn; hai file `styled-report-*.py` của kho QC cũ |
286
+
287
+ ## B7 · Link
288
+
289
+ - [`exec-S-ap-stack-typescript.md`](exec-S-ap-stack-typescript.md) §8 *(`S-ORDER`)* · [`exec-S1-…`](exec-S1-hai-module-thay-qc-playwright.md) *(nguồn `naming` + `reporting`)*
290
+ - [`PLAN_v2.md`](PLAN_v2.md) §2 *(sơ đồ 10 trạm — số đúng đã có sẵn ở đây)* · §7 `E3` *(gỡ `/qc-review`)*
291
+ - `modules/qc-playwright-ts/stack-profile.yaml` §`naming` §`reporting` · `modules/qc-wdio-appium/…` §`reporting`
292
+ - `upstream/qc-base-new/Automation-Standards.md` §1 + OQ-01 · `Mobile-Automation-Standards.md` §1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@educa-corp/sdd-framework",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
4
4
  "description": "Spec Driven Development workflow framework for Claude Code",
5
5
  "bin": {
6
6
  "sdd-framework": "./bin/index.js"