@educa-corp/sdd-framework 0.3.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 (126) 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 +82 -17
  6. package/commands/define-product.md +82 -17
  7. package/commands/dev-gen-test.md +82 -17
  8. package/commands/dev-run-test.md +84 -18
  9. package/commands/dev-run-test.tmpl +2 -1
  10. package/commands/dev-smoke-test.md +82 -17
  11. package/commands/fix-bug.md +137 -20
  12. package/commands/fix-bug.tmpl +29 -3
  13. package/commands/generate-architecture.md +82 -17
  14. package/commands/generate-bdd.md +187 -44
  15. package/commands/generate-bdd.tmpl +92 -17
  16. package/commands/generate-code.md +115 -20
  17. package/commands/generate-code.tmpl +33 -3
  18. package/commands/generate-design-spec.md +82 -17
  19. package/commands/generate-prd.md +82 -17
  20. package/commands/generate-spec-manifest.md +82 -17
  21. package/commands/generate-tech-docs.md +85 -20
  22. package/commands/generate-tech-docs.tmpl +2 -2
  23. package/commands/learn.md +82 -17
  24. package/commands/map-testids.md +82 -17
  25. package/commands/propose-scenario.md +102 -19
  26. package/commands/propose-scenario.tmpl +20 -2
  27. package/commands/qc-analyze.md +82 -17
  28. package/commands/qc-design-test.md +82 -17
  29. package/commands/qc-plan.md +82 -17
  30. package/commands/qc-report.md +82 -17
  31. package/commands/qc-review.md +82 -17
  32. package/commands/qc-run-test.md +104 -19
  33. package/commands/qc-run-test.tmpl +22 -2
  34. package/commands/refine-prd.md +82 -17
  35. package/commands/report-bug.md +82 -17
  36. package/commands/review-code.md +122 -19
  37. package/commands/review-code.tmpl +40 -2
  38. package/commands/review-context.md +124 -21
  39. package/commands/review-context.tmpl +42 -4
  40. package/commands/review-tech-docs.md +113 -19
  41. package/commands/review-tech-docs.tmpl +31 -2
  42. package/commands/setup-ai-first.md +35 -16
  43. package/commands/setup-ai-first.tmpl +19 -6
  44. package/commands/sync.md +15 -4
  45. package/commands/sync.tmpl +12 -2
  46. package/commands/update-framework.md +40 -2
  47. package/commands/update-framework.tmpl +37 -0
  48. package/commands/validate-traces.md +231 -25
  49. package/commands/validate-traces.tmpl +149 -8
  50. package/core/FRAMEWORK_VERSION +1 -1
  51. package/core/README.md +56 -0
  52. package/core/commands/debug.md +82 -17
  53. package/core/commands/define-product.md +82 -17
  54. package/core/commands/dev-gen-test.md +82 -17
  55. package/core/commands/dev-run-test.md +84 -18
  56. package/core/commands/dev-smoke-test.md +82 -17
  57. package/core/commands/fix-bug.md +137 -20
  58. package/core/commands/generate-architecture.md +82 -17
  59. package/core/commands/generate-bdd.md +187 -44
  60. package/core/commands/generate-code.md +115 -20
  61. package/core/commands/generate-design-spec.md +82 -17
  62. package/core/commands/generate-prd.md +82 -17
  63. package/core/commands/generate-spec-manifest.md +82 -17
  64. package/core/commands/generate-tech-docs.md +85 -20
  65. package/core/commands/learn.md +82 -17
  66. package/core/commands/map-testids.md +82 -17
  67. package/core/commands/propose-scenario.md +102 -19
  68. package/core/commands/qc-analyze.md +82 -17
  69. package/core/commands/qc-design-test.md +82 -17
  70. package/core/commands/qc-plan.md +82 -17
  71. package/core/commands/qc-report.md +82 -17
  72. package/core/commands/qc-review.md +82 -17
  73. package/core/commands/qc-run-test.md +104 -19
  74. package/core/commands/refine-prd.md +82 -17
  75. package/core/commands/report-bug.md +82 -17
  76. package/core/commands/review-code.md +122 -19
  77. package/core/commands/review-context.md +124 -21
  78. package/core/commands/review-tech-docs.md +113 -19
  79. package/core/commands/setup-ai-first.md +35 -16
  80. package/core/commands/sync.md +15 -4
  81. package/core/commands/update-framework.md +40 -2
  82. package/core/commands/validate-traces.md +231 -25
  83. package/core/modules/android-compose/stack-profile.yaml +1 -1
  84. package/core/modules/flutter/stack-profile.yaml +1 -1
  85. package/core/modules/ios-swiftui/stack-profile.yaml +1 -1
  86. package/core/modules/java-spring/stack-profile.yaml +1 -1
  87. package/core/modules/nextjs/stack-profile.yaml +1 -1
  88. package/core/modules/nuxt/stack-profile.yaml +1 -1
  89. package/core/modules/phaser-game/stack-profile.yaml +1 -1
  90. package/core/modules/php-laravel/stack-profile.yaml +1 -1
  91. package/core/modules/qc-playwright/stack-profile.yaml +1 -1
  92. package/core/modules/react/stack-profile.yaml +1 -1
  93. package/core/modules/react-native/stack-profile.yaml +1 -1
  94. package/core/modules/vue/stack-profile.yaml +1 -1
  95. package/core/rules/workflow.md +11 -0
  96. package/core/steps/context-loader.md +66 -7
  97. package/core/steps/gate.md +13 -8
  98. package/core/steps/report-footer.md +3 -2
  99. package/core/templates/README.md +47 -0
  100. package/core/templates/feature.template +13 -10
  101. package/core/templates/project-context.yaml +49 -17
  102. package/core/templates/tech-design.template.md +1 -1
  103. package/docs/02-concepts/traceability.md +29 -6
  104. package/docs/04-reference/trace-schema.md +128 -37
  105. package/modules/android-compose/stack-profile.yaml +1 -1
  106. package/modules/flutter/stack-profile.yaml +1 -1
  107. package/modules/ios-swiftui/stack-profile.yaml +1 -1
  108. package/modules/java-spring/stack-profile.yaml +1 -1
  109. package/modules/nextjs/stack-profile.yaml +1 -1
  110. package/modules/nuxt/stack-profile.yaml +1 -1
  111. package/modules/phaser-game/stack-profile.yaml +1 -1
  112. package/modules/php-laravel/stack-profile.yaml +1 -1
  113. package/modules/qc-playwright/stack-profile.yaml +1 -1
  114. package/modules/react/stack-profile.yaml +1 -1
  115. package/modules/react-native/stack-profile.yaml +1 -1
  116. package/modules/vue/stack-profile.yaml +1 -1
  117. package/package.json +50 -49
  118. package/rules/workflow.md +11 -0
  119. package/scripts/migrate-bdd-platform.js +286 -0
  120. package/steps/context-loader.md +66 -7
  121. package/steps/gate.md +13 -8
  122. package/steps/report-footer.md +3 -2
  123. package/templates/README.md +47 -0
  124. package/templates/feature.template +13 -10
  125. package/templates/project-context.yaml +49 -17
  126. package/templates/tech-design.template.md +1 -1
