@educa-corp/sdd-framework 0.9.1 → 0.9.3

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 (69) hide show
  1. package/bin/qc-base-map.json +595 -0
  2. package/bin/self-check.js +146 -0
  3. package/core/FRAMEWORK_VERSION +1 -1
  4. package/core/commands/propose-scenario.md +1 -1
  5. package/core/commands/qc-analyze.md +398 -37
  6. package/core/commands/qc-design-test.md +141 -2
  7. package/core/commands/qc-plan.md +159 -10
  8. package/core/commands/qc-review.md +134 -1
  9. package/core/commands/qc-run-test.md +135 -2
  10. package/core/commands/refine-prd.md +47 -20
  11. package/core/commands/report-bug.md +1 -1
  12. package/core/commands/review-context.md +27 -1
  13. package/core/modules/qc-playwright/stack-profile.yaml +3 -3
  14. package/core/skills/qc/qa-analyst/DOC_GAP.template.md +147 -0
  15. package/core/skills/qc/qa-analyst/acceptance-criteria.md +5 -3
  16. package/core/skills/qc/qa-analyst/business-rules.md +39 -5
  17. package/core/skills/qc/qa-analyst/data-flow.md +6 -4
  18. package/core/skills/qc/qa-analyst/exhaustive-gap-scanner.md +174 -0
  19. package/core/skills/qc/qa-analyst/spec-breakdown.md +10 -8
  20. package/core/skills/qc/qa-analyst/spec-issue-reporter.md +112 -0
  21. package/core/skills/qc/qa-designer/e2e/journey.md +3 -3
  22. package/core/skills/qc/qa-designer/exploratory/charter.md +1 -1
  23. package/core/skills/qc/qa-designer/exploratory/explore-to-functional.md +2 -2
  24. package/core/skills/qc/qa-designer/functional/api.md +3 -3
  25. package/core/skills/qc/qa-designer/functional/gui-feature.md +3 -3
  26. package/core/skills/qc/qa-designer/functional/gui-screen.md +3 -3
  27. package/core/skills/qc/qa-designer/integration/api.md +3 -3
  28. package/core/skills/qc/qa-designer/integration/db.md +3 -3
  29. package/core/skills/qc/qa-designer/integration/gui.md +3 -3
  30. package/core/skills/qc/qa-designer/integration/kafka.md +3 -3
  31. package/core/skills/qc/qa-designer/non-functional.md +3 -3
  32. package/core/skills/qc/qa-planner/risk-model.md +106 -0
  33. package/core/skills/qc/qa-planner/test-plan.md +35 -21
  34. package/core/skills/qc/qa-reviewer/script/e2e.md +1 -1
  35. package/core/skills/qc/qa-reviewer/script/exploratory.md +1 -1
  36. package/core/skills/qc/qa-reviewer/script/functional.md +1 -1
  37. package/core/skills/qc/qa-reviewer/script/integration.md +1 -1
  38. package/core/skills/qc/qa-reviewer/script/non-functional.md +1 -1
  39. package/core/skills/qc/qa-reviewer/test-case/e2e.md +1 -1
  40. package/core/skills/qc/qa-reviewer/test-case/exploratory.md +1 -1
  41. package/core/skills/qc/qa-reviewer/test-case/functional.md +1 -1
  42. package/core/skills/qc/qa-reviewer/test-case/integration.md +2 -2
  43. package/core/skills/qc/qa-reviewer/test-case/non-functional.md +1 -1
  44. package/core/skills/qc/qa-runner/e2e.md +1 -1
  45. package/core/skills/qc/qa-runner/exploratory/session.md +2 -2
  46. package/core/skills/qc/qa-runner/functional/api.md +1 -1
  47. package/core/skills/qc/qa-runner/functional/gui-feature.md +1 -1
  48. package/core/skills/qc/qa-runner/functional/gui-screen.md +1 -1
  49. package/core/skills/qc/qa-runner/integration.md +1 -1
  50. package/core/skills/qc/qa-runner/non-functional.md +1 -1
  51. package/core/skills/qc/qa-runner/report/report.md +1 -1
  52. package/core/steps/context-loader.md +1 -1
  53. package/core/steps/gap-verify.md +231 -0
  54. package/core/steps/qc-scope.md +119 -0
  55. package/core/steps/review-fanout.md +27 -1
  56. package/core/templates/project-context.yaml +5 -3
  57. package/docs/02-concepts/pipeline-steps/08-qc-automation.md +2 -2
  58. package/docs/04-reference/commands.md +1 -1
  59. package/docs/04-reference/configuration.md +146 -146
  60. package/docs/explain/03-refine-prd.md +8 -6
  61. package/docs/explain/15-qc-analyze.md +10 -7
  62. package/docs/explain/16-qc-plan.md +3 -3
  63. package/docs/explain/17-qc-design-test.md +1 -1
  64. package/docs/plans/qc-implementation-log.md +1587 -0
  65. package/docs/plans/qc-merge-plan.md +502 -0
  66. package/docs/plans/qc-sync-command.md +359 -0
  67. package/package.json +1 -1
  68. package/scripts/migrate-qc-docs.js +261 -0
  69. package/core/skills/qc/qa-analyst/DOC_GAPS.template.md +0 -63
@@ -9,6 +9,9 @@ ported_from: ai-automation-qc-base
9
9
  > Stage 1 của QC automation pipeline native (qc-analyze → qc-plan → qc-design-test → qc-review → qc-run-test → qc-report). Port từ qa-analyst của team QC. Markdown-first: không có script ở đây.
10
10
 
11
11
  ## Gate
12
+
13
+ *Checkpoint: **chặn thường** — lệnh ghi 2 file artifact. `--yes` bỏ qua được (gate Bước 3a).*
14
+
12
15
  # Gate — Quy trình vào chuẩn cho mọi lệnh
13
16
 
14
17
  Mọi lệnh PHẢI chạy gate này trước khi thực thi phần logic riêng của nó.
@@ -162,7 +165,7 @@ Mỗi dòng ⚠️/🔴 phải ứng với một trạng thái **context-loader
162
165
  🔴/⚠️ (không chặn ≠ không báo — người đọc log sau này vẫn cần thấy).
163
166
 
164
167
 
