@educa-corp/sdd-framework 0.4.0 → 0.4.2

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 (123) hide show
  1. package/bin/build.js +9 -0
  2. package/bin/index.js +115 -4
  3. package/bin/self-check.js +236 -0
  4. package/bin/trace-schema.json +692 -0
  5. package/commands/debug.md +16 -10
  6. package/commands/define-product.md +16 -10
  7. package/commands/dev-gen-test.md +16 -10
  8. package/commands/dev-run-test.md +18 -11
  9. package/commands/dev-run-test.tmpl +2 -1
  10. package/commands/dev-smoke-test.md +16 -10
  11. package/commands/fix-bug.md +71 -13
  12. package/commands/fix-bug.tmpl +29 -3
  13. package/commands/generate-architecture.md +16 -10
  14. package/commands/generate-bdd.md +118 -35
  15. package/commands/generate-bdd.tmpl +89 -15
  16. package/commands/generate-code.md +49 -13
  17. package/commands/generate-code.tmpl +33 -3
  18. package/commands/generate-design-spec.md +16 -10
  19. package/commands/generate-prd.md +16 -10
  20. package/commands/generate-spec-manifest.md +16 -10
  21. package/commands/generate-tech-docs.md +19 -13
  22. package/commands/generate-tech-docs.tmpl +2 -2
  23. package/commands/learn.md +16 -10
  24. package/commands/map-testids.md +16 -10
  25. package/commands/propose-scenario.md +36 -12
  26. package/commands/propose-scenario.tmpl +20 -2
  27. package/commands/qc-analyze.md +16 -10
  28. package/commands/qc-design-test.md +16 -10
  29. package/commands/qc-plan.md +16 -10
  30. package/commands/qc-report.md +16 -10
  31. package/commands/qc-review.md +16 -10
  32. package/commands/qc-run-test.md +38 -12
  33. package/commands/qc-run-test.tmpl +22 -2
  34. package/commands/refine-prd.md +16 -10
  35. package/commands/report-bug.md +16 -10
  36. package/commands/review-code.md +56 -12
  37. package/commands/review-code.tmpl +40 -2
  38. package/commands/review-context.md +58 -14
  39. package/commands/review-context.tmpl +42 -4
  40. package/commands/review-tech-docs.md +47 -12
  41. package/commands/review-tech-docs.tmpl +31 -2
  42. package/commands/setup-ai-first.md +23 -14
  43. package/commands/setup-ai-first.tmpl +7 -4
  44. package/commands/sync.md +3 -2
  45. package/commands/update-framework.md +40 -2
  46. package/commands/update-framework.tmpl +37 -0
  47. package/commands/validate-traces.md +165 -18
  48. package/commands/validate-traces.tmpl +149 -8
  49. package/core/FRAMEWORK_VERSION +1 -1
  50. package/core/README.md +56 -0
  51. package/core/commands/debug.md +16 -10
  52. package/core/commands/define-product.md +16 -10
  53. package/core/commands/dev-gen-test.md +16 -10
  54. package/core/commands/dev-run-test.md +18 -11
  55. package/core/commands/dev-smoke-test.md +16 -10
  56. package/core/commands/fix-bug.md +71 -13
  57. package/core/commands/generate-architecture.md +16 -10
  58. package/core/commands/generate-bdd.md +118 -35
  59. package/core/commands/generate-code.md +49 -13
  60. package/core/commands/generate-design-spec.md +16 -10
  61. package/core/commands/generate-prd.md +16 -10
  62. package/core/commands/generate-spec-manifest.md +16 -10
  63. package/core/commands/generate-tech-docs.md +19 -13
  64. package/core/commands/learn.md +16 -10
  65. package/core/commands/map-testids.md +16 -10
  66. package/core/commands/propose-scenario.md +36 -12
  67. package/core/commands/qc-analyze.md +16 -10
  68. package/core/commands/qc-design-test.md +16 -10
  69. package/core/commands/qc-plan.md +16 -10
  70. package/core/commands/qc-report.md +16 -10
  71. package/core/commands/qc-review.md +16 -10
  72. package/core/commands/qc-run-test.md +38 -12
  73. package/core/commands/refine-prd.md +16 -10
  74. package/core/commands/report-bug.md +16 -10
  75. package/core/commands/review-code.md +56 -12
  76. package/core/commands/review-context.md +58 -14
  77. package/core/commands/review-tech-docs.md +47 -12
  78. package/core/commands/setup-ai-first.md +23 -14
  79. package/core/commands/sync.md +3 -2
  80. package/core/commands/update-framework.md +40 -2
  81. package/core/commands/validate-traces.md +165 -18
  82. package/core/modules/android-compose/stack-profile.yaml +1 -1
  83. package/core/modules/flutter/stack-profile.yaml +1 -1
  84. package/core/modules/ios-swiftui/stack-profile.yaml +1 -1
  85. package/core/modules/java-spring/stack-profile.yaml +1 -1
  86. package/core/modules/nextjs/stack-profile.yaml +1 -1
  87. package/core/modules/nuxt/stack-profile.yaml +1 -1
  88. package/core/modules/phaser-game/stack-profile.yaml +1 -1
  89. package/core/modules/php-laravel/stack-profile.yaml +1 -1
  90. package/core/modules/qc-playwright/stack-profile.yaml +1 -1
  91. package/core/modules/react/stack-profile.yaml +1 -1
  92. package/core/modules/react-native/stack-profile.yaml +1 -1
  93. package/core/modules/vue/stack-profile.yaml +1 -1
  94. package/core/rules/workflow.md +11 -0
  95. package/core/steps/gate.md +13 -8
  96. package/core/steps/report-footer.md +3 -2
  97. package/core/templates/README.md +47 -0
  98. package/core/templates/feature.template +13 -10
  99. package/core/templates/project-context.yaml +26 -14
  100. package/core/templates/tech-design.template.md +1 -1
  101. package/docs/02-concepts/traceability.md +29 -6
  102. package/docs/04-reference/trace-schema.md +128 -37
  103. package/modules/android-compose/stack-profile.yaml +1 -1
  104. package/modules/flutter/stack-profile.yaml +1 -1
  105. package/modules/ios-swiftui/stack-profile.yaml +1 -1
  106. package/modules/java-spring/stack-profile.yaml +1 -1
  107. package/modules/nextjs/stack-profile.yaml +1 -1
  108. package/modules/nuxt/stack-profile.yaml +1 -1
  109. package/modules/phaser-game/stack-profile.yaml +1 -1
  110. package/modules/php-laravel/stack-profile.yaml +1 -1
  111. package/modules/qc-playwright/stack-profile.yaml +1 -1
  112. package/modules/react/stack-profile.yaml +1 -1
  113. package/modules/react-native/stack-profile.yaml +1 -1
  114. package/modules/vue/stack-profile.yaml +1 -1
  115. package/package.json +50 -49
  116. package/rules/workflow.md +11 -0
  117. package/scripts/migrate-bdd-platform.js +286 -0
  118. package/steps/gate.md +13 -8
  119. package/steps/report-footer.md +3 -2
  120. package/templates/README.md +47 -0
  121. package/templates/feature.template +13 -10
  122. package/templates/project-context.yaml +26 -14
  123. package/templates/tech-design.template.md +1 -1
@@ -45,23 +45,23 @@ Hiển thị và chờ phản hồi:
45
45
  ```
46
46
  ⚙️ MODEL CHECK
47
47
  ──────────────────────────────────────────────────────────────────
48
- Recommended : claude-opus-4 (hoặc model Opus mới nhất)
48
+ Recommended : model Opus mới nhất
49
49
  Why needed : Phân tích spec, review kiến trúc, sinh code đòi hỏi
50
- suy luận sâu. Model nhỏ hơn dễ bỏ sót edge case.
50
+ suy luận sâu. Model nhỏ hơn (Haiku/Sonnet) dễ bỏ sót edge case.
51
51
 
52
52
  Cách đổi trong Claude Code:
53
- SettingsModel chọn "claude-opus"
54
- • hoặc: /modelchọn claude-opus
53
+ /modelchọn model Opus
54
+ • hoặc: SettingsModel
55
55
 
56
- Đang chạy claude-opus?
57
- Y — đúng, đang dùng claude-opus → tiếp tục
56
+ Đang chạy một model Opus?
57
+ Y — đúng → tiếp tục
58
58
  S — bỏ qua kiểm tra (tôi chấp nhận rủi ro chất lượng thấp hơn với model hiện tại)
59
59
  ──────────────────────────────────────────────────────────────────
60
60
  ```
61
61
 
62
62
  - "Y" → tiếp tục sang Bước 1.
