@educa-corp/sdd-framework 0.6.0 → 0.7.0

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 (205) 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 +391 -30
  6. package/core/FRAMEWORK_VERSION +1 -1
  7. package/{commands/extend-prd.md → core/commands/amend-prd.md} +205 -173
  8. package/core/commands/dev-run-test.md +47 -9
  9. package/core/commands/extend-prd.md +39 -12
  10. package/core/commands/generate-bdd.md +43 -4
  11. package/core/commands/generate-code.md +33 -0
  12. package/core/commands/generate-tech-docs.md +34 -2
  13. package/core/commands/qc-run-test.md +29 -3
  14. package/core/commands/refine-prd.md +13 -2
  15. package/core/commands/review-context.md +43 -8
  16. package/core/commands/sync.md +105 -1
  17. package/core/commands/validate-traces.md +284 -11
  18. package/core/rules/workflow.md +34 -0
  19. package/core/steps/context-loader.md +26 -5
  20. package/core/templates/feature.template +1 -1
  21. package/docs/02-concepts/architecture.md +36 -0
  22. package/docs/04-reference/commands.md +148 -134
  23. package/docs/04-reference/trace-schema.md +39 -0
  24. package/docs/explain/02b-extend-prd.md +1 -1
  25. package/docs/explain/02c-amend-prd.md +152 -0
  26. package/docs/explain/28-sync.md +25 -0
  27. package/docs/explain/README.md +136 -135
  28. package/package.json +1 -8
  29. package/commands/debug.md +0 -529
  30. package/commands/debug.tmpl +0 -260
  31. package/commands/define-product.md +0 -438
  32. package/commands/define-product.tmpl +0 -225
  33. package/commands/dev-gen-test.md +0 -700
  34. package/commands/dev-gen-test.tmpl +0 -490
  35. package/commands/dev-run-test.md +0 -435
  36. package/commands/dev-run-test.tmpl +0 -225
  37. package/commands/dev-smoke-test.md +0 -374
  38. package/commands/dev-smoke-test.tmpl +0 -217
  39. package/commands/extend-prd.tmpl +0 -273
  40. package/commands/fix-bug.md +0 -519
  41. package/commands/fix-bug.tmpl +0 -197
  42. package/commands/generate-architecture.md +0 -354
  43. package/commands/generate-architecture.tmpl +0 -197
  44. package/commands/generate-bdd.md +0 -923
  45. package/commands/generate-bdd.tmpl +0 -590
  46. package/commands/generate-code.md +0 -859
  47. package/commands/generate-code.tmpl +0 -649
  48. package/commands/generate-design-spec.md +0 -737
  49. package/commands/generate-design-spec.tmpl +0 -524
  50. package/commands/generate-prd.md +0 -722
  51. package/commands/generate-prd.tmpl +0 -226
  52. package/commands/generate-spec-manifest.md +0 -321
  53. package/commands/generate-spec-manifest.tmpl +0 -164
  54. package/commands/generate-tech-docs.md +0 -920
  55. package/commands/generate-tech-docs.tmpl +0 -273
  56. package/commands/learn.md +0 -399
  57. package/commands/learn.tmpl +0 -130
  58. package/commands/map-testids.md +0 -238
  59. package/commands/map-testids.tmpl +0 -81
  60. package/commands/propose-scenario.md +0 -359
  61. package/commands/propose-scenario.tmpl +0 -202
  62. package/commands/qc-analyze.md +0 -269
  63. package/commands/qc-analyze.tmpl +0 -112
  64. package/commands/qc-design-test.md +0 -226
  65. package/commands/qc-design-test.tmpl +0 -69
  66. package/commands/qc-plan.md +0 -206
  67. package/commands/qc-plan.tmpl +0 -49
  68. package/commands/qc-report.md +0 -217
  69. package/commands/qc-report.tmpl +0 -60
  70. package/commands/qc-review.md +0 -210
  71. package/commands/qc-review.tmpl +0 -53
  72. package/commands/qc-run-test.md +0 -326
  73. package/commands/qc-run-test.tmpl +0 -116
  74. package/commands/refine-prd.md +0 -653
  75. package/commands/refine-prd.tmpl +0 -281
  76. package/commands/report-bug.md +0 -305
  77. package/commands/report-bug.tmpl +0 -148
  78. package/commands/review-code.md +0 -415
  79. package/commands/review-code.tmpl +0 -146
  80. package/commands/review-context.md +0 -902
  81. package/commands/review-context.tmpl +0 -530
  82. package/commands/review-tech-docs.md +0 -561
  83. package/commands/review-tech-docs.tmpl +0 -404
  84. package/commands/setup-ai-first.md +0 -602
  85. package/commands/setup-ai-first.tmpl +0 -450
  86. package/commands/sync.md +0 -430
  87. package/commands/sync.tmpl +0 -429
  88. package/commands/update-framework.md +0 -203
  89. package/commands/update-framework.tmpl +0 -202
  90. package/commands/validate-traces.md +0 -1077
  91. package/commands/validate-traces.tmpl +0 -920
  92. package/hooks/data-guard.js +0 -232
  93. package/hooks/settings.json +0 -19
  94. package/modules/android-compose/module.yaml +0 -13
  95. package/modules/android-compose/stack-profile.yaml +0 -57
  96. package/modules/angular/architecture-snippets/component-patterns.md +0 -187
  97. package/modules/angular/module.yaml +0 -6
  98. package/modules/angular/stack-profile.yaml +0 -38
  99. package/modules/context-engineering/architecture-snippets/context-design.md +0 -119
  100. package/modules/context-engineering/module.yaml +0 -9
  101. package/modules/context-engineering/stack-profile.yaml +0 -61
  102. package/modules/dotnet/architecture-snippets/clean-arch.md +0 -160
  103. package/modules/dotnet/module.yaml +0 -6
  104. package/modules/dotnet/stack-profile.yaml +0 -50
  105. package/modules/flutter/module.yaml +0 -14
  106. package/modules/flutter/stack-profile.yaml +0 -59
  107. package/modules/golang/architecture-snippets/domain-layout.md +0 -283
  108. package/modules/golang/module.yaml +0 -6
  109. package/modules/golang/stack-profile.yaml +0 -40
  110. package/modules/ios-swiftui/module.yaml +0 -13
  111. package/modules/ios-swiftui/stack-profile.yaml +0 -55
  112. package/modules/java-spring/architecture-snippets/layered-arch.md +0 -201
  113. package/modules/java-spring/module.yaml +0 -15
  114. package/modules/java-spring/stack-profile.yaml +0 -28
  115. package/modules/nextjs/architecture-snippets/app-router-patterns.md +0 -269
  116. package/modules/nextjs/module.yaml +0 -14
  117. package/modules/nextjs/stack-profile.yaml +0 -74
  118. package/modules/nuxt/module.yaml +0 -14
  119. package/modules/nuxt/stack-profile.yaml +0 -58
  120. package/modules/phaser-game/architecture-snippets/phaser-scene-patterns.md +0 -646
  121. package/modules/phaser-game/module.yaml +0 -15
  122. package/modules/phaser-game/stack-profile.yaml +0 -90
  123. package/modules/php-laravel/architecture-snippets/service-repository.md +0 -302
  124. package/modules/php-laravel/module.yaml +0 -15
  125. package/modules/php-laravel/stack-profile.yaml +0 -56
  126. package/modules/qc-playwright/stack-profile.yaml +0 -66
  127. package/modules/react/architecture-snippets/hooks-query-patterns.md +0 -254
  128. package/modules/react/module.yaml +0 -14
  129. package/modules/react/stack-profile.yaml +0 -63
  130. package/modules/react-native/module.yaml +0 -14
  131. package/modules/react-native/stack-profile.yaml +0 -56
  132. package/modules/vue/module.yaml +0 -14
  133. package/modules/vue/stack-profile.yaml +0 -65
  134. package/rules/data-protection.md +0 -80
  135. package/rules/workflow.md +0 -99
  136. package/skills/code/SKILL.md +0 -19
  137. package/skills/code/SKILL.tmpl +0 -19
  138. package/skills/debug/SKILL.md +0 -19
  139. package/skills/debug/SKILL.tmpl +0 -19
  140. package/skills/design-spec/SKILL.md +0 -11
  141. package/skills/design-spec/SKILL.tmpl +0 -11
  142. package/skills/discovery/SKILL.md +0 -14
  143. package/skills/discovery/SKILL.tmpl +0 -14
  144. package/skills/prd/SKILL.md +0 -19
  145. package/skills/prd/SKILL.tmpl +0 -19
  146. package/skills/qc/qa-analyst/DOC_GAPS.template.md +0 -63
  147. package/skills/qc/qa-analyst/acceptance-criteria.md +0 -60
  148. package/skills/qc/qa-analyst/business-rules.md +0 -59
  149. package/skills/qc/qa-analyst/data-flow.md +0 -64
  150. package/skills/qc/qa-analyst/spec-breakdown.md +0 -61
  151. package/skills/qc/qa-designer/e2e/journey.md +0 -41
  152. package/skills/qc/qa-designer/exploratory/charter.md +0 -68
  153. package/skills/qc/qa-designer/exploratory/explore-to-functional.md +0 -43
  154. package/skills/qc/qa-designer/functional/api.md +0 -45
  155. package/skills/qc/qa-designer/functional/gui-feature.md +0 -46
  156. package/skills/qc/qa-designer/functional/gui-screen.md +0 -52
  157. package/skills/qc/qa-designer/integration/api.md +0 -42
  158. package/skills/qc/qa-designer/integration/db.md +0 -39
  159. package/skills/qc/qa-designer/integration/gui.md +0 -40
  160. package/skills/qc/qa-designer/integration/kafka.md +0 -40
  161. package/skills/qc/qa-designer/non-functional.md +0 -40
  162. package/skills/qc/qa-planner/test-plan.md +0 -120
  163. package/skills/qc/qa-reviewer/script/e2e.md +0 -87
  164. package/skills/qc/qa-reviewer/script/exploratory.md +0 -45
  165. package/skills/qc/qa-reviewer/script/functional.md +0 -101
  166. package/skills/qc/qa-reviewer/script/integration.md +0 -91
  167. package/skills/qc/qa-reviewer/script/non-functional.md +0 -126
  168. package/skills/qc/qa-reviewer/test-case/e2e.md +0 -73
  169. package/skills/qc/qa-reviewer/test-case/exploratory.md +0 -43
  170. package/skills/qc/qa-reviewer/test-case/functional.md +0 -76
  171. package/skills/qc/qa-reviewer/test-case/integration.md +0 -69
  172. package/skills/qc/qa-reviewer/test-case/non-functional.md +0 -73
  173. package/skills/qc/qa-runner/e2e.md +0 -49
  174. package/skills/qc/qa-runner/exploratory/session.md +0 -36
  175. package/skills/qc/qa-runner/functional/api.md +0 -35
  176. package/skills/qc/qa-runner/functional/gui-feature.md +0 -51
  177. package/skills/qc/qa-runner/functional/gui-screen.md +0 -55
  178. package/skills/qc/qa-runner/integration.md +0 -47
  179. package/skills/qc/qa-runner/non-functional.md +0 -49
  180. package/skills/qc/qa-runner/report/report.md +0 -37
  181. package/skills/setup-ai-first/SKILL.md +0 -19
  182. package/skills/setup-ai-first/SKILL.tmpl +0 -19
  183. package/skills/spec/SKILL.md +0 -19
  184. package/skills/spec/SKILL.tmpl +0 -19
  185. package/skills/test/SKILL.md +0 -18
  186. package/skills/test/SKILL.tmpl +0 -18
  187. package/steps/business-language.md +0 -56
  188. package/steps/capture-lesson.md +0 -112
  189. package/steps/context-loader.md +0 -406
  190. package/steps/gate.md +0 -151
  191. package/steps/report-footer.md +0 -125
  192. package/steps/review-fanout.md +0 -159
  193. package/steps/spawn-agent.md +0 -129
  194. package/steps/trace-mirror.md +0 -53
  195. package/templates/README.md +0 -70
  196. package/templates/architecture.template.md +0 -394
  197. package/templates/ci/trace-gate.yml +0 -146
  198. package/templates/design-spec.template.md +0 -217
  199. package/templates/feature.template +0 -123
  200. package/templates/hooks/pre-push +0 -61
  201. package/templates/platform-guide.template.md +0 -145
  202. package/templates/prd.template.md +0 -283
  203. package/templates/product-definition.template.md +0 -188
  204. package/templates/project-context.yaml +0 -212
  205. 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`
@@ -375,9 +375,37 @@ Chỉ cần kiểm tra trạng thái đã phân giải:
375
375
  | `active_service` đã phân giải thành path service | Tiếp tục với `active_module` đã set. |
376
376
  | `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