165
- *Lưu ý: Với lệnh này, target ở Bước 1 là một UC-ID hoặc file feature/PRD. Đọc spec chính thức của UC đó — file `.feature` (mang `@trace.id={UC-ID}` và mỗi scenario `@trace.scenario={UC-ID}-SC{N}`), PRD, và design-spec — từ feature package `{paths.specs_dir}/{domain}/{prd-slug}/` (file `.feature` dưới `bdd/`, file PRD `{TICKET-ID}-{prd-slug}.md` ở gốc folder, và design-spec dưới `design-spec/`). Spec của framework CHÍNH LÀ source of truth; đừng suy lại các requirement đã có ở đó.*
168
+ *Lưu ý: Với lệnh này, target ở Bước 1 là một **TICKET-ID** (mã PRD), hoặc một UC-ID / file feature / file PRD — cả ba đều quy về TICKET-ID ở §Phạm vi QC. Trạm này chạy cho **cả PRD × một nền**. Đọc spec chính thức của **mọi UC trong phạm vi** — file `.feature` (mang `@trace.id={UC-ID}` và mỗi scenario `@trace.scenario={UC-ID}-SC{N}`), PRD, và design-spec — từ feature package `{paths.specs_dir}/{domain}/{prd-slug}/` (file `.feature` dưới `bdd/`, file PRD `{TICKET-ID}-{prd-slug}.md` ở gốc folder, và design-spec dưới `design-spec/`). Spec của framework CHÍNH LÀ source of truth; đừng suy lại các requirement đã có ở đó. **Ngoài ra đọc tech-doc gộp** `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md` làm **nguồn thứ hai** — xem §Đối chiếu tài liệu kỹ thuật.*
166
169
 
167
170
  ## Context
168
171
  **BẮT BUỘC — đọc `.agent/steps/context-loader.md` và thực thi TOÀN BỘ quy trình trong đó**,
@@ -174,30 +177,198 @@ placeholder bên dưới sẽ rỗng và lệnh sẽ đọc/ghi sai chỗ.
174
177
 
175
178
  ---
176
179
 
177
- ## GuardBDD đã duyệt chưa
180
+ ## Phạm vi QC PRD nào, nền nào, những UC nào
181
+
182
+ # QC Scope — phân giải phạm vi cho mọi lệnh `qc-*`
183
+
184
+ **Chạy TRƯỚC phần logic riêng của lệnh, và SAU `steps/gate.md`.** Bước này chốt bốn thứ mà
185
+ cả 6 trạm QC đều cần, để chúng không tự suy mỗi trạm một kiểu:
186
+
187
+ | Biến | Là gì |
188
+ |---|---|
189
+ | `TICKET-ID` | mã PRD — **thư mục artifact QC mang tên này** |
190
+ | `active_platform` | `web` \| `app` \| `system` \| … — một QC pass khoá đúng MỘT nền |
191
+ | `qc_artifact_dir` | `{paths.qc_dir}/{TICKET-ID}/{active_platform}/` |
192
+ | `uc_list` | các UC của (PRD × nền) này, kèm trạng thái BDD từng UC |
193
+
194
+ > **Vì sao gom về một chỗ.** Luật phân giải nền từng được copy-paste ở 5 lệnh và câu chữ đã
195
+ > lệch nhau. Năm bản của một luật là nơi drift sống: sửa bốn, quên một, và trạm bị quên ghi
196
+ > artifact vào sai thư mục **trong im lặng**.
197
+
198
+ ---
199
+
200
+ ## 1 — `TICKET-ID`
201
+
202
+ Artifact QC gom theo **PRD**, không theo UC. Nên mọi trạm phải quy được về `TICKET-ID`:
203
+
204
+ | `$ARGUMENTS` là | Cách lấy |
205
+ |---|---|
206
+ | **UC-ID** (`{TICKET-ID}-UC{N}`) | phần **trước** `-UC` — đúng luật `steps/gate.md` Bước 1 dùng để tìm tech-doc gộp |
207
+ | **TICKET-ID** | dùng trực tiếp |
208
+ | một **path file** (`.feature` / PRD / design-spec) | phân giải `{domain}` + `{prd-slug}` theo luật `context-loader` Bước 1, rồi lấy `TICKET-ID` từ tên file PRD `{TICKET-ID}-{prd-slug}.md` — file `.md` duy nhất ở gốc feature folder |
178
209
 
179
- Đọc `# @trace.status:` từ header file `.feature` của UC target:
180
- - `approved` tiếp tục bình thường.
181
- - `draft` (hoặc khác `approved`) → **CHECKPOINT cảnh báo mềm** (không chặn cứng — cho phép QC sớm/prototype):
182
- ```
183
- ⚠️ BDD của {UC-ID} đang ở @trace.status: {status} (chưa duyệt). QC chạy trên BDD chưa chốt có thể phải làm lại.
184
- Khuyến nghị: review-context (BDD) sạch + người duyệt đặt `# @trace.status: approved` rồi mới chạy QC.
185
- Vẫn chạy QC bây giờ? (Y/N)
186
- ```
187
- Chỉ tiếp khi chọn Y.
210
+ Đối chiếu: `TICKET-ID` suy ra phải khớp tên file PRD thật. Lệch → **DỪNG**, in cả hai giá
211
+ trị. (Suy sai `TICKET-ID` ghi cả một PRD vào sai thư mục — không có bước nào phía sau bắt được.)
188
212
 
189
213
  ---
190
214
 
191
- ## Platform Resolution *(thiết lập cho cả QC pass mọi stage sau kế thừa)*
215
+ ## 2`active_platform`
216
+
217
+ > **PHẢI phân giải TRƯỚC mọi phép đọc `.feature`.** `{UC-ID}-SC{N}` chỉ độc nhất trong
218
+ > (UC × nền), nên một UC đa nền có **nhiều file `.feature`** — `bdd/web/`, `bdd/app/`,
219
+ > `bdd/system/` — và mỗi file mang `@trace.status` **riêng**: bản web có thể `approved`
220
+ > trong khi bản app còn `draft`. Đọc "file `.feature` của UC" khi chưa biết nền là đọc một
221
+ > file **bất kỳ trong ba**: báo `approved` trong khi bản đang dùng còn nháp, hoặc chặn oan
222
+ > một bản đã duyệt.
223
+
224
+ Theo thứ tự, dừng ở cái đầu tiên khớp:
225
+
226
+ 1. `$ARGUMENTS` nêu nền (`web`/`app`/`system`/…) → dùng.
227
+ 2. Target là một file `.feature` → đọc `# @trace.platform` của nó.
228
+ 3. Glob `{paths.specs_dir}/{domain}/{prd-slug}/bdd/*/` — **đúng một** thư mục nền → dùng nó.
229
+ 4. Glob `{paths.qc_dir}/{TICKET-ID}/*/` — **đúng một** thư mục nền đã có artifact → dùng nó.
230
+ *(chỉ dùng cho trạm 2–6; trạm `/qc-analyze` là trạm tạo ra thư mục đó nên không có gì để soi.)*
231
+ 5. Nhiều nền mà không suy được → hỏi *"QC pass này cho nền nào? (web/app/system)"*.
232
+ **Có `--yes`:** không hỏi — DỪNG với lỗi rõ ràng, vì đoán bừa nền là ghi artifact vào sai
233
+ thư mục và ghi `qc_status` vào sai sổ trace:
234
+ ```
235
+ ❌ {TICKET-ID} có {n} nền ({list}) — không suy được nền nào cho QC pass này.
236
+ Chạy headless thì phải nêu tường minh: /{lệnh} {TICKET-ID} web --yes
237
+ ```
238
+
239
+ Lưu `active_platform`. Từ đây, **mọi** phép đọc `.feature` chỉ đọc thư mục
240
+ `bdd/{active_platform}/` — không trộn SC chéo nền.
192
241
 