63
63
  - "S" → tiếp tục sang Bước 1 (người dùng chấp nhận rủi ro, thêm ⚠️ vào report cuối).
64
- - "N" hoặc bất kỳ giá trị nào khác → **DỪNG.** Xuất: "Vui lòng chuyển sang claude-opus rồi chạy lại lệnh này."
64
+ - "N" hoặc bất kỳ giá trị nào khác → **DỪNG.** Xuất: "Vui lòng chuyển sang một model Opus (`/model`) rồi chạy lại lệnh này."
65
65
 
66
66
  ## Bước 1 — Xác định Target File
67
67
 
@@ -70,7 +70,12 @@ Hiển thị và chờ phản hồi:
70
70
  2. Nếu `$ARGUMENTS` là một **UC-ID / ticket ID / tên rút gọn** (không có path) → phân giải thành file bằng cách glob theo bố cục feature-package. `{prd-slug}` lúc này **chưa biết**, nên dùng wildcard `*` cho segment đó, và `**` đệ quy dưới `bdd/` để phủ hết các thư mục con theo platform (`bdd/web/`, `bdd/app/`, `bdd/system/`):
71
71
  - **Lệnh BDD** (target là `.feature`): `{specs_dir}/{domain}/*/bdd/**/{UC-ID}*.feature` — hoặc `{specs_dir}/*/*/bdd/**/{UC-ID}*.feature` nếu domain cũng chưa biết. Nếu lệnh ngụ ý một platform/scope cụ thể (vd: system tech-doc cần BDD `system/`), ưu tiên kết quả trong thư mục con platform đó.
72
72
  - **Lệnh PRD** (target là file PRD `{TICKET-ID}-{prd-slug}.md` — file `.md` duy nhất ở gốc feature folder, cạnh `bdd/`): `{specs_dir}/{domain}/*/{TICKET-ID}*.md` nếu biết TICKET-ID; nếu không, `{specs_dir}/{domain}/*/*.md` (khớp feature folder có id tương ứng), hoặc `{specs_dir}/*/*/*.md` nếu domain cũng chưa biết. *(Glob `*/*.md` ở cấp gốc folder chỉ khớp PRD — tech-docs/design-spec `.md` nằm sâu hơn trong thư mục con.)*
73
- - **Lệnh tech-docs**: `{specs_dir}/{domain}/*/tech-docs/{UC-ID}*-tech-design*.md`.
73
+ - **Lệnh tech-docs** — target là tech-doc **gộp cấp PRD** `{TICKET-ID}-tech-design.md` (MỘT doc phủ nhiều UC; danh sách UC nằm ở `@trace.ucs`). Vì tên file mang `{TICKET-ID}` chứ **không** mang `{UC-ID}`, phải tách trước khi glob:
74
+ - `$ARGUMENTS` là **UC-ID** (`{TICKET-ID}-UC{N}`) → lấy `{TICKET-ID}` = phần **trước** `-UC`, rồi glob `{specs_dir}/{domain}/*/tech-docs/{TICKET-ID}-tech-design.md`.
75
+ - `$ARGUMENTS` là **TICKET-ID** → glob trực tiếp như trên.
76
+ - Chưa biết domain → `{specs_dir}/*/*/tech-docs/{TICKET-ID}-tech-design.md`.
77
+ - Vẫn không khớp → glob rộng `{specs_dir}/*/*/tech-docs/*tech-design*.md` rồi liệt kê để người dùng chọn.
78
+ *(Đừng glob `{UC-ID}*-tech-design*.md` — nó nở thành `FT-001-UC1*-tech-design*.md` và **không bao giờ** khớp `FT-001-tech-design.md`.)*
74
79
  - **Lệnh design-spec**: `{specs_dir}/{domain}/*/design-spec/{TICKET-ID}*.md`.
75
80
 
76
81
  Khi một file khớp: đặt nó làm target **và** ghi lại `domain` + `prd_slug` từ path của nó (theo quy tắc trích xuất trong `context-loader.md` Bước 1 — `prd_slug` = segment đầu tiên sau `{specs_dir}/{domain}/`). Mọi path mà lệnh đọc/ghi về sau (BDD/tech-docs/design-spec/trace cùng cấp) đều dùng **`prd_slug` đã phân giải đó**, nên tất cả artifact nằm chung một feature package. Nếu nhiều file khớp (vd: nhiều platform), chọn theo platform/scope của lệnh hoặc liệt kê ra và hỏi.
@@ -738,6 +743,7 @@ Gợi ý lệnh kế tiếp hợp lý theo phase của workflow:
738
743
  | /qc-run-test | `/qc-report {UC-ID}` rồi `/qc-review {UC-ID}` (review script) |
739
744
  | /qc-review (script) | `/qc-report {UC-ID}` rồi tạo PR nếu APPROVED |
740
745
  | /qc-report | `/validate-traces {UC-ID}` để làm mới Living Docs (qc_status) |
746
+ | /map-testids | `/qc-design-test {UC-ID}` (QC dựng Page Object từ contract §4.5.6 vừa ghi) |
741
747
  | /generate-tech-docs | `/review-tech-docs {tech-design-file}` |
742
748
  | /review-tech-docs | `/generate-code {feature-file}` nếu APPROVED; sửa doc nếu NEEDS_FIX |
743
749
  | /generate-code | Lần gen đầu → `/review-code {UC-ID}`; gen lại → `/dev-gen-test {UC-ID}` |
@@ -746,8 +752,8 @@ Gợi ý lệnh kế tiếp hợp lý theo phase của workflow:
746
752
  | /dev-run-test (failing) | `/fix-bug {ticket-id}` hoặc `/debug {error}` |
747
753
  | /review-code | `/dev-smoke-test {UC-ID}` hoặc tạo PR |
748
754
  | /dev-smoke-test | Tạo PR và link tới ticket |
749
- | /validate-traces | DRIFT/UNTRACKED → `/generate-code {UC-ID}`; GAP → `/dev-gen-test {UC-ID}`; tất cả OK tạo PR |
750
- | /fix-bug | Tạo PR link tới ticket |
755
+ | /validate-traces | **Cờ 🔴 trước (chặn PR):** SEAM_UNWIRED → nối binding sang class thật, xoá/thay stub · STUB_UNRESOLVED → `/generate-code {owner_uc}` (lấp logic tại chỗ + xoá hàm song song) · ORPHANED/TRACE_ORPHAN → quyết định thủ công (xoá code+test, đưa scenario trở lại `.feature`, hoặc sửa `sc_id` của tag). **Rồi:** DRIFT/UNTRACKED → `/generate-code {UC-ID}` · BDD_DRIFT → `/generate-code {feature-file}` · tech-doc lỗi thời vs BDD → `/generate-tech-docs` → `/review-tech-docs` · PRD drift → `/generate-bdd {prd-file}` · GAP → `/dev-gen-test {UC-ID}`. **Chỉ tạo PR khi mọi cờ 🔴 = 0** |
756
+ | /fix-bug | `/dev-run-test {UC-ID}` (dev_selftest vừa reset về not_run) → tạo PR; nếu fix một `{BUG-ID}` → QC chạy `/qc-run-test {UC-ID}` để verify + đóng bug |
751
757
  | /debug | `/fix-bug {ticket-id}` nếu cần sửa |
752
758
  | /report-bug | Gửi cho dev (`/fix-bug {BUG-ID}`); nếu thiếu coverage → `/propose-scenario {UC-ID}` |
753
759
  | /propose-scenario | Báo PO/Dev review proposal trong `feedback/bdd-proposals/` |
@@ -32,23 +32,23 @@ Hiển thị và chờ phản hồi:
32
32
  ```
33
33
  ⚙️ MODEL CHECK
34
34
  ──────────────────────────────────────────────────────────────────
35
- Recommended : claude-opus-4 (hoặc model Opus mới nhất)
35
+ Recommended : model Opus mới nhất
36
36
  Why needed : Phân tích spec, review kiến trúc, sinh code đòi hỏi
37
- suy luận sâu. Model nhỏ hơn dễ bỏ sót edge case.
37
+ suy luận sâu. Model nhỏ hơn (Haiku/Sonnet) dễ bỏ sót edge case.
38
38
 
39
39
  Cách đổi trong Claude Code:
40
- SettingsModel chọn "claude-opus"
41
- • hoặc: /modelchọn claude-opus
40
+ /modelchọn model Opus
41
+ • hoặc: SettingsModel
42
42
 
43
- Đang chạy claude-opus?
44
- Y — đúng, đang dùng claude-opus → tiếp tục
43
+ Đang chạy một model Opus?
44
+ Y — đúng → tiếp tục
45
45
  S — bỏ qua kiểm tra (tôi chấp nhận rủi ro chất lượng thấp hơn với model hiện tại)
46
46
  ──────────────────────────────────────────────────────────────────
47
47
  ```