377
  | `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.) |
378
+ | `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. |
379
+ | `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
380
  | Single-service (không có section `services`) | `active_module = tech_stack.module` (đã set ở Bước 6.5). Tiếp tục. |
380
381
 
382
+ #### `unrouted` — vì sao KHÔNG chặn ở đây *(G51)*
383
+
384
+ PRD và BDD là artifact **nghiệp vụ**. PO biết `domain` (auth, payment) và biết `platform`
385
+ (*"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,
386
+ và ở feature đầu tiên của một domain mới thì **chưa ai quyết**.
387
+
388
+ Chặn BDD vì lý do đó là đặt cổng **sai phase**: nó chặn phase KHÔNG CẦN biết, trong khi
389
+ `/generate-code` — phase **buộc phải** biết mới ghi được file — mới là chỗ đúng để chặn.
390
+
391
+ **Việc cần làm khi `unrouted`:**
392
+ 1. `@trace.service: unrouted` vào header `.feature` (đừng để trống — cột 23 sẽ mất thông tin)
393
+ 2. **`active_module` chưa biết** ⇒ hỏi người dùng `platform` trực tiếp (`web`/`app`/`system`) thay
394
+ 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:
395
+ `web` → *clicks* · `app` → *taps* · `system` → *calls the API*.
396
+ 3. In ⚠️ vào report:
397
+ ```
398
+ ⚠️ service: unrouted — chưa có mapping cho domain "{domain}"{ platform "{platform}"} trong
399
+ services: của project-context.yaml.
400
+ BDD đã sinh xong và ĐÚNG — đây là việc của architect, không phải của bạn.
401
+ Architect thêm mapping → /validate-traces tự nâng unrouted → path, KHÔNG cần chạy lại lệnh này.
402
+ /generate-code sẽ DỪNG cho tới khi có mapping (nó cần biết ghi vào repo nào).
403
+ ```
404
+
405
+ > **Vì sao không cần chạy lại `/generate-bdd`:** `/validate-traces` đọc lại `services:` **mỗi lần
406
+ > chạy** và nâng `unrouted` → path khi mapping xuất hiện — cùng cách nó đã làm với `spec_ver`.
407
+ > Sổ **tự lành**.
408
+
381
409
  ### Phân giải `active_platform` (umbrella mode)
382
410
 
383
411
  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`.