193
- `{UC-ID}-SC{N}` chỉ độc nhất trong (UC × platform) — `web SC3` và `app SC3` là hai scenario khác nhau, và sổ trace tách theo `{UC-ID}-{platform}.tsv`. Nên **một QC pass khoá đúng MỘT platform**, và mọi artifact QC nằm dưới `{paths.qc_dir}/{UC-ID}/{active_platform}/`.
242
+ ---
243
+
244
+ ## 3 — `qc_artifact_dir`
245
+
246
+ ```
247
+ qc_artifact_dir = {paths.qc_dir}/{TICKET-ID}/{active_platform}/
248
+ ```
249
+
250
+ Chứa: `REQUIREMENT_ANALYSIS.md` · `DOC_GAP.md` · `TEST_PLAN.md` · `test-cases/*.Test.md`
251
+ — **mỗi loại đúng MỘT file cho cả PRD**, các UC là mục/hàng bên trong.
194
252
 
195
- Phân giải `active_platform`:
196
- - Target Bước 1 một file `.feature` đọc `# @trace.platform` của nó.
197
- - `$ARGUMENTS` nêu platform (`web`/`app`/`system`) dùng.
198
- - Ngược lại → hỏi: *"QC pass này cho platform nào? (web/app/system)"* và chờ chọn.
253
+ `{paths.qc_dir}` folder top-level **nhìn thấy** trong repo QC (mặc định `docs/`, **không**
254
+ phải `.agent/` ẩn) để đội QC mở xử output dễ dàng. Spec chính thức ở lại spec submodule
255
+ của PO đừng ghi artifact QC vào đó.
199
256
 