48
48
 
49
49
  - "Y" → tiếp tục sang Bước 1.
50
50
  - "S" → tiếp tục sang Bước 1 (người dùng chấp nhận rủi ro, thêm ⚠️ vào report cuối).
51
- - "N" hoặc bất kỳ giá trị nào khác → **DỪNG.** Xuất: "Vui lòng chuyển sang claude-opus rồi chạy lại lệnh này."
51
+ - "N" hoặc bất kỳ giá trị nào khác → **DỪNG.** Xuất: "Vui lòng chuyển sang một model Opus (`/model`) rồi chạy lại lệnh này."
52
52
 
53
53
  ## Bước 1 — Xác định Target File
54
54
 
@@ -57,7 +57,12 @@ Hiển thị và chờ phản hồi:
57
57
  2. Nếu `$ARGUMENTS` là một **UC-ID / ticket ID / tên rút gọn** (không có path) → phân giải thành file bằng cách glob theo bố cục feature-package. `{prd-slug}` lúc này **chưa biết**, nên dùng wildcard `*` cho segment đó, và `**` đệ quy dưới `bdd/` để phủ hết các thư mục con theo platform (`bdd/web/`, `bdd/app/`, `bdd/system/`):
58
58
  - **Lệnh BDD** (target là `.feature`): `{specs_dir}/{domain}/*/bdd/**/{UC-ID}*.feature` — hoặc `{specs_dir}/*/*/bdd/**/{UC-ID}*.feature` nếu domain cũng chưa biết. Nếu lệnh ngụ ý một platform/scope cụ thể (vd: system tech-doc cần BDD `system/`), ưu tiên kết quả trong thư mục con platform đó.
59
59
  - **Lệnh PRD** (target là file PRD `{TICKET-ID}-{prd-slug}.md` — file `.md` duy nhất ở gốc feature folder, cạnh `bdd/`): `{specs_dir}/{domain}/*/{TICKET-ID}*.md` nếu biết TICKET-ID; nếu không, `{specs_dir}/{domain}/*/*.md` (khớp feature folder có id tương ứng), hoặc `{specs_dir}/*/*/*.md` nếu domain cũng chưa biết. *(Glob `*/*.md` ở cấp gốc folder chỉ khớp PRD — tech-docs/design-spec `.md` nằm sâu hơn trong thư mục con.)*
60
- - **Lệnh tech-docs**: `{specs_dir}/{domain}/*/tech-docs/{UC-ID}*-tech-design*.md`.
60
+ - **Lệnh tech-docs** — target là tech-doc **gộp cấp PRD** `{TICKET-ID}-tech-design.md` (MỘT doc phủ nhiều UC; danh sách UC nằm ở `@trace.ucs`). Vì tên file mang `{TICKET-ID}` chứ **không** mang `{UC-ID}`, phải tách trước khi glob:
61
+ - `$ARGUMENTS` là **UC-ID** (`{TICKET-ID}-UC{N}`) → lấy `{TICKET-ID}` = phần **trước** `-UC`, rồi glob `{specs_dir}/{domain}/*/tech-docs/{TICKET-ID}-tech-design.md`.
62
+ - `$ARGUMENTS` là **TICKET-ID** → glob trực tiếp như trên.
63
+ - Chưa biết domain → `{specs_dir}/*/*/tech-docs/{TICKET-ID}-tech-design.md`.
64
+ - Vẫn không khớp → glob rộng `{specs_dir}/*/*/tech-docs/*tech-design*.md` rồi liệt kê để người dùng chọn.
65
+ *(Đừng glob `{UC-ID}*-tech-design*.md` — nó nở thành `FT-001-UC1*-tech-design*.md` và **không bao giờ** khớp `FT-001-tech-design.md`.)*
61
66
  - **Lệnh design-spec**: `{specs_dir}/{domain}/*/design-spec/{TICKET-ID}*.md`.
62
67
 
63
68
  Khi một file khớp: đặt nó làm target **và** ghi lại `domain` + `prd_slug` từ path của nó (theo quy tắc trích xuất trong `context-loader.md` Bước 1 — `prd_slug` = segment đầu tiên sau `{specs_dir}/{domain}/`). Mọi path mà lệnh đọc/ghi về sau (BDD/tech-docs/design-spec/trace cùng cấp) đều dùng **`prd_slug` đã phân giải đó**, nên tất cả artifact nằm chung một feature package. Nếu nhiều file khớp (vd: nhiều platform), chọn theo platform/scope của lệnh hoặc liệt kê ra và hỏi.
@@ -481,7 +486,16 @@ Tiếp tục sang bước kế tiếp của lệnh đang gọi.
481
486
 
482
487
 
483
488
  > **Proposal của tester (input tuỳ chọn):** trước khi sinh, quét `{paths.bdd_proposals_dir}/` (mặc định `{spec_source}/feedback/bdd-proposals/`) tìm `{UC-ID}-*.md`. Với mỗi proposal:
484
- > - `Status: accepted` (PO/Dev đã duyệt) → chèn scenario vào `.feature` của UC (giữ `@trace`), rồi **lưu trữ**: chuyển file sang `{paths.bdd_proposals_dir}/archived/` + đặt `Status: incorporated`, và **commit + push** spec repo để gỡ khỏi feedback chung.
489
+ > - `Status: accepted` (PO/Dev đã duyệt) → chèn scenario vào `.feature` của UC, **normalize khi chèn** (xem dưới), rồi **lưu trữ**: chuyển file sang `{paths.bdd_proposals_dir}/archived/` + đặt `Status: incorporated`, và **commit + push** spec repo để gỡ khỏi feedback chung.
490
+ >
491
+ > **Normalize — bắt buộc, nếu không scenario sẽ vô hình với trace:**
492
+ > 1. Gán `# @trace.scenario: {UC-ID}-SC{N}` với `{N}` = số SC **kế tiếp** trong file đó (thay placeholder `SC?`).
493
+ > 2. Giữ `# @trace.sc_version: 1.0`. Bổ sung `# @trace.business_rules` nếu proposal để `—` (suy từ AC mà dòng `# Covers:` trỏ tới); không suy được → để `—` và nêu trong report.
494
+ > 3. **Strip** tag `@proposed` / `@from-test` — chúng là nhãn vòng đời proposal, không thuộc BDD canonical.
495
+ > 4. Đặt scenario vào **đúng NHÓM** theo business theme (C.5), không nối vào cuối file.
496
+ > 5. **Append row TSV** cho SC mới (như nhánh "SC mới" ở Write Trace State: `spec_ver = 1.0`, mọi cột gen/test/qc = `—`, `status = UNTRACKED`).
497
+ >
498
+ > **Backward-compat:** proposal cũ mang `@trace.uc=` / `@trace.ac=` (vocabulary trước đây, không thuộc contract `.feature`) → tự map sang canonical (`@trace.uc` bỏ — số UC đã có trong `sc_id`; `@trace.ac` → dòng `# Covers:`) và in một dòng cảnh báo khuyến nghị proposal sau viết theo format mới.
485
499
  > - `Status: proposed`/`rejected` (hoặc thiếu `Status`) → **bỏ qua**, để nguyên cho PO/Dev xử lý (KHÔNG tự đoán, KHÔNG tự đưa vào).
486
500
  > Bỏ qua sạch nếu folder rỗng.
487
501
 