@@ -505,7 +533,18 @@ Trước khi sinh, kiểm tra các file `.feature` có sẵn cho PRD này:
505
533
  F — gen lại toàn bộ scenario
506
534
  N — huỷ
507
535
  ```
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).
536
+ 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 )*:
537
+
538
+ 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:
539
+
540
+ | Tình trạng row trong khoảng | Khuyến nghị |
541
+ |---|---|
542
+ | **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 |
543
+ | UC của target **có** trong `affected_ucs` | **Y** — cập nhật đúng scenario của UC đó |
544
+ | 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 |
545
+ | 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 |
546
+
547
+ > **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
548
 
510
549
  ---
511
550
 
@@ -602,7 +641,7 @@ Với mỗi UC, ghi vào path trên và set `# @trace.platform: {active_platform
602
641
  # @trace.revision: 1 ← field tĩnh; version theo dõi bằng @trace.bdd_version
603
642
  # @trace.domain: <domain>
604
643
  # @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}
644
+ # @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
645
  # @trace.module: {active_module trong umbrella mode; "unknown" trong spec repo mode}
607
646
  # @trace.status: draft
608
647
  # @trace.author: AI-generated
@@ -816,7 +855,7 @@ sc_id\tsc_title\tspec_ver\tgen_ver\timplemented_by\ttest_count\ttest_classes\tde
816
855
  | `fe_phase` | `—` (set bởi `/generate-code --phase` khi FE implement) |