@@ -34,23 +34,23 @@ Hiển thị và chờ phản hồi:
34
34
  ```
35
35
  ⚙️ MODEL CHECK
36
36
  ──────────────────────────────────────────────────────────────────
37
- Recommended : claude-opus-4 (hoặc model Opus mới nhất)
37
+ Recommended : model Opus mới nhất
38
38
  Why needed : Phân tích spec, review kiến trúc, sinh code đòi hỏi
39
- suy luận sâu. Model nhỏ hơn dễ bỏ sót edge case.
39
+ suy luận sâu. Model nhỏ hơn (Haiku/Sonnet) dễ bỏ sót edge case.
40
40
 
41
41
  Cách đổi trong Claude Code:
42
- SettingsModel chọn "claude-opus"
43
- • hoặc: /modelchọn claude-opus
42
+ /modelchọn model Opus
43
+ • hoặc: SettingsModel
44
44
 
45
- Đang chạy claude-opus?
46
- Y — đúng, đang dùng claude-opus → tiếp tục
45
+ Đang chạy một model Opus?
46
+ Y — đúng → tiếp tục
47
47
  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)
48
48
  ──────────────────────────────────────────────────────────────────
49
49
  ```
50
50
 
51
51
  - "Y" → tiếp tục sang Bước 1.
52
52
  - "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).
53
- - "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."
53
+ - "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."
54
54
 
55
55
  ## Bước 1 — Xác định Target File
56
56
 
@@ -59,7 +59,12 @@ Hiển thị và chờ phản hồi:
59
59
  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/`):
60
60
  - **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 đó.
61
61
  - **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.)*
62
- - **Lệnh tech-docs**: `{specs_dir}/{domain}/*/tech-docs/{UC-ID}*-tech-design*.md`.
62
+ - **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:
63
+ - `$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`.
64
+ - `$ARGUMENTS` là **TICKET-ID** → glob trực tiếp như trên.
65
+ - Chưa biết domain → `{specs_dir}/*/*/tech-docs/{TICKET-ID}-tech-design.md`.
66
+ - 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.
67
+ *(Đừ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`.)*
63
68
  - **Lệnh design-spec**: `{specs_dir}/{domain}/*/design-spec/{TICKET-ID}*.md`.
64
69
 
65
70
  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.
@@ -189,23 +194,82 @@ Nếu có section `services`:
189
194
  - Nếu target không mang `@trace.platform` (vd target là PRD `.md`), thử suy từ segment `bdd/{platform}/` trong path target.
190
195
  - Nếu vẫn không xác định được → `active_platform = null`.
191
196
 
192
- **2. Route tới service** — nếu active domain khớp một key trong `services`. Giá trị `services.{domain}` có **hai dạng**; nhận dạng bằng việc có `path` trực tiếp hay không:
197
+ **2. Route tới service** — nếu active domain khớp một key trong `services`.
198
+
199
+ Hình dung việc này như **tra địa chỉ**: đi từ `domain`, có thể qua `platform`, có thể qua `prd_slug`, cho tới khi chỉ còn đúng một submodule.
200
+
201
+ Thứ trỏ tới đích gọi là một **service entry**. Nó chỉ có hai kiểu:
202
+
203
+ | Kiểu | Nhận ra bằng | Nghĩa |
204
+ |---|---|---|
205
+ | **Đã chốt** | có `path` | Xong — đây là submodule cần tìm |
206
+ | **Tra tiếp** | có `by_prd_slug` | Ô này còn nhiều submodule → tra thêm một nấc bằng `prd_slug` (2c) |
207
+
208
+ **Kiểm tra hợp lệ TRƯỚC khi dùng một entry** (làm ngay, đừng đợi tới 2a/2b/2c — sai ở đây mà đi tiếp là route nhầm repo mà không báo gì):
209
+
210
+ | Entry trông thế nào | Xử lý |
211
+ |---|---|
212
+ | có `path`, không có `by_prd_slug` | hợp lệ → dùng |
213
+ | có `by_prd_slug`, không có `path` | hợp lệ → tra tiếp (2c) |
214
+ | **có CẢ HAI** | ❌ lỗi cấu hình → `active_service = unresolved`. **KHÔNG** ưu tiên `path` rồi bỏ qua `by_prd_slug` — như vậy mọi feature sẽ âm thầm route về cùng một repo. Báo đúng key sai để người dùng sửa. |
215
+ | **không có cái nào** (và cũng không phải map platform) | ❌ lỗi cấu hình → `unresolved`, nêu rõ entry thiếu `path`/`by_prd_slug` |
216
+ | `by_prd_slug` chứa entry lại có `by_prd_slug` | ❌ lỗi cấu hình → `unresolved`. Chỉ tra đúng **một** nấc slug, không đệ quy |
217
+
218
+ Còn `services.{domain}` thì có thể là **một service entry** (chốt luôn ở cấp domain), hoặc **một map platform → service entry** (phải qua nấc platform trước). Nhận dạng theo đúng thứ tự này:
219
+
220
+ | Thấy gì trong `services.{domain}` | Đi nhánh |
221
+ |---|---|
222
+ | có `path` | **2a** — chốt luôn |
223
+ | có `by_prd_slug` | **2c** — tra bằng `prd_slug` |
224
+ | không có cả hai (chỉ có các sub-key `system`/`web`/`app`…) | **2b** — tra bằng `platform`, rồi lặp lại đúng bảng này cho entry con |
193
225
 
194
226
  **2a. Dạng phẳng** — `services.{domain}` có **trực tiếp** `path`/`module` (một domain ↔ một service, mọi platform về cùng submodule). Route như cũ:
195
227
  - Lưu `active_service` = `services.{domain}.path`
196
228
  - Lưu `active_service_module` = `services.{domain}.module`
197
229
  - Nếu service có `module` riêng → dùng nó làm `active_module` (override `tech_stack.module`)
198
230
 
199
- **2b. Dạng map-theo-platform** — `services.{domain}` **KHÔNG** có `path` trực tiếp mà chứa các sub-key platform (`system` / `web` / `app`), mỗi cái là `{ path, module }` (một business-domain trải trên nhiều platform/submodule). Route theo `active_platform` (bước 1b):
200
- - Nếu `active_platform` khớp một sub-key → `entry = services.{domain}.{active_platform}`; lưu `active_service = entry.path`, `active_service_module = entry.module` (→ `active_module`, override `tech_stack.module`).
201
- - Nếu `active_platform = null` (chưa xác định platform, vd đang thao tác cấp PRD) → **KHÔNG** chốt một service; đặt `active_service = multi` và lưu `service_candidates = services.{domain}` (toàn map platform→{path,module}). Lệnh cần một service cụ thể (`/generate-code`, `/dev-*`, `/fix-bug`) luôn chạy trên target `.feature` có platform nên sẽ resolve được ở lần chạy đó; lệnh cấp PRD (`/generate-prd`, `/refine-prd`) không cần service cụ thể.
231
+ **2b. Dạng map-theo-platform** — `services.{domain}` **KHÔNG** có `path` trực tiếp mà chứa các sub-key platform (`system` / `web` / `app`), mỗi cái là một **service entry** (một business-domain trải trên nhiều platform/submodule). Route theo `active_platform` (bước 1b):
232
+ - Nếu `active_platform` khớp một sub-key → `entry = services.{domain}.{active_platform}`. Nếu `entry` có `path` → lưu `active_service = entry.path`, `active_service_module = entry.module` (→ `active_module`, override `tech_stack.module`). Nếu `entry` có `by_prd_slug` → **đi tiếp sang 2c** với entry đó.
233
+ - Nếu `active_platform = null` (chưa xác định platform, vd đang thao tác cấp PRD) → **KHÔNG** chốt một service; đặt `active_service = multi`, `service_candidates_kind = platform`, và lưu `service_candidates` = map platform→`{path, module}`. **Làm phẳng luôn ở đây:** platform nào có entry `by_prd_slug` thì giải bằng `prd_slug` hiện tại (target cấp PRD vẫn nằm trong một feature-package nên `prd_slug` đã biết từ bước 1) `service_candidates.{platform}` vẫn là `{path, module}` phẳng. Nếu `prd_slug` không khớp key nào, ghi platform đó là `unresolved` kèm lý do thay vì bỏ im. Nhờ vậy **mọi lệnh downstream chỉ cần biết một kiểu `service_candidates`**. Lệnh cần một service cụ thể (`/generate-code`, `/dev-*`, `/fix-bug`) luôn chạy trên target `.feature` có platform nên sẽ resolve được ở lần chạy đó; lệnh cấp PRD (`/generate-prd`, `/refine-prd`) không cần service cụ thể.
202
234
  - Nếu `active_platform` xác định nhưng không có sub-key tương ứng → `active_service = unresolved` (xem Fallback) với lý do "domain `{domain}` chưa cấu hình platform `{active_platform}`".