@@ -687,9 +701,24 @@ Chỉ cần kiểm tra trạng thái đã phân giải:
687
701
  | `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 là khoá định danh, lệch là lỗi cấu hình cần sửa ở SoT.) |
688
702
  | Single-service (không có section `services`) | `active_module = tech_stack.module` (đã set ở Bước 6.5). Tiếp tục. |
689
703
 
690
- **Output path (umbrella mode):** `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{TICKET-ID}-UC{N}-{slug}.feature`
704
+ ### Phân giải `active_platform` (umbrella mode)
691
705
 
692
- *(Không thêm subfolder theo service: feature-package đã domain-scoped sẵn `{domain}/`, service route 1-1 theo domain nên thêm subfolder service sẽ chỉ lặp lại domain. `active_service` chỉ dùng cho `service_root`/từ vựng, KHÔNG vào path spec.)*
706
+ Umbrella mode không hỏi platform (khác spec repo mode) **suy** từ module của service. Bắt buộc phải giá trị: `active_platform` đi vào **path file**, vào **header `@trace.platform`**, vào **tên sổ trace** `{UC-ID}-{platform}.tsv`.
707
+
708
+ | `active_module` | → `active_platform` |
709
+ |---|---|
710
+ | react · nextjs · vue · nuxt · angular | `web` |
711
+ | flutter · react-native · ios-swiftui · android-compose | `app` |
712
+ | java-spring · golang · dotnet · php-laravel | `system` |
713
+ | context-engineering · phaser-game | theo `platform_type` của stack-profile (`backend` → `system`, còn lại → `web`) |
714
+
715
+ - `active_service = "multi"` + `service_candidates_kind = platform` → **nhiều** `active_platform` (một cho mỗi platform trong `service_candidates`); sinh một file `.feature` cho mỗi platform, module lấy theo `service_candidates.{platform}.module`.
716
+ - Không suy được (module lạ, không có trong bảng và không có `platform_type`) → **DỪNG**, hỏi người dùng chọn `web`/`app`/`system`. **KHÔNG** ghi file khi chưa có `active_platform` — file thiếu platform sẽ vô hình với `/validate-traces` và va chạm tên với platform khác.
717
+
718
+ **Output path (umbrella mode):** `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{active_platform}/{TICKET-ID}-UC{N}-{slug}.feature`
719
+
720
+ *(**Subfolder `{platform}/` LUÔN có — mọi mode.** `web` và `system` của cùng một UC là hai file khác nhau: nếu bỏ subfolder, chúng ra cùng filename và **ghi đè nhau**; ngoài ra trace tách theo platform (`{UC-ID}-{platform}.tsv`) nên bố cục spec phải tách tương ứng.*
721
+ *Cái KHÔNG thêm là subfolder theo **service**: feature-package đã domain-scoped sẵn ở `{domain}/`, mà service route 1-1 theo domain — thêm subfolder service chỉ lặp lại domain. `active_service` chỉ dùng cho `service_root`/từ vựng, KHÔNG vào path spec.)*
693
722
 
694
723
  **Từ vựng theo platform** — điều chỉnh cách viết step BDD theo `active_module`:
695
724
 
@@ -772,9 +801,9 @@ Sau khi sinh tất cả file `.feature` và `.tsv` cho UC được giao, trả v
772
801
 
773
802
  Trước khi sinh, kiểm tra các file `.feature` có sẵn cho PRD này:
774
803
 
775
- 1. Phân giải search path theo mode:
776
- - **Spec repo mode**: `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{active_platform}/{TICKET-ID}-UC*.feature`
777
- - **Umbrella mode**: `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{TICKET-ID}-UC*.feature`
804
+ 1. Search path (**giống nhau cả hai mode** — bố cục `bdd/{platform}/` là chuẩn duy nhất):
805
+ `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{active_platform}/{TICKET-ID}-UC*.feature`
806
+ > **Legacy:** nếu không khớp gì, thử thêm một lần ở bố cục phẳng cũ `…/bdd/{TICKET-ID}-UC*.feature`. Khớp → xử lý như file có sẵn **và** in cảnh báo: `⚠️ File .feature đang ở bố cục phẳng (trước v0.4.1). Chạy: npx sdd-framework --migrate-bdd-platform để chuyển sang bdd/{platform}/.` Đừng tự di chuyển file trong lệnh này.
778
807
  2. Đọc `| **Version** |` hiện tại của PRD từ metadata (vd: `1.2`).
779
808
 
780
809
  **Nếu không có file feature nào** → gen mới, tiếp tục bình thường. Dùng version PRD làm `@trace.prd_version`.
@@ -823,7 +852,7 @@ Trước khi sinh, kiểm tra các file `.feature` có sẵn cho PRD này:
823
852
  | Check | Rule |
824
853
  |-------|------|
825
854
  | C.1 Wireframe Coverage | Mỗi component/action trong Wireframe (PRD §4b) có ≥1 SC. **FE/App: mỗi Screen State (≠default) và mỗi AC-UI behavioral của design-spec (`design_coverage`) cũng phải có ≥1 SC** — dedup với AC nghiệp vụ PRD; bỏ AC-UI visual thuần. |
826
- | C.2 PRD Traceability | Mỗi AC và mỗi BR (gồm từng bullet logic) map tới ≥1 SC. |
855
+ | C.2 PRD Traceability | Mỗi AC **thuộc UC này** (đúng tập ở `**AC liên quan:**` của UC trong PRD §3) và mỗi BR trong bảng Business Rule của UC này map tới ≥1 SC. **KHÔNG** phủ AC của UC khác — đó là việc của `.feature` UC đó. *(AC ở PRD là global cấp PRD, còn `.feature` là per-UC; enforce theo nghĩa "mọi AC của PRD" sẽ bắt AI bịa scenario ngoài scope hoặc báo MISSING giả.)* |
827
856
  | C.3 Business Dictionary | Dùng đúng canonical term từ business-dictionary.md. |
828
857
  | C.4 Banned Terms | 0 banned term trong file — grep trước khi gen. |
829
858
  | C.5 NHÓM Grouping | Feature ≥3 SC → PHẢI có NHÓM grouping theo business theme. |
@@ -879,19 +908,23 @@ CHECKPOINT: "Outline này đúng chưa? Bạn muốn thêm hay bớt SC nào kh
879
908
 
880
909
  ## Generate
881
910
 
882
- **Output path theo mode:**
883
- - **Spec repo mode**: `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{active_platform}/{TICKET-ID}-UC{N}-{slug}.feature`
884
- - **Umbrella mode**: `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{TICKET-ID}-UC{N}-{slug}.feature` *(service route 1-1 theo domain → không thêm subfolder service)*
911
+ **Output path MỘT bố cục duy nhất cho cả hai mode:**
885
912
 
886
- Với mỗi UC, ghi vào path đã phân giải ở trên. Dùng từ vựng cho active platform (từ Platform Selection hoặc Service Detection).
913
+ ```
914
+ {paths.specs_dir}/{domain}/{prd-slug}/bdd/{active_platform}/{TICKET-ID}-UC{N}-{slug}.feature
915
+ ```
916
+
917
+ `{active_platform}` ∈ `web` | `app` | `system` — từ Platform Selection (spec repo mode) hoặc suy từ `active_module` (umbrella mode, xem §Service Detection). **Không có `active_platform` thì không ghi file.**
918
+
919
+ Với mỗi UC, ghi vào path trên và set `# @trace.platform: {active_platform}` trong header (**bắt buộc, mọi mode** — `/generate-code` dùng nó để quyết BE/FE và để định vị sổ trace; `/generate-tech-docs` và `context-loader` cũng đọc nó). Dùng từ vựng cho active platform.
887
920
 