817
856
  | `status` | `UNTRACKED` |
818
857
  | `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. |
858
+ | `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
859
  | `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
860
 
822
861
  ## 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.
@@ -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
  ════════════════════════════════════════════════════════════════════════════
@@ -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
@@ -180,6 +180,90 @@ Nếu người đọc là PO/Dev, thêm một dòng nudge:
180
180
 
181
181
  ---
182
182
 
183
+ ## Step 1e — Spec delta: **tài liệu nào vừa đổi** *(hai mốc, hai câu hỏi khác nhau)*
184
+
185
+ *Câu hỏi số MỘT của dev sau mỗi lần sync là "tài liệu của phần tôi đang làm có đổi không?". Step này trả lời nó. Range `{old_sha}..{new_sha}` đã thu ở Step 1c — không phát sinh fetch.*
186
+
187
+ > **Vì sao step này tồn tại (GAPS-v4 G56).** Bản cũ diff **đúng ba** đường dẫn ở Step 1d —
188
+ > `feedback/bug-reports/`, `feedback/bdd-proposals/`, `feedback/prd-change-requests/` — và
189
+ > `specs/` **không có trong danh sách**. Tức `/sync` hỏi *"có góp ý gì mới không"* (đúng và hữu ích)
190
+ > rồi bỏ qua **chính tài liệu mà mọi lệnh downstream đọc**: PRD · BDD · tech-doc · design-spec.
191
+ >
192
+ > Nó **đang cầm sẵn câu trả lời**: Step 1c vừa thu cả hai đầu SHA, và Step 1d đã chạy một `git diff`.
193
+ > Thêm `specs/` là thêm **một tham số đường dẫn**.
194
+ >
195
+ > Thay vào đó, dòng `Next` in cứng `/validate-traces (full coverage check)` — **y hệt nhau** dù 0 file
196
+ > đổi hay 12 file đổi. Một lời nhắc không bao giờ thay đổi thì **không mang thông tin**, nên bị lướt.
197
+ > Đây đúng lập luận `gate.md` Bước 3b dùng để cắt CHECKPOINT xuống hai dòng khi mọi thứ sạch —
198
+ > *"cổng luôn in ra một bảng giống hệt nhau … nên `Y` thành phản xạ và cổng hỏng âm thầm"*. Nguyên
199
+ > tắc đó đã áp cho `gate`; đây là chỗ nó còn thiếu.
200
+ >
201
+ > Và tệ hơn: lệnh duy nhất được gợi ý là lệnh **đắt nhất** (quét cả repo). Nên con đường duy nhất
202
+ > được chỉ là con đường người ta sẽ không đi. Step này làm nó **có scope**.
203
+ >
204
+ > **Đây KHÔNG phải một detector bị hỏng** — mọi detector đều hoạt động đúng. Đây là một **công tắc
205
+ > bị thiếu**: không ai biết là cần bật.
206
+
207
+ Bỏ qua **im lặng** nếu `{paths.specs_dir}` không tồn tại trong REPO (chọn REPO + range theo đúng quy tắc Step 1d).
208
+
209
+ ### 1e-A — Đổi gì kể từ lần **PULL** trước
210
+
211
+ ```bash
212
+ git -C {REPO} diff --name-status {old_sha}..{new_sha} -- specs/
213
+ ```
214
+
215
+ Nhóm kết quả theo **feature-package** (`specs/{domain}/{prd-slug}/`), và với mỗi file đổi, đọc **nhãn version ở hai đầu** — thứ dev cần không phải tên file mà là *nhãn đã nhảy chưa*:
216
+
217
+ | Loại file | Nhãn đọc ở đâu | Bản cũ đọc bằng |
218
+ |---|---|---|
219
+ | PRD (`.md` ở gốc package) | Metadata `\| **Version** \|` + row `# Change Log` đầu bảng | `git -C {REPO} show {old_sha}:{path}` |
220
+ | `.feature` | `# @trace.bdd_version` | như trên |
221
+ | `tech-docs/*-tech-design.md` | `@trace.revision` | như trên |
222
+ | `design-spec/*.md` | `\| **Version** \|` | như trên |
223
+
224
+ **Cap có công bố:** > **20** file đổi → **bỏ** phần đọc nhãn (mỗi file là một `git show`), chỉ liệt kê package + số file, và **in rõ là đã cap**:
225
+ `ⓘ {n} file đổi (> 20) — bỏ phần so nhãn version để không làm chậm sync. Chạy /validate-traces để có bản đầy đủ.`
226
+ *(Luật framework: cap thì phải nói ra. Một giới hạn im lặng đọc như "đã phủ hết" trong khi không phải.)*
227
+
228
+ ### 1e-B — Đổi gì kể từ lần **AUDIT** gần nhất ⭐
229
+
230
+ *Đây là phần 1e-A **không** trả lời được, và là câu hỏi đúng hơn.*
231
+
232
+ Diff `{old_sha}..{new_sha}` **reset mỗi lần pull**. Pull thứ Hai, thứ Ba, thứ Tư mà không audit lần nào → đến thứ Năm cái thấy được chỉ là delta của **một ngày**, không phải nợ đã tích.
233
+
234
+ Đọc mốc **`spec_baseline`** trong `{living_docs_dir}/trace-report.json` — khối do `/validate-traces` Step 6b ghi (contract: `bin/trace-schema.json` → `spec_edit_detection`). Mỗi entry: `prd_path` · `sha_at_audit` · `version_at_audit`.
235
+
236
+ *Phân giải `living_docs_dir` bằng đúng quy tắc một dòng của Step 5: `{spec_source}/.living-docs` nếu `setup.spec_source` được set, else `.living-docs` ở gốc.*
237
+
238
+ Với mỗi PRD có entry: so `Version` **hiện tại** với `version_at_audit`.
239
+
240
+ | Điều kiện | In gì |
241
+ |---|---|
242
+ | Khác nhau | PRD này đã đổi kể từ lần audit — vào danh sách ⚠️ |
243
+ | Bằng nhau | Bỏ qua *(và `/validate-traces` Step 3.9 sẽ lo ca "nội dung đổi mà nhãn không đổi" — không phải việc của step này)* |
244
+ | Không có `trace-report.json`, hoặc khối `spec_baseline` vắng | In `ⓘ Chưa có mốc audit — chạy /validate-traces một lần để Step 1e-B có hiệu lực từ lần sau.` rồi bỏ qua |
245
+
246
+ **KHÔNG ghi gì cả.** Step này chỉ **đọc** mốc; chủ sở hữu của `spec_baseline` là `/validate-traces` Step 6b. `/sync` ghi mốc sẽ làm mốc audit trượt theo mỗi lần pull — tức phá đúng thứ nó đang dùng.
247
+
248
+ ### Xuất
249
+
250
+ ```
251
+ 📄 Spec đã đổi (pulled this sync):
252
+ payment/create-invoice PRD v1.2 → v1.4
253
+ changelog: UC2: sửa BR5; UC7 mới
254
+ bdd/web/PAY01-UC2.feature (bdd_version 1.3 → 1.4)
255
+ user/create-account tech-docs/USR01-tech-design.md (revision 3 → 4)
256
+ (hoặc: 📄 Spec: không đổi trong lần pull này)
257
+
258
+ ⚠️ 3 PRD đã đổi kể từ lần /validate-traces gần nhất (KHÔNG chỉ lần pull này):
259
+ payment/create-invoice audit tại v1.2 · giờ v1.4
260
+ user/create-account audit tại v2.0 · giờ v2.1
261
+ order/bulk-export audit tại v1.0 · giờ v1.3
262
+ → /validate-traces {các PRD trên}
263
+ (hoặc: ✅ Mọi PRD đã được audit ở version hiện tại)
264
+ ```
265
+
266
+ ---
183
267
  ## Step 2 — Post-sync State Check