200
- Lưu `active_platform`. Đọc **đúng file `.feature` của platform đó** (`{paths.specs_dir}/{domain}/{prd-slug}/bdd/{active_platform}/{UC-ID}*.feature`) làm nguồn SC — không trộn SC chéo platform.
257
+ > **Sổ trace KHÔNG theo layout này.** vẫn `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{active_platform}.tsv`
258
+ > — một sổ cho mỗi (UC × nền), vì mỗi hàng là một scenario. Liên kết giữa hai bên đi qua
259
+ > **cột `UC`** của bảng gap, không qua đường dẫn file.
260
+
261
+ ---
262
+
263
+ ## 4 — `uc_list`
264
+
265
+ Glob `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{active_platform}/*.feature`. Mỗi file → một
266
+ UC: đọc `# @trace.id` (mã UC) và `# @trace.status` từ header.
267
+
268
+ Chia hai nhóm:
269
+
270
+ | Nhóm | Điều kiện | Xử lý |
271
+ |---|---|---|
272
+ | **Trong phạm vi** | `@trace.status: approved` | phân tích / thiết kế / chạy bình thường |
273
+ | **Chưa xét** | khác `approved` | **KHÔNG** phân tích; vẫn ghi một hàng vào bảng phạm vi kèm trạng thái thật |
274
+
275
+ In bảng phạm vi ra trước khi làm gì:
276
+ ```
277
+ Phạm vi QC — {TICKET-ID} / {active_platform}
278
+ ✅ {UC-ID} {tên UC} approved
279
+ ⏸ {UC-ID} {tên UC} draft → chưa xét
280
+ → {n} UC trong phạm vi · {m} chưa xét
281
+ ```
282
+
283
+ **Cờ `--include-draft`:** phân tích cả UC chưa duyệt, nhưng **vẫn in bảng trên** và đánh dấu
284
+ trong artifact là dựa trên BDD nháp.
285
+
286
+ **Không UC nào `approved` và không có `--include-draft` → DỪNG:**
287
+ ```
288
+ ❌ {TICKET-ID} ({active_platform}): 0/{n} UC có BDD approved — không có gì để chạy.
289
+ Cách đúng: người duyệt đặt `# @trace.status: approved` rồi chạy lại.
290
+ Muốn chạy sớm trên BDD nháp (prototype): thêm --include-draft
291
+ ```
292
+
293
+ > **Vì sao có `--include-draft` chứ không chặn cứng.** QC sớm trên BDD nháp là một cách dùng
294
+ > **cố ý được cho phép** từ trước (guard cũ là cảnh báo mềm, không phải chặn). Bỏ hẳn nó là
295
+ > lấy đi một năng lực đang có mà không ai khai. Còn để mặc định `approved`-only thì cái
296
+ > thường gặp là cái an toàn, và cái sớm phải nói ra.
297
+
298
+ > **Vì sao `--yes` không thay được `--include-draft`.** `--yes` nghĩa *"tôi không ngồi đây để
299
+ > trả lời"*; `--include-draft` nghĩa *"tôi biết BDD còn nháp và vẫn muốn chạy"*. Gộp hai cái
300
+ > là để một lần chạy headless âm thầm phân tích spec chưa chốt rồi bàn giao như thể đã chốt.
301
+
302
+
303
+ > **QC chạy trên BDD chưa chốt có thể phải làm lại.** `qc-scope` mặc định chỉ lấy UC có
304
+ > `@trace.status: approved`; UC còn nháp vẫn vào bảng *Phạm vi phân tích* của `DOC_GAP.md`
305
+ > với dấu `⏸ Chưa xét` — **không im lặng bỏ khỏi bảng**, vì "chưa xét" khác "đã xét, sạch".
306
+ > Cố ý QC sớm thì thêm `--include-draft`, và artifact phải ghi rõ nó dựa trên BDD nháp.
307
+
308
+ > **Vì sao trạm này chạy CẢ PRD chứ không từng UC** *(B11)*. Ba lý do, theo thứ tự quan trọng:
309
+ >
310
+ > 1. **Mâu thuẫn chéo UC chỉ lộ ra khi đọc cùng lúc.** UC1 nói một kiểu, UC3 nói kiểu khác —
311
+ > chạy tách từng UC thì về **cấu trúc** là không thể thấy, không phải "khó thấy".
312
+ > 2. **Rẻ hơn.** PRD, bản thiết kế, tài liệu kỹ thuật là nguồn **dùng chung**; chạy per-UC là
313
+ > đọc lại chúng mỗi UC một lượt. Phần dùng chung chiếm đa số đầu vào.
314
+ > 3. **Một tài liệu cho một tính năng** là cách PO và QC vốn làm việc — file gốc của đội QC
315
+ > (`DOC_GAP_FEAT-02-3.md`) không có hậu tố UC, và `qa-planner/test-plan.md` vốn viết
316
+ > *"Test Plan cho một feature"*.
317
+
318
+ ---
319
+
320
+ ## Đối chiếu tài liệu kỹ thuật *(nguồn thứ hai — bắt lệch nghiệp vụ ↔ kỹ thuật)*
321
+
322
+ Định vị tech-doc gộp cấp PRD: `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md`.
323
+ Nó phủ **nhiều UC** — và trạm này cũng phủ nhiều UC, nên đọc **mọi phần chạm `uc_list`**
324
+ (đối chiếu `@trace.ucs` ở header với `uc_list`). Phần thuộc UC ngoài phạm vi (`⏸ Chưa xét`) thì bỏ qua.
325
+
326
+ > **Đây là chỗ layout cấp PRD trả lãi rõ nhất.** Tech-doc gộp là **một** tài liệu phủ cả PRD.
327
+ > Chạy per-UC thì nó bị đọc lại N lần, mỗi lần lọc bỏ gần hết — và mâu thuẫn giữa hai UC trong
328
+ > **cùng** tài liệu đó không lần nào lộ ra, vì không lần nào thấy cả hai.
329
+
330
+ **Không tìm thấy → cảnh báo mềm, KHÔNG chặn** (dự án có thể chưa dựng tech-doc):
331
+ ```
332
+ ⚠️ Không có tech-doc cho {TICKET-ID} — phân tích chỉ dựa trên PRD + BDD + design-spec.
333
+ Lệch giữa yêu cầu nghiệp vụ và hợp đồng kỹ thuật (enum, mã lỗi, ràng buộc field) sẽ KHÔNG được phát hiện ở trạm này.
334
+ ```
335
+
336
+ **Có → đối chiếu các mục sau với PRD/BDD, mỗi chỗ vênh là một gap `CONTRADICTORY`:**
337
+
338
+ | Mục tech-doc | Đối chiếu gì với PRD/BDD |
339
+ |---|---|
340
+ | §3 Mô hình dữ liệu | thực thể/field/quan hệ PRD nhắc tới có khớp không |
341
+ | **§4 Hợp đồng API** | **enum & tập giá trị hợp lệ** · ràng buộc field (độ dài, định dạng, bắt buộc) · **mã lỗi** — PRD nêu bao nhiêu nhánh lỗi, contract định nghĩa bao nhiêu |
342
+ | §4.5 Ánh xạ component UI | màn/component PRD·design-spec mô tả có mặt đủ không |
343
+ | §5 Luồng chính | thứ tự bước, nhánh rẽ có khớp scenario `.feature` không |
344
+ | §6 Điểm tích hợp | side-effect PRD nêu (gửi sự kiện, gọi dịch vụ khác) có được định nghĩa không |
345
+ | §8 Xử lý lỗi & biên | trường hợp biên PRD nêu có đường xử lý không, và ngược lại |
346
+
347
+ > **Vì sao mục này tồn tại.** Có một lớp gap **chỉ lộ ra khi so hai loại tài liệu với nhau** —
348
+ > đọc riêng bên nào cũng thấy hợp lý. Ca điển hình: PRD viết *"chọn lớp 1–6"*, contract định
349
+ > nghĩa enum `1..9`. Không ai đọc cả hai thì không ai thấy, và nó ra tận lúc chạy thật.
350
+ > **Đây là lý do trạm này đọc tech-doc — không phải để hiểu kỹ thuật, mà để bắt chỗ hai bên nói khác nhau.**
351
+
352
+ ### §12 GAP Register — ĐỌC, KHÔNG GHI
353
+
354
+ Tech-doc có sổ ẩn số thiết kế riêng (`§12`), với vòng đời và người chịu trách nhiệm riêng, và
355
+ `/generate-code` đã canh nó. **Trạm này chỉ đọc, tuyệt đối không ghi vào.**
356
+
357
+ Với mỗi mục `open` trong §12 chạm **bất kỳ UC trong `uc_list`**:
358
+ - **KHÔNG mở gap mới** trong `DOC_GAP.md` về cùng chuyện đó.
359
+ - Ghi vào `REQUIREMENT_ANALYSIS.md` mục *"Đang chờ chốt (từ §12 tech-doc)"*: `{id}` · **UC** · điều chưa biết · owner · severity.
360
+ - Test case chạm nó về sau sẽ bị chặn — nhưng bị chặn bởi **một mục đã có người xử lý**, không phải bởi một câu hỏi mới gửi PO.
361
+
362
+ > **Vì sao không ghi vào.** Một ẩn số đã nằm trong §12 nghĩa là **đã có người đang lo**: có
363
+ > owner, có mức chặn, có cổng chặn sinh code. Mở lại nó thành gap QC là gửi PO một câu hỏi
364
+ > về thứ không phải việc của PO, và tạo hai sổ cùng theo dõi một chuyện — rồi chúng lệch nhau.
365
+ > Đây đúng là **câu hỏi lọc Q1** của `steps/gap-verify.md` (*"chỗ này đã được trả lời ở tài liệu
366
+ > khác chưa?"*), chỉ mở rộng phạm vi "tài liệu khác" thêm một nguồn.
367
+
368
+ **Ngoại lệ — mục `spec-defect` là việc của PO.** §12 phân ba loại: `nội tại` (backend tự quyết) ·
369
+ `cross-service` (đội khác) · `spec-defect` (PRD/BDD sai hoặc thiếu). Hai loại đầu → ghi "đang chờ".
370
+ Loại thứ ba **đúng là gap tài liệu** → vẫn ghi vào `DOC_GAP.md` (cột `UC` = UC bị chạm), trỏ
371
+ ngược về `{id}` của §12 để không đếm hai lần.
201
372
 
202
373
  ---
203
374
 
@@ -225,45 +396,235 @@ Thứ tự điển hình: spec-breakdown → business-rules / data-flow → acce
225
396
 
226
397
  File `.feature` chính thức đã định nghĩa scenario là `@trace.scenario={UC-ID}-SC{N}` với
227
398
  `@trace.business_rules`. Map mọi `BR-xx` / `AC-xx` bạn tạo ra tới `{UC-ID}-SC{N}` sở hữu nó
228
- và ghi lại mapping — qc-design-testqc-run-test cần để gắn tag
399
+ và ghi lại mapping — **làm cho từng UC trong `uc_list`**, `BR`/`AC` phải mang UC của nó
400
+ (một file phân tích giờ phủ nhiều UC, nên `BR-01` không còn tự phân biệt được là của UC nào) — qc-design-test và qc-run-test cần nó để gắn tag
229
401
  `@trace.verifies` cho test và ghi `qc_status` theo từng scenario.
230
402
 
231
- ## DOC_GAPS (bắt buộc)
403
+ ## Quét gap — hai nguồn, gộp rồi mới thẩm định
404
+
405
+ Gap đến từ **hai chỗ**, và chúng bổ sung nhau chứ không thay thế:
406
+
407
+ | Nguồn | Trả lời câu | Gap là |
408
+ |---|---|---|
409
+ | **4 kỹ năng phân tích** ở trên | *"yêu cầu là gì?"* | sản phẩm phụ — đang bóc luật nghiệp vụ thì gặp chỗ mâu thuẫn |
410
+ | **Quét theo lăng kính** *(dưới đây)* | *"còn thiếu gì?"* | mục tiêu chính |
411
+
412
+ ### Quét theo lăng kính
413
+
414
+ Chạy `steps/review-fanout.md` với:
415
+
416
+ | Tham số | Giá trị |
417
+ |---|---|
418
+ | `DIMENSIONS` | **4 lăng kính** — `D2 Xử lý lỗi` · `D3 Giao diện` · `D4 Dữ liệu & cấu hình` · `D5 Đối chiếu chéo` **(thu hẹp — xem dưới)** *(định nghĩa ở `{paths.qc_skills_dir}/qa-analyst/exhaustive-gap-scanner.md`)* |
419
+ | `FINDINGS SCHEMA` | như §Output dưới đây |
420
+ | `GRANULARITY` | **`auto`** — chia theo ngưỡng kích thước, KHÔNG ép mịn theo từng UC |
421
+ | `VERIFY` | **`off`** — thẩm định chạy MỘT lần ở bước sau, trên tập đã gộp |
422
+
423
+ **`D5` chạy ở dạng THU HẸP — chỉ 2 trong 4 cặp tài liệu:**
424
+
425
+ | Cặp | |
426
+ |---|---|
427
+ | `PRD ↔ design-spec/` | ✅ **SO** — không ai đối chiếu nội dung. `/generate-bdd` chỉ kiểm `Built from PRD` (số phiên bản); cùng phiên bản mà nội dung lệch thì lọt |
428
+ | `bdd/{platform}/ ↔ design-spec/` | ✅ **SO** — không ai |
429
+ | `PRD ↔ bdd/` | ❌ bỏ — `/review-context` **B1** đã làm |
430
+ | `PRD·bdd/ ↔ tech-docs/` | ❌ bỏ — §Đối chiếu tài liệu kỹ thuật **ở trên** đã làm |
431
+
432
+ > **Cả hai cặp SO đều dính `design-spec/`** — artifact duy nhất trong feature package mà **không
433
+ > tài liệu nào đối chiếu nội dung với nó**. Đừng lẫn với `tech-docs/`: `design-spec/` là *giao diện
434
+ > Designer vẽ*, `tech-docs/` là *hợp đồng hệ thống* — và `tech-docs/` đã được phủ ở §trên.
435
+ >
436
+ > Trạm này **đã đọc `design-spec/`** từ trước (nó nằm trong danh sách nguồn ở Gate), nên `D5`
437
+ > không nạp thêm file nào — chỉ bắt nó **so** thay vì chỉ **đọc**. Rẻ hơn một lăng kính thường.
438
+
439
+ **`D1 Luật nghiệp vụ` là lăng kính duy nhất KHÔNG bật:** `qa-analyst/business-rules.md` đã hỏi
440
+ 4/5 câu của nó, và hỏi cụ thể hơn — *"min/max · ký tự cho phép · trim · định dạng"* thay vì
441
+ *"ngưỡng đã chốt chưa"*.
442
+
443
+ > **Ghi lại vì sao `D5` từng bị tắt:** lý do ban đầu là *"trùng nhiều"* — **đúng một nửa**. Nó phủ
444
+ > **bốn** cặp, chỉ **hai** cặp đã có người làm. Sai vì suy từ ấn tượng thay vì đếm danh sách; bảng
445
+ > kiểm chứng 31 câu hỏi (`docs/plans/qc-implementation-log.md`) là thứ đáng lẽ phải làm **trước**
446
+ > khi quyết. Đừng tắt lại `D5` mà không đọc bảng đó.
447
+
448
+ > **`GRANULARITY = auto`, không phải `per-uc`.** `/refine-prd` ép mịn theo từng UC vì ở tầng PRD
449
+ > một gap bỏ sót **im lặng đi tiếp** tới tận lúc chạy thật. Ở đây khác: gap bỏ sót còn **bốn lớp
450
+ > chặn phía sau** — trạm 3 bật ngược khi không viết nổi giá trị mong đợi, trạm 4 soát độ phủ,
451
+ > trạm 5 phân loại lỗi thật vs script sai. Ép mịn ở đây tốn gấp ~3 lần cho tính năng nhỏ mà đổi
452
+ > lấy một lưới an toàn đã có sẵn ba lớp khác.
232
453
 
233
- Luôn tạo một file gaps theo `{paths.qc_skills_dir}/qa-analyst/DOC_GAPS.template.md`:
234
- - Mỗi gap `GAP-xx`, phân loại MISSING / AMBIGUOUS / CONTRADICTORY / ASSUMPTION / OPEN QUESTION, với severity (🔴 Blocker → 🟢 Low) và function/BR/AC bị ảnh hưởng.
454
+ ### Gộp trước, thẩm định sau
455
+
456
+ Gộp gap từ **cả hai nguồn** vào một tập trước khi sang bước thẩm định.
457
+
458
+ > **Không thẩm định từng nguồn riêng.** Phép kiểm `T6` của `gap-verify` là *"hai gap cùng gốc
459
+ > thì gộp lại"* — nó chỉ chạy được khi **thấy toàn bộ** tập. Thẩm định hai lần trên hai tập rời
460
+ > thì không bắt được trùng lặp chéo nguồn, và PO nhận hai câu hỏi giống nhau.
461
+
462
+ ---
463
+
464
+ ## DOC_GAP (bắt buộc)
465
+
466
+ Luôn tạo **đúng MỘT** file gap cho cả (PRD × nền) theo
467
+ `{paths.qc_skills_dir}/qa-analyst/DOC_GAP.template.md` — các UC là các hàng bên trong, phân
468
+ biệt bằng cột `UC`:
469
+ - **Bảng 11 cột**, cột 2 là `UC`. ID gap `GAP-UC{N}-{nnn}` (vd `GAP-UC1-001`); gap thuộc cả
470
+ PRD → `GAP-GEN-{nnn}`. Đánh số **độc lập trong từng UC** — phân tích lại UC1 KHÔNG được làm
471
+ đổi số gap của UC2, vì test case đã có đang trỏ `🚫 Block: [GAP-UC2-003]`.
472
+ - **Section `Phạm vi phân tích`** — mỗi UC một hàng kèm `@trace.status`, đã phân tích chưa, số
473
+ gap. UC ngoài phạm vi ghi `⏸ Chưa xét`, **không bỏ khỏi bảng**.
474
+ - Mỗi gap phân loại MISSING / AMBIGUOUS / CONTRADICTORY / ASSUMPTION / OPEN QUESTION, với severity (🔴 Blocker → ⚪ Low) và function/BR/AC bị ảnh hưởng.
235
475
  - Không bao giờ bịa câu trả lời; đánh dấu giả định là `ASSUMPTION` để PO/dev confirm.
236
- - Bất kỳ `🔴 Blocker` nào còn `Open` ⇒ UC chưa sẵn sàng cho qc-design-test bàn giao cho qc-plan.
476
+ - Bất kỳ `🔴 Blocker` nào còn `Open` ⇒ **UC cột `UC` của hàng đó** chưa sẵn sàng cho
477
+ qc-design-test — bàn giao cho qc-plan. *Chặn theo từng UC, KHÔNG chặn cả PRD:* một blocker ở
478
+ UC3 không có lý do gì dừng việc thiết kế test cho UC1. Ghi rõ UC nào bị chặn ở report.
237
479
  - **Đẩy các defect spec thực sự lên PO (không chỉ giữ local).** Một blocker là lỗi thật
238
480
  trong spec chính thức — `AMBIGUOUS` / `CONTRADICTORY` / `MISSING` trong PRD/BDD — phải tới
239
- PO qua feedback flow, không chỉ nằm trong `DOC_GAPS.md`: tạo `/report-bug {UC-ID} {desc}`
481
+ PO qua feedback flow, không chỉ nằm trong `DOC_GAP.md`: tạo `/report-bug {UC-ID} {desc}`
482
+ (`{UC-ID}` lấy từ cột `UC` của hàng gap — bug đi theo UC, không theo PRD)
240
483
  (BUG_FLOW của nó phân loại PRD vs BDD), hoặc `/propose-scenario {UC-ID}` nếu gap là thiếu test
241
484
  coverage. Gap `ASSUMPTION` / `OPEN QUESTION` được confirm qua questions-for-dev của qc-plan — không file thành bug.
242
485
 
243
- ## Output
486
+ ### Thẩm định trước khi bàn giao *(bắt buộc)*
244
487
 
245
- Ghi **đúng HAI file** dưới `{paths.qc_dir}/{UC-ID}/{active_platform}/` **đừng** tách phân tích
246
- thành một-file-mỗi-bước (không có file spec-breakdown / business-rules / data-flow / AC riêng):
488
+ Sinh xong `DOC_GAP.md`, **đọc `.agent/steps/gap-verify.md` chạy toàn bộ quy trình trong đó**
489
+ với:
490
+ - `FINDINGS` = mọi gap đang `Open` trong `DOC_GAP.md`
491
+ - `EVIDENCE_ROOT` = `{paths.specs_dir}` — spec repo của PO, **không** phải `{paths.qc_dir}`
492
+ - `VERDICT_FIELD` = cột `Trạng thái` + `Câu trả lời` của bảng gap
493
+ - `RERATE` = `on`
247
494
 
248
- 1. **`{paths.qc_dir}/{UC-ID}/{active_platform}/REQUIREMENT_ANALYSIS.md`**bản phân tích hợp nhất duy nhất.
249
- Section theo thứ tự: phân requirement bảng business-rule (`BR-xx`) data-flow
250
- acceptance-criteria (`AC-xx`), mỗi `BR`/`AC` map tới `{UC-ID}-SC{N}` (của `.feature` platform này) sở hữu nó.
251
- 2. **`{paths.qc_dir}/{UC-ID}/{active_platform}/DOC_GAPS.md`** — file gaps (theo `{paths.qc_skills_dir}/qa-analyst/DOC_GAPS.template.md`).
495
+ Gap rớt thẩm định được **đóng kèm lý do**, KHÔNG xoá người đọc phải kiểm chứng được vì sao
496
+ bị loại. Cập nhật `Tổng số gap` + bảng ưu tiên sau khi áp verdict, và in khối
497
+ `[GAP VERIFY]` + cam kết cuối vào report.
498
+
499
+ > **Vì sao bắt buộc, không phải tuỳ chọn.** Bước phân tích ở trên chỉ có lực **tìm thêm** —
500
+ > bốn skill lần lượt quét spec và mỗi cái đều được khuyến khích ghi ra chỗ nghi ngờ. Không có
501
+ > bước nào hỏi ngược *"cái vừa ghi có thật không?"*. Hệ quả đo được ở đội QC: phần lớn gap sinh
502
+ > ra là gap ảo — spec đã trả lời ở tài liệu khác, hoặc trích dẫn sai, hoặc là chuyện QC tự quyết
503
+ > được. Mà gap ảo không chỉ tốn thời gian PO: nó **làm PO mất tin vào cả danh sách**, và lúc đó
504
+ > những gap thật cũng chết theo. `gap-verify` là bộ lọc duy nhất đứng giữa hai chuyện đó.
505
+
506
+ ## Output
507
+
508
+ Ghi **hai file** dưới `{qc_artifact_dir}` (= `{paths.qc_dir}/{TICKET-ID}/{active_platform}/`)
509
+ + **một** dưới `{paths.refinement_dir}/`.
510
+
511
+ **Mỗi loại đúng MỘT file cho cả PRD** — đừng tách một-file-mỗi-UC, và cũng đừng tách
512
+ một-file-mỗi-bước (không có file spec-breakdown / business-rules / data-flow / AC riêng):
513
+
514
+ 1. **`{qc_artifact_dir}REQUIREMENT_ANALYSIS.md`** — bản phân tích hợp nhất duy nhất cho cả PRD.
515
+ Mở đầu bằng **bảng `Phạm vi phân tích`** (cùng nội dung với bảng trong `DOC_GAP.md`), rồi
516
+ **một mục cho mỗi UC trong phạm vi**, mỗi mục theo thứ tự: phân rã requirement → bảng
517
+ business-rule (`BR-xx`) → data-flow → acceptance-criteria (`AC-xx`), mỗi `BR`/`AC` map tới
518
+ `{UC-ID}-SC{N}` (của `.feature` nền này) sở hữu nó.
519
+ Thêm một mục **"Mâu thuẫn chéo UC"** — chỗ hai UC của cùng PRD nói khác nhau. Rỗng thì ghi
520
+ "Không có". *Đây là thứ chỉ trạm cấp PRD nhìn thấy được; đừng bỏ mục.*
521
+ Cuối file thêm mục **"Đang chờ chốt (từ §12 tech-doc)"** — các ẩn số thiết kế `open` chạm
522
+ các UC trong phạm vi (`{id}` · UC · điều chưa biết · owner · severity). Rỗng thì ghi
523
+ "Không có"; **đừng bỏ mục**.
524
+ 2. **`{qc_artifact_dir}DOC_GAP.md`** — file gap, theo
525
+ `{paths.qc_skills_dir}/qa-analyst/DOC_GAP.template.md` + luật viết ở
526
+ `{paths.qc_skills_dir}/qa-analyst/spec-issue-reporter.md`. Bắt buộc:
527
+ - **Bảng 11 cột** đúng thứ tự, có cột **`UC`** (cột 2) và cột **Giao cho đội** (Dev / PO / BA / Design / Kiến trúc / Dữ liệu).
528
+ - Section **"Phạm vi phân tích"** ngay sau metadata — mỗi UC một hàng kèm `@trace.status`,
529
+ đã phân tích chưa, số gap. UC ngoài phạm vi ghi `⏸ Chưa xét`.
530
+ - Ô câu hỏi đủ **bốn phần** tách bằng `<br/>`: **Bối cảnh → Vấn đề → Tại sao quan trọng → Gợi ý**.
531
+ *Ba phần đầu cho PO xếp ưu tiên; phần cuối cho PO trả lời nhanh mà không phải nghĩ lại từ đầu.*
532
+ - Section **"Tài liệu đầu vào đã đọc để phân tích"** đặt ngay sau metadata — liệt kê **đủ** mọi
533
+ file đã mở. Đây là căn cứ độ phủ: không có nó thì không ai phân biệt được *"đã đọc và không thấy"*
534
+ với *"chưa đọc"*.
535
+ - Mức nặng nhất dùng từ **`🔴 Blocker`** (không phải `Critical`) — `/qc-run-test` đọc đúng từ này
536
+ để đặt *"scenario đang chờ PO"* vào sổ trace.
537
+ 3. **`{paths.refinement_dir}/{TICKET-ID}-qa-findings.yaml`** — **cùng dữ liệu gap**, ở định dạng Review Board đọc được. Xem §Bản findings dưới đây.
252
538
 
253
539
  `{paths.qc_dir}` là folder top-level NHÌN THẤY trong QC repo (mặc định `docs/`, **không** phải
254
540
  `.agent/review/` ẩn) để team QC mở và xử lý output dễ dàng. Spec chính thức ở lại
255
541
  spec submodule của PO — đừng ghi phân tích vào đó.
256
542
 
543
+ ### Bản findings — một nguồn, hai mặt
544
+
545
+ File `.yaml` và `DOC_GAP.md` là **cùng một tập gap**, không phải hai tập. Sinh `DOC_GAP.md`
546
+ trước (nó là bản người đọc), rồi **render** sang `.yaml` — đừng phân tích lại lần hai.
547
+
548
+ Dùng **đúng schema của `/refine-prd`** để Review Board đọc được cả hai loại file:
549
+
550
+ ```yaml
551
+ prd_source: "{paths.specs_dir}/{domain}/{prd-slug}/{TICKET-ID}-{prd-slug}.md"
552
+ ticket_id: "{TICKET-ID}"
553
+ platform: "{active_platform}"
554
+ ucs: ["{UC-ID}", "…"] # các UC TRONG phạm vi (approved) — theo thứ tự
555
+ ucs_skipped: ["{UC-ID}"] # UC chưa xét, kèm lý do ở DOC_GAP §Phạm vi phân tích
556
+ generated_at: "{ISO datetime}"
557
+ generated_by: "qc-analyze"
558
+ status: "pending_review"
559
+
560
+ findings:
561
+ - id: "F001"
562
+ lens: "QA" # LUÔN là QA — file này chỉ có một lăng kính
563
+ severity: "critical" # critical | major | minor ← map từ 🔴/🟠/🟡⚪ của DOC_GAP
564
+ section: "{section PRD/BDD chứa vấn đề}"
565
+ uc_id: "{UC-ID}"
566
+ quote: "{trích nguyên văn ≤120 ký tự từ spec tại đúng chỗ}"
567
+ finding: "{gap là gì}"
568
+ suggestion: "{cần PO/BA làm rõ điều gì}"
569
+ resolution_edge_cases: [] # để [] — phân tích bậc-hai là việc của /refine-prd
570
+ auto_fixable: false # LUÔN false — xem cảnh báo dưới
571
+ status: "pending"
572
+ applied_via: ""
573
+ gap_ref: "GAP-UC1-001" # trỏ ngược về hàng trong DOC_GAP.md (ID mang UC)
574
+
575
+ summary:
576
+ total_findings: {N}
577
+ by_severity: { critical: {N}, major: {N}, minor: {N} }
578
+ by_lens: { QA: {N} }
579
+ recommendation: "APPROVED_WITH_MINOR_CHANGES | NEEDS_REVISION | BLOCKED"
580
+ ```
581
+
582
+ > **`auto_fixable` LUÔN `false`, và KHÔNG có `--resume` cho file này.**
583
+ >
584
+ > Review Board có nút *"chấp nhận rồi tự sửa PRD"*. Với gap của `/refine-prd` thì đúng — nó chạy
585
+ > ở **thời điểm PRD**, sửa PRD lúc đó là sửa đúng chỗ đúng lúc.
586
+ >
587
+ > Gap của lệnh này phát hiện **sau khi code đã xong**. Tự sửa PRD ở thời điểm đó là **sửa sau lưng
588
+ > cả dây chuyền**: BDD sinh từ PRD cũ, code sinh từ BDD đó, sổ kết quả kiểm thử neo vào scenario
589
+ > của BDD đó. Đổi PRD mà không đi lại đường ấy thì mọi thứ phía sau nói dối.
590
+ >
591
+ > Đường đúng vẫn là kênh đã có: `/report-bug` cho defect spec thật, `/propose-scenario` cho thiếu
592
+ > độ phủ. File `.yaml` này để **PO đọc và quyết trong một chỗ quen**, không phải để máy tự áp.
593
+
594
+ **File riêng, không ghi chung với `/refine-prd`.** Cả hai giờ đều ở cấp PRD, nên khác biệt nằm
595
+ ở **hậu tố**: `{TICKET-ID}-qa-findings.yaml` (trạm này) vs `{prd-slug}-findings.yaml`
596
+ (`/refine-prd`). Đừng gộp. Ghi chung sẽ phá trường `applied_to_version` mà `/refine-prd` dùng để
597
+ phân biệt *"PRD đổi do chính tôi áp fix"* với *"có người lạ sửa"* — và nó sẽ mãi mãi tưởng có
598
+ người sửa sau lưng, mỗi lần chạy đều quét lại toàn bộ kèm cảnh báo giả.
599
+
600
+ ---
601
+
257
602
  ## Report
258
603
 
259
604
  **Đọc `.agent/steps/report-footer.md`** và áp đúng khuôn footer trong đó (Status Badge ·
260
605
  Output Artifacts · Next) cho report cuối, kèm khối bên dưới.
261
606
 
262
607
  ```