888
921
  ```gherkin
889
922
  # ============================================================
890
923
  # @trace.id: {TICKET-ID}-UC{N}
891
924
  # @trace.title: <Feature name>
892
- # @trace.revision: 1 ← field tĩnh; dùng @trace.bdd_version để theo dõi version (tăng bởi /review-context --fix hoặc --resume)
925
+ # @trace.revision: 1 ← field tĩnh; version theo dõi bằng @trace.bdd_version
893
926
  # @trace.domain: <domain>
894
- # @trace.platform: {active_platform — web | app | system | (bỏ trong umbrella mode)}
927
+ # @trace.platform: {active_platform — web | app | system} ← BẮT BUỘC mọi mode; phải khớp segment bdd/{platform}/ của path
895
928
  # @trace.service: {active_service — bỏ trong spec repo mode}
896
929
  # @trace.module: {active_module trong umbrella mode; "unknown" trong spec repo mode}
897
930
  # @trace.status: draft
@@ -899,8 +932,8 @@ Với mỗi UC, ghi vào path đã phân giải ở trên. Dùng từ vựng cho
899
932
  # @trace.created_at: {YYYY-MM-DD}
900
933
  # @trace.prd: {TICKET-ID}
901
934
  # @trace.prd_version: {đọc từ metadata PRD "| **Version** |"}
902
- # @trace.bdd_version: {1.0 nếu gen mới; tăng 0.1 khi gen lại vd 1.0 1.1}
903
- # @trace.business_rules: {TICKET-ID}-UC{N}-BR1, {TICKET-ID}-UC{N}-BR2
935
+ # @trace.bdd_version: {cấp FILE — 1.0 nếu gen mới; tăng 0.1 khi gen lại. Khác @trace.sc_version (cấp từng SC) bên dưới}
936
+ # @trace.business_rules: {TICKET-ID}-UC{N}-BR{m}, {TICKET-ID}-UC{N}-BR{m+1} ← {m} lấy NGUYÊN từ PRD §3: BR đánh số LIÊN TỤC toàn PRD, KHÔNG reset theo UC
904
937
  # @trace.dataset: {domain}.testdata.yaml
905
938
  # ============================================================
906
939
 
@@ -947,8 +980,8 @@ Feature: <Feature name>
947
980
 
948
981
  # Side-effects: <liệt kê ngắn các Then side-effect cần verify>
949
982
  # @trace.scenario: {TICKET-ID}-UC{N}-SC1
950
- # @trace.sc_version: 1.0
951
- # @trace.business_rules: {TICKET-ID}-UC{N}-BR1
983
+ # @trace.sc_version: 1.0 ← cấp SCENARIO. Sửa thân SC này (tên/step/table/side-effect) thì +0.1, nếu không code cũ mãi hiện OK
984
+ # @trace.business_rules: {TICKET-ID}-UC{N}-BR{m}
952
985
  @happy
953
986
  Scenario: <mô tả business outcome — dùng động từ chính xác: create/receive/assign/block>
954
987
  Given <input state — alias từ dataset>
@@ -959,7 +992,7 @@ Feature: <Feature name>
959
992
  # Side-effects: <...>
960
993
  # @trace.scenario: {TICKET-ID}-UC{N}-SC2
961
994
  # @trace.sc_version: 1.0
962
- # @trace.business_rules: {TICKET-ID}-UC{N}-BR1
995
+ # @trace.business_rules: {TICKET-ID}-UC{N}-BR{m}
963
996
  @happy @alternative
964
997
  Scenario: <cùng theme NHÓM 1 nhưng path khác — vd: giá trị enum khác>
965
998
  Given <state>
@@ -973,7 +1006,7 @@ Feature: <Feature name>
973
1006
  # Side-effects: <...>
974
1007
  # @trace.scenario: {TICKET-ID}-UC{N}-SC3
975
1008
  # @trace.sc_version: 1.0
976
- # @trace.business_rules: {TICKET-ID}-UC{N}-BR2
1009
+ # @trace.business_rules: {TICKET-ID}-UC{N}-BR{m+2}
977
1010
  @edge
978
1011
  Scenario: <scenario boundary / error>
979
1012
  Given <state>
@@ -985,8 +1018,8 @@ Feature: <Feature name>
985
1018
  # AC1 (...) → SC1, SC2
986
1019
  # AC2 (...) → SC3
987
1020
  # BR mapping (mỗi bullet PHẢI có ≥1 SC — C.2):
988
- # {TICKET-ID}-UC{N}-BR1 (...) → SC1, SC2
989
- # {TICKET-ID}-UC{N}-BR2 (...) → SC3
1021
+ # {TICKET-ID}-UC{N}-BR{m} (...) → SC1, SC2
1022
+ # {TICKET-ID}-UC{N}-BR{m+2} (...) → SC3
990
1023
  # Wireframe mapping (mỗi component/action ≥1 SC — C.1):
991
1024
  # Screen "<screen name>":
992
1025
  # [x] <action 1> → SC1
@@ -999,6 +1032,9 @@ Feature: <Feature name>
999
1032
 
1000
1033
  # === PRE-MERGE CHECKLIST ===
1001
1034
  # - [ ] Mỗi SC có Side-effects + @trace.scenario + @trace.sc_version + @trace.business_rules
1035
+ # - [ ] SỬA nội dung một SC (tên / step / data table / side-effect) → đã bump @trace.sc_version của
1036
+ # CHÍNH SC đó (+0.1). Quên bump = code sinh từ SC cũ vẫn hiện OK, không ai biết phải regen.
1037
+ # (Đổi @trace.business_rules / tag / comment → KHÔNG bump: không đổi hành vi cần implement.)
1002
1038
  # - [ ] Coverage Matrix: 0 dòng MISSING (C.1)
1003
1039
  # - [ ] FE/App: mỗi Screen State (≠default) + AC-UI behavioral của design-spec có ≥1 SC (C.1 mở rộng)
1004
1040
  # - [ ] Mỗi AC/BR map tới ≥1 SC (C.2)
@@ -1009,7 +1045,35 @@ Feature: <Feature name>
1009
1045
 
1010
1046
  ```
1011
1047
 
1012
- *(Template `.feature` **single-source** `templates/feature.template` sửa file đó để đổi cấu trúc mọi `.feature` sinh ra. Coverage Matrix + Pre-merge Checklist nằm ở **cuối** template, thêm vào cuối mỗi file.)*
1048
+ > **Template này đến từ đâuđọc trước khi định "customize":**
1049
+ > Skeleton trên là **single-source** ở `templates/feature.template` **của repo framework**, được `{{include}}` **nướng cứng vào lệnh này lúc `npm run build`**. Muốn đổi cấu trúc mọi `.feature` sinh ra: sửa file đó **trong repo framework** rồi build lại + phát hành.
1050
+ >
1051
+ > **Sửa `.agent/templates/feature.template` trong project KHÔNG có tác dụng** — không lệnh nào đọc file đó; nó chỉ là bản tham khảo. Và nó **sẽ bị ghi đè im lặng** ở lần `/update-framework` kế tiếp (`--init` copy `core/` → `.agent/` vô điều kiện; file duy nhất được giữ là `.agent/project-context.yaml`).
1052
+ >
1053
+ > Coverage Matrix + Pre-merge Checklist nằm ở **cuối** template, thêm vào cuối mỗi file.
1054
+
1055
+ ### Bump `@trace.sc_version` *(CHỈ khi gen lại — file `.feature` đã tồn tại)*
1056
+
1057
+ *Bỏ qua hoàn toàn khi gen mới: mọi SC nhận `1.0`.*
1058
+
1059
+ `@trace.sc_version` là version **của từng scenario** — nó 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`). Không bump = code sinh từ scenario cũ mãi mãi hiện `OK`. Phân biệt với `@trace.bdd_version` (version **cả file**, không đủ phân giải để biết SC nào cần regen).
1060
+
1061
+ Trước khi ghi file, với **mỗi** SC, so **thân scenario** bản mới vs bản trên disk theo 4 thành phần:
1062
+
1063
+ 1. dòng `Scenario:` (tên)
1064
+ 2. chuỗi step `Given` / `When` / `Then` / `And` (nội dung + thứ tự)
1065
+ 3. nội dung data table (nếu có)
1066
+ 4. dòng `# Side-effects:`
1067
+
1068
+ | Kết quả so | Hành động |
1069
+ |---|---|
1070
+ | Khác ở **bất kỳ** thành phần nào | `@trace.sc_version` += `0.1` (vd `1.0` → `1.1`) |
1071
+ | Giống hoàn toàn | **GIỮ NGUYÊN** — bump vô cớ sẽ tạo `DRIFT` giả, làm cờ mất giá trị |
1072
+ | SC mới (chưa có trong bản cũ) | `1.0` |
1073
+
1074
+ *(Thay đổi ngoài 4 thành phần trên — `@trace.business_rules`, tag `@happy`/`@edge`, comment — KHÔNG bump: chúng không đổi hành vi mà code phải implement.)*
1075
+
1076
+ In danh sách SC được bump vào report cuối để người dùng biết cái nào sẽ hiện `DRIFT`.
1013
1077
 
1014
1078
  ---
1015
1079
 
@@ -1030,7 +1094,19 @@ sc_id\tsc_title\tspec_ver\tgen_ver\timplemented_by\ttest_count\ttest_classes\tde
1030
1094
  - SC đã có trong `.tsv` VÀ `spec_ver` không đổi → chỉ cập nhật: `sc_title`, `prd_version`, `bdd_version`, `prd_status`, `uc_status`, `last_updated`. Giữ nguyên các cột khác.
1031
1095
  - SC đã có trong `.tsv` VÀ `spec_ver` đổi (scenario bị sửa) → cập nhật: `sc_title`, `spec_ver`, `prd_version`, `bdd_version`, `prd_status`, `uc_status`, `last_updated` VÀ set `status = DRIFT` ngay (để TSV phản ánh drift mà không cần đợi `/validate-traces`). Giữ nguyên `gen_ver`, `implemented_by`, `test_count`, `test_classes`, `tech_doc_revision`, `fe_tech_doc_revision`.
1032
1096
  - SC mới (thêm trong lần gen lại này) → append row mới với `gen_ver`, `implemented_by`, `test_count`, `test_classes`, `dev_selftest`, `dev_selftest_at`, `qc_status`, `qc_run_at`, `qc_owner`, `qc_blocked_by`, `tech_doc_revision`, `fe_tech_doc_revision` đều set `—`.