184
268
 
185
269
  Chạy `git status --short` và kiểm tra entry submodule bị modified (dòng bắt đầu bằng ` M` mà path khớp một submodule).
@@ -396,6 +480,22 @@ Umbrella state
396
480
  ⚠️ Pointer changed: git add {spec_source} && git commit -m "chore: sync specs"
397
481
  (or: ✅ Clean — no commit needed)
398
482
 
483
+ Spec đã đổi (pulled this sync) ← Step 1e-A
484
+ 📄 payment/create-invoice PRD v1.2 → v1.4
485
+ changelog: UC2: sửa BR5; UC7 mới
486
+ bdd/web/PAY01-UC2.feature (bdd_version 1.3 → 1.4)
487
+ user/create-account tech-docs/USR01-tech-design.md (revision 3 → 4)
488
+ (hoặc: 📄 Spec: không đổi trong lần pull này)
489
+ (hoặc: ⓘ 34 file đổi (> 20) — đã bỏ phần so nhãn version cho nhanh)
490
+
491
+ Nợ audit đã tích (KHÔNG chỉ lần pull này) ← Step 1e-B
492
+ ⚠️ 3 PRD đã đổi kể từ lần /validate-traces gần nhất:
493
+ payment/create-invoice audit tại v1.2 · giờ v1.4
494
+ user/create-account audit tại v2.0 · giờ v2.1
495
+ order/bulk-export audit tại v1.0 · giờ v1.3
496
+ (hoặc: ✅ Mọi PRD đã được audit ở version hiện tại)
497
+ (hoặc: ⓘ Chưa có mốc audit — chạy /validate-traces một lần để có hiệu lực từ lần sau)
498
+
399
499
  Tester feedback (pulled this sync)
400
500
  📥 1 bug report: BUG-20260608-01 FT-001 [Code]
401
501
  1 proposal: FT-001-trailing-spaces → AC2 (pending review)
@@ -426,5 +526,9 @@ Spec Manifest
426
526
  ---
427
527
  Status : ✅ Complete | ⚠️ Warnings
428
528
  Output Artifacts: updated .trace-mirror/ (panel mirror), spec-manifest.yaml
429
- Next : /validate-traces (full coverage check) | /generate-code {UC-ID} (start coding)
529
+ (Step 1e chỉ ĐỌC không bao giờ ghi spec_baseline; chủ sở hữu là /validate-traces Step 6b)
530
+ Next : {phụ thuộc kết quả Step 1e — KHÔNG in một hằng số}
531
+ • có PRD trong danh sách 1e-B → /validate-traces {các PRD đó}
532
+ • 1e-B sạch, 1e-A có đổi → /generate-code {UC-ID} cho phần vừa đổi
533
+ • cả hai sạch → ✅ Spec khớp audit — không cần audit lại
430
534
  ```