203
235
 
204
- *(Cả 2a/2b: override `paths.specs_dir`/`paths.tech_docs_dir` per-service CHỈ khi `setup.spec_source` KHÔNG được đặt. Khi `spec_source` ĐƯỢC đặt, MỌI BDD/tech-doc artifact liên team để bước 4 route sang spec repo; KHÔNG pin per-service đây.)*
236
+ **2c. Dạng map-theo-prd_slug** một service entry chứa `by_prd_slug` thay cho `path`: **một ô của bảng định tuyến ứng với NHIỀU submodule**, mỗi feature-package một submodule. Dùng khi một platform (hoặc cả một domain) bị chia thành nhiều repo theo feature — ví dụ mỗi mini-game webview là một repo riêng.
237
+
238
+ Đến đây `prd_slug` đã được trích ở bước 1 (không cần detect thêm gì). Route:
239
+
240
+ - Nếu `prd_slug` khớp một key dưới `by_prd_slug` → `entry = {…}.by_prd_slug.{prd_slug}`; lưu `active_service = entry.path`, `active_service_module = entry.module` (→ `active_module`, override `tech_stack.module`).
241
+ **"Khớp" ở đây là khớp CHÍNH XÁC toàn chuỗi, phân biệt hoa/thường.** KHÔNG prefix, KHÔNG bỏ hậu tố, KHÔNG so gần đúng: `dap-chuot-v2` **không** khớp key `dap-chuot`; `Ban-Cung` **không** khớp `ban-cung`. Feature mới tách ra từ một feature cũ là một repo khác cho tới khi có người khai nó vào bảng.
242
+ - Nếu `prd_slug = null` (chưa xác định feature-package — chỉ xảy ra khi không có target file, vd `$ARGUMENTS` rỗng) → **KHÔNG** chốt một service; đặt `active_service = multi`, `service_candidates_kind = prd_slug`, và lưu `service_candidates` = toàn map `by_prd_slug` (slug → `{path, module}`).
243
+ ⚠️ Đây là kiểu `service_candidates` **khác** với 2b — lệnh nào duyệt `service_candidates` theo platform (vd `/generate-bdd` sinh `bdd/{platform}/`) phải kiểm `service_candidates_kind = platform` trước; gặp `prd_slug` thì DỪNG và yêu cầu người dùng chỉ rõ target, đừng coi slug là platform.
244
+ - Nếu `prd_slug` xác định nhưng **không** có key tương ứng → `active_service = unresolved` với lý do rõ: "domain `{domain}`{, platform `{active_platform}`} chưa cấu hình prd_slug `{prd_slug}`". **KHÔNG** tự đoán submodule gần đúng theo tên.
245
+
246
+ Vị trí đặt `by_prd_slug` — hợp lệ ở **cả hai cấp**:
247
+ - **Dưới một platform** (lồng trong 2b): `services.{domain}.{platform}.by_prd_slug` — platform đó có nhiều repo, các platform khác vẫn `{path, module}` như thường.
248
+ - **Ngay dưới domain** (thay cho `path` của 2a): `services.{domain}.by_prd_slug` — domain không chia platform nhưng vẫn nhiều repo theo feature.
249
+
250
+ *(`by_prd_slug` lồng trong `by_prd_slug` là vô nghĩa — nếu gặp, coi là lỗi cấu hình: `active_service = unresolved`, nêu rõ để người dùng sửa file. Một entry vừa có `path` vừa có `by_prd_slug` cũng là lỗi cấu hình — báo lỗi, không âm thầm ưu tiên cái nào.)*
251
+
252
+ Ví dụ (một domain trải nhiều platform, riêng `webview` chia theo feature):
253
+ ```yaml
254
+ services:
255
+ learning:
256
+ system: { path: "backend", module: "java-spring" }
257
+ web: { path: "web-app", module: "nextjs" }
258
+ webview:
259
+ by_prd_slug:
260
+ dap-chuot: { path: "games/whac-a-mole", module: "phaser-game" }
261
+ ban-cung: { path: "games/archery", module: "phaser-game" }
262
+ ```
263
+ → target `specs/learning/dap-chuot/bdd/webview/UC1.feature` cho `domain = learning`,
264
+ `active_platform = webview`, `prd_slug = dap-chuot` → `active_service = games/whac-a-mole`.
265
+
266
+ *(Cả 2a/2b/2c: override `paths.specs_dir`/`paths.tech_docs_dir` per-service CHỈ khi `setup.spec_source` KHÔNG được đặt. Khi `spec_source` ĐƯỢC đặt, MỌI BDD/tech-doc là artifact liên team → để bước 4 route sang spec repo; KHÔNG pin per-service ở đây.)*
205
267
 
206
268
  **3. Fallback**:
207
269
  - Không phát hiện được domain, hoặc domain không khớp key nào trong `services` → giữ path mặc định từ Bước 1, đặt `active_service = unresolved`.
208
270
  - Domain khớp một map-theo-platform (2b) nhưng `active_platform` xác định mà thiếu sub-key tương ứng → `active_service = unresolved`, ghi lý do rõ để lệnh DỪNG báo lỗi cấu hình (không tự đoán platform).
271
+ - Entry là map-theo-prd_slug (2c) nhưng `prd_slug` xác định mà thiếu key tương ứng → `active_service = unresolved`, ghi lý do rõ (không tự đoán submodule).
272
+ - Entry sai cấu trúc (vừa có `path` vừa có `by_prd_slug`, hoặc `by_prd_slug` lồng nhau) → `active_service = unresolved`, nêu đúng key sai để người dùng sửa `project-context.yaml`.
209
273
 
210
274
  **4. Tự động override theo spec source** — nếu `setup.spec_source` được đặt VÀ path tương ứng chưa được set tường minh trong `paths:`:
211
275
  - Override `paths.specs_dir` → `{spec_source}/specs` — **luôn khi `spec_source` được đặt.** Mọi spec artifact (PRD, BDD, tech-docs, design-spec) nằm dưới gốc spec thống nhất trong spec repo dùng chung theo bố cục feature-package: `{spec_source}/specs/{domain}/{prd-slug}/`. Mọi umbrella (FE/App/BE) đều đọc từ đây. *(`specs/` theo service chỉ khi không có `spec_source`.)*
@@ -355,7 +419,7 @@ active_module = tech_stack.module (vd: "java-spring", "react", "flutter")
355
419
  | `platform_type` | Modules |
356
420
  |---|---|
357
421
  | `backend` | `java-spring`, `golang`, `dotnet`, `php-laravel`, `context-engineering` |
358
- | `web-frontend` | `react`, `nextjs`, `vue`, `nuxt`, `angular` |
422
+ | `web-frontend` | `react`, `nextjs`, `vue`, `nuxt`, `angular`, `phaser-game` |
359
423
  | `mobile` | `flutter`, `react-native`, `ios-swiftui`, `android-compose` |
360
424
 
361
425
  Nếu `tech_stack.module` rỗng hoặc không nhận diện được → set `platform_type = "unknown"` và gắn cờ ⚠️ trong recap Bước 7.
@@ -402,7 +466,7 @@ Dict : {loaded — N canonical terms, M banned terms | missing}
402
466
  Entities : {loaded — EntityA, EntityB, EntityC | missing}
403
467
  Lessons : {loaded — N guardrails | chưa có}
404
468
  Platform : {active_platform: system | web | app | — nếu chưa xác định}
405
- Service : {active_service} ({active_service_module}) | multi (map-theo-platform, chốt khi target platform) | single-service
469
+ Service : {active_service} ({active_service_module}) [← domain{/platform}{/prd_slug} nếu route qua by_prd_slug] | multi (map-theo-platform hoặc map-theo-prd_slug, chốt khi target đủ platform/prd_slug) | single-service
406
470
  Svc Root : {service_root} — đã nạp conventions + trace_dir từ config service | —
407
471
  Status : {FULL | PARTIAL — thiếu: CLAUDE.md / business-dict / core-entities | MINIMAL}