1033
- - SC không còn trong `.feature` (bị xoá) xoá row của nó. *(An toàn: sổ này chỉ chứa scenario của `{active_platform}`, so với `.feature` của chính platform đó không bao giờ đụng scenario platform khác.)*
1097
+ - SC không còn trong `.feature` (bị xoá / gộp / đổi số) **phụ thuộc SC đó đã code chưa:**
1098
+ - `implemented_by == —` (**chưa** có code) → **xoá row**. Không có gì mồ côi.
1099
+ - `implemented_by != —` (**ĐÃ** có code) → **GIỮ row**, set `status = ORPHANED`, giữ nguyên `implemented_by` / `test_count` / `test_classes` / các cột qc, cập nhật `last_updated`. **KHÔNG xoá** — xoá row thì method đó thành vô hình: không `UNTRACKED`, không `GAP`, không `DRIFT`, không xuất hiện ở report nào, mà vẫn nằm trong code và vẫn được caller gọi. Coverage còn *đẹp hơn* thực tế vì mẫu số nhỏ đi.
1100
+ In cảnh báo nổi bật ở report cuối:
1101
+ ```
1102
+ ⚠️ ORPHANED — {UC-ID}-SC{N} "{sc_title}" đã bị xoá khỏi .feature nhưng còn code:
1103
+ {implemented_by} (+ {test_count} test: {test_classes})
1104
+ Không tự hết — chọn MỘT:
1105
+ (a) behavior không còn cần → xoá method + test, rồi xoá row khỏi .tsv
1106
+ (b) SC bị xoá do nhầm → đưa scenario trở lại .feature (row về DRIFT/OK bình thường)
1107
+ (/validate-traces giữ cờ ORPHANED 🔴 và chặn "pass" tới khi xử lý xong.)
1108
+ ```
1109
+ *(An toàn: sổ này chỉ chứa scenario của `{active_platform}`, so với `.feature` của chính platform đó — không bao giờ đụng scenario platform khác.)*
1034
1110
 
1035
1111
  **Giá trị ghi cho mỗi scenario:**
1036
1112
 
@@ -1163,6 +1239,7 @@ Gợi ý lệnh kế tiếp hợp lý theo phase của workflow:
1163
1239
  | /qc-run-test | `/qc-report {UC-ID}` rồi `/qc-review {UC-ID}` (review script) |
1164
1240
  | /qc-review (script) | `/qc-report {UC-ID}` rồi tạo PR nếu APPROVED |
1165
1241
  | /qc-report | `/validate-traces {UC-ID}` để làm mới Living Docs (qc_status) |
1242
+ | /map-testids | `/qc-design-test {UC-ID}` (QC dựng Page Object từ contract §4.5.6 vừa ghi) |
1166
1243
  | /generate-tech-docs | `/review-tech-docs {tech-design-file}` |
1167
1244
  | /review-tech-docs | `/generate-code {feature-file}` nếu APPROVED; sửa doc nếu NEEDS_FIX |
1168
1245
  | /generate-code | Lần gen đầu → `/review-code {UC-ID}`; gen lại → `/dev-gen-test {UC-ID}` |
@@ -1171,8 +1248,8 @@ Gợi ý lệnh kế tiếp hợp lý theo phase của workflow:
1171
1248
  | /dev-run-test (failing) | `/fix-bug {ticket-id}` hoặc `/debug {error}` |
1172
1249
  | /review-code | `/dev-smoke-test {UC-ID}` hoặc tạo PR |
1173
1250
  | /dev-smoke-test | Tạo PR và link tới ticket |
1174
- | /validate-traces | DRIFT/UNTRACKED → `/generate-code {UC-ID}`; GAP → `/dev-gen-test {UC-ID}`; tất cả OK tạo PR |
1175
- | /fix-bug | Tạo PR link tới ticket |
1251
+ | /validate-traces | **Cờ 🔴 trước (chặn PR):** SEAM_UNWIRED → nối binding sang class thật, xoá/thay stub · STUB_UNRESOLVED → `/generate-code {owner_uc}` (lấp logic tại chỗ + xoá hàm song song) · ORPHANED/TRACE_ORPHAN → quyết định thủ công (xoá code+test, đưa scenario trở lại `.feature`, hoặc sửa `sc_id` của tag). **Rồi:** DRIFT/UNTRACKED → `/generate-code {UC-ID}` · BDD_DRIFT → `/generate-code {feature-file}` · tech-doc lỗi thời vs BDD → `/generate-tech-docs` → `/review-tech-docs` · PRD drift → `/generate-bdd {prd-file}` · GAP → `/dev-gen-test {UC-ID}`. **Chỉ tạo PR khi mọi cờ 🔴 = 0** |
1252
+ | /fix-bug | `/dev-run-test {UC-ID}` (dev_selftest vừa reset về not_run) → tạo PR; nếu fix một `{BUG-ID}` → QC chạy `/qc-run-test {UC-ID}` để verify + đóng bug |
1176
1253
  | /debug | `/fix-bug {ticket-id}` nếu cần sửa |
1177
1254
  | /report-bug | Gửi cho dev (`/fix-bug {BUG-ID}`); nếu thiếu coverage → `/propose-scenario {UC-ID}` |
1178
1255
  | /propose-scenario | Báo PO/Dev review proposal trong `feedback/bdd-proposals/` |
@@ -1207,9 +1284,9 @@ Next (spec repo):
1207
1284
  → Sau khi gen hết platform: commit + push + báo team dev
1208
1285
  → Team dev đọc BDD từ spec submodule — không chạy /generate-bdd ở phía họ
1209
1286
 
1210
- [Umbrella mode — service: {active_service}]
1287
+ [Umbrella mode — service: {active_service} · platform: {active_platform} (suy từ module {active_module})]
1211
1288
  Files:
1212
- {paths.specs_dir}/{domain}/{prd-slug}/bdd/{TICKET-ID}-UC1-{slug}.feature ({N} scenarios)
1289
+ {paths.specs_dir}/{domain}/{prd-slug}/bdd/{active_platform}/{TICKET-ID}-UC1-{slug}.feature ({N} scenarios)
1213
1290
  Trace:
1214
1291
  {paths.trace_dir}/{domain}/{prd-slug}/{TICKET-ID}-UC1-{active_platform}.tsv ({N} rows)
1215
1292
  Next (umbrella):
@@ -1217,5 +1294,11 @@ Next (umbrella):
1217
1294
  → /generate-tech-docs {feature-file}
1218
1295
  → /generate-code {feature-file}
1219
1296
 
1297
+ {chỉ khi gen lại VÀ có ≥1 SC bị bump — ngược lại bỏ cả khối}
1298
+ 🔄 sc_version đã bump (scenario đổi nội dung → code cũ lỗi thời):
1299
+ {UC-ID}-SC2 1.0 → 1.1 {sc_title}
1300
+ {UC-ID}-SC5 1.2 → 1.3 {sc_title}
1301
+ → {n} SC này sẽ hiện DRIFT ở /validate-traces. Sinh lại code: /generate-code {feature-file}
1302
+
1220
1303
  📊 Living Docs: chạy /validate-traces (hoặc /sync) để push trace này lên dashboard spec-module.
1221
1304
  ```
@@ -7,7 +7,16 @@
7
7
  {{include:steps/context-loader.md}}
8
8
 
9
9
  > **Proposal của tester (input tuỳ chọn):** trước khi sinh, quét `{paths.bdd_proposals_dir}/` (mặc định `{spec_source}/feedback/bdd-proposals/`) tìm `{UC-ID}-*.md`. Với mỗi proposal:
10
- > - `Status: accepted` (PO/Dev đã duyệt) → chèn scenario vào `.feature` của UC (giữ `@trace`), rồi **lưu trữ**: chuyển file sang `{paths.bdd_proposals_dir}/archived/` + đặt `Status: incorporated`, và **commit + push** spec repo để gỡ khỏi feedback chung.
10
+ > - `Status: accepted` (PO/Dev đã duyệt) → chèn scenario vào `.feature` của UC, **normalize khi chèn** (xem dưới), rồi **lưu trữ**: chuyển file sang `{paths.bdd_proposals_dir}/archived/` + đặt `Status: incorporated`, và **commit + push** spec repo để gỡ khỏi feedback chung.
11
+ >
12
+ > **Normalize — bắt buộc, nếu không scenario sẽ vô hình với trace:**
13
+ > 1. Gán `# @trace.scenario: {UC-ID}-SC{N}` với `{N}` = số SC **kế tiếp** trong file đó (thay placeholder `SC?`).
14
+ > 2. Giữ `# @trace.sc_version: 1.0`. Bổ sung `# @trace.business_rules` nếu proposal để `—` (suy từ AC mà dòng `# Covers:` trỏ tới); không suy được → để `—` và nêu trong report.
15
+ > 3. **Strip** tag `@proposed` / `@from-test` — chúng là nhãn vòng đời proposal, không thuộc BDD canonical.
16
+ > 4. Đặt scenario vào **đúng NHÓM** theo business theme (C.5), không nối vào cuối file.
17
+ > 5. **Append row TSV** cho SC mới (như nhánh "SC mới" ở Write Trace State: `spec_ver = 1.0`, mọi cột gen/test/qc = `—`, `status = UNTRACKED`).
18
+ >
19
+ > **Backward-compat:** proposal cũ mang `@trace.uc=` / `@trace.ac=` (vocabulary trước đây, không thuộc contract `.feature`) → tự map sang canonical (`@trace.uc` bỏ — số UC đã có trong `sc_id`; `@trace.ac` → dòng `# Covers:`) và in một dòng cảnh báo khuyến nghị proposal sau viết theo format mới.
11
20
  > - `Status: proposed`/`rejected` (hoặc thiếu `Status`) → **bỏ qua**, để nguyên cho PO/Dev xử lý (KHÔNG tự đoán, KHÔNG tự đưa vào).
