@educa-corp/sdd-framework 0.6.0 → 0.7.1

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 (223) hide show
  1. package/bin/gate-trace.js +25 -2
  2. package/bin/index.js +32 -5
  3. package/bin/lint-trace.js +41 -0
  4. package/bin/self-check.js +430 -3
  5. package/bin/trace-schema.json +418 -31
  6. package/core/FRAMEWORK_VERSION +1 -1
  7. package/{commands/extend-prd.md → core/commands/amend-prd.md} +206 -173
  8. package/core/commands/dev-run-test.md +48 -10
  9. package/core/commands/extend-prd.md +39 -12
  10. package/core/commands/generate-bdd.md +52 -10
  11. package/core/commands/generate-code.md +35 -2
  12. package/core/commands/generate-tech-docs.md +36 -4
  13. package/core/commands/map-testids.md +1 -1
  14. package/core/commands/qc-run-test.md +29 -3
  15. package/core/commands/refine-prd.md +13 -2
  16. package/core/commands/review-context.md +43 -8
  17. package/core/commands/sync.md +105 -1
  18. package/core/commands/validate-traces.md +289 -16
  19. package/core/rules/workflow.md +34 -0
  20. package/core/steps/context-loader.md +27 -6
  21. package/core/templates/feature.template +1 -1
  22. package/core/templates/project-context.yaml +3 -3
  23. package/core/templates/tech-design.template.md +2 -2
  24. package/docs/02-concepts/architecture.md +37 -1
  25. package/docs/02-concepts/overview.md +1 -1
  26. package/docs/02-concepts/pipeline-steps/02-specification.md +13 -7
  27. package/docs/02-concepts/pipeline-steps/07-dev-selftest.md +2 -0
  28. package/docs/02-concepts/pipeline-steps/08-qc-automation.md +1 -0
  29. package/docs/02-concepts/pipeline-steps/09-validate-traces.md +34 -3
  30. package/docs/02-concepts/pipeline-steps/10-feedback-loop.md +10 -1
  31. package/docs/02-concepts/traceability.md +187 -183
  32. package/docs/03-guides/architect.md +13 -4
  33. package/docs/03-guides/developer.md +1 -0
  34. package/docs/03-guides/product-owner.md +89 -72
  35. package/docs/03-guides/tester-qa.md +81 -81
  36. package/docs/04-reference/commands.md +148 -134
  37. package/docs/04-reference/trace-schema.md +45 -1
  38. package/docs/explain/02b-extend-prd.md +1 -1
  39. package/docs/explain/02c-amend-prd.md +152 -0
  40. package/docs/explain/06-generate-bdd.md +1 -1
  41. package/docs/explain/13-dev-run-test.md +15 -1
  42. package/docs/explain/19-qc-run-test.md +91 -87
  43. package/docs/explain/21-validate-traces.md +79 -75
  44. package/docs/explain/28-sync.md +25 -0
  45. package/docs/explain/README.md +136 -135
  46. package/package.json +1 -8
  47. package/commands/debug.md +0 -529
  48. package/commands/debug.tmpl +0 -260
  49. package/commands/define-product.md +0 -438
  50. package/commands/define-product.tmpl +0 -225
  51. package/commands/dev-gen-test.md +0 -700
  52. package/commands/dev-gen-test.tmpl +0 -490
  53. package/commands/dev-run-test.md +0 -435
  54. package/commands/dev-run-test.tmpl +0 -225
  55. package/commands/dev-smoke-test.md +0 -374
  56. package/commands/dev-smoke-test.tmpl +0 -217
  57. package/commands/extend-prd.tmpl +0 -273
  58. package/commands/fix-bug.md +0 -519
  59. package/commands/fix-bug.tmpl +0 -197
  60. package/commands/generate-architecture.md +0 -354
  61. package/commands/generate-architecture.tmpl +0 -197
  62. package/commands/generate-bdd.md +0 -923
  63. package/commands/generate-bdd.tmpl +0 -590
  64. package/commands/generate-code.md +0 -859
  65. package/commands/generate-code.tmpl +0 -649
  66. package/commands/generate-design-spec.md +0 -737
  67. package/commands/generate-design-spec.tmpl +0 -524
  68. package/commands/generate-prd.md +0 -722
  69. package/commands/generate-prd.tmpl +0 -226
  70. package/commands/generate-spec-manifest.md +0 -321
  71. package/commands/generate-spec-manifest.tmpl +0 -164
  72. package/commands/generate-tech-docs.md +0 -920
  73. package/commands/generate-tech-docs.tmpl +0 -273
  74. package/commands/learn.md +0 -399
  75. package/commands/learn.tmpl +0 -130
  76. package/commands/map-testids.md +0 -238
  77. package/commands/map-testids.tmpl +0 -81
  78. package/commands/propose-scenario.md +0 -359
  79. package/commands/propose-scenario.tmpl +0 -202
  80. package/commands/qc-analyze.md +0 -269
  81. package/commands/qc-analyze.tmpl +0 -112
  82. package/commands/qc-design-test.md +0 -226
  83. package/commands/qc-design-test.tmpl +0 -69
  84. package/commands/qc-plan.md +0 -206
  85. package/commands/qc-plan.tmpl +0 -49
  86. package/commands/qc-report.md +0 -217
  87. package/commands/qc-report.tmpl +0 -60
  88. package/commands/qc-review.md +0 -210
  89. package/commands/qc-review.tmpl +0 -53
  90. package/commands/qc-run-test.md +0 -326
  91. package/commands/qc-run-test.tmpl +0 -116
  92. package/commands/refine-prd.md +0 -653
  93. package/commands/refine-prd.tmpl +0 -281
  94. package/commands/report-bug.md +0 -305
  95. package/commands/report-bug.tmpl +0 -148
  96. package/commands/review-code.md +0 -415
  97. package/commands/review-code.tmpl +0 -146
  98. package/commands/review-context.md +0 -902
  99. package/commands/review-context.tmpl +0 -530
  100. package/commands/review-tech-docs.md +0 -561
  101. package/commands/review-tech-docs.tmpl +0 -404
  102. package/commands/setup-ai-first.md +0 -602
  103. package/commands/setup-ai-first.tmpl +0 -450
  104. package/commands/sync.md +0 -430
  105. package/commands/sync.tmpl +0 -429
  106. package/commands/update-framework.md +0 -203
  107. package/commands/update-framework.tmpl +0 -202
  108. package/commands/validate-traces.md +0 -1077
  109. package/commands/validate-traces.tmpl +0 -920
  110. package/hooks/data-guard.js +0 -232
  111. package/hooks/settings.json +0 -19
  112. package/modules/android-compose/module.yaml +0 -13
  113. package/modules/android-compose/stack-profile.yaml +0 -57
  114. package/modules/angular/architecture-snippets/component-patterns.md +0 -187
  115. package/modules/angular/module.yaml +0 -6
  116. package/modules/angular/stack-profile.yaml +0 -38
  117. package/modules/context-engineering/architecture-snippets/context-design.md +0 -119
  118. package/modules/context-engineering/module.yaml +0 -9
  119. package/modules/context-engineering/stack-profile.yaml +0 -61
  120. package/modules/dotnet/architecture-snippets/clean-arch.md +0 -160
  121. package/modules/dotnet/module.yaml +0 -6
  122. package/modules/dotnet/stack-profile.yaml +0 -50
  123. package/modules/flutter/module.yaml +0 -14
  124. package/modules/flutter/stack-profile.yaml +0 -59
  125. package/modules/golang/architecture-snippets/domain-layout.md +0 -283
  126. package/modules/golang/module.yaml +0 -6
  127. package/modules/golang/stack-profile.yaml +0 -40
  128. package/modules/ios-swiftui/module.yaml +0 -13
  129. package/modules/ios-swiftui/stack-profile.yaml +0 -55
  130. package/modules/java-spring/architecture-snippets/layered-arch.md +0 -201
  131. package/modules/java-spring/module.yaml +0 -15
  132. package/modules/java-spring/stack-profile.yaml +0 -28
  133. package/modules/nextjs/architecture-snippets/app-router-patterns.md +0 -269
  134. package/modules/nextjs/module.yaml +0 -14
  135. package/modules/nextjs/stack-profile.yaml +0 -74
  136. package/modules/nuxt/module.yaml +0 -14
  137. package/modules/nuxt/stack-profile.yaml +0 -58
  138. package/modules/phaser-game/architecture-snippets/phaser-scene-patterns.md +0 -646
  139. package/modules/phaser-game/module.yaml +0 -15
  140. package/modules/phaser-game/stack-profile.yaml +0 -90
  141. package/modules/php-laravel/architecture-snippets/service-repository.md +0 -302
  142. package/modules/php-laravel/module.yaml +0 -15
  143. package/modules/php-laravel/stack-profile.yaml +0 -56
  144. package/modules/qc-playwright/stack-profile.yaml +0 -66
  145. package/modules/react/architecture-snippets/hooks-query-patterns.md +0 -254
  146. package/modules/react/module.yaml +0 -14
  147. package/modules/react/stack-profile.yaml +0 -63
  148. package/modules/react-native/module.yaml +0 -14
  149. package/modules/react-native/stack-profile.yaml +0 -56
  150. package/modules/vue/module.yaml +0 -14
  151. package/modules/vue/stack-profile.yaml +0 -65
  152. package/rules/data-protection.md +0 -80
  153. package/rules/workflow.md +0 -99
  154. package/skills/code/SKILL.md +0 -19
  155. package/skills/code/SKILL.tmpl +0 -19
  156. package/skills/debug/SKILL.md +0 -19
  157. package/skills/debug/SKILL.tmpl +0 -19
  158. package/skills/design-spec/SKILL.md +0 -11
  159. package/skills/design-spec/SKILL.tmpl +0 -11
  160. package/skills/discovery/SKILL.md +0 -14
  161. package/skills/discovery/SKILL.tmpl +0 -14
  162. package/skills/prd/SKILL.md +0 -19
  163. package/skills/prd/SKILL.tmpl +0 -19
  164. package/skills/qc/qa-analyst/DOC_GAPS.template.md +0 -63
  165. package/skills/qc/qa-analyst/acceptance-criteria.md +0 -60
  166. package/skills/qc/qa-analyst/business-rules.md +0 -59
  167. package/skills/qc/qa-analyst/data-flow.md +0 -64
  168. package/skills/qc/qa-analyst/spec-breakdown.md +0 -61
  169. package/skills/qc/qa-designer/e2e/journey.md +0 -41
  170. package/skills/qc/qa-designer/exploratory/charter.md +0 -68
  171. package/skills/qc/qa-designer/exploratory/explore-to-functional.md +0 -43
  172. package/skills/qc/qa-designer/functional/api.md +0 -45
  173. package/skills/qc/qa-designer/functional/gui-feature.md +0 -46
  174. package/skills/qc/qa-designer/functional/gui-screen.md +0 -52
  175. package/skills/qc/qa-designer/integration/api.md +0 -42
  176. package/skills/qc/qa-designer/integration/db.md +0 -39
  177. package/skills/qc/qa-designer/integration/gui.md +0 -40
  178. package/skills/qc/qa-designer/integration/kafka.md +0 -40
  179. package/skills/qc/qa-designer/non-functional.md +0 -40
  180. package/skills/qc/qa-planner/test-plan.md +0 -120
  181. package/skills/qc/qa-reviewer/script/e2e.md +0 -87
  182. package/skills/qc/qa-reviewer/script/exploratory.md +0 -45
  183. package/skills/qc/qa-reviewer/script/functional.md +0 -101
  184. package/skills/qc/qa-reviewer/script/integration.md +0 -91
  185. package/skills/qc/qa-reviewer/script/non-functional.md +0 -126
  186. package/skills/qc/qa-reviewer/test-case/e2e.md +0 -73
  187. package/skills/qc/qa-reviewer/test-case/exploratory.md +0 -43
  188. package/skills/qc/qa-reviewer/test-case/functional.md +0 -76
  189. package/skills/qc/qa-reviewer/test-case/integration.md +0 -69
  190. package/skills/qc/qa-reviewer/test-case/non-functional.md +0 -73
  191. package/skills/qc/qa-runner/e2e.md +0 -49
  192. package/skills/qc/qa-runner/exploratory/session.md +0 -36
  193. package/skills/qc/qa-runner/functional/api.md +0 -35
  194. package/skills/qc/qa-runner/functional/gui-feature.md +0 -51
  195. package/skills/qc/qa-runner/functional/gui-screen.md +0 -55
  196. package/skills/qc/qa-runner/integration.md +0 -47
  197. package/skills/qc/qa-runner/non-functional.md +0 -49
  198. package/skills/qc/qa-runner/report/report.md +0 -37
  199. package/skills/setup-ai-first/SKILL.md +0 -19
  200. package/skills/setup-ai-first/SKILL.tmpl +0 -19
  201. package/skills/spec/SKILL.md +0 -19
  202. package/skills/spec/SKILL.tmpl +0 -19
  203. package/skills/test/SKILL.md +0 -18
  204. package/skills/test/SKILL.tmpl +0 -18
  205. package/steps/business-language.md +0 -56
  206. package/steps/capture-lesson.md +0 -112
  207. package/steps/context-loader.md +0 -406
  208. package/steps/gate.md +0 -151
  209. package/steps/report-footer.md +0 -125
  210. package/steps/review-fanout.md +0 -159
  211. package/steps/spawn-agent.md +0 -129
  212. package/steps/trace-mirror.md +0 -53
  213. package/templates/README.md +0 -70
  214. package/templates/architecture.template.md +0 -394
  215. package/templates/ci/trace-gate.yml +0 -146
  216. package/templates/design-spec.template.md +0 -217
  217. package/templates/feature.template +0 -123
  218. package/templates/hooks/pre-push +0 -61
  219. package/templates/platform-guide.template.md +0 -145
  220. package/templates/prd.template.md +0 -283
  221. package/templates/product-definition.template.md +0 -188
  222. package/templates/project-context.yaml +0 -212
  223. package/templates/tech-design.template.md +0 -490
