@educa-corp/sdd-framework 0.9.6 → 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 (147) hide show
  1. package/bin/lint-trace.js +4 -4
  2. package/bin/qc-base-map.json +13 -11
  3. package/bin/self-check.js +497 -16
  4. package/bin/trace-schema.json +3226 -2656
  5. package/core/FRAMEWORK_VERSION +1 -1
  6. package/core/commands/amend-prd.md +7 -1
  7. package/core/commands/debug.md +8 -2
  8. package/core/commands/define-product.md +38 -1
  9. package/core/commands/dev-gen-test.md +9 -3
  10. package/core/commands/dev-run-test.md +8 -2
  11. package/core/commands/dev-smoke-test.md +7 -1
  12. package/core/commands/extend-prd.md +7 -1
  13. package/core/commands/fix-bug.md +11 -5
  14. package/core/commands/generate-architecture.md +9 -1
  15. package/core/commands/generate-bdd.md +45 -5
  16. package/core/commands/generate-code.md +43 -4
  17. package/core/commands/generate-design-spec.md +7 -1
  18. package/core/commands/generate-prd.md +9 -1
  19. package/core/commands/generate-spec-manifest.md +7 -1
  20. package/core/commands/generate-tech-docs.md +41 -1
  21. package/core/commands/learn.md +7 -1
  22. package/core/commands/map-testids.md +11 -5
  23. package/core/commands/propose-scenario.md +7 -1
  24. package/core/commands/qc-analyze.md +12 -6
  25. package/core/commands/qc-automation-assess.md +356 -0
  26. package/core/commands/qc-design-script.md +430 -0
  27. package/core/commands/qc-design-test.md +98 -20
  28. package/core/commands/qc-plan.md +9 -3
  29. package/core/commands/qc-report.md +92 -77
  30. package/core/commands/qc-review-script.md +342 -0
  31. package/core/commands/{qc-review.md → qc-review-testcase.md} +86 -54
  32. package/core/commands/qc-run-manualtest.md +401 -0
  33. package/core/commands/qc-run-script.md +421 -0
  34. package/core/commands/refine-prd.md +7 -1
  35. package/core/commands/report-bug.md +9 -3
  36. package/core/commands/review-code.md +9 -3
  37. package/core/commands/review-context.md +11 -3
  38. package/core/commands/review-tech-docs.md +11 -3
  39. package/core/commands/setup-ai-first.md +7 -1
  40. package/core/commands/validate-traces.md +10 -4
  41. package/core/modules/qc-playwright-ts/module.yaml +13 -0
  42. package/core/modules/qc-playwright-ts/stack-profile.yaml +99 -0
  43. package/core/modules/qc-wdio-appium/module.yaml +20 -0
  44. package/core/modules/qc-wdio-appium/stack-profile.yaml +107 -0
  45. package/core/rules/workflow.md +2 -2
  46. package/core/skills/qc/_shared/self-review-principles.md +2 -2
  47. package/core/skills/qc/qa-analyst/DOC_GAP.template.md +1 -1
  48. package/core/skills/qc/qa-analyst/data-flow.md +1 -1
  49. package/core/skills/qc/qa-analyst/spec-issue-reporter.md +1 -1
  50. package/core/skills/qc/qa-automation-assess/matrix.md +123 -0
  51. package/core/skills/qc/qa-designer/e2e/journey.md +1 -1
  52. package/core/skills/qc/qa-designer/exploratory/explore-to-functional.md +1 -1
  53. package/core/skills/qc/{qa-runner → qa-designer}/exploratory/session.md +8 -2
  54. package/core/skills/qc/qa-designer/functional/api.md +2 -2
  55. package/core/skills/qc/qa-designer/functional/gui-feature.md +1 -1
  56. package/core/skills/qc/qa-designer/functional/gui-screen.md +1 -1
  57. package/core/skills/qc/qa-designer/functional/job.md +128 -0
  58. package/core/skills/qc/qa-designer/integration/api.md +2 -2
  59. package/core/skills/qc/qa-designer/integration/db.md +2 -2
  60. package/core/skills/qc/qa-designer/integration/gui.md +1 -1
  61. package/core/skills/qc/qa-designer/integration/{kafka.md → queue.md} +21 -5
  62. package/core/skills/qc/qa-designer/non-functional.md +1 -1
  63. package/core/skills/qc/qa-designer/shared/skill-decision-tree.md +17 -0
  64. package/core/skills/qc/qa-designer/shared/tc-metadata-format.md +28 -6
  65. package/core/skills/qc/qa-reviewer/script/_shared/review-rules.md +121 -0
  66. package/core/skills/qc/qa-reviewer/script/api/auth.md +49 -0
  67. package/core/skills/qc/qa-reviewer/script/api/endpoint.md +89 -0
  68. package/core/skills/qc/qa-reviewer/script/api/security.md +46 -0
  69. package/core/skills/qc/qa-reviewer/script/exploratory.md +3 -3
  70. package/core/skills/qc/qa-reviewer/script/mobile/e2e.md +41 -0
  71. package/core/skills/qc/qa-reviewer/script/mobile/functional.md +90 -0
  72. package/core/skills/qc/qa-reviewer/script/mobile/integration.md +41 -0
  73. package/core/skills/qc/qa-reviewer/script/mobile/non-functional.md +43 -0
  74. package/core/skills/qc/qa-reviewer/script/web/e2e.md +46 -0
  75. package/core/skills/qc/qa-reviewer/script/web/functional.md +111 -0
  76. package/core/skills/qc/qa-reviewer/script/web/integration.md +46 -0
  77. package/core/skills/qc/qa-reviewer/script/web/non-functional.md +49 -0
  78. package/core/skills/qc/qa-reviewer/shared/read-doc-gap-inputs.md +1 -1
  79. package/core/skills/qc/qa-reviewer/shared/review-file-template.md +29 -10
  80. package/core/skills/qc/qa-reviewer/test-case/e2e.md +2 -2
  81. package/core/skills/qc/qa-reviewer/test-case/exploratory.md +1 -1
  82. package/core/skills/qc/qa-reviewer/test-case/functional.md +2 -2
  83. package/core/skills/qc/qa-reviewer/test-case/integration.md +2 -2
  84. package/core/skills/qc/qa-reviewer/test-case/non-functional.md +2 -2
  85. package/core/skills/qc/qa-script-designer/_shared/api-conventions.md +94 -0
  86. package/core/skills/qc/qa-script-designer/_shared/file-naming-and-folders.md +109 -0
  87. package/core/skills/qc/qa-script-designer/_shared/mobile-conventions.md +196 -0
  88. package/core/skills/qc/qa-script-designer/_shared/web-conventions.md +257 -0
  89. package/core/skills/qc/qa-script-designer/api/auth.md +43 -0
  90. package/core/skills/qc/qa-script-designer/api/endpoint.md +61 -0
  91. package/core/skills/qc/qa-script-designer/api/security.md +41 -0
  92. package/core/skills/qc/qa-script-designer/mobile/e2e.md +35 -0
  93. package/core/skills/qc/qa-script-designer/mobile/functional/feature.md +32 -0
  94. package/core/skills/qc/qa-script-designer/mobile/functional/screen.md +42 -0
  95. package/core/skills/qc/qa-script-designer/mobile/integration.md +39 -0
  96. package/core/skills/qc/qa-script-designer/mobile/non-functional.md +39 -0
  97. package/core/skills/qc/qa-script-designer/web/e2e.md +36 -0
  98. package/core/skills/qc/qa-script-designer/web/functional/api.md +39 -0
  99. package/core/skills/qc/qa-script-designer/web/functional/gui-feature.md +34 -0
  100. package/core/skills/qc/qa-script-designer/web/functional/gui-screen.md +42 -0
  101. package/core/skills/qc/qa-script-designer/web/integration.md +43 -0
  102. package/core/skills/qc/qa-script-designer/web/non-functional.md +42 -0
  103. package/core/skills/qc/qa-script-runner/mobile/run.md +38 -0
  104. package/core/skills/qc/qa-script-runner/report.md +41 -0
  105. package/core/skills/qc/qa-script-runner/web/run.md +48 -0
  106. package/core/steps/context-loader.md +1 -1
  107. package/core/steps/gate.md +7 -1
  108. package/core/steps/qc-scope.md +45 -2
  109. package/core/steps/qc-stamp.md +4 -4
  110. package/core/steps/report-footer.md +10 -9
  111. package/docs/02-concepts/pipeline-steps/07-dev-selftest.md +1 -1
  112. package/docs/02-concepts/pipeline-steps/08-qc-automation.md +13 -12
  113. package/docs/02-concepts/pipeline-steps/10-feedback-loop.md +3 -3
  114. package/docs/02-concepts/traceability.md +1 -1
  115. package/docs/03-guides/developer.md +1 -1
  116. package/docs/03-guides/tester-qa.md +40 -11
  117. package/docs/04-reference/commands.md +4 -2
  118. package/docs/04-reference/modules.md +2 -1
  119. package/docs/04-reference/trace-schema.md +4 -4
  120. package/docs/explain/17-qc-design-test.md +5 -5
  121. package/docs/explain/18-qc-review.md +42 -20
  122. package/docs/explain/19-qc-run-test.md +13 -10
  123. package/docs/explain/20-qc-report.md +3 -3
  124. package/docs/explain/23-fix-bug.md +2 -2
  125. package/docs/explain/README.md +2 -2
  126. package/docs/plans/qc-surgery/01-checklist.md +86 -21
  127. package/docs/plans/qc-surgery/PLAN_v2.md +295 -0
  128. package/docs/plans/qc-surgery/exec-S-ap-stack-typescript.md +420 -0
  129. package/docs/plans/qc-surgery/exec-S0-guard-cam-stack-cu.md +400 -0
  130. package/docs/plans/qc-surgery/exec-S1-hai-module-thay-qc-playwright.md +267 -0
  131. package/docs/plans/qc-surgery/exec-S2-qa-runner-thanh-script-designer-runner.md +340 -0
  132. package/docs/plans/qc-surgery/exec-S3-viet-lai-tieu-chi-review-script.md +322 -0
  133. package/docs/plans/qc-surgery/exec-S5-an-theo-don-dau-vet-stack-cu.md +292 -0
  134. package/package.json +1 -1
  135. package/core/commands/qc-run-test.md +0 -561
  136. package/core/modules/qc-playwright/stack-profile.yaml +0 -66
  137. package/core/skills/qc/qa-reviewer/script/e2e.md +0 -95
  138. package/core/skills/qc/qa-reviewer/script/functional.md +0 -109
  139. package/core/skills/qc/qa-reviewer/script/integration.md +0 -99
  140. package/core/skills/qc/qa-reviewer/script/non-functional.md +0 -134
  141. package/core/skills/qc/qa-runner/e2e.md +0 -49
  142. package/core/skills/qc/qa-runner/functional/api.md +0 -35
  143. package/core/skills/qc/qa-runner/functional/gui-feature.md +0 -57
  144. package/core/skills/qc/qa-runner/functional/gui-screen.md +0 -61
  145. package/core/skills/qc/qa-runner/integration.md +0 -47
  146. package/core/skills/qc/qa-runner/non-functional.md +0 -49
  147. package/core/skills/qc/qa-runner/report/report.md +0 -37