408
472
  ```
@@ -472,17 +536,38 @@ Nếu version SC trong `.feature` khác `spec_ver` của `.tsv` → cập nhật
472
536
 
473
537
  Cũng phát hiện SC có trong `.feature` (platform đó) nhưng thiếu trong `.tsv` → thêm row mới với `status: UNTRACKED`. *(sc_id trùng số giữa các platform là 2 scenario khác nhau → mỗi sổ platform giữ tập SC riêng, không dedupe chéo platform.)*
474
538
 
539
+ ### Step 2b — Reverse audit (bắt tag mồ côi)
540
+
541
+ *Step 2 đi chiều **spec → code** (mỗi row TSV, SC đó implement tới đâu). Step này đi **chiều ngược** — bắt lớp lỗi mà Step 2 cấu trúc không thể thấy: code trỏ vào một scenario **không còn tồn tại**. Xảy ra khi gen lại BDD làm một SC biến mất (gộp / đổi số / xoá) trong khi code implement nó vẫn nằm đó, vẫn được caller gọi.*
542
+
543
+ **Quét (gộp vào cùng lượt quét code của Step 5b — không thêm pass mới):** dưới `{code_base_package}` (CLAUDE.md §2) + `{paths.src_dir}`, thu mọi `@trace.implements={UC-ID}-SC{N}`; trong thư mục test thu mọi `@trace.verifies={UC-ID}-SC{N}`.
544
+
545
+ Với mỗi tag, hỏi: `SC{N}` đó có tồn tại trong `.feature` của đúng platform không?
546
+
547
+ | Điều kiện | Cờ | Ý nghĩa |
548
+ |---|---|---|
549
+ | SC không có trong `.feature`, **và** row TSV còn (đã mang `status = ORPHANED` do `/generate-bdd` giữ lại) | `ORPHANED` 🔴 | Đã được ghi nhận — đang chờ người quyết định |
550
+ | SC không có trong `.feature`, **và** không có row TSV nào | `TRACE_ORPHAN` 🔴 | Nợ cũ: row bị xoá bởi version trước, hoặc tag ghi sai UC/SC id ngay từ đầu. **Không có chỗ nào khác bắt được cái này.** |
551
+ | SC có trong `.feature` | *(sạch)* | |
552
+
553
+ **Với `TRACE_ORPHAN`:** đừng tự tạo row TSV (chưa biết nó nên là scenario nào) và **đừng** sửa/xoá code. Chỉ report kèm đúng hai đường ra ở §Output.
554
+
555
+ Không tìm thấy tag mồ côi nào → bỏ qua im lặng.
556
+
475
557
  ### Step 3 — Tính `status` theo từng scenario
476
558
 
477
559
  Áp dụng quy tắc theo thứ tự ưu tiên (first-match-wins):
478
560
 
479
561
  | Rule | Status | Điều kiện |
480
562
  |------|--------|-----------|
563
+ | 0 | `ORPHANED` | SC của row này **không còn trong `.feature`** (Step 2b) AND `implemented_by != —` — code trỏ vào scenario đã bị xoá |
481
564
  | 1 | `UNTRACKED` | `implemented_by == —` (chưa sinh code) |
482
565
  | 2 | `DRIFT` | `implemented_by != —` AND `spec_ver != gen_ver` (spec đã đổi sau lần codegen — code cũ, **ưu tiên regen trước khi test**) |
483
566
  | 3 | `GAP` | `implemented_by != —` AND (`test_count == —` OR `test_count == 0`) |
484
567
  | 4 | `OK` | tất cả: `spec_ver == gen_ver`, `implemented_by != —`, `test_count > 0` |
485
568
 
569
+ > **Vì sao ORPHANED là Rule 0 (xét TRƯỚC mọi rule khác):** 4 rule kia đều giả định scenario **còn tồn tại** — chúng trả lời "spec này implement tới đâu". `ORPHANED` trả lời câu ngược: "code này còn spec nào bảo lãnh không". Nếu để rule khác thắng, mỗi giá trị đều **route người dùng sang một lệnh vô nghĩa**: `GAP` → `/dev-gen-test` sinh test cho scenario không tồn tại · `DRIFT` → `/generate-code` cố sinh lại từ SC đã bị xoá · `OK` → coi là sạch và cho tạo PR. Row cũng KHÔNG được xoá — xoá đi thì code thành vô hình (chính là bug gốc).
570
+
486
571
  > **Vì sao DRIFT xét trước GAP:** một scenario đã có code, chưa test, **và** spec vừa drift phải hiện `DRIFT` (không phải `GAP`) — vì `generate-code` xử `GAP` = "skip codegen, chạy /dev-gen-test" còn `DRIFT` = "regenerate". Nếu GAP thắng, code lỗi thời bị bỏ qua và test lại sinh trên code cũ. UNTRACKED vẫn phải là Rule 1 để scenario chưa code (gen_ver `—`) không lọt vào DRIFT.
487
572
 
488
573
  ### Step 4 — PRD version drift check
@@ -505,6 +590,26 @@ Mỗi PRD có **một** tech-doc gộp `{paths.tech_docs_dir}/{domain}/{prd-slug
505
590
 
506
591
  Skip cột nào chưa có revision đã lưu (`—`), hoặc cả UC chưa có tech-doc.
507
592
 
593
+ ### Step 5c — BDD version drift check
594
+
595
+ *Đối xứng với Step 4 (PRD drift). Trước đây tầng BDD là tầng DUY NHẤT không có cờ drift — dù `/generate-code` vẫn ghi `@trace.bdd_version` vào code và JSON report vẫn lưu nó. Dữ liệu có, chỉ thiếu phép so.*
596
+
597
+ **Chiều BDD → code.** Với mỗi UC × platform, so:
598
+ - `@trace.bdd_version` **hiện tại** của `.feature` (`bdd/{platform}/{UC-ID}*.feature`)
599
+ - `@trace.bdd_version` trong các file code implement UC đó
600
+
601
+ Code mang version cũ hơn → gắn cờ `BDD_DRIFT`. Kèm theo, liệt kê các SC của UC đó đang `DRIFT` (từ Step 3) để chỉ đúng chỗ cần regen — `bdd_version` nói "file đã đổi", `sc_version` nói "đổi ở SC nào".
602
+
603
+ > **Bổ trợ, không thay thế `sc_version`:** `sc_version` bắt thay đổi trong **thân scenario**. `bdd_version` bắt thay đổi ở **cấp file** mà `sc_version` không thấy: `Background`, `@trace.dataset`, khối BUSINESS DEFINITION, Popup/Modal Lifecycle, Display Logic Matrix, Coverage Matrix. Code sinh ra phụ thuộc cả hai.
604
+
605
+ **File code KHÔNG có tag `@trace.bdd_version`** (code sinh trước khi tag này bắt buộc) → không kết luận drift được. Đếm và in **một dòng** tổng hợp:
606
+ ```
607
+ ⚠️ {n} file thiếu tag @trace.bdd_version → drift detection mù ở các file này.
608
+ Bổ sung tag khi sửa file lần tới (/review-code lăng kính Traceability sẽ bắt).
609
+ ```
610
+
611
+ **Chiều BDD → tech-doc** *(chỉ report, cổng chặn nằm ở `/review-tech-docs`)*: đọc map `@trace.bdd_versions` của tech-doc gộp; platform nào có `.feature` **mới hơn** entry trong map → gắn cờ `TECHDOC_STALE_VS_BDD`. Đây là ca nguy hiểm hơn drift-về-code: `/generate-code` DS3 thấy tech-doc `approved` sẽ lấy shape §4 **nguyên văn** làm contract "đã chốt", nên contract dựng từ BDD cũ sẽ lan thẳng vào code.
612
+
508
613
  ### Step 5b — Seam & Stub Audit (mồ côi khi ghép luồng)
509
614
 
510
615
  *Bắt lỗi "gen từng BDD thì đúng, ghép cả luồng thì hỏng": chỗ giả lập còn rỗng trong khi hàng thật đã tồn tại ở nơi khác — luồng chạy vào no-op / hàm thật không ai gọi. Build vẫn xanh, test từng-UC vẫn xanh, nên không cổng nào khác bắt được. Hai loại: `seam` (port cross-UC chưa nối) và `stub` (method trắng nội-feature chưa lấp).*
@@ -549,6 +654,7 @@ Không tìm thấy seam/stub nào → bỏ qua im lặng.
549
654
 
550
655
  Với mỗi file `.tsv` đã xử lý: ghi `spec_ver`, `status`, `last_updated` đã cập nhật lại disk.
551
656
  Đồng thời **đồng bộ `uc_status` ← `@trace.status`** của file `.feature` tương ứng (header `.feature` là nguồn-sự-thật về duyệt BDD — người đặt `approved` sau khi review sạch, giống PO đặt PRD Metadata `Status`). Nhờ vậy `approved_ucs` trên dashboard phản ánh đúng thay vì luôn = 0.
657
+ Và **đồng bộ `prd_status` ← `| **Status** |`** của PRD tương ứng (`{paths.specs_dir}/{domain}/{prd-slug}/{TICKET-ID}-{prd-slug}.md`) — đối xứng với `uc_status`: PRD Metadata là nguồn-sự-thật về duyệt PRD. Không có bước này thì `prd_status` là **write-once** (chỉ `/generate-bdd` ghi một lần) và sẽ giữ `approved` vĩnh viễn sau khi `/refine-prd` hay `/review-context --fix` reset PRD về `draft`. *(Step 4 đã đọc file PRD này rồi — không phát sinh I/O.)*
552
658
  **Đừng** sửa `dev_selftest`/`dev_selftest_at` (do `/dev-run-test` sở hữu) hay `qc_status`/`qc_run_at`/`qc_owner`/`qc_blocked_by` (do `/qc-run-test` + `/report-bug` sở hữu); lệnh này chỉ đọc chúng cho report.
553
659
 
554
660
  ### Step 7 — Tính aggregate cho dashboard
@@ -559,12 +665,24 @@ approved_prds = PRDs with | Status | approved
559
665
  total_ucs = count distinct UC-IDs across all .tsv files (strip the -{platform} suffix from the filename)
560
666
  approved_ucs = UCs with uc_status == approved
561
667
  draft_ucs = UCs with uc_status == draft
562
- total_scs = total rows across all .tsv files (a UC's SCs are counted per platform — no cross-platform dedupe by sc_id)
668
+ total_scs = rows across all .tsv files WHERE status != ORPHANED
669
+ # (a UC's SCs are counted per platform — no cross-platform dedupe by sc_id)
670
+ # ORPHANED bị LOẠI khỏi mẫu số: nó không còn là scope nữa (scenario đã bị xoá
671
+ # khỏi .feature). Tính vào mẫu số sẽ bóp méo coverage theo hướng xấu đi vì một
672
+ # thứ không ai cần implement. Nó được đếm riêng ở orphaned_count + cờ 🔴.
563
673
  code_coverage = rows where implemented_by != — / total_scs
564
674
  test_coverage = rows where test_count > 0 / total_scs
565
675
  drift_count = rows where status == DRIFT
566
676
  untracked_count = rows where status == UNTRACKED
567
677
  gap_count = rows where status == GAP
678
+ fe_on_mock = rows where fe_phase == ui # FE đã có UI nhưng CÒN DÙNG MOCK — chưa wire API thật
679
+ fe_integrated = rows where fe_phase == integrated # FE đã wire adapter thật theo tech-doc §4.5.4
680
+ # fe_phase trả lời câu của PM: "màn nào demo được nhưng chưa nối backend?". Row `ui` là
681
+ # công việc CHƯA XONG dù status có thể đã là OK (có code + có test trên mock).
682
+ orphaned_count = rows where status == ORPHANED # code còn, scenario đã bị xoá khỏi .feature (Step 2b/Rule 0)
683
+ trace_orphan_count = số tag @trace.implements/@trace.verifies trỏ vào SC không tồn tại VÀ không có row TSV (Step 2b)
684
+ bdd_drift_count = số UC×platform bị cờ BDD_DRIFT (code mang @trace.bdd_version cũ hơn .feature — Step 5c)
685
+ techdoc_stale_vs_bdd_count = số platform mà tech-doc dựng từ bdd_version cũ hơn .feature hiện tại (Step 5c)
568
686
  seam_unwired_count = số seam bị cờ SEAM_UNWIRED (hàng thật đã có nhưng consumer còn wire vào stub — Step 5b)
569
687
  stub_unresolved_count = số stub bị cờ STUB_UNRESOLVED (method còn trắng dù owner đã gen / có hàm song song — Step 5b)
570
688
  dev_selftest_passing = rows where dev_selftest == pass
@@ -608,6 +726,12 @@ Schema:
608
726
  "drift_count": 0,
609
727
  "gap_count": 0,
610
728
  "untracked_count": 0,
729
+ "fe_on_mock": 0,
730
+ "fe_integrated": 0,
731
+ "orphaned_count": 0,
732
+ "trace_orphan_count": 0,
733
+ "bdd_drift_count": 0,
734
+ "techdoc_stale_vs_bdd_count": 0,
611
735
  "seam_unwired_count": 0,
612
736
  "stub_unresolved_count": 0,
613
737
  "dev_selftest_passing": 0,
@@ -655,6 +779,7 @@ Schema:
655
779
  "tech_doc_revision": 0,
656
780
  "fe_tech_doc_revision": 0,
657
781
  "status": "OK | DRIFT | GAP | UNTRACKED",
782
+ "orphaned": false,
658
783
  "last_updated": "<YYYY-MM-DD>"
659
784
  }
660
785
  ]