@@ -6,6 +6,11 @@
6
6
  > |---|---|---|
7
7
  > | `/refine-prd` | *"PRD hiện tại có **vấn đề** gì?"* | 3 lăng kính review soi nội dung ĐANG CÓ |
8
8
  > | **`/extend-prd`** | *"PRD hiện tại **thiếu** cái gì mới?"* | PO + hòm thư `prd-change-requests/` |
9
+ > | `/amend-prd` | *"một yêu cầu đang có cần **ĐỔI** thành gì?"* | PO khai tường minh ID cần sửa |
10
+ >
11
+ > **Ranh giới với `/amend-prd`:** lệnh này **chỉ THÊM** — Bước 5 §3 đòi output là *"superset chặt"*.
12
+ > Nó có **một** cửa sửa nội dung cũ (Bước 3.2 case 1, "mâu thuẫn rule") nhưng cửa đó **phái sinh**:
13
+ > chỉ mở khi phần THÊM làm một BR cũ sai. Muốn đổi một yêu cầu mà **không** thêm gì mới → `/amend-prd`.
9
14
  >
10
15
  > `/refine-prd` **không** thêm được UC/AC/BR mới — nó tự cấm ở Resume Mode Phase 2 (*"không thay đổi
11
16
  > bất kỳ section nào không được tham chiếu bởi một finding được chấp nhận"*), và findings của nó sinh
@@ -274,8 +279,9 @@ Các từ như `cờ / flag`, `biến / trường / field`, `giá trị / value`
274
279
  ℹ️ {n} file BDD đã sinh cho PRD này: {danh sách UC × platform}
275
280
  Lệnh này CHỈ đánh số nối tiếp (UC{max_uc+1}, BR{max_br+1}) — KHÔNG bao giờ đánh lại
276
281
  ID cũ, nên các liên kết @trace.business_rules hiện có KHÔNG bị ảnh hưởng.
277
- Sau khi thêm: chỉ cần /generate-bdd cho UC MỚI; các UC cũ không phải gen lại
278
- (/validate-traces sẽ xếp chúng vào PRD_STALE_REF, không phải 🟠 PRD_DRIFT).
282
+ Sau khi thêm: /generate-bdd cho UC MỚI, cho mỗi UC cũ Bước 3.2 kết luận là
283
+ "sửa BR/AC cũ" (BR đó đổi hành vi BDD của nó lỗi thời thật → 🟠 PRD_DRIFT).
284
+ UC cũ KHÔNG bị sửa gì thì không phải gen lại (ⓘ PRD_STALE_REF).
279
285
  ```
280
286
 
281
287
  ---
@@ -340,7 +346,8 @@ Thêm : UC{max_uc+1} "{tên}" [hoặc: mở rộng UC{k}]
340
346
  Sửa cái cũ : {danh sách BR/AC bị sửa do va chạm — hoặc "không"}
341
347
  Từ request : {danh sách file request được đưa vào — hoặc "không"}
342
348
  Version : {current} → {new} ({major|minor}) · Status → draft
343
- BDD ảnh hưởng: cần /generate-bdd cho UC mới; {n} UC KHÔNG phải gen lại
349
+ BDD ảnh hưởng: /generate-bdd cho UC mới{, và cho UC{k} BR8 bị sửa}
350
+ {n} UC cũ không đụng gì → KHÔNG phải gen lại
344
351
 
345
352
  Tiếp tục? (Y/N)
346
353
  ```
@@ -406,10 +413,23 @@ Vị trí ghi từng loại nội dung:
406
413
  2. Cập nhật Metadata: `Version` = mới · `Updated` = hôm nay · **`Status` = `draft`** *(thêm yêu cầu = phải duyệt lại)*.
407
414
  3. Thêm row lên **đầu** bảng `# Change Log`:
408
415
  ```
409
- | {new_version} | {today} | {tóm tắt — BẮT BUỘC nêu UC/AC/BR bị ảnh hưởng} |
416
+ | {new_version} | {today} | {changelog_scope} |
417
+ ```
418
+ **`{changelog_scope}` — mỗi mệnh đề mở đầu bằng UC SỞ HỮU** *(contract: `bin/trace-schema.json` → `changelog_row_contract`)*. Nguồn: UC mới (Bước 4) + **UC sở hữu mỗi BR/AC bị sửa** (kết luận Bước 3.2). Ngăn nhau bằng `;`. Nội dung không thuộc UC nào (§1c phụ thuộc, §1d quy ước) → `PRD-global`.
419
+
420
+ **Ví dụ đúng:**
421
+ ```
422
+ thêm UC7 (xuất nhiều file): AC12-AC14, BR21-BR23; UC3: sửa BR8 (nâng giới hạn 5→20)
410
423
  ```
411
- **Ví dụ đúng:** `thêm UC7 (xuất nhiều file): AC12-AC14, BR21-BR23; sửa BR8 (nâng giới hạn 5→20)`
412
- **Ví dụ SAI:** `cập nhật theo yêu cầu mới` ← mơ hồ → `/generate-bdd` sẽ khuyến nghị gen lại **toàn bộ**, và `/validate-traces` sẽ gắn `PRD_DRIFT` 🟠 cho **mọi** UC thay vì chỉ UC mới. Một dòng viết ẩu làm mất cả hai bộ lọc.
424
+ **Ví dụ SAI — mơ hồ:** `cập nhật theo yêu cầu mới` ← `/generate-bdd` sẽ khuyến nghị gen lại **toàn bộ**, và `/validate-traces` gắn `PRD_DRIFT` 🟠 cho **mọi** UC thay vì chỉ UC mới. Mất bộ lọc theo hướng **ỒN**.
425
+
426
+ **Ví dụ SAI — nêu BR mà bỏ UC sở hữu:** `…; sửa BR8 (nâng giới hạn 5→20)` ← thiếu `UC3:`.
427
+
428
+ > ⚠️ **Đây là ca nguy hiểm HƠN ca mơ hồ, và là lý do luật này thành contract (G53).** Row trên nêu rất nhiều ID nên **không** bị coi là mơ hồ. Nhưng `/validate-traces` Step 4 khớp bằng phép thử *"**UC** này có trong tập bị ảnh hưởng?"* — tập là `{UC7, AC12-14, BR21-23, BR8}`, và **UC3 không có trong đó**. Nên UC3 → ⓘ `PRD_STALE_REF` *"không phải lỗi"*, trong khi BR mà nó sở hữu **vừa đổi hành vi 5→20**.
429
+ >
430
+ > Và nó không dừng ở một cờ sai: rào an toàn của `--realign-prd-version` chỉ **từ chối khi UC là 🟠**. Ở đây nó là **ⓘ** ⇒ rào **mở cửa** ⇒ nhãn `prd_version` được dán lại trong cả TSV lẫn tag code ⇒ **cờ sạch vĩnh viễn trên một thay đổi chưa ai implement**. Mất bộ lọc theo hướng **IM LẶNG**.
431
+ >
432
+ > Viết `UC3:` là một tiền tố ba ký tự. Bỏ nó là mở một đường tự động che lỗi.
413
433
  4. Cập nhật dòng đầu section: `> Hiện tại: **v{new}** ({today}) · Lịch sử đầy đủ → [changelog](./changelog/{TICKET-ID}-{prd-slug}.changelog.md)`
414
434
  5. **Rollover** (cửa sổ trượt 5 row): bảng `# Change Log` vượt **5** row → chuyển mọi row vượt 5 (cũ nhất) sang **đầu** bảng của `{specs_dir}/{domain}/{prd-slug}/changelog/{TICKET-ID}-{prd-slug}.changelog.md`; PRD giữ 5 row gần nhất. Tạo dir + file theo skeleton của `/refine-prd` Phase 3 nếu chưa có.
415
435
 
@@ -440,13 +460,19 @@ Ví dụ footer cho lệnh này:
440
460
 
441
461
  Version : v{old} → v{new} ({major|minor}) · Status → draft
442
462
  Thêm : UC{N} "{tên}" · AC{a}-AC{b} · BR{c}-BR{d}
443
- Sửa cũ : {BR8 — nâng giới hạn 5→20 | không}
463
+ Sửa cũ : {UC3: BR8 — nâng giới hạn 5→20 | không} ← LUÔN nêu UC sở hữu
444
464
  Request : {2 file → archived/ (incorporated v{new}) | không}
445
- Changelog : | v{new} | {today} | thêm UC{N}: AC{a}-AC{b}, BR{c}-BR{d}; sửa BR8 |
465
+ Changelog : | v2.0 | 2026-08-19 | thêm UC7: AC12-AC14, BR21-BR23; UC3: sửa BR8 (giới hạn 5→20) |
446
466
 
447
467
  Guard sau-ghi : ✅ {n} UC · {m} AC · {k} BR · {j} changelog row cũ — còn nguyên
448
468
 
449
- UC KHÔNG đổi ({n}): {UC1, UC2, UC3}
469
+ UC BỊ SỬA nội dung ({n}): {UC3}
470
+ → CẦN /generate-bdd rồi /generate-code cho các UC này — BR/AC của chúng vừa đổi hành vi.
471
+ /validate-traces sẽ xếp chúng vào 🟠 PRD_DRIFT (đúng).
472
+ ❌ TUYỆT ĐỐI KHÔNG dùng --realign-prd-version cho chúng — đó là dán nhãn lên thay đổi
473
+ chưa ai implement.
474
+
475
+ UC KHÔNG đổi ({n}): {UC1, UC2, UC4…} ← = existing_ucs TRỪ danh sách "UC BỊ SỬA" ở trên
450
476
  → KHÔNG cần /generate-bdd hay /generate-code cho các UC này.
451
477
  /validate-traces sẽ xếp chúng vào ⓘ PRD_STALE_REF (nhãn version cũ, nội dung không đổi).
452
478
  Sạch bằng: /validate-traces --realign-prd-version {UC-ID}
@@ -466,7 +492,8 @@ Next : /refine-prd {prd-file} ← soi phần vừa thêm qua 3 lăng
466
492
  • Feature CÓ màn hình → /generate-design-spec {prd-file} (design-spec sẽ tự
467
493
  phát hiện lỗi thời vs PRD mới và bắt sign-off lại) rồi /generate-bdd
468
494
  • Thuần backend → /generate-bdd {prd-file} thẳng
469
- CHỈ gen BDD/code cho UC MỚI. UC cũ: dùng --realign-prd-version.
495
+ → gen BDD/code cho UC MỚI **và** UC bị sửa BR/AC (xem "UC BỊ SỬA nội dung").
496
+ CHỈ UC cũ không đụng gì mới dùng --realign-prd-version.
470
497
  ```
471
498
 
472
499
  ---
@@ -478,9 +505,9 @@ Next : /refine-prd {prd-file} ← soi phần vừa thêm qua 3 lăng
478
505
  - [ ] Guard sau-ghi đã chạy và PASS: mọi UC/AC/BR/changelog row cũ còn nguyên
479
506
  - [ ] Bước 3.2 đã hỏi đủ 3 câu va chạm (mâu thuẫn rule · trùng lặp · phụ thuộc)
480
507
  - [ ] Mỗi AC mới có ≥1 ref `_(BR: …)_`; mỗi UC mới có dòng "AC liên quan"; hai chiều khớp nhau
481
- - [ ] Dòng changelog **nêu UC/AC/BR** — không mơ hồ *(contract cho `/generate-bdd` + bộ lọc `PRD_STALE_REF`)*
508
+ - [ ] Dòng changelog: mỗi mệnh đề **mở đầu bằng UC sở hữu** (`UC3: sửa BR8`) **không** BR/AC đứng một mình, **không** mơ hồ *(contract: `changelog_row_contract`; BR trơ trọi ⇒ UC đó thành ⓘ ⇒ `--realign` che lỗi)*
482
509
  - [ ] `Status` đã reset về `draft`
483
510
  - [ ] Hình dạng bảng BR giữ nguyên như cũ (không đổi 3-cột ↔ mở-cột ở lệnh này)
484
511
  - [ ] Không có banned term; 0 thuật ngữ kỹ thuật/UI trong text mới
485
512
  - [ ] Request đã xử lý → `incorporated` + `archived/` + commit; request chưa xử lý → giữ `Open`
486
- - [ ] Report nêu danh sách **UC không đổi** + route `--realign-prd-version` cho chúng
513
+ - [ ] Report nêu **CẢ HAI** danh sách: **UC BỊ SỬA nội dung** (→ `/generate-bdd`, cấm `--realign`) và **UC không đổi** (→ `--realign-prd-version`). Danh sách thứ hai = `existing_ucs` **TRỪ** danh sách thứ nhất — không được lấy trọn `existing_ucs`
@@ -222,11 +222,14 @@ Hỏi người dùng chọn platform target:
222
222
 
223
223
  ```
224
224
  BDD này dành cho platform nào?
225
- 1. web — FE/Web (React, Next.js, Angular, Vue, Nuxt)
226
- 2. app — Mobile (Flutter, React Native, iOS, Android)
227
- 3. systemSystem/BE BDD (tổng hợp từ web + app BDD có sẵn)
225
+ 1. web — FE/Web trong browser (React, Next.js, Angular, Vue, Nuxt)
226
+ 2. app — Mobile native (Flutter, React Native, iOS, Android)
227
+ 3. webviewBundle web NHÚNG trong app native (Phaser game, mini-app)
228
+ 4. system — System/BE BDD (tổng hợp từ BDD client có sẵn)
228
229
  ```
229
230
 
231
+ *Chỉ hiện những platform project thực sự dùng: nếu `services.{domain}` là map-theo-platform (context-loader 2b) thì lấy đúng các sub-key của nó làm danh sách; ngược lại hiện đủ bốn. `webview` là một **delivery surface** riêng — không phải `web` (browser) và không phải `app` (native) — nên nó có BDD, design-spec và sổ trace riêng.*
232
+
230
233
  Chờ người dùng chọn. Set `active_platform` = giá trị đã chọn.
231
234
 
232
235
  **Output path (spec repo mode):**
@@ -244,7 +247,7 @@ Chờ người dùng chọn. Set `active_platform` = giá trị đã chọn.
244
247
 
245
248
  ## System BDD Synthesis (active_platform = system)
246
249
 
247
- *Chỉ áp dụng khi platform = system. Bỏ qua với web app.*
250
+ *Chỉ áp dụng khi platform = `system`. Bỏ qua với mọi platform client (`web`/`app`/`webview`/…).*
248
251
 
249
252
  ### Step S0 — Brownfield Check
250
253
 
@@ -375,9 +378,37 @@ Chỉ cần kiểm tra trạng thái đã phân giải:
375
378
  | `active_service` đã phân giải thành path service | Tiếp tục với `active_module` đã set. |
376
379
  | `active_service = "multi"` **và** `service_candidates_kind = platform` (domain map-theo-platform, target PRD chưa gắn 1 platform) | Tiếp tục — BDD là artifact liên team, platform-split. Sinh `bdd/{platform}/` cho các platform có trong `service_candidates`; từ vựng lấy theo `service_candidates.{platform}.module`. KHÔNG cần chốt 1 service. Platform nào bị đánh dấu `unresolved` trong candidates (thiếu `prd_slug` tương ứng dưới `by_prd_slug`) → vẫn sinh BDD nhưng gắn ⚠️ nêu rõ chưa có repo nhận. |
377
380
  | `active_service = "multi"` **và** `service_candidates_kind = prd_slug` | **DỪNG.** Candidates đang là map feature→repo, KHÔNG phải map platform — sinh `bdd/{slug}/` là sai bố cục. Yêu cầu người dùng chạy lại với target file cụ thể để `prd_slug` được xác định. |
378
- | `active_service = "unresolved"` (có section `services` nhưng domain PRD không khớp entry nào) | **DỪNG**, báo: "Domain `{domain}` của PRD không khớp service nào trong `services:` của project-context.yaml bổ sung mapping rồi chạy lại." (Không đoán/hỏi tay domain khoá định danh, lệch là lỗi cấu hình cần sửa ở SoT.) |
381
+ | `active_service = "unrouted"` (chưa mapping cho domain/platform/prd_slug này) | **TIẾP TỤC**ghi `@trace.service: unrouted`, in ⚠️, **KHÔNG dừng**. Xem khối bên dưới. |
382
+ | `active_service = "unresolved"` (config **sai cấu trúc**: entry vừa có `path` vừa có `by_prd_slug`, hoặc `by_prd_slug` lồng nhau) | **DỪNG**, báo đúng key sai để người dùng sửa `project-context.yaml`. Đây là **bug cấu hình**, không phải trạng thái chờ. |
379
383
  | Single-service (không có section `services`) | `active_module = tech_stack.module` (đã set ở Bước 6.5). Tiếp tục. |
380
384
 
385
+ #### `unrouted` — vì sao KHÔNG chặn ở đây *(G51)*
386
+
387
+ PRD và BDD là artifact **nghiệp vụ**. PO biết `domain` (auth, payment) và biết `platform`
388
+ (*"người dùng làm việc này trên web hay app?"*) — nhưng **không** biết code sẽ nằm repo nào,
389
+ và ở feature đầu tiên của một domain mới thì **chưa ai quyết**.
390
+
391
+ Chặn BDD vì lý do đó là đặt cổng **sai phase**: nó chặn phase KHÔNG CẦN biết, trong khi
392
+ `/generate-code` — phase **buộc phải** biết mới ghi được file — mới là chỗ đúng để chặn.
393
+
394
+ **Việc cần làm khi `unrouted`:**
395
+ 1. `@trace.service: unrouted` vào header `.feature` (đừng để trống — cột 23 sẽ mất thông tin)
396
+ 2. **`active_module` chưa biết** ⇒ hỏi người dùng `platform` trực tiếp (`web`/`app`/`system`) thay
397
+ vì suy từ module. Đây là câu hỏi **nghiệp vụ**, PO trả lời được. Từ vựng step lấy theo platform:
398
+ `web` → *clicks* · `app` → *taps* · `system` → *calls the API*.
399
+ 3. In ⚠️ vào report:
400
+ ```
401
+ ⚠️ service: unrouted — chưa có mapping cho domain "{domain}"{ platform "{platform}"} trong
402
+ services: của project-context.yaml.
403
+ BDD đã sinh xong và ĐÚNG — đây là việc của architect, không phải của bạn.
404
+ Architect thêm mapping → /validate-traces tự nâng unrouted → path, KHÔNG cần chạy lại lệnh này.
405
+ /generate-code sẽ DỪNG cho tới khi có mapping (nó cần biết ghi vào repo nào).
406
+ ```
407
+
408
+ > **Vì sao không cần chạy lại `/generate-bdd`:** `/validate-traces` đọc lại `services:` **mỗi lần
409
+ > chạy** và nâng `unrouted` → path khi mapping xuất hiện — cùng cách nó đã làm với `spec_ver`.
410
+ > Sổ **tự lành**.
411
+
381
412
  ### Phân giải `active_platform` (umbrella mode)
382
413
 
383
414
  Umbrella mode không hỏi platform (khác spec repo mode) — nó **suy** từ module của service. Bắt buộc phải có giá trị: `active_platform` đi vào **path file**, vào **header `@trace.platform`**, và vào **tên sổ trace** `{UC-ID}-{platform}.tsv`.
@@ -411,7 +442,7 @@ Umbrella mode không hỏi platform (khác spec repo mode) — nó **suy** từ
411
442
 
412
443
  ## Design Spec — Gate & Load (chỉ FE/App)
413
444
 
414
- *Chỉ chạy khi target platform là FE/App — spec mode: `active_platform {web, app, app-ios, app-android}`; umbrella mode: `active_module` là module FE/App (react/nextjs/vue/nuxt/angular/flutter/react-native/ios-swiftui/android-compose). Bỏ qua HOÀN TOÀN với `system` và backend/brownfield.*
445
+ *Chỉ chạy khi target platform là FE/App — spec mode: `active_platform` platform client (mọi giá trị **trừ** `system` — `web`, `app`, `webview`, `app-ios`, `app-android`, …); umbrella mode: `active_module` là module FE/App (react/nextjs/vue/nuxt/angular/flutter/react-native/ios-swiftui/android-compose). Bỏ qua HOÀN TOÀN với `system` và backend/brownfield.*
415
446
 
416
447
  **1. Định vị design-spec của platform:**
417
448
  `{paths.specs_dir}/{domain}/{prd-slug}/design-spec/{TICKET-ID}-design-spec-{active_platform}-{slug}.md`
@@ -505,7 +536,18 @@ Trước khi sinh, kiểm tra các file `.feature` có sẵn cho PRD này:
505
536
  F — gen lại toàn bộ scenario
506
537
  N — huỷ
507
538
  ```
508
- 3. Tiếp tục theo lựa chọn của người dùng. **Nếu changelog row không nêu rõ UC/AC/BR bị đổi (mơ hồ) → khuyến nghị F** (gen lại toàn bộ) thay Y, để khỏi sót scenario bị ảnh hưởng (lưới an toàn không chắc đổi đâu thì quét rộng).
539
+ 3. Tiếp tục theo lựa chọn của người dùng nhưng **khuyến nghị Y hay F thì đọc `{changelog_scope}` của các row đó** *(contract: `bin/trace-schema.json` `changelog_row_contract`; cùng dữ liệu `/validate-traces` Step 4 dùng để lọc 🟠 vs )*:
540
+
541
+ Mỗi mệnh đề trong `{changelog_scope}` mở đầu bằng đơn vị sở hữu (`{UC-ID}:` hoặc `PRD-global:`). Dựng `affected_ucs` **theo đúng ba bước của `/validate-traces` Step 4**, gồm cả bước 2 — phép phân giải **`BR/AC → UC sở hữu`**: `BR{n}` → UC có BR đó trong bảng Business Rule (PRD §3) · `AC{n}` → UC có AC đó ở dòng `**AC liên quan:**`. Rồi:
542
+
543
+ | Tình trạng row trong khoảng | Khuyến nghị |
544
+ |---|---|
545
+ | **Bất kỳ** row **mơ hồ** (không nêu được đơn vị sở hữu, hoặc BR/AC không phân giải được về UC) | **F** — gen lại toàn bộ. Không chắc đổi ở đâu thì quét rộng |
546
+ | UC của target **có** trong `affected_ucs` | **Y** — cập nhật đúng scenario của UC đó |
547
+ | UC của target vào `affected_ucs` **CHỈ** qua mệnh đề mang hậu tố **`[no-behavior]`** | **N** — không có gì để gen lại; đó là thay đổi thuần cấu trúc, producer đã chứng minh không đổi hành vi |
548
+ | UC của target **không** có trong `affected_ucs` | **N** — bump này không đụng UC này. Nhãn version lệch sẽ được `/validate-traces --realign-prd-version` dọn |
549
+
550
+ > **Vì sao phải phân giải BR/AC (G53), không chỉ khớp UC-ID:** một row `thêm UC7: AC12-AC14; UC3: sửa BR8` là đúng contract. Nhưng nếu ai ghi thiếu `UC3:` — thành `…; sửa BR8` — thì row **không** mơ hồ (nó nêu đủ ID) mà UC3 vẫn không xuất hiện khi chỉ khớp UC-ID. Kết quả: khuyến nghị **N** cho đúng UC vừa bị đổi hành vi. Phân giải BR8 → UC3 là thứ chặn ca đó, và nó phải giống hệt phép phân giải của `/validate-traces` — hai consumer đọc cùng một dòng thì không được hiểu khác nhau.
509
551
 
510
552
  ---
511
553
 
@@ -602,7 +644,7 @@ Với mỗi UC, ghi vào path trên và set `# @trace.platform: {active_platform
602
644
  # @trace.revision: 1 ← field tĩnh; version theo dõi bằng @trace.bdd_version
603
645
  # @trace.domain: <domain>
604
646
  # @trace.platform: {active_platform — web | app | system} ← BẮT BUỘC mọi mode; phải khớp segment bdd/{platform}/ của path
605
- # @trace.service: {active_service BẮT BUỘC mọi mode. "" single-service/spec repo mode; "multi" nếu chưa chốt; "unresolved" nếu routing sai. Nguồn của cột TSV `service` trace gộp không tách theo service nên đây là chỗ DUY NHẤT mang thông tin sở hữu}
647
+ # @trace.service: {service của ĐÚNG platform file nàyBẮT BUỘC mọi mode. Nguồn của cột TSV `service`; trace gộp không tách theo service nên đây là chỗ DUY NHẤT mang thông tin sở hữu ở cấp row. Bốn giá trị: {path} · "unrouted" (chưa ai quyết repo — HỢP LỆ, cờ 🟠, KHÔNG chặn) · "unresolved" (config sai cấu trúc — bug) · "—" (single-service). KHÔNG ghi "multi": file này đã có MỘT platform xác định nên service_candidates.{platform}.path đã biết — ghi path đó (G51)}
606
648
  # @trace.module: {active_module trong umbrella mode; "unknown" trong spec repo mode}
607
649
  # @trace.status: draft
608
650
  # @trace.author: AI-generated
@@ -756,7 +798,7 @@ In danh sách SC được bump vào report cuối để người dùng biết c
756
798
 
757
799
  ## Write Trace State
758
800
 
759
- Sau khi sinh tất cả file `.feature`, tạo hoặc cập nhật **sổ trace theo platform** `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{active_platform}.tsv` cho mỗi UC — một sổ riêng cho `system` / `web` / `app`. Vì `sc_id` = `{UC-ID}-SC{N}` chỉ độc nhất trong (UC × platform) (mỗi platform tự đánh số SC từ 1), **mỗi platform một file** để scenario platform này không đè/xoá platform khác. Lệnh luôn biết `active_platform` (từ Platform Selection / Service Detection) nên chỉ ghi đúng sổ của platform đang gen.
801
+ Sau khi sinh tất cả file `.feature`, tạo hoặc cập nhật **sổ trace theo platform** `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{active_platform}.tsv` cho mỗi UC — một sổ riêng cho mỗi platform (`system` / `web` / `app` / `webview` / …). Vì `sc_id` = `{UC-ID}-SC{N}` chỉ độc nhất trong (UC × platform) (mỗi platform tự đánh số SC từ 1), **mỗi platform một file** để scenario platform này không đè/xoá platform khác. Lệnh luôn biết `active_platform` (từ Platform Selection / Service Detection) nên chỉ ghi đúng sổ của platform đang gen.
760
802
 
761
803
  > **Umbrella + `spec_source`:** cả file `.feature` **và** trace `.tsv` đều ghi vào **spec repo** (`{spec_source}/specs/{domain}/{prd-slug}/bdd/…` và `{spec_source}/.trace/{domain}/{prd-slug}/…`, do context-loader phân giải) — một thao tác ghi **single-repo**, commit/push vào spec submodule. (Trace được gộp trong spec repo để PM quản lý mọi status ở một chỗ; các lệnh phía code cập nhật liên-repo sau.)
762
804
 
@@ -816,7 +858,7 @@ sc_id\tsc_title\tspec_ver\tgen_ver\timplemented_by\ttest_count\ttest_classes\tde
816
858
  | `fe_phase` | `—` (set bởi `/generate-code --phase` khi FE implement) |
817
859
  | `status` | `UNTRACKED` |
818
860
  | `last_updated` | hôm nay `YYYY-MM-DD` |
819
- | `service` | `@trace.service` từ header `.feature` — đội/submodule sở hữu scenario này. `multi` nếu chưa chốt (map-theo-platform cấp PRD), `unresolved` nếu domain không khớp entry nào, `—` single-service mode. **Đừng bỏ trống** — trace gộp không tách theo service nên đây là chỗ DUY NHẤT mang thông tin sở hữu ở cấp row. |
861
+ | `service` | `@trace.service` từ header `.feature` — đội/submodule sở hữu scenario này. Bốn giá trị: `{path}` · **`unrouted`** (chưa ai quyết repo hợp lệ, cờ 🟠) · `unresolved` (config sai cấu trúc bug) · `—` (single-service). **Đừng bỏ trống** — trace gộp không tách theo service nên đây là chỗ DUY NHẤT mang thông tin sở hữu ở cấp row.<br>⚠️ **KHÔNG ghi `multi` vào file `.feature`** *(G51)*: khi split theo platform, mỗi file đã có **một** platform xác định nên `service_candidates.{platform}.path` **đã biết** — ghi path đó. `multi` chỉ là trạng thái trung gian ở cấp PRD trong bộ nhớ, không phải giá trị được ghi ra. |
820
862
  | `design_spec_version` | `\| **Version** \|` của design-spec đã nạp ở §Design Spec — Gate & Load. `—` cho `system`/backend (không có design-spec), và `—` khi người dùng chọn "Y — vẫn sinh BDD" mà không có design-spec. |
821
863
 
822
864
  ## Refresh Panel Mirror
@@ -191,6 +191,39 @@ Lệnh này giới hạn nghiêm ngặt trong **một file feature** được tr
191
191
 
192
192
  ---
193
193
 
194
+ ## Guard — biết ghi code vào REPO NÀO chưa *(chặn CỨNG — G51)*
195
+
196
+ *Chỉ áp ở umbrella/multi-service (có section `services`). Single-service thì bỏ qua.*
197
+
198
+ Đọc `@trace.service` từ header `.feature` target (và `service_root` từ context-loader Bước 1.6):
199
+
200
+ | Giá trị | Hành động |
201
+ |---|---|
202
+ | `{path}` — đã route | Tiếp tục. |
203
+ | **`unrouted`** | **DỪNG.** Chưa ai quyết repo cho domain này. |
204
+ | **`unresolved`** | **DỪNG.** Config sai cấu trúc. |
205
+ | `service_root = null` | **DỪNG.** Không có mốc thư mục để ghi file. |
206
+
207
+ ```
208
+ 🔴 Chưa biết ghi code vào repo nào — service của {UC-ID} đang là "{value}".
209
+
210
+ Lệnh này ghi file source TƯƠNG ĐỐI với service_root, nên không có mapping thì
211
+ không có chỗ ghi. (Trước G51 nó ghi source vào một thư mục tên đúng chữ
212
+ "unresolved/" — im lặng.)
213
+
214
+ Sửa: thêm mapping cho domain "{domain}" vào `services:` của .agent/project-context.yaml
215
+ rồi chạy /validate-traces (nó nâng unrouted → path, sổ tự lành), sau đó chạy lại lệnh này.
216
+
217
+ BDD của bạn KHÔNG sai và KHÔNG cần sinh lại — đây là bước cấu hình của architect.
218
+ ```
219
+
220
+ > **Vì sao cổng nằm ở ĐÂY chứ không ở `/generate-bdd`** *(G51)*: PRD/BDD là artifact **nghiệp vụ** —
221
+ > PO biết `domain` và `platform`, không biết repo, và ở feature đầu tiên của domain mới thì chưa ai
222
+ > quyết. Trước G51 cổng đặt ngược: `/generate-bdd` **dừng hẳn** (phase không cần biết) còn lệnh này
223
+ > **không kiểm gì** (phase buộc phải biết). Đây là chỗ duy nhất thật sự không chạy nổi khi thiếu.
224
+
225
+ ---
226
+
194
227
  ## Guard — BDD & Design Spec đã sẵn sàng chưa *(cảnh báo MỀM — đồng bộ generate-bdd)*
195
228
 
196
229
  **BDD (mọi platform) — DS1:** đọc `# @trace.status:` từ header `.feature` target.
@@ -233,7 +266,7 @@ Lệnh này giới hạn nghiêm ngặt trong **một file feature** được tr
233
266
 
234
267
  ## Phase Detection
235
268
 
236
- > **Nguồn chuẩn quyết BE/FE = `@trace.platform` của FILE FEATURE** (`system` → BE · `web`/`app` FE). KHÔNG dùng `platform_type` (suy từ module) để quyết BE/FE — nó chỉ dùng cho **idiom stack/module** (cú pháp, layer, thư viện). Lý do: repo fullstack một-module (vd Next.js có API route) có `platform_type` cố định một giá trị, nhưng vẫn có cả feature `system` (BE) lẫn `web` (FE) — chỉ tag của chính feature mới đúng.
269
+ > **Nguồn chuẩn quyết BE/FE = `@trace.platform` của FILE FEATURE** — **`system` → BE · MỌI platform khác → FE** (`web`, `app`, `webview`, bất kỳ surface nào project khai thêm). Luật viết bằng **phủ định**, không phải liệt kê: liệt kê `web`/`app` làm platform thứ tư không khớp nhánh nào, và lệnh sẽ phải tự đoán — sinh sai loại code mà không cờ nào báo. KHÔNG dùng `platform_type` (suy từ module) để quyết BE/FE — nó chỉ dùng cho **idiom stack/module** (cú pháp, layer, thư viện). Lý do: repo fullstack một-module (vd Next.js có API route) có `platform_type` cố định một giá trị, nhưng vẫn có cả feature `system` (BE) lẫn `web` (FE) — chỉ tag của chính feature mới đúng.
237
270
 
238
271
  Parse `$ARGUMENTS` tìm flag `--phase` và `--force`:
239
272
 
@@ -242,7 +275,7 @@ Parse `$ARGUMENTS` tìm flag `--phase` và `--force`:
242
275
  | `--phase=ui` | FE Phase 1 — sinh UI + layer mock API từ System BDD contract |
243
276
  | `--phase=integration` | FE Phase 2 — thay mock adapter bằng lời gọi API thật từ tech docs |
244
277
  | `--force` | "Gen lại tường minh" — **CHỈ** bỏ qua guard status ở §Read Trace State (không skip row đang `OK`). Xem định nghĩa hẹp bên dưới. |
245
- | *(không có)* | Default — full: **BE/`system`** → full backend; **FE (`web`/`app`)** → **FE full** (sinh UI + wire API thật trong một lần, không qua bước mock) |
278
+ | *(không có)* | Default — full: **`system`** → full backend; **mọi platform khác** (`web`/`app`/`webview`/…) → **FE full** (sinh UI + wire API thật trong một lần, không qua bước mock) |
246
279
 
247
280
  > **`--force` có phạm vi HẸP — đây là ranh giới cứng, không phải khuyến nghị.**
248
281
  > Nó bỏ qua **đúng một** thứ: luật "row `OK` thì skip" ở §Read Trace State. **Mọi guard khác giữ nguyên hiệu lực:** Scope Lock (cấm implement scenario của `.feature` khác) · quy tắc EXTEND phi-phá-huỷ (đọc lại trước khi ghi · CẤM full Write trên file đã tồn tại · output phải là superset chặt) · Guard sau-ghi · Fill-before-create · Build Verify.
@@ -232,7 +232,7 @@ Kiểm tra `output_path` đã tồn tại chưa.
232
232
 
233
233
  - **Chưa tồn tại → chế độ FRESH.** Tạo doc từ template, chỉ điền (các) UC trong `input_features`. (Section của các UC không thuộc batch này giữ placeholder `{…}` / được thêm ở lần chạy sau.)
234
234
  - **Đã tồn tại → chế độ APPEND.** Doc là tăng dần — không bao giờ regenerate từ đầu (sẽ đè mất chỉnh tay và sign-off của reviewer). Đọc bảng **§10 UC Coverage** và **Changelog** hiện có → `covered_ucs`. Với mỗi UC trong `input_features`, phân loại:
235
- - **UC mới** (không có trong `covered_ucs`) → **thêm** các section của nó: sequence diagram §5 mới **đúng lane platform** (5.A/5.B/5.C, đánh số sau cái cuối cùng hiện có *trong lane đó*); với §4.5 — nếu **platform** này mới với doc → nhóm `### 4.5 — {platform}` mới, ngược lại thêm sub-block `§4.5.1.x {Screen} — {UC}` + row vào §4.5.6 dùng chung của nhóm platform đó (đừng lặp nhóm); row mới ở §3/§4.3/§8/§9. Rồi cập nhật §10 (row khoá theo platform×SC) và thêm một row Changelog.
235
+ - **UC mới** (không có trong `covered_ucs`) → **thêm** các section của nó: sequence diagram §5 mới **đúng lane platform** (5.A/5.B/5.C, đánh số sau cái cuối cùng hiện có *trong lane đó*); với §4.5 — nếu **platform** này mới với doc → nhóm `### 4.5 — {platform}` mới, ngược lại thêm sub-block `§4.5.1.x {Screen} — {UC}` + row vào §4.5.6 dùng chung của nhóm platform đó (đừng lặp nhóm); row mới ở §3/§4.3/§8/§9. Rồi cập nhật §10 (row khoá theo platform×SC) và thêm một row Changelog **theo format ở Bước 1b**.
236
236
  - **UC đã phủ được trỏ lại** (có trong `covered_ucs`) → đây là refresh/mở rộng có chủ đích (vd tech lead giờ trỏ vào BDD `web/` của một UC mà backend đã thiết kế, hoặc BDD bump version). Xác nhận trước khi đụng nội dung có sẵn:
237
237
  ```
238
238
  ↻ {UC-ID} đã có trong {TICKET-ID}-tech-design.md.
@@ -245,6 +245,38 @@ Lưu `mode` (`fresh` | `append`) và, theo từng UC của batch, hành động
245
245
 
246
246
  ---
247
247
 
248
+ ## Bước 1b — Format row Changelog *(contract máy đọc — áp cho CẢ Fresh lẫn Append)*
249
+
250
+ Row Changelog của tech-doc **không phải ghi chú cho người đọc** — `/validate-traces` Step 5 đọc nó để quyết mỗi UC ăn cờ 🟠 `TECHDOC_DRIFT` hay ⓘ `TECHDOC_STALE_REF`.
251
+
252
+ Format *(contract: `bin/trace-schema.json` → `changelog_row_contract`)*:
253
+
254
+ ```
255
+ | {revision} | {YYYY-MM-DD} | {changelog_scope} |
256
+ ```
257
+
258
+ **`{changelog_scope}` — mỗi mệnh đề mở đầu bằng UC SỞ HỮU.** Nguồn: các UC trong `input_features` của batch vừa thêm/sửa (Bước 1 đã phân loại từng UC là `add-new` / `extend-platform` / `refresh` / `skip` — UC `skip` **KHÔNG** vào dòng này). Ngăn nhau bằng `;`. Nội dung không thuộc UC nào (§11 Cross-cutting, §2 kiến trúc chung) → `doc-global`.
259
+
260
+ | Ca | Ví dụ đúng |
261
+ |---|---|
262
+ | Fresh | `1 \| 2026-08-19 \| UC1, UC2: sinh lần đầu từ BDD system v1.4` |
263
+ | Thêm UC mới | `2 \| 2026-08-22 \| UC3: thêm §5.9 sequence + §10 coverage, từ BDD system v1.0` |
264
+ | Thêm platform cho UC đã phủ | `3 \| 2026-08-25 \| UC1: thêm block client web §4.5.1.2, từ BDD web v1.2` |
265
+ | Refresh vì BDD bump | `4 \| 2026-08-28 \| UC2: refresh §4.1 endpoint theo BDD system v1.6` |
266
+ | Chỉ sửa phần chung | `5 \| 2026-08-30 \| doc-global: bổ sung §11 chuẩn logging` |
267
+
268
+ *(Tech-doc **không** dùng hậu tố `[no-behavior]` — `doc-global` đã đủ: không nêu UC nào thì không UC nào ăn cờ. Marker đó chỉ dành cho producer biết chính xác `check_id` của từng fix mình vừa áp, tức `/review-context --fix`.)*
269
+
270
+ > **Vì sao khai format ở đây (G58).** Trước đó Bước 1 và §Sinh chỉ nói *"thêm một row Changelog"* — **không format, không ví dụ, không nhắc phải nêu UC**. Trong khi `/validate-traces` Step 5 lọc 🟠-vs-ⓘ **bằng chính row đó**, và chế độ APPEND bump `@trace.revision` chung cho cả doc nên **mọi UC cũ lệch revision** dù phần của chúng không đổi một dòng.
271
+ >
272
+ > Chỗ duy nhất có format là một **comment HTML** trong `templates/tech-design.template.md` — mà chế độ APPEND theo định nghĩa **không đọc lại template**. Nên contract đang phụ thuộc vào việc agent tình cờ nhìn thấy một dòng comment ở file khác. Chưa nổ vì ví dụ trong comment tình cờ đúng; đây là nợ chờ lệch, cùng lớp với G52/G53.
273
+ >
274
+ > Hai kiểu viết sai và hậu quả — **đối xứng hoàn toàn với phía PRD**:
275
+ > - **Mơ hồ** (`cập nhật tech design`) → Step 5 gắn 🟠 cho **MỌI** UC của doc. Ồn tới mức cờ mất giá trị.
276
+ > - **Nêu §/SC mà bỏ UC** (`thêm §5.9, §10`) → nêu đủ ID để **không** bị coi là mơ hồ, nhưng Step 5 khớp theo **UC**; nên UC vừa được thêm lại rơi vào ⓘ và `--realign-techdoc-revision` sẽ dán nhãn lại. Im lặng.
277
+
278
+ ---
279
+
248
280
  ## Bước 2 — Cổng Chất lượng (mọi feature nguồn)
249
281
 
250
282
  Với **mỗi** feature BDD trong scope:
@@ -346,7 +378,7 @@ Ghi/mở rộng `{output_path}` dùng template dưới đây, chỉ sinh **nội
346
378
  - **§1/§2** (Overview/Actors, Architecture) là cấp PRD: viết ở lần chạy đầu; các lần sau chỉ mở rộng nếu batch thêm actor/integration thật sự mới.
347
379
  - **§10 UC Coverage** — một row UC (có cột Platforms) + bảng con coverage-scenario khoá theo **(platform, SC)** — mỗi platform×SC một row, vì cùng số SC ở platform khác nhau là scenario khác nhau. Đây là mỏ neo mà chế độ APPEND đọc. Luôn cập nhật nó cho (các) UC/platform của batch.
348
380
 
349
- **Chế độ APPEND (doc đã tồn tại):** **đừng** viết lại section có sẵn. Chèn diagram §5 của UC batch **vào đúng lane platform** (5.A/5.B/5.C, đánh số sau cái cuối trong lane đó, tiêu đề `platform · SC`), các row mới ở §3/§4.3/§8/§9; với §4.5 — platform mới → nhóm `### 4.5 — {platform}` mới, ngược lại thêm sub-block `§4.5.1.x {Screen} — {UC}` + row vào §4.5.6 dùng chung của nhóm (không lặp nhóm); rồi cập nhật §10 (row khoá theo platform×SC) và thêm một row Changelog. Bump `@trace.revision` và làm mới `@trace.ucs` / `@trace.platforms` ở header, và cập nhật entry của platform vừa đụng trong map `@trace.bdd_versions` (vd set `web=2.0`, giữ nguyên `system`).
381
+ **Chế độ APPEND (doc đã tồn tại):** **đừng** viết lại section có sẵn. Chèn diagram §5 của UC batch **vào đúng lane platform** (5.A/5.B/5.C, đánh số sau cái cuối trong lane đó, tiêu đề `platform · SC`), các row mới ở §3/§4.3/§8/§9; với §4.5 — platform mới → nhóm `### 4.5 — {platform}` mới, ngược lại thêm sub-block `§4.5.1.x {Screen} — {UC}` + row vào §4.5.6 dùng chung của nhóm (không lặp nhóm); rồi cập nhật §10 (row khoá theo platform×SC) và thêm một row Changelog **theo format ở Bước 1b**. Bump `@trace.revision` và làm mới `@trace.ucs` / `@trace.platforms` ở header, và cập nhật entry của platform vừa đụng trong map `@trace.bdd_versions` (vd set `web=2.0`, giữ nguyên `system`).
350
382
 
351
383
  <!--
352
384
  ════════════════════════════════════════════════════════════════════════════
@@ -389,7 +421,7 @@ Ghi/mở rộng `{output_path}` dùng template dưới đây, chỉ sinh **nội
389
421
  @trace.ucs: {TICKET-ID}-UC1, {TICKET-ID}-UC2{, …}
390
422
  @trace.service: {service — từ header BDD @trace.service}
391
423
  @trace.module: {module liên quan — vd dotnet, angular}
392
- @trace.platforms: {system | web | app — tuỳ thư mục BDD nào tồn tại}
424
+ @trace.platforms: {system | web | app | webview | … — tuỳ thư mục BDD nào tồn tại}
393
425
  @trace.bdd_versions: {MAP theo từng platform — số nhiều, KHÁC @trace.bdd_version (scalar) của .feature — vd system=1.5, web=1.9, app=1.7; chỉ platform có mặt. Mỗi feature mang bdd_version riêng; đừng gộp về một số.}
394
426
  @trace.api_source: {existing | —}
395
427
  @trace.revision: 1
@@ -762,7 +794,7 @@ sequenceDiagram
762
794
 
763
795
  | UC | Feature | Platforms | Section phủ | Trạng thái |
764
796
  |----|---------|-----------|------------------|--------|
765
- | {TICKET-ID}-UC1 | {title} | {system, web, app} | §… | ✅ Covered |
797
+ | {TICKET-ID}-UC1 | {title} | {system, web, app, webview…} | §… | ✅ Covered |
766
798
 
767
799
  ### Độ phủ Scenario UC1
768
800
 
@@ -180,7 +180,7 @@ placeholder bên dưới sẽ rỗng và lệnh sẽ đọc/ghi sai chỗ.
180
180
 
181
181
  Phân giải `platform` từ `@trace.platform` / `platform_type`. Test-id là chuyện của **FE/App** — nếu `system` / backend → HALT:
182
182
  ```
183
- ❌ /map-testids chỉ áp dụng cho FE/App (web/app). BE không có UI test-id.
183
+ ❌ /map-testids chỉ áp dụng cho platform CLIENT (mọi platform trừ `system` — web/app/webview/…). BE không có UI test-id.
184
184
  ```
185
185
  Phân giải attribute test-id từ `@trace.testid_attr` (hoặc theo module): web `data-testid` · React Native `testID` · Flutter `Key`/`Semantics(identifier:)` · native iOS `accessibilityIdentifier`.
186
186
 
@@ -222,7 +222,7 @@ Sau khi chạy, cập nhật **sổ của platform đang test** `{paths.trace_di
222
222
 
223
223
  | Cột | Giá trị |
224
224
  |--------|-------|
225
- | `qc_status` | `pass` nếu mọi QC test của SC này pass · `fail` nếu có cái fail · `skip` nếu tất cả skip/xfail · `not_run` nếu không QC test nào phủ |
225
+ | `qc_status` | Đọc cột `status` của row **TRƯỚC** — xem §Guard ngay dưới bảng. Row `OK`/`GAP`/`UNTRACKED`: `pass` nếu mọi QC test của SC này pass · `fail` nếu có cái fail · `skip` nếu tất cả skip/xfail · `not_run` nếu không QC test nào phủ nó. Row **`DRIFT`/`ORPHANED`**: **không bao giờ ghi `pass`** — hạ về `not_run` |
226
226
  | `qc_run_at` | hôm nay `YYYY-MM-DD` |
227
227
  | `last_updated` | hôm nay `YYYY-MM-DD` |
228
228
  | `qc_owner` | **SC đang chờ ai** (view "pending" của PM/PO): `dev` nếu FAIL = product-gap (defect thật → dev fix) · `po` nếu `skip`/`not_run` vì một **`DOC_GAPS` 🔴 Blocker đang open** chặn test (PO phải làm rõ PRD/BDD) · `—` nếu `pass`, hoặc FAIL = script-bug (QC tự fix — tạm thời) |
@@ -232,9 +232,35 @@ Set `qc_owner`/`qc_blocked_by` cùng với `qc_status`. Khi `pass`, **clear** c
232
232
  Với FAIL product-gap, set `qc_owner=dev` ngay; `BUG-{id}` được backfill vào `qc_blocked_by`
233
233
  khi QC chạy `/report-bug` mà `/qc-report` nhắc.
234
234
 
235
+ ### Guard — ĐỌC `status` trước khi ghi `pass` *(bắt buộc)*
236
+
237
+ *Contract: `bin/trace-schema.json` → `positive_assertion_guards`. Đối xứng hoàn toàn với `/dev-run-test`; dữ liệu đã có trong sổ, không phát sinh I/O.*
238
+
239
+ `pass` **không** mang nghĩa *"QC test đã chạy và xanh"*. Nó mang nghĩa **"scenario này đã được nghiệm thu theo spec HIỆN TẠI"**.
240
+
241
+ | `status` của row | `qc_status` ghi gì | `qc_run_at` |
242
+ |---|---|---|
243
+ | `OK` · `GAP` · `UNTRACKED` | như bảng trên | hôm nay |
244
+ | **`DRIFT`** | test **pass** → **`not_run`** *(KHÔNG `pass`)* · **fail** → **`fail`** như thường · **skip** → `skip` như thường | `—` nếu ghi `not_run` |
245
+ | **`ORPHANED`** | **`not_run`** — scenario đã bị xoá khỏi `.feature` | `—` |
246
+
247
+ **Chỉ `pass` bị chặn.** `fail` là tin xấu thật, `skip` là giá trị trung tính — cả hai không phải lời khẳng định nên ghi bình thường. Chặn chúng là biến một guard chống-báo-cáo-sai thành một guard che-tin-xấu.
248
+
249
+ Khi hạ về `not_run` vì `DRIFT`/`ORPHANED`, **KHÔNG** clear `qc_owner`/`qc_blocked_by` và **KHÔNG** chạy §Đóng bug đã verify — hai bước đó chỉ dành cho `pass` **thật**. Đóng một bug dựa trên một lần QC chạy trên spec đã đổi là đóng sai. In:
250
+ ```
251
+ ⚠️ {sc_id} — QC test XANH nhưng row đang {DRIFT | ORPHANED}, nên KHÔNG ghi pass.
252
+ Không bug nào được đóng ở lần chạy này cho SC đó.
253
+ Làm: /generate-code {UC-ID} → /qc-design-test lại → chạy lại lệnh này.
254
+ ```
255
+
256
+ > **Vì sao (GAPS-v4 G55).** Bản cũ khai `qc_status` **trực giao** với `status` — trực giao về *kết quả chạy* thì đúng, nhưng **không** trực giao về *quyền được khẳng định*. `/generate-bdd` hạ `qc_status → not_run` khi spec đổi (kèm lý do *"cái này làm cờ nói dối"*), rồi lần `/qc-run-test` kế tiếp dựng lại `pass` với ngày mới. Ở đây hậu quả còn đi xa hơn `/dev-run-test`: một `pass` sai còn **đóng một bug** (§Đóng bug đã verify) — nên guard phải chặn cả nhánh đó.
257
+ >
258
+ > **Tầng thứ hai độc lập:** `lint-trace` **T12** bắt đúng trạng thái này ở sổ thật, bất kể lệnh nào ghi ra.
259
+
235
260
  Giữ nguyên mọi cột khác — **không bao giờ** đụng `dev_selftest`/`dev_selftest_at`
236
- (do `/dev-run-test` sở hữu). `qc_status` (QC chính thức) và `dev_selftest` (dev smoke) là
237
- hai tín hiệu riêng; cả hai trực giao với `status` (OK/GAP/DRIFT/UNTRACKED/ORPHANED = coverage).
261
+ (do `/dev-run-test` sở hữu; nó có guard riêng cùng loại). `qc_status` (QC chính thức) và
262
+ `dev_selftest` (dev smoke) là hai tín hiệu riêng; cả hai trực giao với `status` về **kết quả
263
+ chạy**, nhưng **KHÔNG** trực giao về **quyền khẳng định `pass`** — xem §Guard ở trên.
238
264
 
239
265
  ## Đóng bug đã verify *(chạy TRƯỚC khi clear `qc_blocked_by`)*
240
266
 
@@ -1,5 +1,10 @@
1
1
  # /refine-prd — Phân tích PRD qua 3 lăng kính review
2
2
 
3
+ > **Ranh giới — lệnh này chỉ áp được fix cho vấn đề mà CHÍNH NÓ tìm ra.** Resume Mode Phase 2 tự
4
+ > cấm đụng bất kỳ section nào không được một finding chấp nhận trỏ tới, và findings sinh từ việc soi
5
+ > PRD hiện có — nên **không có đường nào để một ý định MỚI của PO đi vào**.
6
+ > Thêm UC/AC/BR mới → `/extend-prd`. **Đổi** một yêu cầu đang đúng cú pháp → **`/amend-prd`**.
7
+
3
8
  ## Gate
4
9
 
5
10
  *Checkpoint: **chặn CỨNG** — --resume áp findings trực tiếp vào PRD. `--yes` KHÔNG bỏ qua được (gate Bước 3a).*
@@ -621,9 +626,15 @@ Cập nhật `status: "applied"` ở **root level** của file findings (không
621
626
  5. Cập nhật `# Change Log` của PRD — **bảng phẳng 1 dòng/version, cửa sổ trượt 5 entry** (nếu gặp format cũ `### v{X}` block → chuẩn hoá sang bảng phẳng khi cập nhật):
622
627
  - Thêm row mới lên **đầu** bảng:
623
628
  ```
624
- | {new_version} | {today} | {tóm tắt — KÈM UC/AC/BR bị ảnh hưởng, vd "UC2: sửa BR5; thêm AC7"} |
629
+ | {new_version} | {today} | {changelog_scope} |
625
630
  ```
626
- *(Nêu UC/AC/BR giúp `/generate-bdd` Version Check biết scenario nào cần cập nhật.)*
631
+ **`{changelog_scope}` mỗi mệnh đề mở đầu bằng UC SỞ HỮU** *(contract: `bin/trace-schema.json` `changelog_row_contract`)*. Nguồn: `uc_id` / `section` của các finding được chấp nhận. Ngăn nhau bằng `;`; finding global (`uc_id: ""`) → `PRD-global`.
632
+
633
+ **Ví dụ đúng:** `UC2: sửa BR5, thêm AC7; PRD-global: làm rõ scope §1`
634
+
635
+ ⚠️ **BR/AC không bao giờ đứng một mình.** `sửa BR5` (thiếu `UC2:`) nêu đủ nhiều ID để **không** bị coi là mơ hồ, nhưng `/validate-traces` Step 4 khớp bằng phép thử *"**UC** này có trong tập?"* — nên UC2 rơi vào ⓘ `PRD_STALE_REF` trong khi BR5 của nó vừa đổi, và `--realign-prd-version` (chỉ chặn 🟠) sẽ dán nhãn version lại lên đó. Đây là G53: mất bộ lọc theo hướng **im lặng**, nguy hiểm hơn hướng mơ hồ/ồn.
636
+
637
+ *(Consumer của dòng này: `/generate-bdd` Version Check — biết scenario nào cần cập nhật; `/validate-traces` Step 4 — lọc 🟠 `PRD_DRIFT` vs ⓘ `PRD_STALE_REF`.)*
627
638
  - Cập nhật dòng đầu section: `> Hiện tại: **v{new_version}** ({today}) · Lịch sử đầy đủ → [changelog](./changelog/{TICKET-ID}-{prd-slug}.changelog.md)`
628
639
  - **Rollover (giữ PRD gọn — đây là chuẩn chung, /review-context cũng theo):** nếu bảng `# Change Log` có **> 5 row** → chuyển **mọi row vượt 5** (cũ nhất) sang **đầu** bảng của file kho `{paths.specs_dir}/{domain}/{prd-slug}/changelog/{TICKET-ID}-{prd-slug}.changelog.md` (giữ thứ tự mới→cũ); PRD chỉ giữ **5 row gần nhất**. Tạo thư mục `changelog/` + file kho nếu chưa có, theo skeleton:
629
640
  ```
@@ -760,14 +760,21 @@ Với mỗi finding có `auto_fixable: true`, theo thứ tự (critical → majo
760
760
 
761
761
  **Với file PRD:**
762
762
 
763
- | check_id | Áp dụng gì |
764
- |----------|--------------|
765
- | P1 (Banned term) | Thay mọi lần xuất hiện banned term bằng canonical term |
766
- | P1 (Thuật ngữ kỹ thuật/UI) | Diễn đạt lại theo Business Language Guard (Nhóm 1) / chuyển Design Spec (2) / bỏ về Tech Docs (3) |
767
- | P4 (Structure) | Thêm skeleton section/metadata còn thiếu (row Status vắng → thêm mặc định `draft`); greenfield → xoá section "Existing API Contract" rỗng |
763
+ | check_id | Áp dụng gì | Đổi hành vi? |
764
+ |----------|--------------|:---:|
765
+ | P1 (Banned term) | Thay mọi lần xuất hiện banned term bằng canonical term | **CÓ** |
766
+ | P1 (Thuật ngữ kỹ thuật/UI) | Diễn đạt lại theo Business Language Guard (Nhóm 1) / chuyển Design Spec (2) / bỏ về Tech Docs (3) | **CÓ** |
767
+ | P4 (Structure) | Thêm skeleton section/metadata còn thiếu (row Status vắng → thêm mặc định `draft`); greenfield → xoá section "Existing API Contract" rỗng | KHÔNG |
768
768
 
769
769
  > **Chạy Business Language Guard trên text vừa sửa TRƯỚC khi ghi** (xem section "Ngôn ngữ nghiệp vụ") — không để bản auto-fix tự kéo thuật ngữ kỹ thuật vào.
770
770
 
771
+ **Cột "Đổi hành vi?" là đầu vào của Phase 3** — nó quyết định UC bị đụng có ăn cờ 🟠 `PRD_DRIFT` hay ở lại ⓘ. Ghi lại `check_id` + `uc_id` của **từng** finding vừa áp; Phase 3 cần cả hai.
772
+
773
+ | | Nghĩa | Vì sao |
774
+ |---|---|---|
775
+ | **CÓ** | Câu văn nghiệp vụ trong PRD đã khác đi | P1 banned-term: term đó **cũng nằm trong `.feature` đã sinh** nên BDD lỗi thời thật về thuật ngữ (B2/C4 sẽ bắt) — đổi từ trong PRD mà không nêu UC là **bỏ sót**, không phải trung tính. P1 tech-jargon: auto-fix của nó **viết lại câu** AC/BR, và nhánh 2/3 **lấy nội dung ra khỏi** PRD |
776
+ | **KHÔNG** | Chỉ thêm/bỏ vỏ cấu trúc, 0 nội dung nghiệp vụ mới | Thêm một heading rỗng hay row `Status` mặc định không làm BDD của UC đó lỗi thời. Đánh dấu nó là drift chính là **báo động giả** |
777
+
771
778
  **Với file BDD:**
772
779
 
773
780
  | check_id | Áp dụng gì |
@@ -786,8 +793,29 @@ Sau khi áp dụng mỗi finding, đánh dấu nó `status: "applied"` + `applie
786
793
 
787
794
  ### Phase 3 — Version bump
788
795
 
789
- - **PRD**: nếu ≥1 finding được áp dụng → bump version **minor** (auto-fix chỉ áp dụng thay banned-term P1 thêm skeleton P4không bao giờ thay đổi cấu trúc UC hay nội dung BR, nên minor bump luôn đúng), **reset `| **Status** | draft |` trong Metadata** (PRD vừa đổi sau khi duyệt → con dấu duyệt cũ hết hiệu lực, phải duyệt lại — đồng bộ với /refine-prd), thêm entry Changelog:
790
- `| {new_version} | {today} | Auto-fix: applied {N} auto-fixable findings |` — bảng phẳng + **rollover giữ 5 row gần nhất** (dồn dư sang `changelog/{TICKET-ID}-{prd-slug}.changelog.md`); xem quy ước đầy đủ ở refine-prd Phase 3.
796
+ - **PRD**: nếu ≥1 finding được áp dụng → bump version **minor** (auto-fix **không bao giờ thêm/xoá UCkhông tái cấu trúc scope** kể cả P1 tech-jargon nhánh 2/3, thứ di dời *chi tiết cơ chế* xuống đúng tầng, ý định nghiệp vụ không đổi; nên minor luôn đúng), **reset `| **Status** | draft |` trong Metadata** (PRD vừa đổi sau khi duyệt → con dấu duyệt cũ hết hiệu lực, phải duyệt lại — đồng bộ với /refine-prd), thêm entry Changelog:
797
+
798
+ `| {new_version} | {today} | Auto-fix — {changelog_scope} |` — bảng phẳng + **rollover giữ 5 row gần nhất** (dồn dư sang `changelog/{TICKET-ID}-{prd-slug}.changelog.md`); xem quy ước đầy đủ ở refine-prd Phase 3.
799
+
800
+ **`{changelog_scope}` — BẮT BUỘC, dựng từ `uc_id` + `check_id` của các finding `status: applied`** *(contract: `bin/trace-schema.json` → `changelog_row_contract`)*:
801
+
802
+ 1. Gom các finding vừa áp theo `uc_id`. `uc_id: ""` → nhóm `PRD-global`.
803
+ 2. Mỗi nhóm thành một mệnh đề `{uc_id}: {tóm tắt các check}`, ngăn nhau bằng `;`.
804
+ 3. Nhóm mà **mọi** finding trong đó đều ở hàng **"Đổi hành vi? KHÔNG"** (Phase 2) → gắn hậu tố **`[no-behavior]`**. Nhóm có **dù chỉ một** finding "CÓ" → **KHÔNG** gắn.
805
+
806
+ ```
807
+ | 1.4 | 2026-08-19 | Auto-fix — UC5: banned-term (khách hàng→người mua); PRD-global: skeleton §4b [no-behavior] |
808
+ ```
809
+
810
+ → `/validate-traces`: **UC5 🟠 `PRD_DRIFT`** (đúng — có sửa thật) · **các UC còn lại ⓘ `PRD_STALE_REF`** (đúng — không đụng).
811
+
812
+ > **Vì sao BẮT BUỘC (G52).** Bản cũ ghi cứng `Auto-fix: applied {N} auto-fixable findings` — **không nêu UC nào**. `/validate-traces` Step 4 lọc 🟠-vs-ⓘ bằng cách hỏi *"row changelog có nêu UC này không"*, và một row không nêu gì thì rơi vào lưới an toàn *"mơ hồ → 🟠 cho **MỌI** UC"*. Nên sửa một từ trong UC5 của PRD 8 UC làm **cả 8 UC** ăn cờ 🟠 và route sang `/generate-bdd`.
813
+ >
814
+ > Lưới an toàn đó **đúng khi thiếu thông tin** — nhưng ở đây **không thiếu**: findings YAML có `uc_id` **bắt buộc** cho mỗi finding, và Phase 2 vừa đánh dấu `status: applied` cho từng cái. Lệnh **đang cầm** câu trả lời lúc nó ghi dòng đó, rồi vứt đi.
815
+ >
816
+ > Cái mất không phải 7 lần kiểm vô ích. `--fix` là đường rẻ nhất trong lane PO nên nó chạy nhiều nhất; sau vài sprint `PRD_DRIFT` sáng thường trực, **người đọc học cách bỏ qua, rồi lần lệch THẬT cũng bị bỏ qua cùng** — chính câu `/validate-traces` Step 4 dùng để biện minh cho bộ lọc.
817
+ >
818
+ > **Cách làm đúng đã có sẵn ngay dưới đây, ở nhánh BDD:** *"tăng `sc_version` của **đúng scenario đó**… Scenario không bị sửa → **giữ nguyên**… bump vô cớ tạo `DRIFT` giả và làm cờ mất giá trị"*. Nhánh BDD phân loại check theo *có đổi thân scenario hay không* rồi chỉ đánh dấu đơn vị bị đụng. Ba bước trên là **đúng cách đó**, áp cho PRD.
791
819
  - **BDD**: nếu ≥1 finding được áp dụng → tăng `@trace.bdd_version` lên 0.1, **reset `# @trace.status: draft`** trong header (BDD đổi sau khi duyệt → phải duyệt lại — đồng bộ với cơ chế reset draft của PRD)
792
820
  - **BDD — `@trace.sc_version` theo từng scenario (BẮT BUỘC):** với **mỗi scenario có ≥1 finding được áp dụng làm đổi thân nó** — R3 (diễn đạt lại step), R7 (thay giá trị cụ thể), R9 (thêm cột data table), R10 (thêm Note), B6 (thêm `And` side-effect), B2 (đổi tên entity/field trong step/table) — tăng `# @trace.sc_version` của **đúng scenario đó** lên 0.1. Scenario không bị sửa → **giữ nguyên**.
793
821
  - Đây là tín hiệu DUY NHẤT cho `/validate-traces` biết code của SC đó đã lỗi thời (`spec_ver != gen_ver` → `DRIFT`). `bdd_version` ở cấp file không đủ phân giải để biết SC nào cần regen.
@@ -808,6 +836,9 @@ Còn pending (cần quyết định của con người): {N}
808
836
  - F00X [{severity}] {tóm tắt finding} ← mở file findings trong Review Board
809
837
 
810
838
  {If PRD}: Version bumped: {old} → {new} | Status: reset về draft (cần duyệt lại)
839
+ {If PRD}: Changelog : | {new} | {today} | {changelog_scope} |
840
+ ↳ UC sẽ hiện 🟠 PRD_DRIFT: {UC5} · UC ở lại ⓘ STALE_REF: {UC1-4, UC6-8}
841
+ (nhóm [no-behavior] và UC không đụng → KHÔNG cần /generate-bdd)
811
842
  {If BDD}: bdd_version: {old} → {new} | @trace.status: reset về draft (cần duyệt lại)
812
843
  {If BDD, chỉ khi có ≥1 SC bump}: sc_version: {UC-ID}-SC2 1.0→1.1, {UC-ID}-SC5 1.2→1.3
813
844
  ↳ {n} SC này sẽ hiện DRIFT ở /validate-traces → /generate-code {feature-file} để sinh lại
@@ -862,7 +893,8 @@ Với mỗi finding `accepted`/`modified` sau khi áp xong → đặt `status: "
862
893
  | P4 (Structure) | Thêm section/metadata field còn thiếu (row Status vắng → thêm mặc định `draft`) |
863
894
  | P5 (Custom) | Áp dụng như nêu trong suggestion/note |
864
895
 
865
- → Sau khi áp dụng, bump version PRD (minor), **reset `| **Status** | draft |` trong Metadata** (PRD vừa đổi sau khi duyệt → phải duyệt lại — đồng bộ với /refine-prd), thêm row Changelog (bảng phẳng, **kê UC/AC/BR bị ảnh hưởng**, + **rollover giữ 5 row gần nhất** dồn dư sang `changelog/` — xem quy ước ở refine-prd Phase 3), và ghi `applied_to_version: "{new_version}"` ở root level của findings (xem "Chọn full vs delta").
896
+ → Sau khi áp dụng, bump version PRD (minor), **reset `| **Status** | draft |` trong Metadata** (PRD vừa đổi sau khi duyệt → phải duyệt lại — đồng bộ với /refine-prd), thêm row Changelog `| {new_version} | {today} | {changelog_scope} |` (bảng phẳng + **rollover giữ 5 row gần nhất** dồn dư sang `changelog/` — xem quy ước ở refine-prd Phase 3), và ghi `applied_to_version: "{new_version}"` ở root level của findings (xem "Chọn full vs delta").
897
+ **`{changelog_scope}` dựng theo đúng 3 bước ở Phase 3 của `--fix`** — gom theo `uc_id`, mỗi nhóm một mệnh đề `{uc_id}: {mô tả}`, `uc_id: ""` → `PRD-global`. Ở Resume Mode phần lớn finding là loại **con người quyết** nên **đổi hành vi** — chỉ gắn `[no-behavior]` cho nhóm thuần cấu trúc (P4). **BR/AC phải đi KÈM UC sở hữu** (`UC3: sửa BR8`), không bao giờ đứng một mình: consumer khớp theo UC, nên `sửa BR8` trơ trọi làm UC3 bị xếp ⓘ trong khi BR8 vừa đổi (G53).
866
898
 
867
899
  **Với finding BDD:**
868
900
  | check_id | Làm gì |
@@ -892,6 +924,9 @@ Changes:
892
924
  - {tóm tắt change 2}
893
925
 
894
926
  {If PRD}: Version bumped: {old} → {new} | Status: reset về draft (cần duyệt lại)
927
+ {If PRD}: Changelog : | {new} | {today} | {changelog_scope} |
928
+ ↳ UC sẽ hiện 🟠 PRD_DRIFT: {UC5} · UC ở lại ⓘ STALE_REF: {UC1-4, UC6-8}
929
+ (nhóm [no-behavior] và UC không đụng → KHÔNG cần /generate-bdd)
895
930
  {If BDD}: bdd_version: {old} → {new} | @trace.status: reset về draft (cần duyệt lại)
896
931
  {If BDD, chỉ khi có ≥1 SC bump}: sc_version: {UC-ID}-SC2 1.0→1.1, {UC-ID}-SC5 1.2→1.3
897
932
  ↳ {n} SC này sẽ hiện DRIFT ở /validate-traces → /generate-code {feature-file} để sinh lại