12
21
  > Bỏ qua sạch nếu folder rỗng.
13
22
 
@@ -213,9 +222,24 @@ Chỉ cần kiểm tra trạng thái đã phân giải:
213
222
  | `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 là khoá định danh, lệch là lỗi cấu hình cần sửa ở SoT.) |
214
223
  | Single-service (không có section `services`) | `active_module = tech_stack.module` (đã set ở Bước 6.5). Tiếp tục. |
215
224
 
216
- **Output path (umbrella mode):** `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{TICKET-ID}-UC{N}-{slug}.feature`
225
+ ### Phân giải `active_platform` (umbrella mode)
217
226
 
218
- *(Không thêm subfolder theo service: feature-package đã domain-scoped sẵn `{domain}/`, service route 1-1 theo domain nên thêm subfolder service sẽ chỉ lặp lại domain. `active_service` chỉ dùng cho `service_root`/từ vựng, KHÔNG vào path spec.)*
227
+ Umbrella mode không hỏi platform (khác spec repo mode) **suy** từ module của service. Bắt buộc phải giá trị: `active_platform` đi vào **path file**, vào **header `@trace.platform`**, vào **tên sổ trace** `{UC-ID}-{platform}.tsv`.
228
+
229
+ | `active_module` | → `active_platform` |
230
+ |---|---|
231
+ | react · nextjs · vue · nuxt · angular | `web` |
232
+ | flutter · react-native · ios-swiftui · android-compose | `app` |
233
+ | java-spring · golang · dotnet · php-laravel | `system` |
234
+ | context-engineering · phaser-game | theo `platform_type` của stack-profile (`backend` → `system`, còn lại → `web`) |
235
+
236
+ - `active_service = "multi"` + `service_candidates_kind = platform` → **nhiều** `active_platform` (một cho mỗi platform trong `service_candidates`); sinh một file `.feature` cho mỗi platform, module lấy theo `service_candidates.{platform}.module`.
237
+ - Không suy được (module lạ, không có trong bảng và không có `platform_type`) → **DỪNG**, hỏi người dùng chọn `web`/`app`/`system`. **KHÔNG** ghi file khi chưa có `active_platform` — file thiếu platform sẽ vô hình với `/validate-traces` và va chạm tên với platform khác.
238
+
239
+ **Output path (umbrella mode):** `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{active_platform}/{TICKET-ID}-UC{N}-{slug}.feature`
240
+
241
+ *(**Subfolder `{platform}/` LUÔN có — mọi mode.** `web` và `system` của cùng một UC là hai file khác nhau: nếu bỏ subfolder, chúng ra cùng filename và **ghi đè nhau**; ngoài ra trace tách theo platform (`{UC-ID}-{platform}.tsv`) nên bố cục spec phải tách tương ứng.*
242
+ *Cái KHÔNG thêm là subfolder theo **service**: feature-package đã domain-scoped sẵn ở `{domain}/`, mà service route 1-1 theo domain — thêm subfolder service chỉ lặp lại domain. `active_service` chỉ dùng cho `service_root`/từ vựng, KHÔNG vào path spec.)*
219
243
 
220
244
  **Từ vựng theo platform** — điều chỉnh cách viết step BDD theo `active_module`:
221
245
 
@@ -298,9 +322,9 @@ Sau khi sinh tất cả file `.feature` và `.tsv` cho UC được giao, trả v
298
322
 
299
323
  Trước khi sinh, kiểm tra các file `.feature` có sẵn cho PRD này:
300
324
 
301
- 1. Phân giải search path theo mode:
302
- - **Spec repo mode**: `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{active_platform}/{TICKET-ID}-UC*.feature`
303
- - **Umbrella mode**: `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{TICKET-ID}-UC*.feature`
325
+ 1. Search path (**giống nhau cả hai mode** — bố cục `bdd/{platform}/` là chuẩn duy nhất):
326
+ `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{active_platform}/{TICKET-ID}-UC*.feature`
327
+ > **Legacy:** nếu không khớp gì, thử thêm một lần ở bố cục phẳng cũ `…/bdd/{TICKET-ID}-UC*.feature`. Khớp → xử lý như file có sẵn **và** in cảnh báo: `⚠️ File .feature đang ở bố cục phẳng (trước v0.4.1). Chạy: npx sdd-framework --migrate-bdd-platform để chuyển sang bdd/{platform}/.` Đừng tự di chuyển file trong lệnh này.
304
328
  2. Đọc `| **Version** |` hiện tại của PRD từ metadata (vd: `1.2`).
305
329
 
306
330
  **Nếu không có file feature nào** → gen mới, tiếp tục bình thường. Dùng version PRD làm `@trace.prd_version`.
@@ -349,7 +373,7 @@ Trước khi sinh, kiểm tra các file `.feature` có sẵn cho PRD này:
349
373
  | Check | Rule |
350
374
  |-------|------|
351
375
  | C.1 Wireframe Coverage | Mỗi component/action trong Wireframe (PRD §4b) có ≥1 SC. **FE/App: mỗi Screen State (≠default) và mỗi AC-UI behavioral của design-spec (`design_coverage`) cũng phải có ≥1 SC** — dedup với AC nghiệp vụ PRD; bỏ AC-UI visual thuần. |
352
- | C.2 PRD Traceability | Mỗi AC và mỗi BR (gồm từng bullet logic) map tới ≥1 SC. |
376
+ | C.2 PRD Traceability | Mỗi AC **thuộc UC này** (đúng tập ở `**AC liên quan:**` của UC trong PRD §3) và mỗi BR trong bảng Business Rule của UC này map tới ≥1 SC. **KHÔNG** phủ AC của UC khác — đó là việc của `.feature` UC đó. *(AC ở PRD là global cấp PRD, còn `.feature` là per-UC; enforce theo nghĩa "mọi AC của PRD" sẽ bắt AI bịa scenario ngoài scope hoặc báo MISSING giả.)* |
353
377
  | C.3 Business Dictionary | Dùng đúng canonical term từ business-dictionary.md. |
354
378
  | C.4 Banned Terms | 0 banned term trong file — grep trước khi gen. |
355
379
  | C.5 NHÓM Grouping | Feature ≥3 SC → PHẢI có NHÓM grouping theo business theme. |
@@ -405,17 +429,49 @@ CHECKPOINT: "Outline này đúng chưa? Bạn muốn thêm hay bớt SC nào kh
405
429
 
406
430
  ## Generate
407
431
 
408
- **Output path theo mode:**
409
- - **Spec repo mode**: `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{active_platform}/{TICKET-ID}-UC{N}-{slug}.feature`
410
- - **Umbrella mode**: `{paths.specs_dir}/{domain}/{prd-slug}/bdd/{TICKET-ID}-UC{N}-{slug}.feature` *(service route 1-1 theo domain → không thêm subfolder service)*
432
+ **Output path MỘT bố cục duy nhất cho cả hai mode:**
411
433
 
412
- Với mỗi UC, ghi vào path đã phân giải ở trên. Dùng từ vựng cho active platform (từ Platform Selection hoặc Service Detection).
434
+ ```
435
+ {paths.specs_dir}/{domain}/{prd-slug}/bdd/{active_platform}/{TICKET-ID}-UC{N}-{slug}.feature
436
+ ```
437
+
438
+ `{active_platform}` ∈ `web` | `app` | `system` — từ Platform Selection (spec repo mode) hoặc suy từ `active_module` (umbrella mode, xem §Service Detection). **Không có `active_platform` thì không ghi file.**
439
+
440
+ Với mỗi UC, ghi vào path trên và set `# @trace.platform: {active_platform}` trong header (**bắt buộc, mọi mode** — `/generate-code` dùng nó để quyết BE/FE và để định vị sổ trace; `/generate-tech-docs` và `context-loader` cũng đọc nó). Dùng từ vựng cho active platform.
413
441
 