263
- /qc-analyze Hoàn tất — {UC-ID}
264
- Files: {paths.qc_dir}/{UC-ID}/{active_platform}/REQUIREMENT_ANALYSIS.md + DOC_GAPS.md (2 files)
265
- Gaps: {N} ({blockers} blocker) ← blocker là spec-defect? → /report-bug {UC-ID} | coverage gap /propose-scenario {UC-ID}
266
- SC mapping: {M} BR/AC map tới {K} scenario
267
- Next: /qc-plan {UC-ID} risk / what-if / questions-for-dev
268
- (giải quyết các gap 🔴 Blocker với PO/Dev trước)
608
+ /qc-analyze Hoàn tất — {TICKET-ID} ({active_platform})
609
+ Phạm vi: {n}/{N} UC phân tích{nếu có UC chưa xét: " · ⏸ {m} chưa xét: {danh sách UC-ID} (BDD chưa approved)"}
610
+ Files : {paths.qc_dir}/{TICKET-ID}/{active_platform}/REQUIREMENT_ANALYSIS.md + DOC_GAP.md (11 cột)
611
+ {paths.refinement_dir}/{TICKET-ID}-qa-findings.yaml ← mở bằng Review Board (chuột phải)
612
+ Nguồn : PRD · BDD({active_platform}, {n} UC) · design-spec · tech-doc{nếu thiếu tech-doc: " (THIẾU — không đối chiếu được nghiệp vụ ↔ kỹ thuật)"}
613
+ Quét : 4 kỹ năng phân tích + 4 lăng kính (xử lỗi · giao diện · dữ liệu & cấu hình
614
+ · đối chiếu chéo: PRD↔design-spec, bdd↔design-spec)
615
+ Verify: {raw} gap thô → {N} còn Open (❌ {invalid} bịa/đã-trả-lời · ⚠️ {reclass} không phải gap nghiệp vụ · 🔁 {merge} trùng)
616
+ Gaps : {N} ({blockers} blocker) — theo UC: {UC1: n · UC2: n · …}{nếu có: " · toàn PRD: {n}"}
617
+ ← blocker là spec-defect? → /report-bug {UC-ID của hàng đó} | coverage gap → /propose-scenario {UC-ID}
618
+ Chéo UC: {X} mâu thuẫn giữa các UC của cùng PRD (đã ghi vào REQUIREMENT_ANALYSIS §Mâu thuẫn chéo UC)
619
+ Chặn : {danh sách UC có 🔴 Blocker còn Open} — các UC còn lại vẫn thiết kế test được bình thường
620
+ Chờ chốt: {G} ẩn số §12 tech-doc đang open chạm các UC này (đã ghi vào REQUIREMENT_ANALYSIS, KHÔNG hỏi lại PO)
621
+ SC map: {M} BR/AC map tới {K} scenario
622
+ Next : /qc-plan {TICKET-ID} {active_platform} ← rủi ro / what-if / câu hỏi cho dev
623
+ (giải quyết các gap 🔴 Blocker với PO/Dev trước)
269
624
  ```
625
+
626
+ > **Dòng `Next` là bắt buộc in, không phải trang trí.** Dây gốc của đội QC ra **cả** file gap
627
+ > **và** kế hoạch test trong một lần chạy (13/14 lần đo được ở repo của họ). Ở framework đó là
628
+ > **hai lệnh**. Người quen dây cũ sẽ dừng lại ở đây và tưởng đã xong — dòng này là chỗ duy nhất
629
+ > nói cho họ biết còn một bước nữa.
630
+