@@ -0,0 +1,400 @@
1
+ ---
2
+ buoc: Bước S · việc S0
3
+ title: Đèn cơ học cấm stack QC cũ quay lại
4
+ phu_thuoc: không (đây là việc đầu tiên của Bước S)
5
+ trang_thai: ĐÃ TRIỂN KHAI 2026-09-17 · phạm vi thu về TẦNG SCRIPT · self-check ĐỎ 19 file (đúng ý đồ G3)
6
+ ---
7
+
8
+ > ⚠️ **Đọc §1.2b trước.** Bản đầu của file này lấy phạm vi `skills/qc/**` (đỏ **27 file**).
9
+ > Quyết định **S-SCOPE** ngày 2026-09-17 thu lại còn **tầng script** (đỏ **19 file**) — 5 tầng
10
+ > tài liệu *(phân tích · doc-gap · test plan · thiết kế TC · review TC)* **không đổi stack**.
11
+ > Mọi con số "27" dưới đây được giữ nguyên như bản đo gốc và có ghi chú kèm.
12
+
13
+ # S0 — Dựng đèn trước, dọn sau
14
+
15
+ ← [`exec-S-ap-stack-typescript.md`](exec-S-ap-stack-typescript.md) · [`PLAN_v2.md`](PLAN_v2.md) · [`01-checklist.md`](01-checklist.md)
16
+
17
+ | | |
18
+ |---|---|
19
+ | **Lớp** | Bước S · việc **1/6** — chạy **trước** S1–S5 |
20
+ | **File code** | `bin/trace-schema.json` *(`forbidden_patterns`)* · `bin/self-check.js` *(`allSourceFiles`, R5)* |
21
+ | **File test** | `test/run.js` — *(xem §1.1: có thể không đụng)* |
22
+ | **Ngày xong** | — |
23
+ | **Phụ thuộc** | không — S0 cố ý chạy khi repo **còn bẩn** |
24
+ | **Ai dùng nó** | `node bin/self-check.js` — 1 trong 4 lệnh kiểm, chạy sau mỗi bước |
25
+
26
+ ---
27
+
28
+ ## 0 · Đo được — 2026-09-17
29
+
30
+ ### 0.1 · Đèn sẽ đỏ như thế nào
31
+
32
+ Phạm vi quét **67 file** *(`skills/qc/**` · `commands/qc-*.tmpl` · `modules/qc-*/**`)* →
33
+ **27 file đỏ · 146 dòng**:
34
+
35
+ | Nhóm | File đỏ | Số phận |
36
+ |---|:-:|---|
37
+ | `skills/qc/` **còn lại** | **14** | phải sửa tay |
38
+ | `skills/qc/qa-runner/` | 7 *(trong 8)* | **xoá** ở S2 — tự hết đỏ |
39
+ | `commands/qc-*.tmpl` | 5 | phải sửa tay |
40
+ | `modules/qc-*/` | 1 | **thay** ở S1 |
41
+
42
+ Năm file nặng nhất: `modules/qc-playwright/stack-profile.yaml` **19 dòng** ·
43
+ `qa-reviewer/script/non-functional.md` **15** · `qa-runner/functional/gui-screen.md` **12** ·
44
+ `qa-reviewer/script/functional.md` **10** · `qa-runner/functional/gui-feature.md` **10**.
45
+
46
+ ### 0.2 · 🔴 Đèn tìm ra 10 file mà danh sách của Bước S BỎ SÓT
47
+
48
+ [`exec-S §B2`](exec-S-ap-stack-typescript.md) liệt kê: `qa-runner` · `qa-reviewer/script/*` ·
49
+ `matrix.md` · 3 lệnh script · `qc-report.tmpl` · module · `trace-schema` · `qc-base-map` · docs.
50
+ Đèn đỏ ở **10 file nữa**, không có trong bất kỳ dòng nào của kế hoạch:
51
+
52
+ | File chưa ai kể tên | Dòng | Nó đang dạy gì theo stack cũ |
53
+ |---|:-:|---|
54
+ | `skills/qc/qa-designer/shared/tc-metadata-format.md` | 3 | `@pytest.mark.flaky(...)` trong Tags · `conftest.py` fixture · section `#### Python Test Mapping` |
55
+ | `skills/qc/qa-reviewer/shared/review-check-groups.md` | 4 | *"marker pytest khớp Tags"* · `@pytest.mark.flaky(reruns=2, reruns_delay=3)` |
56
+ | `skills/qc/qa-planner/test-plan.md` | 3 | Exit criteria ghi *"báo cáo pytest-html"* |
57
+ | `commands/qc-design-test.tmpl` | 3 | *"Output feed vào qc-design-script (Python)"* |
58
+ | `skills/qc/qa-designer/shared/precision-rules.md` | 2 | teardown thuộc `conftest.py` scope=function |
59
+ | `skills/qc/qa-designer/api/auth-sequence.md` | 1 | *"Token fixture (Python)"* |
60
+ | `skills/qc/qa-reviewer/shared/review-file-template.md` | 1 | `- Mobile: @pytest.mark.flaky(...)` |
61
+ | `skills/qc/qa-reviewer/test-case/functional.md` | 1 | *"Review Python script → dùng qa-reviewer"* |
62
+ | `skills/qc/qa-reviewer/test-case/non-functional.md` | 1 | `@pytest.mark.performance\|security\|a11y\|i18n` |
63
+ | `commands/qc-analyze.tmpl` | 1 | *"…hay Python (đó là qc-design-test / qc-design-script)"* |
64
+
65
+ **Đây là toàn bộ lý do S0 đi trước.** Tám trong mười file nằm ở tầng **thiết kế test case** —
66
+ `qa-designer`, `qa-reviewer/test-case` — chỗ mà không ai nghĩ *"đổi stack script"* sẽ chạm tới.
67
+ Đi theo danh sách tay của Bước S thì mười file này **ở lại**, và `.Test.md` sinh ra sau đó vẫn
68
+ mang `@pytest.mark.flaky` trong Tags — tức stack cũ sống tiếp trong **đầu vào** của stack mới.
69
+
70
+ ### 0.3 · Hai phát hiện làm đổi nội dung Bước S *(không chỉ đổi đèn)*
71
+
72
+ | # | Phát hiện | Nguồn |
73
+ |:-:|---|---|
74
+ | **P1** | Framework hiện ghi **"No Allure"** như một luật cứng *(`stack-profile.yaml` · 12 file nhắc `Allure`)*. Chuẩn MỚI **ngược lại**: mobile **bắt buộc** Allure Report v2.x | `Mobile-Automation-Standards.md:48` · `API-Testing-Standards.md:49` · `Automation-Standards.md:515` *(OQ-01: web dùng Playwright HTML, Allure defer Phase 2)* |
75
+ | **P2** | `BasePage` **sống sót** sang stack mới — không phải dấu vết cần xoá | `Automation-Standards.md:221` `export class BasePage` · `:108` `base.page.ts` |
76
+
77
+ ---
78
+
79
+ ## 1 · BA CÂU CHỜ CHỐT
80
+
81
+ ### 1.1 · Đèn sống ở đâu — R5 hay một test mới?
82
+
83
+ **Bằng chứng: cơ chế này ĐÃ TỒN TẠI.** `bin/self-check.js:279` — **R5 · *"pattern bị cấm quay
84
+ lại"***, đọc `forbidden_patterns` từ `bin/trace-schema.json`. Hiện có **11 entry**, mỗi entry
85
+ gồm `pattern` · `gap` · `reason` · `except`. Và nó còn làm một việc mà một test viết vội sẽ
86
+ không làm: **cảnh báo khi một `except` trở nên vô nghĩa** *(`self-check.js:293`)* — tức danh
87
+ sách miễn trừ không mục ruỗng theo thời gian.
88
+
89
+ **Nhưng R5 có hai giới hạn thật, đã đo:**
90
+
91
+ | Giới hạn | Đo được | Hệ quả |
92
+ |---|---|---|
93
+ | `allSourceFiles()` **không quét `skills/`** | chỉ có `commands/*.tmpl` · `steps/` · `templates/` · `rules/` · `modules/` | **21 trong 27** file đỏ nằm ngoài tầm R5 |
94
+ | `s.text.includes(fp.pattern)` — so chuỗi thô, **không có phạm vi** | `pytest` trên toàn scan set → dính `dev-gen-test` · `dev-run-test` · `dev-smoke-test` · `sync.tmpl` | 4 dương tính giả *(xem §1.3)* |
95
+
96
+ | Phương án | Việc phải làm | Cái giá |
97
+ |---|---|---|
98
+ | **A · R5 + mở scan set + thêm trường `scope`** ⭐ | `allSourceFiles()` thêm `skills/**/*.md`; `forbidden_patterns[].scope` = mảng glob, **thiếu `scope` = toàn scan set** *(11 entry cũ không đổi hành vi)*; thêm 6 entry | đụng vào một rule đang xanh — nhưng **đã đo: mở scan set sang 64 file `skills/` làm 11 pattern cũ hit thêm 0 lần** |
99
+ | **B · test riêng trong `test/run.js`** *(bản exec-S viết)* | 1 test mới, regex tự do, allowlist riêng | **cơ chế thứ hai cho cùng một lớp việc** — hai chỗ khai pattern bị cấm, và cái mới không có luật *"`except` phải kèm lý do"* lẫn cảnh báo except chết |
100
+ | **C · cả hai** | R5 cho pattern thô, test cho phép đếm = 0 | thừa; hai đèn cùng đỏ một lỗi làm người ta sửa một chỗ rồi tưởng xong |
101
+
102
+ **Khuyến nghị: A.** Lý do không phải *"đỡ code hơn"* — mà là: danh sách pattern bị cấm của dự
103
+ án này **phải có đúng một chỗ để đọc**. Có hai chỗ thì lần sau người ta thêm vào chỗ sai và đèn
104
+ không bao giờ sáng. *(Nếu chọn A thì `test/run.js` **không đổi**, vẫn 244 test — khác con số
105
+ "244 → 245" ghi ở [`exec-S §8`](exec-S-ap-stack-typescript.md), tôi sẽ sửa lại bảng đó.)*
106
+
107
+ ---
108
+
109
+ ### 1.2 · Phạm vi có gồm `docs/` không?
110
+
111
+ **Đo được:** ngoài 27 file trong phạm vi, `docs/` *(trừ `docs/plans/`)* còn **7 file · 14 dòng**
112
+ dính. Trong đó **2 file là lane DEV** — `docs/explain/13-dev-run-test.md` ·
113
+ `14-dev-smoke-test.md` — **hợp lệ vĩnh viễn** *(xem §1.3)*. Còn lại **5 file QC thật**:
114
+ `02-concepts/pipeline-steps/08-qc-automation.md` · `03-guides/tester-qa.md` ·
115
+ `04-reference/modules.md` · `explain/17-qc-design-test.md` · `explain/19-qc-run-test.md`.
116
+
117
+ | Phương án | Cái giá |
118
+ |---|---|
119
+ | **Không gồm `docs/`** ⭐ | S5 sửa 5 file bằng tay; sau này docs **drift lại được trong im lặng**. Nhưng `docs/` chưa bao giờ nằm trong scan set của self-check — kéo nó vào là mở một mặt trận mới giữa Bước S |
120
+ | Gồm `docs/**` trừ `docs/plans/` | chặt hơn; phải thêm `except` cho 2 file DEV, và mọi đoạn **kể lịch sử** *("stack cũ là Python")* sau này đều phải xin `except` |
121
+
122
+ **Khuyến nghị: không gồm `docs/` ở S0.** Ghi thành **một dòng nợ có chủ**, không phải một chỗ
123
+ quên. Lý do: `docs/` là nơi duy nhất **cần** nói *"trước đây là Python"* — đèn ở đó sẽ cãi nhau
124
+ với chính nhu cầu của tài liệu, và cãi nhau thì người ta cấp `except` bừa. `docs/plans/` thì
125
+ chắc chắn phải nằm ngoài: cả file anh đang đọc vi phạm pattern **hàng chục lần**.
126
+
127
+ ---
128
+
129
+ ### 1.2b · 🔴 S-SCOPE — phạm vi thu về TẦNG SCRIPT *(chốt 2026-09-17, SAU khi S0 đã chạy)*
130
+
131
+ **Quyết định:** chỉ thay TypeScript cho Python **ở các trạm phía sau (tầng script)**, theo
132
+ `qc-base-new`. Năm tầng tài liệu **không đổi stack**: phân tích yêu cầu · quét doc-gap · lập test
133
+ plan · thiết kế test case · review test case.
134
+
135
+ **Đo trước khi chốt** *(đây là số làm quyết định này có căn cứ)*:
136
+
137
+ | Đo | Kết quả |
138
+ |---|---|
139
+ | 5 tầng tài liệu | **41 file · 0 file chạy được** — 100 % `.md`, là hướng dẫn cho agent |
140
+ | Lệnh QC gọi script của framework | **0** |
141
+ | `upstream/qc-base-new/` (7 file · 98 KB) nhắc `python\|pytest\|pandas\|jupyter\|.py` | **0 lần** |
142
+ | Python **chạy được** duy nhất từng có ở lane QC | 2 file kho CŨ: `qa-gate/styled-report-*.py`, state `undecided`, `targets: []`, **chưa bao giờ port** |
143
+
144
+ Nên *"Python vs TypeScript"* **không phải một trục có thật** ở 5 tầng đó: không có chương trình
145
+ nào ở đấy, bằng ngôn ngữ nào cũng vậy.
146
+
147
+ **Phạm vi đèn đổi theo:**
148
+
149
+ ```
150
+ skills/qc/qa-runner/** skills/qc/qa-reviewer/script/**
151
+ skills/qc/qa-automation-assess/** commands/qc-*.tmpl
152
+ modules/qc-*/**
153
+ ```
154
+
155
+ **Kết quả đo lại: đỏ 27 → 19 file.** Tám file rời khỏi tầm — và 16 dòng của chúng chia **bốn
156
+ nhóm không cùng loại**:
157
+
158
+ | Nhóm | Dòng | Là gì | Xử lý |
159
+ |---|:-:|---|---|
160
+ | **A** khai stack trong Test Plan | 3 | `qa-planner/test-plan.md` — *"báo cáo pytest-html + Playwright Trace"* | **nợ** — Test Plan là tài liệu chị QC **ký**; ký một kế hoạch mô tả công cụ không còn dùng là sai ở mức hồ sơ |
161
+ | **B** marker/fixture pytest làm **quy ước viết TC** | 10 | `@pytest.mark.flaky` trong Tags · `conftest.py` trong Preconditions | **nợ, chủ là chị QC** — xem ⚠️ dưới |
162
+ | **C** luật **CẤM** khuôn cũ | 2 | *"Không thêm `#### Python Test Mapping`"* · *"còn `#### Python Test Mapping` → `[OLD_TEMPLATE]`"* | **GIỮ NGUYÊN — đây là guard, không phải Python** |
163
+ | **D** chỉ đường | 1 | *"Review Python script → dùng qa-reviewer"* | nợ, 1 dòng |
164
+
165
+ > 🔴 **Nhóm C là chỗ bản S0 đầu tiên sai.** Hai dòng đó **cấm** Python, và đèn đang bắt chúng.
166
+ > Ai dọn theo danh sách đỏ một cách máy móc sẽ **xoá mất một guard đang làm việc**. Thu phạm vi
167
+ > đã giải quyết: chúng nằm ngoài tầm, **không cần `except`** — và như vậy tốt hơn `except`, vì
168
+ > `except` là *"ngoại lệ trong vùng ta quản"*, còn đây là *"không thuộc vùng ta quản"*.
169
+
170
+ > ⚠️ **Nhóm B không phải chuyện dịch cú pháp — nó đảo ngược ý nghĩa.**
171
+ > `@pytest.mark.flaky(reruns=2, reruns_delay=3)` = **tự chạy lại 2 lần, test VẪN trong suite
172
+ > chính**. `@flaky` của chuẩn mới *(`Automation-Standards.md` §11)* = **QUARANTINE: exclude khỏi
173
+ > main suite**, điều tra trong 1 sprint, ghi Automation Health Log. Hôm nay
174
+ > `review-check-groups.md:93` **ép** mọi TC mobile mang marker cũ; dịch thẳng sang chuẩn mới
175
+ > thành *ép mọi TC mobile bị cách ly khỏi suite chính*.
176
+ > Thêm nữa: **retry ×2 đã chuyển sang runner từ Đợt 1 b3** — marker trong TC giờ là **nguồn thứ
177
+ > hai** quyết định cùng một việc. Đây là **một quyết định về luật QC**, thuộc chị QC.
178
+
179
+ ---
180
+
181
+ ### 1.3 · Bộ pattern — cấm chữ gì, và cố ý KHÔNG cấm chữ gì?
182
+
183
+ > 🔴 **Điểm dễ sai nhất của cả S0.** Xem B1.
184
+
185
+ **Đề xuất 6 pattern**, tất cả `scope: ["skills/qc/**", "commands/qc-*.tmpl", "modules/qc-*/**"]`:
186
+
187
+ | Pattern | Bắt gì | File dính *(trong phạm vi)* |
188
+ |---|---|:-:|
189
+ | `pytest` | tên test runner cũ | 18 |
190
+ | `Python` · `python` | tên ngôn ngữ — **hai entry** vì R5 so chuỗi phân biệt hoa thường | 16 · 11 |
191
+ | `conftest.py` | cơ chế fixture của pytest | 8 |
192
+ | `_page.py` | quy ước đặt tên Page Object cũ | 4 |
193
+ | `.py` | mọi đuôi file Python còn sót | 17 |
194
+
195
+ **Cố ý KHÔNG cấm — có bằng chứng:**
196
+
197
+ | Chữ | Dính | Vì sao giữ |
198
+ |---|:-:|---|
199
+ | `Allure` | 12 file | Chuẩn mobile MỚI **bắt buộc** Allure v2.x *(P1)*. Cấm nó là mã hoá một luật **ngược tài liệu Approved** |
200
+ | `BasePage` | 6 file | Chuẩn web MỚI vẫn dùng — `export class BasePage` *(P2)* |
201
+ | `time.sleep` | 9 file | Là **anti-pattern nội dung**, không phải dấu vết stack. Luật *"không hardcode wait"* sống tiếp, chỉ đổi cách viết (`waitForTimeout`). Thuộc S2, không thuộc đèn |
202
+ | `test_<feature>` | 9 file | Quy ước đặt tên — sẽ đổi ở S2 cùng nội dung, cấm ở đây là đỏ hai lần một lỗi |
203
+
204
+ **Khuyến nghị: đúng 6 pattern trên.** Hẹp, và nói rõ là hẹp *([`PLAN_v2 §8 luật 4`](PLAN_v2.md))*.
205
+
206
+ ---
207
+
208
+ # PHẦN A — Chuyện gì đang xảy ra
209
+
210
+ ## A1 · Vấn đề
211
+
212
+ Đội QC bỏ một bộ công cụ hồi tháng trước, nhưng hơn trăm dòng viết theo bộ cũ vẫn nằm rải rác
213
+ trong bộ khung — và cho tới sáng nay **không ai biết chính xác là bao nhiêu dòng, ở những file
214
+ nào**. Dọn bằng mắt thì sẽ sót, và chỗ sót không kêu lên: nó lặng lẽ dạy sai cho lần chạy sau.
215
+ Còn một nửa nữa của vấn đề: dọn xong rồi, vài tháng sau một người chép lại một đoạn từ file cũ,
216
+ và hiện không có gì chặn.
217
+
218
+ ## A2 · Cách giải quyết, nói bằng một hình ảnh
219
+
220
+ Như dán biển *"không để đồ ở đây"* lên lối thoát hiểm, thay vì dặn miệng từng người rồi mong
221
+ người vào sau còn nhớ. Biển không dọn hộ đống đồ đang có — nó chỉ bảo đảm rằng dọn xong thì
222
+ không ai chất lại mà không bị nhắc.
223
+
224
+ ## A3 · Xong rồi thì thấy gì khác
225
+
226
+ Chạy `node bin/self-check.js` **ngay hôm nay** sẽ ra một dòng đỏ liệt kê đúng **27 file**. Dọn
227
+ xong ở các việc sau thì dòng đó biến mất. Và nếu về sau ai chép lại một đoạn cũ vào, nó hiện lại
228
+ ngay lần chạy kế — không cần ai nhớ luật.
229
+
230
+ ## A4 · Thuật ngữ dùng ở trên
231
+
232
+ - **bộ công cụ / stack** — ngôn ngữ + thư viện mà test được viết bằng. Bộ cũ: Python + pytest. Bộ mới: TypeScript + Playwright.
233
+ - **đèn / guard / rule** — một phép kiểm tự động; đỏ thì không được commit.
234
+ - **`self-check`** — một trong bốn lệnh kiểm, soi tính nhất quán của chính bộ khung. Đang có 22 luật, đánh số R1–R22.
235
+ - **R5** — luật *"pattern bị cấm quay lại"*, đã có sẵn; S0 thêm việc cho nó chứ không dựng luật mới.
236
+ - **pattern** — một đoạn chữ bị cấm xuất hiện, ví dụ `pytest`.
237
+ - **`except`** — danh sách file được phép chứa đoạn chữ đó, **mỗi file kèm một lý do viết ra**.
238
+ - **phạm vi / scope** — vùng file mà một luật soi tới. Ngoài vùng thì luật im.
239
+
240
+ ---
241
+
242
+ > ### ✅ Phép thử bắt buộc trước khi coi Phần A là xong
243
+ > **Đã thử với:** tự đọc to · **ngày:** 2026-09-17 ·
244
+ > **phải giải thích thêm chỗ nào:** câu *"đèn không dọn hộ"* ở A2 — người nghe lần đầu tưởng
245
+ > S0 đã sửa xong 27 file. Đã thêm một câu ở A3 tách rõ *"hôm nay đỏ"* và *"dọn ở các việc sau"*.
246
+
247
+ ---
248
+
249
+ # PHẦN B — Chi tiết kỹ thuật
250
+
251
+ ## B1 · Cách hiển nhiên là gì, và vì sao nó sai
252
+
253
+ ### Cách hiển nhiên — cấm chữ `Python` và `pytest` trên toàn repo
254
+
255
+ 9/10 người viết đúng một dòng: *"không file nào được chứa `python` hay `pytest`"*, chạy, thấy
256
+ đỏ, và thấy mình đang làm đúng.
257
+
258
+ **Vì sao sai — ba lý do, tất cả đo được:**
259
+
260
+ **1 · Python là ngôn ngữ sản phẩm hạng nhất của chính framework này.** `modules/context-engineering`
261
+ là một module Python thật; `commands/dev-gen-test.tmpl:190` ghi *"**Nếu language = Python** (mặc
262
+ định)"*; `commands/dev-run-test.tmpl:69` in `pytest tests/{domain}/{test_file}.py -v`;
263
+ `commands/sync.tmpl:317` nhận diện dự án qua `requirements.txt` / `pyproject.toml`. Cấm toàn cục
264
+ làm đỏ **4 lệnh lane DEV** — **100 % dương tính giả**. Thứ bị bỏ là **Python với tư cách stack
265
+ TEST của QC**, không phải Python. Đèn phải khoá theo **phạm vi**, không theo từ.
266
+
267
+ **2 · Cấm theo "mùi của stack cũ" sẽ cấm nhầm thứ sống sót.** Cách hiển nhiên phiên bản hăng hái
268
+ hơn là quét luôn mọi chữ trông giống bộ cũ. Đo thì: `Allure` dính **12 file** — và
269
+ `Mobile-Automation-Standards.md:48` khai **Reporting | Allure Report | v2.x** là bắt buộc;
270
+ `BasePage` dính **6 file** — và `Automation-Standards.md:221` có `export class BasePage`. Cấm hai
271
+ chữ này là **mã hoá vào máy một luật ngược với tài liệu Approved**, rồi sáu tháng sau có người
272
+ mất nửa ngày để hiểu vì sao đèn không cho họ làm đúng chuẩn.
273
+
274
+ > Riêng `Allure` còn là một **mâu thuẫn nội dung** mà Bước S phải xử, không phải chuyện đèn:
275
+ > framework đang ghi *"No Allure"* như luật cứng, chuẩn mobile mới thì bắt buộc. Xem §0.3 P1.
276
+
277
+ **3 · Dựng một test mới là bỏ qua một cơ chế đã có.** `R5` + `forbidden_patterns` đã làm đúng
278
+ việc này cho **11 pattern**, và có một tính chất mà test viết tay hay thiếu: nó **cảnh báo khi
279
+ một `except` không còn cần thiết**. Đặt danh sách cấm ở chỗ thứ hai nghĩa là lần sau người ta
280
+ thêm vào chỗ sai — và một đèn không bao giờ sáng thì tệ hơn không có đèn, vì nó trông như có.
281
+
282
+ ## B2 · Cách làm đúng
283
+
284
+ **Ba sửa đổi nhỏ, không sửa đổi nào chạm nội dung QC** *(nội dung là việc của S1–S5)*.
285
+
286
+ **① Mở scan set.** `bin/self-check.js` · `allSourceFiles()` thêm một dòng:
287
+ `push(path.join(ROOT,'skills'), f => f.endsWith('.md'))`.
288
+
289
+ > **Đã đo trước khi đề xuất:** chạy thử 11 pattern hiện có trên **64 file** `skills/**/*.md` →
290
+ > **0 hit**. Mở scan set **không làm đỏ** bất cứ thứ gì đang xanh. Con số này là điều kiện để
291
+ > phương án A an toàn; không có nó thì đây chỉ là một lời hứa.
292
+
293
+ **② Thêm trường `scope` cho `forbidden_patterns`.** Mảng glob. **Thiếu `scope` = toàn scan set**
294
+ — nên **11 entry cũ giữ nguyên hành vi, không phải sửa một entry nào**. Lọc thêm đúng một mệnh
295
+ đề vào vòng lặp R5 ở `self-check.js:284`:
296
+
297
+ ```
298
+ const inScope = s => !fp.scope || fp.scope.some(g => match(g, s.rel));
299
+ const hits = sources.filter(s => inScope(s) && s.text.includes(fp.pattern) && !except.has(s.rel));
300
+ ```
301
+
302
+ **③ Thêm 6 entry**, mỗi entry `scope: ["skills/qc/**","commands/qc-*.tmpl","modules/qc-*/**"]`,
303
+ `gap: "S0"`, và `reason` nói **hậu quả**, không nói luật:
304
+
305
+ > *"Stack test của QC chốt 2026-09-10 là TypeScript + Playwright (web/api) và WDIO+Appium
306
+ > (mobile), xác nhận qua email. Một dòng `pytest` còn sót trong phạm vi QC không làm đỏ test
307
+ > nào — nó chỉ làm lệnh sinh ra script mà không dự án nào chạy được, và lỗi đó hiện ra ở lần
308
+ > chạy thật của QC chứ không ở CI. Python vẫn hợp lệ ở lane DEV; vì thế entry này có `scope`."*
309
+
310
+ **Vì sao 6 pattern chứ không 2:** `pytest` + `Python` bỏ sót **8 file** chỉ mang dấu vết hình
311
+ dạng (`conftest.py`, `_page.py`, `.py` trần). Vì sao không 10: xem §1.3 — bốn chữ còn lại sống
312
+ sót sang stack mới hoặc thuộc tầng nội dung.
313
+
314
+ **Vì sao `Python` và `python` là hai entry:** `String.includes` phân biệt hoa thường, và R5 cố ý
315
+ dùng so chuỗi thô thay vì regex *(pattern là **chữ** người đọc được, không phải biểu thức người
316
+ phải giải mã)*. Hai entry là cái giá của tính chất đó — chấp nhận, và ghi lại đây để người sau
317
+ đừng "tối ưu" nó thành regex rồi làm hỏng 11 entry kia.
318
+
319
+ ## B3 · Nếu làm sai thì hỏng theo kiểu nào
320
+
321
+ **Ồn 🟠 — và Ồn là kiểu hỏng riêng của mọi cái đèn.**
322
+
323
+ Hỏng ở đâu: phạm vi quá rộng *(cấm toàn repo)* → 4 lệnh DEV đỏ oan. Người ta không gỡ luật —
324
+ người ta cấp `except` cho 4 file, rồi lần sau cấp tiếp, và sau đủ nhiều lần thì `except` dài hơn
325
+ danh sách cấm. Lúc đó **R5 vẫn xanh và không còn nghĩa gì**. Ai phát hiện: không ai — đó là chỗ
326
+ tệ. Một luật bị rỗng ruột trông y hệt một luật đang làm việc.
327
+
328
+ Kiểu hỏng phụ — **Im lặng 🔴** nếu phạm vi quá hẹp: bỏ `docs/` ra ngoài *(khuyến nghị §1.2)*
329
+ nghĩa là Python **quay lại qua đường tài liệu** được. Đây là một **lỗ đã biết, cố ý để lại**,
330
+ và vì thế nó phải thành một dòng nợ có người chịu — xem §9.
331
+
332
+ ## B4 · Verify bằng gì
333
+
334
+ | # | Phép thử | Kết quả mong đợi | **Chạy thật 09-17** |
335
+ |:-:|---|---|---|
336
+ | 1 | `node bin/self-check.js` **ngay sau ①②③, trước khi sửa nội dung** | **R5 ĐỎ** | ✅ **ĐỎ 27 file** *(phạm vi gốc)* → sau **S-SCOPE**: **ĐỎ 19 file** — 7 `qa-runner` *(sẽ xoá)* · 5 `qa-reviewer/script` · 1 `qa-automation-assess` · 5 `commands/qc-*` · 1 `modules/qc-*` |
337
+ | 2 | So output `self-check` trước/sau khi **chỉ** làm ① | khác biệt **0 dòng** | ✅ **xanh y nguyên** — mở scan set sang `skills/` không đỏ thêm gì |
338
+ | 3 | `node test/run.js` | **244/244**, không đổi | ✅ **244/244** |
339
+ | 4 | `node bin/build.js` · `node bin/lint-trace.js` | xanh | ✅ cả hai xanh |
340
+ | 5 | **Phá:** thêm `pytest` vào `commands/dev-gen-test.tmpl` | **KHÔNG đỏ** | ✅ **0 lần nhắc** trong output R5 — `scope` chặn thật |
341
+ | 6 | **Phá:** thêm `pytest` vào một file `skills/qc` **đang sạch** | **ĐỎ** | ✅ `qa-runner/exploratory/session.md` → **1 lần nhắc** |
342
+ | 6b | **Phá:** thêm `pytest` vào **tầng tài liệu** `qa-designer/e2e/journey.md` | **KHÔNG đỏ** *(sau S-SCOPE)* | ✅ **0 lần nhắc** — 5 tầng tài liệu thật sự ngoài tầm |
343
+ | 7a | **Phá:** `except` trỏ file **đã sạch** | **WARN R5** | ✅ `⚠️ [R5] \`_page.py\`: except \`…/risk-model.md\` không còn chứa pattern (hoặc ngoài scope)` |
344
+ | 7b | **Phá:** `except` trỏ file **đang đỏ** | file đó **biến mất** khỏi danh sách | ✅ `matrix.md` rời khỏi danh sách của `_page.py`, 3 file kia ở lại |
345
+ | 9 | `scope` phủ **thư mục S2 sắp tạo** *(`qa-script-designer/**` · `qa-script-runner/**`)* | 2 glob có mặt · WARN *"khớp 0 file"* cho tới khi S2 tạo chúng | ✅ **2 WARN đúng 2 thư mục đó** — và WARN sẽ tự tắt khi S2 tạo file |
346
+ | 10 | Sau S2, glob `skills/qc/qa-runner/**` trở thành glob chết | WARN *"khớp 0 file"* → **gỡ glob trong CHÍNH commit của S2** | ⏳ chờ S2 |
347
+ | 8 | Sau S1–S5 | R5 **xanh**, `except` **rỗng**, **0 WARN scope** | ⏳ chờ S1–S5 |
348
+
349
+ > **Phép thử 5 và 6 ra ngược nhau — đó là điều phải chứng minh.** Cùng đỏ nghĩa là `scope` bị bỏ
350
+ > qua; cùng xanh nghĩa là pattern không khớp gì. Một xanh một đỏ mới nói `scope` đang làm việc.
351
+ >
352
+ > **Đèn đỏ ở đâu — bản ghi 2026-09-17** *(đây là bằng chứng của `G3`, chụp lại vì cây git sẽ
353
+ > xanh suốt — xem [`exec-S §8`](exec-S-ap-stack-typescript.md))*:
354
+ >
355
+ > | Pattern | File đỏ |
356
+ > |---|:-:|
357
+ > | `pytest` | 21 |
358
+ > | `Python` | 19 |
359
+ > | `python` | 13 |
360
+ > | `conftest.py` | 8 |
361
+ > | `_page.py` | 4 |
362
+ > | `.py` | 16 |
363
+ > | **hợp** | **27** |
364
+
365
+ > Phép thử **5** và **6** là hai phép quan trọng nhất và chúng phải chạy **ngược nhau**. Một cái
366
+ > xanh một cái đỏ mới chứng minh `scope` đúng; cả hai cùng đỏ nghĩa là `scope` bị bỏ qua, cả hai
367
+ > cùng xanh nghĩa là pattern không khớp gì cả.
368
+
369
+ ## B5 · Bài học
370
+
371
+ - **2026-09-17** — Định viết đèn thành một test mới trong `test/run.js` *(exec-S §B2 đã viết thế)*. Đọc `self-check.js:279` thì **R5 đã là đúng cơ chế đó**, kèm cả `except` có lý do và cảnh báo except chết. Nguyên nhân thật: kế hoạch được viết từ *"cần một đèn"* chứ không từ *"đã có đèn nào chưa"*. Sửa: trước khi thêm một cơ chế, **grep tên lớp việc trong `bin/` đã**.
372
+ - **2026-09-17** — Bản nháp đầu định cấm luôn `Allure` và `BasePage` vì trông giống stack cũ. Đối chiếu `qc-base-new/` thì cả hai **sống sót**, và Allure còn **bắt buộc** cho mobile. Nguyên nhân thật: suy *"cũ"* từ trí nhớ thay vì tra tài liệu Approved. Sửa: mọi pattern định cấm phải `grep` qua `upstream/qc-base-new/` trước.
373
+ - **2026-09-17** — Phạm vi đèn trỏ `skills/qc/qa-runner/**` — **thư mục S2 sắp XOÁ** — và không trỏ `qa-script-designer/**` · `qa-script-runner/**` — **hai thư mục S2 sắp TẠO**. Sau S2 đèn sẽ **xanh vì rỗng, không vì sạch**: 6 file skill TypeScript mới ra đời **không ai canh**. Nguyên nhân thật: phạm vi được viết theo **hiện trạng** trong khi rule này sinh ra để canh **trạng thái sau khi đổi**. Sửa: thêm 2 glob, **và** thêm cảnh báo `scope` khớp 0 file — vì lỗi này tôi bắt được bằng tay, lần sau sẽ không ai bắt.
374
+ - **2026-09-17** — Lúc chạy phép thử 7, dùng `JSON.stringify(schema, null, 2)` để chèn `except` tạm → **reformat toàn bộ 3.100 dòng** của `trace-schema.json`. Không hỏng gì vì có bản sao, nhưng `git diff --stat` suýt không đọc được. Nguyên nhân thật: sửa một file cấu hình lớn bằng parse–ghi-lại thay vì sửa tại chỗ. Sửa: phép thử phá trên file JSON lớn phải **sao lưu trước, `git diff --stat` sau** — đã làm và xác nhận đúng **38 dòng thêm, 0 dòng đổi**.
375
+
376
+ ## B6 · Copy được / không copy được
377
+
378
+ | | |
379
+ |---|---|
380
+ | ✅ **Copy được sang dự án khác** | Dựng đèn **trước** khi dọn, và lấy danh sách đỏ làm danh sách việc *(đèn tìm ra 10 file mà người viết kế hoạch không nghĩ tới)*; `scope` mặc định = toàn bộ để entry cũ không đổi hành vi; mỗi miễn trừ kèm lý do + cảnh báo khi miễn trừ hết cần; hai phép thử phá **ngược nhau** để chứng minh phạm vi |
381
+ | ⚠️ **Chỉ đúng ở đây** | Tên `R5` và hình dạng `forbidden_patterns`; việc Python là ngôn ngữ sản phẩm hợp lệ *(dự án khác có thể cấm toàn cục là đúng)*; 6 pattern cụ thể; `docs/plans/` nằm ngoài vì kế hoạch phải trích dẫn pattern |
382
+
383
+ ## B7 · Link
384
+
385
+ - [`exec-S-ap-stack-typescript.md`](exec-S-ap-stack-typescript.md) — §8 *(S0 là việc 1/6)*, §B2 *(danh sách file — §0.2 của file này bổ sung 10 file)*
386
+ - [`PLAN_v2.md`](PLAN_v2.md) — §7 `G3` *("dựng rule trước khi vá")* · §8 luật 1 và luật 4
387
+ - `bin/self-check.js:279` — R5 · `:90` `allSourceFiles()` · `:293` cảnh báo except chết
388
+ - `bin/trace-schema.json` — `forbidden_patterns` *(11 entry)*
389
+ - `upstream/qc-base-new/` — `Automation-Standards.md:221` `BasePage` · `Mobile-Automation-Standards.md:48` Allure
390
+
391
+ ---
392
+
393
+ ## 9 · Nợ S0 để lại — có chủ, không phải chỗ quên
394
+
395
+ | Nợ | Vì sao để lại | Ai / khi nào |
396
+ |---|---|---|
397
+ | `docs/` **ngoài tầm đèn** — 5 file QC còn Python sau S5 vẫn không được canh | `docs/` cần nói *"trước đây là Python"*; đèn ở đó sẽ đẻ ra `except` bừa *(§1.2)* | rà lại sau Đợt 4, khi biết docs ổn định tới đâu |
398
+ | Đèn canh **chữ**, không canh **hình dạng** — một skill viết đúng TypeScript nhưng bố cục thư mục kiểu pytest vẫn qua | Canh hình dạng cần parse, không phải so chuỗi | nếu tái phát thì mới dựng; đừng dựng trước |
399
+ | **8 file · 16 dòng ở 5 tầng tài liệu** vẫn mô tả stack Python — ngoài tầm đèn sau `S-SCOPE` | Quyết định 09-17: 5 tầng đó không đổi stack | **nhóm A + D** *(4 dòng)*: sửa bất cứ lúc nào, không chặn ai. **nhóm B** *(10 dòng)*: **chị QC** — xem ⚠️ ở §1.2b, nó đảo ngược ý nghĩa chứ không phải đổi cú pháp. **nhóm C** *(2 dòng)*: **không sửa**, đó là guard |
400
+ | **Hệ quả trực tiếp của nhóm B:** `.Test.md` do trạm 3 sinh ra vẫn dặn QC ghi `@pytest.mark.flaky`, còn trạm 6 sinh script TypeScript **không hiểu marker đó** | Là giá của `S-SCOPE`, đã biết khi chốt | người sinh script phải tự dịch mỗi lần. Ghi ra đây để nó là **một lựa chọn**, không phải một chỗ quên |