414
442
  ```gherkin
415
443
  {{include:templates/feature.template}}
416
444
  ```
417
445
 
418
- *(Template `.feature` **single-source** `templates/feature.template` sửa file đó để đổi cấu trúc mọi `.feature` sinh ra. Coverage Matrix + Pre-merge Checklist nằm ở **cuối** template, thêm vào cuối mỗi file.)*
446
+ > **Template này đến từ đâuđọc trước khi định "customize":**
447
+ > Skeleton trên là **single-source** ở `templates/feature.template` **của repo framework**, được `{{include}}` **nướng cứng vào lệnh này lúc `npm run build`**. Muốn đổi cấu trúc mọi `.feature` sinh ra: sửa file đó **trong repo framework** rồi build lại + phát hành.
448
+ >
449
+ > **Sửa `.agent/templates/feature.template` trong project KHÔNG có tác dụng** — không lệnh nào đọc file đó; nó chỉ là bản tham khảo. Và nó **sẽ bị ghi đè im lặng** ở lần `/update-framework` kế tiếp (`--init` copy `core/` → `.agent/` vô điều kiện; file duy nhất được giữ là `.agent/project-context.yaml`).
450
+ >
451
+ > Coverage Matrix + Pre-merge Checklist nằm ở **cuối** template, thêm vào cuối mỗi file.
452
+
453
+ ### Bump `@trace.sc_version` *(CHỈ khi gen lại — file `.feature` đã tồn tại)*
454
+
455
+ *Bỏ qua hoàn toàn khi gen mới: mọi SC nhận `1.0`.*
456
+
457
+ `@trace.sc_version` là version **của từng scenario** — nó 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`). Không bump = code sinh từ scenario cũ mãi mãi hiện `OK`. Phân biệt với `@trace.bdd_version` (version **cả file**, không đủ phân giải để biết SC nào cần regen).
458
+
459
+ Trước khi ghi file, với **mỗi** SC, so **thân scenario** bản mới vs bản trên disk theo 4 thành phần:
460
+
461
+ 1. dòng `Scenario:` (tên)
462
+ 2. chuỗi step `Given` / `When` / `Then` / `And` (nội dung + thứ tự)
463
+ 3. nội dung data table (nếu có)
464
+ 4. dòng `# Side-effects:`
465
+
466
+ | Kết quả so | Hành động |
467
+ |---|---|
468
+ | Khác ở **bất kỳ** thành phần nào | `@trace.sc_version` += `0.1` (vd `1.0` → `1.1`) |
469
+ | Giống hoàn toàn | **GIỮ NGUYÊN** — bump vô cớ sẽ tạo `DRIFT` giả, làm cờ mất giá trị |
470
+ | SC mới (chưa có trong bản cũ) | `1.0` |
471
+
472
+ *(Thay đổi ngoài 4 thành phần trên — `@trace.business_rules`, tag `@happy`/`@edge`, comment — KHÔNG bump: chúng không đổi hành vi mà code phải implement.)*
473
+
474
+ In danh sách SC được bump vào report cuối để người dùng biết cái nào sẽ hiện `DRIFT`.
419
475
 
420
476
  ---
421
477
 
@@ -436,7 +492,19 @@ sc_id\tsc_title\tspec_ver\tgen_ver\timplemented_by\ttest_count\ttest_classes\tde
436
492
  - SC đã có trong `.tsv` VÀ `spec_ver` không đổi → chỉ cập nhật: `sc_title`, `prd_version`, `bdd_version`, `prd_status`, `uc_status`, `last_updated`. Giữ nguyên các cột khác.
437
493
  - SC đã có trong `.tsv` VÀ `spec_ver` đổi (scenario bị sửa) → cập nhật: `sc_title`, `spec_ver`, `prd_version`, `bdd_version`, `prd_status`, `uc_status`, `last_updated` VÀ set `status = DRIFT` ngay (để TSV phản ánh drift mà không cần đợi `/validate-traces`). Giữ nguyên `gen_ver`, `implemented_by`, `test_count`, `test_classes`, `tech_doc_revision`, `fe_tech_doc_revision`.
438
494
  - SC mới (thêm trong lần gen lại này) → append row mới với `gen_ver`, `implemented_by`, `test_count`, `test_classes`, `dev_selftest`, `dev_selftest_at`, `qc_status`, `qc_run_at`, `qc_owner`, `qc_blocked_by`, `tech_doc_revision`, `fe_tech_doc_revision` đều set `—`.
439
- - SC không còn trong `.feature` (bị xoá) xoá row của nó. *(An toàn: sổ này chỉ chứa scenario của `{active_platform}`, so với `.feature` của chính platform đó không bao giờ đụng scenario platform khác.)*
495
+ - SC không còn trong `.feature` (bị xoá / gộp / đổi số) **phụ thuộc SC đó đã code chưa:**
496
+ - `implemented_by == —` (**chưa** có code) → **xoá row**. Không có gì mồ côi.
497
+ - `implemented_by != —` (**ĐÃ** có code) → **GIỮ row**, set `status = ORPHANED`, giữ nguyên `implemented_by` / `test_count` / `test_classes` / các cột qc, cập nhật `last_updated`. **KHÔNG xoá** — xoá row thì method đó thành vô hình: không `UNTRACKED`, không `GAP`, không `DRIFT`, không xuất hiện ở report nào, mà vẫn nằm trong code và vẫn được caller gọi. Coverage còn *đẹp hơn* thực tế vì mẫu số nhỏ đi.
498
+ In cảnh báo nổi bật ở report cuối:
499
+ ```
500
+ ⚠️ ORPHANED — {UC-ID}-SC{N} "{sc_title}" đã bị xoá khỏi .feature nhưng còn code:
501
+ {implemented_by} (+ {test_count} test: {test_classes})
502
+ Không tự hết — chọn MỘT:
503
+ (a) behavior không còn cần → xoá method + test, rồi xoá row khỏi .tsv
504
+ (b) SC bị xoá do nhầm → đưa scenario trở lại .feature (row về DRIFT/OK bình thường)
505
+ (/validate-traces giữ cờ ORPHANED 🔴 và chặn "pass" tới khi xử lý xong.)
506
+ ```
507
+ *(An toàn: sổ này chỉ chứa scenario của `{active_platform}`, so với `.feature` của chính platform đó — không bao giờ đụng scenario platform khác.)*
440
508
 
441
509
  **Giá trị ghi cho mỗi scenario:**
442
510
 
@@ -487,9 +555,9 @@ Next (spec repo):
487
555
  → Sau khi gen hết platform: commit + push + báo team dev
488
556
  → Team dev đọc BDD từ spec submodule — không chạy /generate-bdd ở phía họ
489
557
 
490
- [Umbrella mode — service: {active_service}]
558
+ [Umbrella mode — service: {active_service} · platform: {active_platform} (suy từ module {active_module})]
491
559
  Files:
492
- {paths.specs_dir}/{domain}/{prd-slug}/bdd/{TICKET-ID}-UC1-{slug}.feature ({N} scenarios)
560
+ {paths.specs_dir}/{domain}/{prd-slug}/bdd/{active_platform}/{TICKET-ID}-UC1-{slug}.feature ({N} scenarios)
493
561
  Trace:
494
562
  {paths.trace_dir}/{domain}/{prd-slug}/{TICKET-ID}-UC1-{active_platform}.tsv ({N} rows)
495
563
  Next (umbrella):
@@ -497,5 +565,11 @@ Next (umbrella):
497
565
  → /generate-tech-docs {feature-file}
498
566
  → /generate-code {feature-file}
499
567
 
568
+ {chỉ khi gen lại VÀ có ≥1 SC bị bump — ngược lại bỏ cả khối}
569
+ 🔄 sc_version đã bump (scenario đổi nội dung → code cũ lỗi thời):
570
+ {UC-ID}-SC2 1.0 → 1.1 {sc_title}
571
+ {UC-ID}-SC5 1.2 → 1.3 {sc_title}
572
+ → {n} SC này sẽ hiện DRIFT ở /validate-traces. Sinh lại code: /generate-code {feature-file}
573
+
500
574
  📊 Living Docs: chạy /validate-traces (hoặc /sync) để push trace này lên dashboard spec-module.
501
575
  ```