@@ -696,6 +821,42 @@ Schema:
696
821
  "fix": "/generate-bdd <prd-file> then /generate-code <UC-ID>"
697
822
  }
698
823
  ],
824
+ "bdd_drift": [
825
+ {
826
+ "uc_id": "<UC-ID>",
827
+ "platform": "web | app | system",
828
+ "code_bdd_version": "<@trace.bdd_version trong code>",
829
+ "current_bdd_version": "<@trace.bdd_version của .feature>",
830
+ "drifted_scs": ["<SC đang DRIFT của UC này>"],
831
+ "fix": "/generate-code <feature-file>"
832
+ }
833
+ ],
834
+ "techdoc_stale_vs_bdd": [
835
+ {
836
+ "uc_id": "<UC-ID>",
837
+ "platform": "web | app | system",
838
+ "techdoc_bdd_version": "<entry trong map @trace.bdd_version của tech-doc>",
839
+ "current_bdd_version": "<@trace.bdd_version của .feature>",
840
+ "fix": "/generate-tech-docs <feature-file> then /review-tech-docs"
841
+ }
842
+ ],
843
+ "orphaned": [
844
+ {
845
+ "sc_id": "<SC-ID đã bị xoá khỏi .feature>",
846
+ "platform": "web | app | system",
847
+ "implemented_by": "<ClassName.method còn tồn tại>",
848
+ "test_classes": ["<test còn trỏ vào SC này>"],
849
+ "fix": "xoá code + test, HOẶC đưa scenario trở lại .feature"
850
+ }
851
+ ],
852
+ "trace_orphan": [
853
+ {
854
+ "tag": "@trace.implements | @trace.verifies",
855
+ "sc_id": "<SC-ID không tồn tại>",
856
+ "file": "<file mang tag>",
857
+ "fix": "sửa sc_id cho đúng SC hiện có, HOẶC xoá code/test nếu không còn cần"
858
+ }
859
+ ],
699
860
  "techdoc_drift": [
700
861
  {
701
862
  "uc_id": "<UC-ID>",
@@ -743,6 +904,10 @@ Schema:
743
904
  - `test_classes`: dùng `[]` (không phải `"—"`) khi không có test class
744
905
  - `tech_doc_revision` / `fe_tech_doc_revision`: dùng integer; `0` nếu chưa sinh
745
906
  - `code_coverage_pct` / `test_coverage_pct`: làm tròn về integer gần nhất (0–100)
907
+ - **`status` trong JSON CỐ TÌNH chỉ có 4 giá trị** `OK`/`DRIFT`/`GAP`/`UNTRACKED` — KHÔNG ghi `ORPHANED` vào field này. VS Code extension "Spec Driven Docs Tools" (sống **ngoài** repo này) switch trên `status`; thêm giá trị thứ 5 sẽ rơi vào nhánh không khớp và có thể làm row mất khỏi panel.
908
+ Row `ORPHANED` xuất ra JSON là: `"status": "DRIFT"` + `"orphaned": true`. Panel chưa hỗ trợ vẫn hiện nó như `DRIFT` — đủ đúng về nghĩa ("code không khớp spec, cần xử lý") và **không im lặng**; panel có đọc `orphaned` thì hiện nhãn riêng. Chi tiết đầy đủ luôn có ở `orphaned[]` và ở report terminal.
909
+ **TSV giữ nguyên chữ `ORPHANED`** trong cột `status` — TSV là nguồn-sự-thật, JSON chỉ là bản xuất cho panel.
910
+ - `orphaned` (boolean): `true` chỉ khi cột `status` của TSV là `ORPHANED`; mọi row khác ghi `false` (đừng bỏ trống — panel đọc field vắng dễ ra `undefined`).
746
911
  - Luôn ghi vào `{paths.trace_dir}/trace-report.json` bất kể domain filter — nếu có domain filter, chỉ gồm các PRD đó trong `prds[]` nhưng ghi domain vào field `domain`
747
912
  - **TSV `"—"` mapping**: khi đọc file TSV, map giá trị dash sang kiểu JSON: `implemented_by: "—"` → `null`; `test_count: "—"` → `0`; `test_classes: "—"` → `[]`; `tech_doc_revision: "—"` → `0`; `fe_tech_doc_revision: "—"` → `0`; `dev_selftest: "—"` → `"not_run"`; `dev_selftest_at: "—"` → `null`; `qc_status: "—"` → `"not_run"`; `qc_run_at: "—"` → `null`; `qc_owner: "—"` → `null`; `qc_blocked_by: "—"` → `null`
748
913
  - **Backward-compat:** TSV cũ có thể thiếu cột mới hơn trong header — coi cột vắng nào là giá trị rỗng của nó (đừng báo lỗi): `qc_owner`/`qc_blocked_by` (pre-19-col) → `null`; `fe_tech_doc_revision` (pre-22-col) → `0`. Lần `/generate-bdd` gen lại tiếp theo nâng header lên layout 22 cột hiện tại.
@@ -853,6 +1018,7 @@ Gợi ý lệnh kế tiếp hợp lý theo phase của workflow:
853
1018
  | /qc-run-test | `/qc-report {UC-ID}` rồi `/qc-review {UC-ID}` (review script) |
854
1019
  | /qc-review (script) | `/qc-report {UC-ID}` rồi tạo PR nếu APPROVED |
855
1020
  | /qc-report | `/validate-traces {UC-ID}` để làm mới Living Docs (qc_status) |
1021
+ | /map-testids | `/qc-design-test {UC-ID}` (QC dựng Page Object từ contract §4.5.6 vừa ghi) |
856
1022
  | /generate-tech-docs | `/review-tech-docs {tech-design-file}` |
857
1023
  | /review-tech-docs | `/generate-code {feature-file}` nếu APPROVED; sửa doc nếu NEEDS_FIX |
858
1024
  | /generate-code | Lần gen đầu → `/review-code {UC-ID}`; gen lại → `/dev-gen-test {UC-ID}` |
@@ -861,8 +1027,8 @@ Gợi ý lệnh kế tiếp hợp lý theo phase của workflow:
861
1027
  | /dev-run-test (failing) | `/fix-bug {ticket-id}` hoặc `/debug {error}` |
862
1028
  | /review-code | `/dev-smoke-test {UC-ID}` hoặc tạo PR |
863
1029
  | /dev-smoke-test | Tạo PR và link tới ticket |
864
- | /validate-traces | DRIFT/UNTRACKED → `/generate-code {UC-ID}`; GAP → `/dev-gen-test {UC-ID}`; tất cả OK tạo PR |
865
- | /fix-bug | Tạo PR link tới ticket |
1030
+ | /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** |
1031
+ | /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 |
866
1032
  | /debug | `/fix-bug {ticket-id}` nếu cần sửa |
867
1033
  | /report-bug | Gửi cho dev (`/fix-bug {BUG-ID}`); nếu thiếu coverage → `/propose-scenario {UC-ID}` |
868
1034
  | /propose-scenario | Báo PO/Dev review proposal trong `feedback/bdd-proposals/` |
@@ -892,10 +1058,11 @@ Next : {lệnh gợi ý kèm ví dụ tham số}
892
1058
  │ {N} {N} {N} {N}% {N}% {N} {N} {N} │
893
1059
  │ {A} appr {A} appr {X}/{T} SCs {X}/{T} SCs │
894
1060
  └─────────────────────────────────────────────────────────────────────────────────────┘
895
- {nếu seam_unwired_count > 0 hoặc stub_unresolved_count > 0, in dòng GATE — ngược lại bỏ}
896
- 🔴 GATE — luồng ghép có MỒ CÔI: {seam_unwired_count} SEAM_UNWIRED + {stub_unresolved_count} STUB_UNRESOLVED.
897
- Build/test từng-UC vẫn xanh nhưng luồng to chạy vào no-op / hàm thật không ai gọi.
898
- KHÔNG coi pass tới khi cả hai = 0 (xem Seam & Stub Audit bên dưới).
1061
+ {in dòng GATE nếu BẤT KỲ cờ 🔴 nào > 0 (seam_unwired · stub_unresolved · orphaned · trace_orphan) — ngược lại bỏ cả khối}
1062
+ 🔴 GATE — có MỒ CÔI: {seam_unwired_count} SEAM_UNWIRED · {stub_unresolved_count} STUB_UNRESOLVED
1063
+ · {orphaned_count} ORPHANED · {trace_orphan_count} TRACE_ORPHAN
1064
+ Build xanh, test từng-UC xanh, coverage đẹp nhưng luồng ghép chạy vào no-op,
1065
+ hoặc code đang trỏ vào scenario đã bị xoá. KHÔNG coi là pass tới khi CẢ BỐN = 0.
899
1066
 
900
1067
  | UC-ID | SC | Title (truncated) | Spec | Gen | Code | Tests | Status |
901
1068
  |-------------|------|------------------------------|-------|-------|----------------------|----------------|----------|
@@ -903,11 +1070,43 @@ Next : {lệnh gợi ý kèm ví dụ tham số}
903
1070
  | {UC}-UC1 | SC2 | {title...} | v1.1 | v1.0 | ✅ {Controller.fn} | ✅ 3 tests | DRIFT |
904
1071
  | {UC}-UC1 | SC6 | {title...} | v1.0 | — | — | — | UNTRACKED|
905
1072
  | {UC}-UC2 | SC1 | {title...} | v1.0 | v1.0 | ✅ {Controller.fn} | — | GAP |
1073
+ | {UC}-UC2 | SC7 | {title...} ⚠ đã xoá khỏi spec| — | v1.0 | ✅ {Controller.fn} | ✅ 2 tests | ORPHANED |
906
1074
 
907
1075
  Drift Detail:
908
1076
  {UC}-UC1-SC2 — spec v1.1 nhưng code sinh từ v1.0
909
1077
  → Chạy lại: /generate-code {UC-ID}
910
1078
 
1079
+ BDD Version Drift (file .feature đổi ở cấp file — Background/dataset/business definition):
1080
+ {UC}-UC1 (web) — code sinh từ BDD v1.4, .feature giờ v1.6 [SC đang DRIFT: SC2, SC5]
1081
+ → /generate-code {feature-file}
1082
+ ⚠️ {n} file code thiếu tag @trace.bdd_version → drift detection mù ở các file này
1083
+
1084
+ Tech-doc lỗi thời so với BDD:
1085
+ {UC}-UC3 (system) — tech-doc dựng từ BDD v1.5, .feature giờ v2.0
1086
+ ⚠️ Nguy hiểm hơn drift-về-code: DS3 của /generate-code coi §4 approved là contract
1087
+ "đã chốt" và lấy shape NGUYÊN VĂN → contract từ BDD cũ lan thẳng vào code.
1088
+ → /generate-tech-docs {feature-file} → /review-tech-docs (cổng T-BDD)
1089
+
1090
+ FE còn dùng mock (fe_phase = ui — có UI + test nhưng CHƯA nối API thật):
1091
+ {UC}-UC1 (web) — {n} SC ở fe_phase=ui
1092
+ → /generate-code {feature-file} --phase=integration (hoặc để trống --phase cho fe_full)
1093
+ ⚠️ Các SC này có thể đang hiện OK: có code, có test — nhưng test chạy trên mock.
1094
+ Đừng coi là xong tính năng.
1095
+
1096
+ Orphaned (scenario đã bị xoá khỏi .feature nhưng code còn):
1097
+ {UC}-UC2-SC7 (web) — "{sc_title}"
1098
+ Code : {ControllerClass}.{method}
1099
+ Test : {TestClass} (2 tests)
1100
+ Không tự hết — chọn MỘT:
1101
+ (a) code không còn cần → xoá method + test, rồi xoá row khỏi .tsv
1102
+ (b) SC bị xoá do nhầm → đưa scenario trở lại .feature → row về DRIFT/OK bình thường
1103
+
1104
+ Trace orphan (tag trỏ vào SC không tồn tại, KHÔNG có row .tsv nào):
1105
+ {file}:{line} — @trace.implements={UC}-UC1-SC9 nhưng .feature chỉ có tới SC5
1106
+ → Sửa sc_id cho đúng SC hiện có, HOẶC xoá code/test nếu không còn cần
1107
+ (Không lệnh nào khác bắt được cái này — row .tsv đã bị xoá bởi version cũ,
1108
+ hoặc tag ghi sai id ngay từ đầu.)
1109
+
911
1110
  PRD Version Drift:
912
1111
  {UC}-UC2 — code ở PRD v1.0, PRD giờ ở v1.2
913
1112
  Thay đổi kể từ v1.0:
@@ -930,11 +1129,18 @@ Seam & Stub Audit (mồ côi khi ghép luồng):
930
1129
  ⓘ STUB_PENDING — {ClassName#method} ({stub_for}): owner {owner_uc} chưa gen (chưa phải lỗi)
931
1130
 
932
1131
  Recommendations:
933
- - /generate-code {UC-ID} cho scenario DRIFT và UNTRACKED
934
- - /dev-gen-test {UC-ID} cho GAP (thiếu test)
935
- - /generate-bdd {prd-file} cho PRD version drift
1132
+ - /generate-code {UC-ID} cho scenario DRIFT và UNTRACKED
1133
+ - /dev-gen-test {UC-ID} cho GAP (thiếu test)
1134
+ - /generate-bdd {prd-file} cho PRD version drift
1135
+ - /generate-code {feature-file} cho BDD_DRIFT (code sinh từ .feature cũ hơn)
1136
+ - /generate-tech-docs + /review-tech-docs cho tech-doc lỗi thời so với BDD
936
1137
  - Nối binding thủ công cho mỗi SEAM_UNWIRED 🔴 (hàng thật đã có, còn kẹt stub)
937
1138
  - /generate-code {owner_uc} cho mỗi STUB_UNRESOLVED 🔴 (lấp method trắng tại chỗ + xoá hàm song song)
1139
+ - Quyết định thủ công cho mỗi ORPHANED / TRACE_ORPHAN 🔴 — xoá code+test, hoặc đưa
1140
+ scenario trở lại .feature, hoặc sửa sc_id của tag.
1141
+ KHÔNG có lệnh tự xử: cần người xác nhận behavior còn cần hay không.
1142
+
1143
+ ⚠️ Chỉ tạo PR khi mọi cờ 🔴 = 0 (SEAM_UNWIRED · STUB_UNRESOLVED · ORPHANED · TRACE_ORPHAN).
938
1144
 
939
1145
  [Chỉ umbrella mode]
940
1146
  Living Docs canonical → {living_docs_dir}/ (specs module — shared, gitignored)
@@ -54,4 +54,4 @@ testing:
54
54
 
55
55
  trace_tags:
56
56
  implements: "// @trace.implements={UC-ID}-SC{N}"
57
- source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{UC-ID}.feature"
57
+ source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{platform}/{UC-ID}-{slug}.feature"
@@ -56,4 +56,4 @@ testing:
56
56
 
57
57
  trace_tags:
58
58
  implements: "// @trace.implements={UC-ID}-SC{N}"
59
- source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{UC-ID}.feature"
59
+ source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{platform}/{UC-ID}-{slug}.feature"
@@ -52,4 +52,4 @@ testing:
52
52
 
53
53
  trace_tags:
54
54
  implements: "// @trace.implements={UC-ID}-SC{N}"
55
- source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{UC-ID}.feature"
55
+ source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{platform}/{UC-ID}-{slug}.feature"
@@ -23,6 +23,6 @@ coding_standards:
23
23
 
24
24
  trace_tags:
25
25
  implements: "@trace.implements={UC-ID}-SC{N}"
26
- source: "@trace.source=specs/{domain}/{prd-slug}/bdd/{UC-ID}.feature"
26
+ source: "@trace.source=specs/{domain}/{prd-slug}/bdd/{platform}/{UC-ID}-{slug}.feature"
27
27
  verifies: "@trace.verifies={UC-ID}"
28
28
  test_type: "@trace.test_type=unit|integration"
@@ -69,6 +69,6 @@ testing:
69
69
 
70
70
  trace_tags:
71
71
  implements: "// @trace.implements={UC-ID}-SC{N}"
72
- source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{UC-ID}.feature"
72
+ source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{platform}/{UC-ID}-{slug}.feature"
73
73
  verifies: "// @trace.verifies={UC-ID}"
74
74
  test_type: "// @trace.test_type=unit|e2e"
@@ -53,6 +53,6 @@ testing:
53
53
 
54
54
  trace_tags:
55
55
  implements: "// @trace.implements={UC-ID}-SC{N}"
56
- source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{UC-ID}.feature"
56
+ source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{platform}/{UC-ID}-{slug}.feature"
57
57
  verifies: "// @trace.verifies={UC-ID}"
58
58
  test_type: "// @trace.test_type=unit|integration"
@@ -85,6 +85,6 @@ testing:
85
85
 
86
86
  trace_tags:
87
87
  implements: "// @trace.implements={UC-ID}-SC{N}"
88
- source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{UC-ID}.feature"
88
+ source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{platform}/{UC-ID}-{slug}.feature"
89
89
  verifies: "// @trace.verifies={UC-ID}"
90
90
  test_type: "// @trace.test_type=unit|integration"
@@ -51,6 +51,6 @@ testing:
51
51
 
52
52
  trace_tags:
53
53
  implements: "// @trace.implements={UC-ID}-SC{N}"
54
- source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{UC-ID}.feature"
54
+ source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{platform}/{UC-ID}-{slug}.feature"
55
55
  verifies: "// @trace.verifies={UC-ID}"
56
56
  test_type: "// @trace.test_type=unit|feature"
@@ -59,7 +59,7 @@ testing:
59
59
  trace_tags:
60
60
  # QC tests map back to the framework's scenarios — drives qc_status in the trace TSV.
61
61
  verifies: "# @trace.verifies={UC-ID}-SC{N}"
62
- source: "# @trace.source=<official .feature path>"
62
+ source: "# @trace.source=specs/{domain}/{prd-slug}/bdd/{platform}/{UC-ID}-{slug}.feature"
63
63
  test_type: "# @trace.test_type=functional|integration|e2e|non-functional"
64
64
 
65
65
  # qc_status: /qc-run-test writes pass|fail|skip|not_run + qc_run_at into {trace_dir}/{UC-ID}.tsv
@@ -58,6 +58,6 @@ testing:
58
58
 
59
59
  trace_tags:
60
60
  implements: "// @trace.implements={UC-ID}-SC{N}"
61
- source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{UC-ID}.feature"
61
+ source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{platform}/{UC-ID}-{slug}.feature"
62
62
  verifies: "// @trace.verifies={UC-ID}"
63
63
  test_type: "// @trace.test_type=unit|integration"
@@ -53,4 +53,4 @@ testing:
53
53
 
54
54
  trace_tags:
55
55
  implements: "// @trace.implements={UC-ID}-SC{N}"
56
- source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{UC-ID}.feature"
56
+ source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{platform}/{UC-ID}-{slug}.feature"
@@ -60,6 +60,6 @@ testing:
60
60
 
61
61
  trace_tags:
62
62
  implements: "// @trace.implements={UC-ID}-SC{N}"
63
- source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{UC-ID}.feature"
63
+ source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{platform}/{UC-ID}-{slug}.feature"
64
64
  verifies: "// @trace.verifies={UC-ID}"
65
65
  test_type: "// @trace.test_type=unit|integration"
@@ -18,6 +18,17 @@
18
18
  - Do NOT create files outside the directories specified in `project-context.yaml → paths`.
19
19
  - If new scope is discovered mid-command, STOP and ask: "I found additional scope [{description}]. Should I include it? (Y/N)"
20
20
 
21
+ ## Trace Contract
22
+
23
+ - Contract trace (field `@trace.*`, cột `.tsv`, path pattern, giá trị enum) có **một
24
+ nguồn-sự-thật máy đọc**: `bin/trace-schema.json`. Bản cho người đọc:
25
+ `docs/04-reference/trace-schema.md` — giữ hai file đồng bộ.
26
+ - Đổi contract (thêm/bỏ/đổi nghĩa một field, path, hay giá trị enum) → **sửa
27
+ `bin/trace-schema.json` TRƯỚC**, rồi mới sửa lệnh. `npm run build` chạy
28
+ `bin/self-check.js` và **fail** nếu lệnh lệch schema.
29
+ - Field có consumer mà **không có producer** là lỗi chặn build — đó chính là hình dạng
30
+ của G1 (`@trace.sc_version`: 3 consumer, 0 producer, DRIFT chết mà không ai báo).
31
+
21
32
  ## Code Generation
22
33
 
23
34
  - Never generate code for files not backed by a `.feature` spec (unless `/fix-bug` or `/debug`).