@educa-corp/sdd-framework 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/bin/build.js +113 -19
  2. package/bin/gate-trace.js +487 -0
  3. package/bin/index.js +445 -146
  4. package/bin/lint-trace.js +643 -0
  5. package/bin/self-check.js +804 -2
  6. package/bin/trace-schema.json +621 -10
  7. package/core/FRAMEWORK_VERSION +1 -1
  8. package/core/README.md +20 -0
  9. package/core/commands/amend-prd.md +518 -0
  10. package/core/commands/debug.md +123 -511
  11. package/core/commands/define-product.md +86 -510
  12. package/core/commands/dev-gen-test.md +86 -510
  13. package/core/commands/dev-run-test.md +133 -519
  14. package/core/commands/dev-smoke-test.md +86 -510
  15. package/core/commands/extend-prd.md +128 -522
  16. package/core/commands/fix-bug.md +118 -509
  17. package/core/commands/generate-architecture.md +94 -515
  18. package/core/commands/generate-bdd.md +128 -513
  19. package/core/commands/generate-code.md +119 -510
  20. package/core/commands/generate-design-spec.md +86 -510
  21. package/core/commands/generate-prd.md +89 -510
  22. package/core/commands/generate-spec-manifest.md +86 -510
  23. package/core/commands/generate-tech-docs.md +120 -512
  24. package/core/commands/learn.md +172 -496
  25. package/core/commands/map-testids.md +86 -510
  26. package/core/commands/propose-scenario.md +86 -510
  27. package/core/commands/qc-analyze.md +86 -510
  28. package/core/commands/qc-design-test.md +86 -510
  29. package/core/commands/qc-plan.md +86 -510
  30. package/core/commands/qc-report.md +86 -510
  31. package/core/commands/qc-review.md +86 -510
  32. package/core/commands/qc-run-test.md +115 -513
  33. package/core/commands/refine-prd.md +112 -522
  34. package/core/commands/report-bug.md +86 -510
  35. package/core/commands/review-code.md +123 -511
  36. package/core/commands/review-context.md +136 -522
  37. package/core/commands/review-tech-docs.md +90 -511
  38. package/core/commands/setup-ai-first.md +166 -138
  39. package/core/commands/sync.md +155 -107
  40. package/core/commands/update-framework.md +16 -103
  41. package/core/commands/validate-traces.md +426 -511
  42. package/core/hooks/data-guard.js +174 -83
  43. package/core/hooks/settings.json +2 -1
  44. package/core/rules/workflow.md +64 -4
  45. package/core/steps/capture-lesson.md +34 -1
  46. package/core/steps/context-loader.md +50 -8
  47. package/core/steps/gate.md +92 -35
  48. package/core/steps/report-footer.md +23 -0
  49. package/core/templates/README.md +24 -1
  50. package/core/templates/ci/trace-gate.yml +146 -0
  51. package/core/templates/feature.template +1 -1
  52. package/core/templates/hooks/pre-push +61 -0
  53. package/docs/02-concepts/architecture.md +61 -6
  54. package/docs/02-concepts/traceability.md +57 -0
  55. package/docs/03-guides/architect.md +63 -0
  56. package/docs/04-reference/commands.md +148 -134
  57. package/docs/04-reference/model-selection.md +32 -19
  58. package/docs/04-reference/trace-schema.md +39 -0
  59. package/docs/explain/02b-extend-prd.md +1 -1
  60. package/docs/explain/02c-amend-prd.md +152 -0
  61. package/docs/explain/21-validate-traces.md +2 -1
  62. package/docs/explain/27-learn.md +5 -3
  63. package/docs/explain/28-sync.md +25 -0
  64. package/docs/explain/README.md +136 -135
  65. package/package.json +5 -9
  66. package/commands/debug.md +0 -917
  67. package/commands/debug.tmpl +0 -257
  68. package/commands/define-product.md +0 -862
  69. package/commands/define-product.tmpl +0 -225
  70. package/commands/dev-gen-test.md +0 -1124
  71. package/commands/dev-gen-test.tmpl +0 -490
  72. package/commands/dev-run-test.md +0 -859
  73. package/commands/dev-run-test.tmpl +0 -225
  74. package/commands/dev-smoke-test.md +0 -798
  75. package/commands/dev-smoke-test.tmpl +0 -217
  76. package/commands/extend-prd.md +0 -907
  77. package/commands/extend-prd.tmpl +0 -270
  78. package/commands/fix-bug.md +0 -910
  79. package/commands/fix-bug.tmpl +0 -197
  80. package/commands/generate-architecture.md +0 -775
  81. package/commands/generate-architecture.tmpl +0 -194
  82. package/commands/generate-bdd.md +0 -1347
  83. package/commands/generate-bdd.tmpl +0 -590
  84. package/commands/generate-code.md +0 -1283
  85. package/commands/generate-code.tmpl +0 -649
  86. package/commands/generate-design-spec.md +0 -1161
  87. package/commands/generate-design-spec.tmpl +0 -524
  88. package/commands/generate-prd.md +0 -1143
  89. package/commands/generate-prd.tmpl +0 -223
  90. package/commands/generate-spec-manifest.md +0 -745
  91. package/commands/generate-spec-manifest.tmpl +0 -164
  92. package/commands/generate-tech-docs.md +0 -1344
  93. package/commands/generate-tech-docs.tmpl +0 -273
  94. package/commands/learn.md +0 -723
  95. package/commands/learn.tmpl +0 -63
  96. package/commands/map-testids.md +0 -662
  97. package/commands/map-testids.tmpl +0 -81
  98. package/commands/propose-scenario.md +0 -783
  99. package/commands/propose-scenario.tmpl +0 -202
  100. package/commands/qc-analyze.md +0 -693
  101. package/commands/qc-analyze.tmpl +0 -112
  102. package/commands/qc-design-test.md +0 -650
  103. package/commands/qc-design-test.tmpl +0 -69
  104. package/commands/qc-plan.md +0 -630
  105. package/commands/qc-plan.tmpl +0 -49
  106. package/commands/qc-report.md +0 -641
  107. package/commands/qc-report.tmpl +0 -60
  108. package/commands/qc-review.md +0 -634
  109. package/commands/qc-review.tmpl +0 -53
  110. package/commands/qc-run-test.md +0 -750
  111. package/commands/qc-run-test.tmpl +0 -116
  112. package/commands/refine-prd.md +0 -1074
  113. package/commands/refine-prd.tmpl +0 -278
  114. package/commands/report-bug.md +0 -729
  115. package/commands/report-bug.tmpl +0 -148
  116. package/commands/review-code.md +0 -803
  117. package/commands/review-code.tmpl +0 -143
  118. package/commands/review-context.md +0 -1323
  119. package/commands/review-context.tmpl +0 -527
  120. package/commands/review-tech-docs.md +0 -982
  121. package/commands/review-tech-docs.tmpl +0 -401
  122. package/commands/setup-ai-first.md +0 -574
  123. package/commands/setup-ai-first.tmpl +0 -378
  124. package/commands/sync.md +0 -486
  125. package/commands/sync.tmpl +0 -384
  126. package/commands/update-framework.md +0 -290
  127. package/commands/update-framework.tmpl +0 -188
  128. package/commands/validate-traces.md +0 -1435
  129. package/commands/validate-traces.tmpl +0 -854
  130. package/hooks/data-guard.js +0 -141
  131. package/hooks/settings.json +0 -18
  132. package/modules/android-compose/module.yaml +0 -13
  133. package/modules/android-compose/stack-profile.yaml +0 -57
  134. package/modules/angular/architecture-snippets/component-patterns.md +0 -187
  135. package/modules/angular/module.yaml +0 -6
  136. package/modules/angular/stack-profile.yaml +0 -38
  137. package/modules/context-engineering/architecture-snippets/context-design.md +0 -119
  138. package/modules/context-engineering/module.yaml +0 -9
  139. package/modules/context-engineering/stack-profile.yaml +0 -61
  140. package/modules/dotnet/architecture-snippets/clean-arch.md +0 -160
  141. package/modules/dotnet/module.yaml +0 -6
  142. package/modules/dotnet/stack-profile.yaml +0 -50
  143. package/modules/flutter/module.yaml +0 -14
  144. package/modules/flutter/stack-profile.yaml +0 -59
  145. package/modules/golang/architecture-snippets/domain-layout.md +0 -283
  146. package/modules/golang/module.yaml +0 -6
  147. package/modules/golang/stack-profile.yaml +0 -40
  148. package/modules/ios-swiftui/module.yaml +0 -13
  149. package/modules/ios-swiftui/stack-profile.yaml +0 -55
  150. package/modules/java-spring/architecture-snippets/layered-arch.md +0 -201
  151. package/modules/java-spring/module.yaml +0 -15
  152. package/modules/java-spring/stack-profile.yaml +0 -28
  153. package/modules/nextjs/architecture-snippets/app-router-patterns.md +0 -269
  154. package/modules/nextjs/module.yaml +0 -14
  155. package/modules/nextjs/stack-profile.yaml +0 -74
  156. package/modules/nuxt/module.yaml +0 -14
  157. package/modules/nuxt/stack-profile.yaml +0 -58
  158. package/modules/phaser-game/architecture-snippets/phaser-scene-patterns.md +0 -646
  159. package/modules/phaser-game/module.yaml +0 -15
  160. package/modules/phaser-game/stack-profile.yaml +0 -90
  161. package/modules/php-laravel/architecture-snippets/service-repository.md +0 -302
  162. package/modules/php-laravel/module.yaml +0 -15
  163. package/modules/php-laravel/stack-profile.yaml +0 -56
  164. package/modules/qc-playwright/stack-profile.yaml +0 -66
  165. package/modules/react/architecture-snippets/hooks-query-patterns.md +0 -254
  166. package/modules/react/module.yaml +0 -14
  167. package/modules/react/stack-profile.yaml +0 -63
  168. package/modules/react-native/module.yaml +0 -14
  169. package/modules/react-native/stack-profile.yaml +0 -56
  170. package/modules/vue/module.yaml +0 -14
  171. package/modules/vue/stack-profile.yaml +0 -65
  172. package/rules/data-protection.md +0 -80
  173. package/rules/workflow.md +0 -73
  174. package/scripts/init.sh +0 -49
  175. package/scripts/upgrade.sh +0 -94
  176. package/skills/code/SKILL.md +0 -19
  177. package/skills/code/SKILL.tmpl +0 -19
  178. package/skills/debug/SKILL.md +0 -19
  179. package/skills/debug/SKILL.tmpl +0 -19
  180. package/skills/design-spec/SKILL.md +0 -11
  181. package/skills/design-spec/SKILL.tmpl +0 -11
  182. package/skills/discovery/SKILL.md +0 -14
  183. package/skills/discovery/SKILL.tmpl +0 -14
  184. package/skills/prd/SKILL.md +0 -19
  185. package/skills/prd/SKILL.tmpl +0 -19
  186. package/skills/qc/qa-analyst/DOC_GAPS.template.md +0 -63
  187. package/skills/qc/qa-analyst/acceptance-criteria.md +0 -60
  188. package/skills/qc/qa-analyst/business-rules.md +0 -59
  189. package/skills/qc/qa-analyst/data-flow.md +0 -64
  190. package/skills/qc/qa-analyst/spec-breakdown.md +0 -61
  191. package/skills/qc/qa-designer/e2e/journey.md +0 -41
  192. package/skills/qc/qa-designer/exploratory/charter.md +0 -68
  193. package/skills/qc/qa-designer/exploratory/explore-to-functional.md +0 -43
  194. package/skills/qc/qa-designer/functional/api.md +0 -45
  195. package/skills/qc/qa-designer/functional/gui-feature.md +0 -46
  196. package/skills/qc/qa-designer/functional/gui-screen.md +0 -52
  197. package/skills/qc/qa-designer/integration/api.md +0 -42
  198. package/skills/qc/qa-designer/integration/db.md +0 -39
  199. package/skills/qc/qa-designer/integration/gui.md +0 -40
  200. package/skills/qc/qa-designer/integration/kafka.md +0 -40
  201. package/skills/qc/qa-designer/non-functional.md +0 -40
  202. package/skills/qc/qa-planner/test-plan.md +0 -120
  203. package/skills/qc/qa-reviewer/script/e2e.md +0 -87
  204. package/skills/qc/qa-reviewer/script/exploratory.md +0 -45
  205. package/skills/qc/qa-reviewer/script/functional.md +0 -101
  206. package/skills/qc/qa-reviewer/script/integration.md +0 -91
  207. package/skills/qc/qa-reviewer/script/non-functional.md +0 -126
  208. package/skills/qc/qa-reviewer/test-case/e2e.md +0 -73
  209. package/skills/qc/qa-reviewer/test-case/exploratory.md +0 -43
  210. package/skills/qc/qa-reviewer/test-case/functional.md +0 -76
  211. package/skills/qc/qa-reviewer/test-case/integration.md +0 -69
  212. package/skills/qc/qa-reviewer/test-case/non-functional.md +0 -73
  213. package/skills/qc/qa-runner/e2e.md +0 -49
  214. package/skills/qc/qa-runner/exploratory/session.md +0 -36
  215. package/skills/qc/qa-runner/functional/api.md +0 -35
  216. package/skills/qc/qa-runner/functional/gui-feature.md +0 -51
  217. package/skills/qc/qa-runner/functional/gui-screen.md +0 -55
  218. package/skills/qc/qa-runner/integration.md +0 -47
  219. package/skills/qc/qa-runner/non-functional.md +0 -49
  220. package/skills/qc/qa-runner/report/report.md +0 -37
  221. package/skills/setup-ai-first/SKILL.md +0 -19
  222. package/skills/setup-ai-first/SKILL.tmpl +0 -19
  223. package/skills/spec/SKILL.md +0 -19
  224. package/skills/spec/SKILL.tmpl +0 -19
  225. package/skills/test/SKILL.md +0 -18
  226. package/skills/test/SKILL.tmpl +0 -18
  227. package/steps/business-language.md +0 -56
  228. package/steps/capture-lesson.md +0 -79
  229. package/steps/context-loader.md +0 -385
  230. package/steps/gate.md +0 -94
  231. package/steps/report-footer.md +0 -102
  232. package/steps/review-fanout.md +0 -159
  233. package/steps/spawn-agent.md +0 -129
  234. package/steps/trace-mirror.md +0 -53
  235. package/templates/README.md +0 -47
  236. package/templates/architecture.template.md +0 -394
  237. package/templates/design-spec.template.md +0 -217
  238. package/templates/feature.template +0 -123
  239. package/templates/platform-guide.template.md +0 -145
  240. package/templates/prd.template.md +0 -283
  241. package/templates/product-definition.template.md +0 -188
  242. package/templates/project-context.yaml +0 -212
  243. package/templates/tech-design.template.md +0 -490
@@ -937,17 +937,37 @@
937
937
  "Đội/submodule sở hữu scenario. Nguồn: @trace.service của header .feature.",
938
938
  "VÌ SAO TỒN TẠI (G31): validate-traces Step 0 và trace-mirror.md đều khẳng định trace gộp",
939
939
  "'không cần tách theo service vì mỗi row mang @trace.service' — nhưng TSV KHÔNG có cột nào",
940
- "cho nó và JSON report cũng không. đúng chế độ phổ biến nhất (umbrella + spec_source),",
941
- "trace hoàn toàn không trả lời được 'scenario này thuộc đội nào' — câu hỏi số MỘT của một",
942
- "dự án nhiều đội. Nghịch lý: nhánh LEGACY (trace tách theo service) thì gắn field service",
943
- "vào JSON; nhánh 'phổ biến' thì không. Cột này làm lời khẳng định kia thành sự thật.",
944
- "Giá trị: path service | 'multi' (chưa chốt) | 'unresolved' (routing sai) | '—' (single-service)."
940
+ "cho nó và JSON report cũng không. Cột này làm lời khẳng định kia thành sự thật.",
941
+ "",
942
+ "BỐN GIÁ TRỊ G51 tách hai cái từng bị gộp:",
943
+ " {path} = đã route xong, đây repo sở hữu",
944
+ " unrouted = CHƯA AI QUYẾT repo. Trạng thái HỢP LỆ BÌNH THƯỜNG feature đầu tiên của",
945
+ " một domain mới. Cờ 🟠 SERVICE_UNROUTED. KHÔNG chặn BDD, KHÔNG chặn PR.",
946
+ " unresolved = CONFIG SAI CẤU TRÚC (một entry vừa có path vừa có by_prd_slug, hoặc",
947
+ " by_prd_slug lồng nhau). Đây là BUG cần sửa file, không phải trạng thái chờ.",
948
+ " '—' = single-service mode, không có khái niệm service",
949
+ "",
950
+ "VÌ SAO PHẢI TÁCH (G51): trước đó 'unresolved' mang CẢ HAI nghĩa nên cả hai chịu chung một",
951
+ "hình phạt — /generate-bdd DỪNG HẲN. Nhưng PRD/BDD là artifact NGHIỆP VỤ: PO biết domain",
952
+ "(auth, payment) và biết platform (web/app — người dùng làm việc đó ở đâu), nhưng KHÔNG biết",
953
+ "code sẽ nằm repo nào, và thường lúc đó CHƯA AI QUYẾT. Nên cổng đặt sai phase: nó chặn phase",
954
+ "KHÔNG CẦN biết, còn generate-code — phase BUỘC PHẢI biết mới ghi được file — thì không kiểm.",
955
+ "Ca bị đánh nặng nhất là feature ĐẦU TIÊN của một domain mới: chưa có mapping ⇒ PO bị chặn",
956
+ "bởi một quyết định thuộc architect, ở phase architect còn chưa vào.",
957
+ "",
958
+ "AI PHÂN GIẢI: validate-traces đọc lại services: mỗi lần chạy và nâng unrouted → path khi",
959
+ "mapping xuất hiện — giống cách nó đã làm với spec_ver. Architect thêm mapping ⇒ sổ TỰ LÀNH,",
960
+ "không sửa tay, KHÔNG phải chạy lại /generate-bdd. Vì thế validate-traces là producer thứ hai",
961
+ "của cột này, và đó là ngoại lệ CÓ CHỦ Ý với luật 'mỗi cột một chủ': nó không ghi giá trị mới,",
962
+ "nó chỉ phân giải một placeholder mà generate-bdd đã cố ý để lại."
945
963
  ],
946
964
  "written_by": [
947
- "generate-bdd"
965
+ "generate-bdd",
966
+ "validate-traces"
948
967
  ],
949
968
  "read_by": [
950
- "validate-traces"
969
+ "validate-traces",
970
+ "generate-code"
951
971
  ]
952
972
  },
953
973
  {
@@ -1063,6 +1083,19 @@
1063
1083
  ],
1064
1084
  "vocabularies": {
1065
1085
  "$comment": "Mọi giá trị phải xuất hiện ở ít nhất một actor — giá trị chết là dấu hiệu contract lệch.",
1086
+ "$lint_binding_comment": [
1087
+ "BINDING CHO lint-trace (G38). R6 canh 'giá trị có được actor nào DÙNG không' — đó là",
1088
+ "câu hỏi về contract. Nhưng nó không trả lời được 'file .tsv THẬT có tuân enum này không',",
1089
+ "vì không rule nào mở sổ thật ra xem. bin/lint-trace.js làm việc đó, và nó cần biết mỗi",
1090
+ "vocabulary neo vào ĐÂU. Mỗi vocabulary PHẢI khai đúng một trong:",
1091
+ " column → một cột của TSV chính",
1092
+ " columns → nhiều cột của TSV chính",
1093
+ " aux_table + aux_column → một cột của bảng phụ trong aux_tables",
1094
+ " filename_segment → một đoạn trong tên file sổ",
1095
+ " $lint → cố ý ngoài phạm vi lint, PHẢI nêu lý do",
1096
+ "Thiếu tất cả → self-check.js R8a fail build. Đây là van chống lặp lại chính G38:",
1097
+ "thêm vocabulary mới mà quên dạy checker thì build đỏ, không phải 6 tháng sau mới biết."
1098
+ ],
1066
1099
  "trace_status": {
1067
1100
  "values": [
1068
1101
  "OK",
@@ -1078,13 +1111,19 @@
1078
1111
  "web",
1079
1112
  "app",
1080
1113
  "system"
1081
- ]
1114
+ ],
1115
+ "filename_segment": "{UC-ID}-{platform}.tsv",
1116
+ "$filename_note": "Tên sổ là nơi DUY NHẤT mang platform của row — validate-traces Step 2 lấy platform từ đây để tìm .feature đúng thư mục con. Sổ thiếu đoạn này thì cả sổ vô hình (đúng hình dạng G3/G8)."
1082
1117
  },
1083
1118
  "approval_status": {
1084
1119
  "values": [
1085
1120
  "draft",
1086
1121
  "in-review",
1087
1122
  "approved"
1123
+ ],
1124
+ "columns": [
1125
+ "prd_status",
1126
+ "uc_status"
1088
1127
  ]
1089
1128
  },
1090
1129
  "fe_phase": {
@@ -1094,15 +1133,48 @@
1094
1133
  ],
1095
1134
  "column": "fe_phase"
1096
1135
  },
1136
+ "dev_selftest_status": {
1137
+ "$comment": [
1138
+ "G49 — hai cột này mang enum nhưng trước đó KHÔNG có vocabulary, nên lint T3 không canh:",
1139
+ "`dev_selftest = passed`, `qc_status = OK`, `qc_status = pending` đều đi qua sạch.",
1140
+ "Mà đây là hai cột của G28 ('cờ nói dối') và là hai cột có NHIỀU NGƯỜI GHI NHẤT trong",
1141
+ "schema — 5 lệnh và 3 lệnh, mỗi lệnh ở một nhánh điều kiện khác nhau, tất cả bằng prose.",
1142
+ "Cột dễ lệch giá trị nhất trong sổ, và là cột duy nhất mang enum mà không được canh.",
1143
+ "",
1144
+ "VÌ SAO SỐNG ĐƯỢC QUA G30: G30 mở phạm vi schema cho TAG (@trace.*). Hai cột này không",
1145
+ "có tag tương ứng — chúng chỉ sống trong TSV. Nên chúng nằm ngoài lăng kính của G30, và",
1146
+ "trước G38 thì không có lăng kính nào soi cột-không-có-tag."
1147
+ ],
1148
+ "values": [
1149
+ "pass",
1150
+ "fail",
1151
+ "not_run"
1152
+ ],
1153
+ "column": "dev_selftest"
1154
+ },
1155
+ "qc_run_status": {
1156
+ "$comment": "Xem dev_selftest_status. Có thêm `skip` — QC chủ động bỏ qua scenario (khác not_run: chưa chạy).",
1157
+ "values": [
1158
+ "pass",
1159
+ "fail",
1160
+ "skip",
1161
+ "not_run"
1162
+ ],
1163
+ "column": "qc_status"
1164
+ },
1097
1165
  "seam_status": {
1098
1166
  "values": [
1099
1167
  "PENDING",
1100
1168
  "READY",
1101
1169
  "RESOLVED"
1102
- ]
1170
+ ],
1171
+ "aux_table": "_seams.tsv",
1172
+ "aux_column": "status"
1103
1173
  },
1104
1174
  "audit_flags": {
1175
+ "$lint": "n/a — không sống trong TSV. Cờ audit được tính lúc chạy và sống trong trace-report.json (issues + summary counter); R7 canh đủ ba tầng của chúng. lint-trace chỉ kiểm file trên đĩa, nên không có gì để neo.",
1105
1176
  "values": [
1177
+ "PRD_UNTRACKED_EDIT",
1106
1178
  "PRD_DRIFT",
1107
1179
  "PRD_STALE_REF",
1108
1180
  "BDD_DRIFT",
@@ -1116,10 +1188,549 @@
1116
1188
  "SEAM_UNWIRED",
1117
1189
  "STUB_UNRESOLVED",
1118
1190
  "SEAM_PENDING",
1119
- "STUB_PENDING"
1191
+ "STUB_PENDING",
1192
+ "SERVICE_UNROUTED"
1193
+ ]
1194
+ }
1195
+ },
1196
+ "gate": {
1197
+ "$comment": [
1198
+ "CỔNG CHẶN PR (G39). README khai bằng chữ in đậm rằng 4 cờ này 'chặn PR', kèm câu",
1199
+ "'build xanh + test từng-UC xanh KHÔNG phát hiện được' — đó là điểm bán hàng cốt lõi.",
1200
+ "Nhưng trước G39 không có gì chặn: 0 CI, 0 git hook, 0 exit code. /validate-traces in",
1201
+ "một report vào terminal Claude Code, nơi không CI nào đọc được. Kỷ luật là honor-system,",
1202
+ "và cái gì không chặn thì sau sprint thứ ba không ai làm.",
1203
+ "",
1204
+ "bin/gate-trace.js đọc khối này để biết ĐẾM CỜ NÀO — không hard-code. Vì sao quan trọng:",
1205
+ "gate đọc summary[counter]. Nếu counter bị đổi tên mà gate không biết, `undefined > 0`",
1206
+ "là false ⇒ GATE PASS TRÊN CODE HỎNG. Đó là false-green im lặng, đúng hình dạng G1.",
1207
+ "Nên gate PHẢI fail khi counter nó chờ không có mặt trong report, và self-check R9 canh",
1208
+ "cả hai đầu (counter có trong summary của validate-traces · issues_key có trong issues).",
1209
+ "",
1210
+ "Cờ 🟠 (PRD_DRIFT, BDD_DRIFT, TECHDOC_DRIFT, …) CỐ Ý không nằm đây — chúng là 'code chưa",
1211
+ "theo kịp spec', một loại nợ khác, không làm hỏng luồng đang chạy. Chặn PR vì nợ là làm",
1212
+ "cổng bị tắt. Xem README §Traceability."
1213
+ ],
1214
+ "report_path": "{trace_dir}/trace-report.json",
1215
+ "config_preconditions": {
1216
+ "$comment": [
1217
+ "TẦNG G0 — 'sổ có ĐƯỢC BẢO VỆ không?', hỏi TRƯỚC cả ba tầng kia.",
1218
+ "",
1219
+ "Vì sao tách khỏi `blocking`: `blocking` có nghĩa hẹp là CODE ĐANG HỎNG (4 cờ 🔴), và",
1220
+ "R9(e) tồn tại để giữ đúng ranh giới đó — nó fail build nếu ai nhét một cờ NỢ (🟠) vào.",
1221
+ "Hai điều kiện dưới đây thuộc loại THỨ BA: chưa có gì sai, nhưng LƯỚI AN TOÀN ĐANG TẮT.",
1222
+ "Ví như: động cơ hỏng (🔴) · đến hạn thay dầu (🟠) · CHƯA CÀI DÂY AN TOÀN (G0).",
1223
+ "Trộn chúng vào `blocking` là làm mờ chính ranh giới R9(e) bảo vệ.",
1224
+ "",
1225
+ "Vì sao hỏi TRƯỚC: G1 kiểm rất kỹ cấu trúc của một quyển sổ mà có thể KHÔNG NẰM TRONG",
1226
+ "GIT. Kiểm cấu trúc của thứ sắp mất thì vô nghĩa.",
1227
+ "",
1228
+ "Cách hoạt động: cả hai điều kiện đã được `bin/lint-trace.js` phát hiện dưới dạng WARN.",
1229
+ "gate NÂNG chúng thành lỗi chặn. Một implementation, hai mức nghiêm trọng — cố ý:",
1230
+ " pre-push (lint) → NHẮC. Chặn push vì một dòng cấu hình sẽ dạy dev gõ --no-verify,",
1231
+ " và mất luôn phần lint THẬT (sổ hỏng cấu trúc).",
1232
+ " CI (gate) → CHẶN. Đây là chỗ đúng để chặn.",
1233
+ "Cùng cách phân vai G39 đã chọn cho pre-push vs CI."
1234
+ ],
1235
+ "promote_lint_rules": [
1236
+ {
1237
+ "rule": "T11",
1238
+ "why": "sổ trace đang bị gitignore — nó KHÔNG được lưu vào git, mất là mất toàn bộ (G29)"
1239
+ },
1240
+ {
1241
+ "rule": "T10",
1242
+ "why": "sổ thiếu luật merge — hai nhánh cùng ghi sẽ mất row của nhau, im lặng (G40)"
1243
+ }
1244
+ ]
1245
+ },
1246
+ "checkpoint_levels": {
1247
+ "$comment": [
1248
+ "MỨC CHẶN CHECKPOINT CỦA TỪNG LỆNH (nợ 'nhãn chặn cứng' sau G41).",
1249
+ "",
1250
+ "G41 viết ba mức vào steps/gate.md Bước 3a — nhưng KHÔNG lệnh nào tự khai mình thuộc",
1251
+ "mức nào. Nên agent phải TỰ SUY từ bảng đó mỗi lần đọc: '/extend-prd sửa một PRD đã",
1252
+ "duyệt — có tính là ghi đè file đã có không?'. Suy đúng thì không sao; suy sai một lần",
1253
+ "theo hướng nhẹ hơn thì `--yes` bỏ qua được một cổng đáng ra KHÔNG BAO GIỜ được bỏ.",
1254
+ "",
1255
+ "Đây là hình dạng đã gặp bốn lần trong loạt GAP này: luật ĐÚNG, viết RÕ, và KHÔNG AI",
1256
+ "CANH. G1 ('phải bump sc_version') · G28 ('đổi spec thì hạ qc_status') · G41 ('read-only",
1257
+ "được miễn CHECKPOINT' — gate không thi hành) · và cái này. Ba lần trước đều chữa bằng",
1258
+ "cùng một cách: khai tường minh + để máy canh. R11 là phần 'máy canh'.",
1259
+ "",
1260
+ "KHÔNG liệt kê `normal`: nó là mặc định, và liệt kê ra là tạo một danh sách phải bảo",
1261
+ "trì mãi mãi — thêm lệnh mới mà quên thêm vào đó thì lại sai lần nữa.",
1262
+ "",
1263
+ "PHẠM VI: `hard` là 'ghi đè thứ NGƯỜI DÙNG ĐÃ DUYỆT'. /generate-code · /generate-bdd ·",
1264
+ "/generate-tech-docs · /map-testids CỐ Ý không nằm đây — chúng ghi thứ SINH RA ĐƯỢC từ",
1265
+ "spec, không phải thứ người dùng đã duyệt bằng tay. Ba script migrate cũng không: chúng",
1266
+ "là CLI, dry-run mặc định, không đi qua gate."
1267
+ ],
1268
+ "label_marker": "Checkpoint:",
1269
+ "hard": [
1270
+ {
1271
+ "cmd": "generate-prd",
1272
+ "why": "ghi đè PRD đã có → mất changelog, ĐÁNH SỐ LẠI BR, phá @trace.business_rules trong mọi .feature đã sinh"
1273
+ },
1274
+ {
1275
+ "cmd": "extend-prd",
1276
+ "why": "sửa PRD đã DUYỆT"
1277
+ },
1278
+ {
1279
+ "cmd": "amend-prd",
1280
+ "why": "SỬA TẠI CHỖ một AC/BR/UC đã duyệt — thao tác ghi DUY NHẤT trong framework được phép làm output KHÔNG phải superset của bản cũ"
1281
+ },
1282
+ {
1283
+ "cmd": "refine-prd",
1284
+ "why": "--resume áp findings trực tiếp vào PRD"
1285
+ },
1286
+ {
1287
+ "cmd": "generate-architecture",
1288
+ "why": "ghi đè architecture.md đã verified_by một người thật"
1289
+ }
1290
+ ],
1291
+ "none": [
1292
+ {
1293
+ "cmd": "review-code",
1294
+ "why": "read-only"
1295
+ },
1296
+ {
1297
+ "cmd": "validate-traces",
1298
+ "why": "read-only (ghi trace-report.json + TSV status, không đụng spec/code)"
1299
+ },
1300
+ {
1301
+ "cmd": "debug",
1302
+ "why": "read-only"
1303
+ },
1304
+ {
1305
+ "cmd": "review-context",
1306
+ "why": "read-only (ghi findings vào .agent/review/)"
1307
+ },
1308
+ {
1309
+ "cmd": "review-tech-docs",
1310
+ "why": "read-only (ghi findings vào .agent/review/)"
1311
+ }
1312
+ ]
1313
+ },
1314
+ "audit_invalidating_tags": {
1315
+ "$comment": [
1316
+ "TAG NÀO TRONG CODE, KHI ĐỔI, LÀM AUDIT HẾT TƯƠI (nợ #4 sau Wave 1).",
1317
+ "",
1318
+ "gate chứng minh 'report khớp SỔ', không chứng minh 'sổ khớp CODE' — việc đó cần",
1319
+ "/validate-traces, một lệnh LLM không chạy được trong CI thường. Nên có lỗ: dev sửa",
1320
+ "code mà không audit lại thì sổ+report vẫn khớp nhau, gate vẫn PASS, và mọi cờ 🔴 nói",
1321
+ "về trạng thái TRƯỚC khi sửa. Job `require-fresh-audit` trong templates/ci/ bịt lỗ đó",
1322
+ "bằng một PROXY: không verify được thì ĐÒI BẰNG CHỨNG có người vừa verify.",
1323
+ "",
1324
+ "VÌ SAO LÀ TAG CHỨ KHÔNG PHẢI `^src/`: bản đầu của job hỏi 'PR có sửa src/ không?' và",
1325
+ "vì thế phải để TẮT mặc định — nó ĐO SAI THỨ. Framework có luật boundary-only tagging",
1326
+ "(docs/02-concepts/traceability.md): chỉ Controller/Handler/Middleware/Steps mang tag;",
1327
+ "Entity/Repository/DTO/Interface/Base KHÔNG. Nên `^src/` chặn PR chỉ sửa một DTO — một",
1328
+ "file không mang lời khẳng định trace nào. Cái gì báo oan thì bị tắt, rồi mất luôn phần",
1329
+ "thật sự cần chặn.",
1330
+ "Câu hỏi đúng không phải 'PR có sửa src/?' mà 'PR có sửa thứ mà report đang KHẲNG ĐỊNH?'",
1331
+ "— và report khẳng định qua tag, tag thì nhìn thấy trong diff.",
1332
+ "",
1333
+ "PHẠM VI: chỉ 7 tag là NGUỒN của 4 cờ chặn PR. Tag version (@trace.prd_version,",
1334
+ "tech_doc_revision, design_spec_version) sinh ra cờ 🟠 — cờ đó cố ý không chặn, nên đổi",
1335
+ "chúng không cần đòi audit lại.",
1336
+ "",
1337
+ "GIỚI HẠN ĐÃ BIẾT: đổi tên class (AuthService → AuthenticationService) không chạm dòng",
1338
+ "tag ⇒ tag-diff bỏ lọt, dù cột `implemented_by` giờ trỏ vào tên không còn. Chấp nhận có",
1339
+ "chủ ý: ca đó ít gặp hơn hẳn ca sửa-code-thường, và nó KHÔNG im lặng (lần /validate-traces",
1340
+ "kế tiếp báo ngay). Còn báo oan thì xảy ra mỗi ngày và hậu quả là CỔNG BỊ TẮT — mất tất cả.",
1341
+ "Biến thể chặt hơn ('PR chạm file nào CÓ chứa tag') ghi trong comment của template.",
1342
+ "",
1343
+ "self-check R10 canh templates/ci/trace-gate.yml có nhắc đủ 7 tag này. Tag đổi tên mà",
1344
+ "file CI không biết ⇒ grep không khớp gì ⇒ job LUÔN XANH ⇒ cổng mù im lặng."
1345
+ ],
1346
+ "ci_template": "templates/ci/trace-gate.yml",
1347
+ "tags": [
1348
+ {
1349
+ "tag": "@trace.implements",
1350
+ "flags": [
1351
+ "ORPHANED",
1352
+ "TRACE_ORPHAN"
1353
+ ]
1354
+ },
1355
+ {
1356
+ "tag": "@trace.verifies",
1357
+ "flags": [
1358
+ "ORPHANED",
1359
+ "TRACE_ORPHAN"
1360
+ ]
1361
+ },
1362
+ {
1363
+ "tag": "@trace.seam_port",
1364
+ "flags": [
1365
+ "SEAM_UNWIRED"
1366
+ ]
1367
+ },
1368
+ {
1369
+ "tag": "@trace.seam_pending",
1370
+ "flags": [
1371
+ "SEAM_UNWIRED"
1372
+ ]
1373
+ },
1374
+ {
1375
+ "tag": "@trace.stub",
1376
+ "flags": [
1377
+ "STUB_UNRESOLVED"
1378
+ ]
1379
+ },
1380
+ {
1381
+ "tag": "@trace.stub_owner",
1382
+ "flags": [
1383
+ "STUB_UNRESOLVED"
1384
+ ]
1385
+ },
1386
+ {
1387
+ "tag": "@trace.stub_for",
1388
+ "flags": [
1389
+ "STUB_UNRESOLVED"
1390
+ ]
1391
+ }
1392
+ ]
1393
+ },
1394
+ "report_root_keys": {
1395
+ "$comment": [
1396
+ "KEY Ở CẤP GỐC CỦA trace-report.json MÀ gate-trace ĐỌC (G57).",
1397
+ "",
1398
+ "VÌ SAO KHỐI NÀY TỒN TẠI: `report.domain` từng có 1 consumer (gate-trace, và nó CHẶN PR nếu",
1399
+ "giá trị khác 'all') và 0 producer — validate-traces không có cờ nào để đặt nó. Năm chỗ trong",
1400
+ "lệnh mô tả một 'domain argument' / 'domain filter' KHÔNG TỒN TẠI: ghi chú Gate ('target là",
1401
+ "một tên domain hoặc UC-ID cụ thể từ $ARGUMENTS') · Step 1 đọc TSV 'khớp domain target' ·",
1402
+ "schema report '<domain argument, or all if no filter>' · Step 8 'nếu có domain filter, chỉ",
1403
+ "gồm các PRD đó trong prds[]' · dòng trace-history mang field domain.",
1404
+ "Nên ô đó LUÔN ghi 'all', và phần kiểm của gate CHƯA BAO GIỜ chạy một lần nào.",
1405
+ "",
1406
+ "Đây ĐÚNG hình dạng R1 fail build vì nó ('field có consumer mà không có producer' — ca",
1407
+ "@trace.sc_version: 3 consumer, 0 producer, sống qua nhiều version không ai bắt được). Nó sống",
1408
+ "được vì R1 chỉ canh field trong SỔ TSV và tag trong CODE — không rule nào canh key ở cấp gốc",
1409
+ "của BIÊN BẢN JSON. Cùng điểm mù mà R13 vừa bịt cho `spec_baseline`.",
1410
+ "",
1411
+ "Và ghi chú Gate còn tệ hơn im lặng: một agent đọc 'target là một tên domain hoặc UC-ID' sẽ",
1412
+ "TIN rằng scoping hoạt động.",
1413
+ "",
1414
+ "R9(h) là phần 'máy canh': mỗi key phải CÓ MẶT trong skeleton JSON của validate-traces VÀ được",
1415
+ "gate-trace đọc qua `report.{key}`. Cả hai đầu, không đầu nào được lửng."
1416
+ ],
1417
+ "keys": [
1418
+ {
1419
+ "key": "scope",
1420
+ "shape": "{ kind: all | domain | prd | uc, value: string }",
1421
+ "written_by": "validate-traces",
1422
+ "read_by": "gate-trace",
1423
+ "why": "Gate PHẢI chặn mọi biên bản phiến diện, không chỉ biên bản hẹp-theo-domain — xem $comment của scope_kinds cho ca false-green mà `domain` một mình không bắt được."
1424
+ },
1425
+ {
1426
+ "key": "domain",
1427
+ "shape": "tên domain, hoặc 'all'",
1428
+ "written_by": "validate-traces",
1429
+ "read_by": "gate-trace",
1430
+ "why": "Giữ cho TƯƠNG THÍCH NGƯỢC: biên bản sinh trước G57 không có `scope`. Gate rơi về logic cũ + in ⚠️ 'kiểm độ phủ đang ở mức yếu' thay vì fail — người dùng chưa chạy lại /validate-traces không phải là lỗi của họ. Cùng khuôn backward-compat G48 đã dùng cho `platform`."
1431
+ }
1432
+ ],
1433
+ "scope_kinds": {
1434
+ "$comment": [
1435
+ "BẪY FALSE-GREEN mà bản fix G57 sẽ TỰ TAY MỞ nếu không siết G2 cùng lúc.",
1436
+ "",
1437
+ "gate-trace bản cũ: `if (domain !== 'all') { others = domainsOnDisk().filter(≠domain);",
1438
+ "if (others.length) fail }`. Trong repo MỘT domain, `others` là RỖNG ⇒ KHÔNG fail. Nên một",
1439
+ "biên bản hẹp-theo-PRD (hoặc hẹp-theo-UC) trong repo một domain sẽ được gate nhận là 'toàn",
1440
+ "bộ' — đúng cái 'cấp giấy xanh cho thứ chưa ai xem' mà chú thích của chính nó cảnh báo.",
1441
+ "",
1442
+ "Hiện tại vô hại vì không ai scope được. Nó BẬT NGAY khi scoping thành khả dụng. Nên điều",
1443
+ "kiện đúng là: `scope.kind !== 'all'` ⇒ FAIL, KHÔNG ngoại lệ — không phụ thuộc vào việc trên",
1444
+ "đĩa có bao nhiêu domain.",
1445
+ "",
1446
+ "Thêm cờ scope mà không siết G2 là biến cổng thành SÂN KHẤU — đúng cái G39 dựng lên để chống."
1447
+ ],
1448
+ "values": ["all", "domain", "prd", "uc"],
1449
+ "gate_rule": "scope.kind !== 'all' ⇒ FAIL G2, không ngoại lệ",
1450
+ "flags": [
1451
+ { "flag": "--domain", "kind": "domain", "arg": "{domain}" },
1452
+ { "flag": "--prd", "kind": "prd", "arg": "{TICKET-ID}" },
1453
+ { "flag": "--uc", "kind": "uc", "arg": "{UC-ID}" }
1120
1454
  ]
1455
+ }
1456
+ },
1457
+ "blocking": [
1458
+ {
1459
+ "counter": "orphaned_count",
1460
+ "issues_key": "orphaned",
1461
+ "label_fields": [
1462
+ "sc_id",
1463
+ "platform"
1464
+ ],
1465
+ "why": "code + test còn trỏ vào scenario đã bị xoá khỏi .feature"
1466
+ },
1467
+ {
1468
+ "counter": "trace_orphan_count",
1469
+ "issues_key": "trace_orphan",
1470
+ "label_fields": [
1471
+ "sc_id",
1472
+ "file"
1473
+ ],
1474
+ "why": "tag trong code trỏ vào SC-ID không tồn tại — không chỗ nào khác bắt được"
1475
+ },
1476
+ {
1477
+ "counter": "seam_unwired_count",
1478
+ "issues_key": "seam_unwired",
1479
+ "label_fields": [
1480
+ "port",
1481
+ "consumer_uc"
1482
+ ],
1483
+ "why": "luồng ghép đang chạy vào stub thay vì hàng thật"
1484
+ },
1485
+ {
1486
+ "counter": "stub_unresolved_count",
1487
+ "issues_key": "stub_unresolved",
1488
+ "label_fields": [
1489
+ "artifact",
1490
+ "consumer_uc"
1491
+ ],
1492
+ "why": "hàm rỗng chưa được lấp, nhưng caller đã gọi"
1493
+ }
1494
+ ],
1495
+ "reconcile": {
1496
+ "$comment": [
1497
+ "Gate đọc trace-report.json — một file CÓ THỂ CŨ. Không kiểm tươi thì gate là sân khấu:",
1498
+ "chạy /validate-traces một lần, commit report, rồi sửa spec/code tuỳ ý — CI đọc report cũ",
1499
+ "và cho qua mãi. So sánh theo thời gian commit KHÔNG dùng được (report và code thường nằm",
1500
+ "trong CÙNG một commit, nên mốc thời gian luôn 'cũ hơn'). Cách dùng được: đối chiếu report",
1501
+ "với chính sổ TSV mà nó khai là đang mô tả. Bất biến dựa vào: /validate-traces Step 6 ghi",
1502
+ "`status` trở lại TSV, nên NGAY SAU một lần chạy, TSV và report phải khớp.",
1503
+ "hard = lệch thì FAIL (đếm số row là tuyệt đối, không nhập nhằng).",
1504
+ "soft = lệch thì WARN (phụ thuộc quy ước đếm — vd ORPHANED được report biểu diễn bằng",
1505
+ "boolean `orphaned` chứ không phải giá trị `status`, nên không ép cứng)."
1506
+ ],
1507
+ "hard": {
1508
+ "total_scs": "số row dữ liệu trong mọi TSV thuộc phạm vi report"
1509
+ },
1510
+ "soft": {
1511
+ "drift_count": "DRIFT",
1512
+ "gap_count": "GAP",
1513
+ "untracked_count": "UNTRACKED",
1514
+ "orphaned_count": "ORPHANED"
1515
+ },
1516
+ "per_sc": {
1517
+ "$comment": [
1518
+ "G48 — đối chiếu TỪNG scenario thay vì chỉ tổng số. So tổng bỏ lọt ca thường gặp nhất",
1519
+ "khi spec tiến hoá: một SC bị xoá + một SC được thêm trong cùng một lần → tổng KHÔNG",
1520
+ "đổi → report cũ vẫn được coi là tươi. Khoá phải gồm platform: sc_id một mình không",
1521
+ "định danh được scenario ('sc_id trùng số giữa các platform là 2 scenario khác nhau').",
1522
+ "",
1523
+ "Chuẩn hoá ORPHANED: JSON CỐ Ý chỉ có 4 giá trị status, và row ORPHANED xuất ra là",
1524
+ "`status: DRIFT` + `orphaned: true` (validate-traces §Rules — để VS Code extension",
1525
+ "switch trên status không rơi vào nhánh không khớp). Nên khi so với cột `status` của",
1526
+ "TSV, phải quy đổi `orphaned:true` → ORPHANED TRƯỚC. Quy ước này ĐƯỢC GHI RÕ nên",
1527
+ "status so được ở mức `hard`; nếu nó chỉ là suy đoán thì phải để `soft`.",
1528
+ "",
1529
+ "BACKWARD-COMPAT: report sinh bởi version trước G48 không có `platform`. Gate rơi về",
1530
+ "so tổng số + in ⚠️ 'kiểm độ tươi đang ở mức yếu', KHÔNG fail — người dùng chưa chạy",
1531
+ "lại /validate-traces không phải là lỗi của họ."
1532
+ ],
1533
+ "key_fields": [
1534
+ "sc_id",
1535
+ "platform"
1536
+ ],
1537
+ "compare": "status",
1538
+ "orphaned_flag": "orphaned",
1539
+ "orphaned_status": "ORPHANED"
1540
+ }
1121
1541
  }
1122
1542
  },
1543
+ "changelog_row_contract": {
1544
+ "$comment": [
1545
+ "DÒNG CHANGELOG LÀ CONTRACT MÁY ĐỌC (G52 · G53 · G58).",
1546
+ "",
1547
+ "PRD phủ nhiều UC nhưng `Version` là MỘT scalar; tech-doc gộp cấp PRD phủ nhiều UC",
1548
+ "nhưng `@trace.revision` cũng là một scalar. Nên sửa một UC làm MỌI UC lệch version.",
1549
+ "Bộ lọc ở validate-traces Step 4/5 chống báo động oan bằng cách đọc dòng changelog để",
1550
+ "biết lần bump đó ĐỤNG UC NÀO — nên dòng đó không phải văn xuôi cho người đọc, nó là",
1551
+ "ĐẦU VÀO của một phép phân loại 🟠-vs-ⓘ.",
1552
+ "",
1553
+ "VÌ SAO CẦN KHỐI NÀY: trước đó contract này là contract DUY NHẤT có consumer ĐƯỢC CANH",
1554
+ "(R1–R11 canh tên cột/field) mà KHÔNG producer nào được canh. Bốn lệnh ghi nó, ba hình",
1555
+ "dạng khác nhau, không ai kiểm — và hỏng theo CẢ HAI hướng:",
1556
+ " G52 /review-context --fix ghi `Auto-fix: applied {N} findings` — 0 scope ⇒ 🟠 cho MỌI",
1557
+ " UC. Lệnh chạy thường xuyên nhất trong lane PO là lệnh gây nhiễu nhất; sau vài",
1558
+ " sprint PRD_DRIFT sáng thường trực và không ai đọc nữa — mất luôn cờ đúng.",
1559
+ " G53 /extend-prd ghi `sửa BR8` — nêu BR mà KHÔNG nêu UC sở hữu ⇒ UC đổi thật bị xếp ⓘ",
1560
+ " ⇒ rào của `--realign-prd-version` (chỉ chặn 🟠) MỞ CỬA ⇒ nhãn version bị dán lại",
1561
+ " trên một BR vừa đổi hành vi. Đây là hướng IM LẶNG, nặng hơn hướng ỒN.",
1562
+ " G58 /generate-tech-docs chỉ nói `thêm một row Changelog` — không format, không ví dụ.",
1563
+ " Chưa nổ vì comment trong template tình cờ đúng. Nợ chờ lệch.",
1564
+ "",
1565
+ "LỜI GIẢI ĐÃ TỒN TẠI TRONG CHÍNH FRAMEWORK: nhánh BDD của review-context Fix Mode",
1566
+ "Phase 3 làm đúng chuẩn — bump `sc_version` theo TỪNG scenario bị đụng, liệt kê check",
1567
+ "nào đổi thân scenario, và nói thẳng lý do (`bump vô cớ tạo DRIFT giả và làm cờ mất giá",
1568
+ "trị`). Nhánh PRD ngay trên nó thì không. Khối này chỉ nâng cách làm đó lên thành",
1569
+ "contract cho cả bốn producer.",
1570
+ "",
1571
+ "self-check R12 là phần `máy canh`: producer phải nhắc `scope_token`, consumer phải",
1572
+ "nhắc `scope_token` + `neutral_marker` + `resolve_to_uc.anchor`. Marker được GHI mà",
1573
+ "consumer không HIỂU thì tệ hơn không có marker — nó bị coi là văn xuôi và lọt im lặng."
1574
+ ],
1575
+ "scope_token": "{changelog_scope}",
1576
+ "scope_grammar": "Mỗi mệnh đề mở đầu bằng ĐƠN VỊ SỞ HỮU rồi tới mô tả: `{UC-ID}: {mô tả}`. Nhiều mệnh đề ngăn bằng `;`. Nội dung không thuộc UC nào → `PRD-global: {mô tả}`. BR/AC luôn đi KÈM UC sở hữu (`UC3: sửa BR8`) và KHÔNG BAO GIỜ đứng một mình (`sửa BR8`) — consumer khớp theo UC, nên một BR đứng một mình làm UC sở hữu nó bị xếp ⓘ trong khi nội dung của nó vừa đổi.",
1577
+ "ambiguous_fallback": "Không mệnh đề nào nêu được đơn vị sở hữu → consumer coi row là MƠ HỒ và gắn 🟠 cho MỌI UC (lưới an toàn: không suy đoán được thì quét rộng). Đây là hành vi ĐÚNG khi thiếu thông tin — nhưng là hành vi SAI khi producer CÓ thông tin mà không ghi.",
1578
+ "neutral_marker": "[no-behavior]",
1579
+ "neutral_semantics": "Hậu tố đặt sau một mệnh đề mà producer CHỨNG MINH ĐƯỢC là không đổi hành vi. Consumer: UC nêu trong mệnh đề có marker này KHÔNG bị 🟠 — nó ở lại ⓘ. Chỉ producer nào biết chính xác loại fix mình vừa áp mới được dùng; KHÔNG dùng cho người tự khai.",
1580
+ "neutral_checks": [
1581
+ { "check": "P4:skeleton", "why": "thêm section/metadata skeleton còn THIẾU — cấu trúc, không có nội dung nghiệp vụ nào mới" },
1582
+ { "check": "P4:status-row", "why": "thêm `| **Status** | draft |` khi row vắng — giá trị mặc định, không phải quyết định nghiệp vụ" },
1583
+ { "check": "P4:drop-empty-api-section", "why": "xoá section `Existing API Contract` RỖNG ở greenfield — xoá chỗ trống" }
1584
+ ],
1585
+ "not_neutral_checks": [
1586
+ { "check": "P1:banned-term", "why": "term đó cũng nằm trong .feature đã sinh — BDD lỗi thời THẬT về thuật ngữ (B2/C4 sẽ bắt). Đổi từ trong PRD mà không nêu UC là bỏ sót, không phải trung tính." },
1587
+ { "check": "P1:tech-jargon", "why": "auto-fix của nó là `diễn đạt lại` / `chuyển Design Spec` / `bỏ về Tech Docs` — VIẾT LẠI câu văn AC/BR, và nhánh 2/3 LẤY nội dung ra khỏi PRD. Không chứng minh được là trung tính." }
1588
+ ],
1589
+ "resolve_to_uc": {
1590
+ "required": true,
1591
+ "anchor": "BR/AC → UC sở hữu",
1592
+ "how": "Chuẩn hoá tập bị ảnh hưởng VỀ UC trước khi so: BR → UC có BR đó trong bảng Business Rule (PRD §3); AC → UC có AC đó ở dòng `**AC liên quan:**`. Không có bước này thì phép thử `UC này có trong tập?` luôn FALSE cho một row chỉ nêu BR/AC ⇒ ⓘ sai (G53). Step 4 đã mở file PRD nên không phát sinh I/O.",
1593
+ "fallback": "BR/AC không phân giải được về UC nào (ID đã bị xoá, hoặc PRD lệch cấu trúc) → coi row là MƠ HỒ (🟠 cho mọi UC), KHÔNG bỏ qua mệnh đề đó."
1594
+ },
1595
+ "producers": [
1596
+ { "cmd": "refine-prd", "artifact": "PRD", "where": "Phase 3", "scope_source": "`uc_id` / `section` của các finding được chấp nhận" },
1597
+ { "cmd": "extend-prd", "artifact": "PRD", "where": "Bước 6", "scope_source": "UC mới + UC SỞ HỮU mỗi BR/AC bị sửa (kết luận của Bước 3.2)" },
1598
+ { "cmd": "amend-prd", "artifact": "PRD", "where": "Bước 5", "scope_source": "UC SỞ HỮU của mỗi ID mà PO khai tường minh ở Bước 1 (`amend_targets`)" },
1599
+ { "cmd": "review-context", "artifact": "PRD", "where": "Fix Mode Phase 3", "scope_source": "`uc_id` của mọi finding `status: applied` (findings YAML — field bắt buộc)", "emits_neutral_marker": true, "why_emits": "nó là producer DUY NHẤT biết chính xác `check_id` của từng fix vừa áp, nên là producer duy nhất chứng minh được tính trung tính" },
1600
+ { "cmd": "generate-tech-docs", "artifact": "tech-design.md", "where": "Bước 1 (APPEND) + §Sinh", "scope_source": "UC trong `input_features` của batch vừa thêm/sửa" }
1601
+ ],
1602
+ "consumers": [
1603
+ { "cmd": "validate-traces", "where": "Step 4 (PRD) · Step 5 (tech-doc)", "use": "phân loại 🟠 DRIFT vs ⓘ STALE_REF theo từng UC" },
1604
+ { "cmd": "generate-bdd", "where": "Version Check", "use": "khuyến nghị Y (cập nhật hẹp) vs F (gen lại toàn bộ)" }
1605
+ ]
1606
+ },
1607
+ "spec_edit_detection": {
1608
+ "$comment": [
1609
+ "PHÁT HIỆN SỬA TÀI LIỆU NGOÀI ĐƯỜNG CHÍNH THỨC (G54, nửa sau).",
1610
+ "",
1611
+ "TOÀN BỘ lưới an toàn của framework so NHÃN VERSION, không so NỘI DUNG — không có một",
1612
+ "content hash nào ở đâu. Nên một PRD bị sửa tay mà không bump version là điểm mù TUYỆT",
1613
+ "ĐỐI: validate-traces thấy `PRD version == prd_version` ⇒ sạch · gate-trace thấy report",
1614
+ "khớp sổ ⇒ PASS · require-fresh-audit thấy PR không chạm tag ⇒ không đòi audit. Cả ba",
1615
+ "tầng xanh, và cả ba ĐÚNG theo định nghĩa của chính chúng.",
1616
+ "",
1617
+ "Và G54 không chỉ CHO PHÉP sửa tay — nó ĐẨY người dùng về đó: /generate-prd chỉ có một",
1618
+ "cổng chặn-cứng rồi vẫn ghi đè (mất changelog, đánh số lại BR) · /extend-prd chỉ",
1619
+ "add-only · /refine-prd chỉ áp được finding do chính nó tìm ra. Trước /amend-prd, hành",
1620
+ "vi hợp lý duy nhất còn lại LÀ mở file .md ra gõ. Thiết kế tự tạo ra con đường nó không",
1621
+ "canh được.",
1622
+ "",
1623
+ "VÌ SAO KHÔNG DÙNG CONTENT HASH: git đã băm nội dung hộ rồi. Thêm một cột hash vào sổ",
1624
+ "TSV là thêm một thứ ghi-tay-bởi-LLM (đúng lớp lỗi G38 mà lint-trace tồn tại để chống),",
1625
+ "trong khi `git diff` trả lời chính xác cùng câu hỏi và không sai được.",
1626
+ "",
1627
+ "VÌ SAO KHÔNG NẰM TRONG `gate.blocking` — quyết định có chủ ý, không phải bỏ sót:",
1628
+ " 1. `gate.blocking` có nghĩa HẸP là CODE ĐANG HỎNG, và R9(e) tồn tại để giữ ranh giới",
1629
+ " đó. Cờ này nói về SPEC; code có thể đang hoàn toàn đúng.",
1630
+ " 2. Nợ tồn khi mới bật: mọi project đang chạy đều đã có PRD sửa tay. Một cờ chặn mới",
1631
+ " sẽ đỏ khắp nơi ở lần chạy đầu ⇒ người ta TẮT CỔNG ⇒ mất luôn 4 cờ 🔴 thật. Đó",
1632
+ " đúng là thất bại R9(e) được viết ra để chặn, chỉ đến bằng một cửa khác.",
1633
+ " 3. Nó vẫn 🔴 trong REPORT và in mỗi lần chạy — đủ để thấy, không đủ để làm tắt cổng.",
1634
+ "Team nào đã dọn sạch nợ tồn thì tự thêm counter này vào `gate.blocking`; một dòng.",
1635
+ "",
1636
+ "ĐƯỜNG RA TỰ LÀNH — cố ý KHÔNG có lệnh escape riêng: cách sửa đúng là bump version +",
1637
+ "ghi một row changelog (tức chính việc /amend-prd làm hộ). Làm xong thì",
1638
+ "`version != version_at_audit` ⇒ cờ tự tắt, và logic PRD_DRIFT bình thường tiếp quản.",
1639
+ "Thêm một `--accept-edit` sẽ là thêm một đường DÁN NHÃN lên thay đổi chưa ai xem — đúng",
1640
+ "cái sai mà rào của `--realign` tồn tại để chặn."
1641
+ ],
1642
+ "flag": "PRD_UNTRACKED_EDIT",
1643
+ "detects": "nội dung file PRD đã đổi kể từ lần audit trước NHƯNG nhãn `Version` không đổi ⇒ có người sửa ngoài /generate-prd · /extend-prd · /amend-prd · /refine-prd · /review-context",
1644
+ "baseline_fields": {
1645
+ "$comment": "validate-traces GHI khối này vào trace-report.json mỗi lần chạy; lần chạy sau ĐỌC nó làm mốc. Thiếu nửa GHI thì cờ không bao giờ bật — R6/R7 chỉ canh được nửa BÁO CÁO (giá trị enum có xuất hiện · counter có trong summary), nên self-check R13 canh nửa này.",
1646
+ "path": "spec_baseline",
1647
+ "keys": ["prd_path", "sha_at_audit", "version_at_audit"]
1648
+ },
1649
+ "evidence": [
1650
+ { "source": "git diff", "cmd": "git -C {specs repo} diff --name-only {sha_at_audit}..HEAD -- {prd_path}", "catches": "sửa đã commit" },
1651
+ { "source": "git status", "cmd": "git -C {specs repo} status --porcelain -- {prd_path}", "catches": "sửa CHƯA commit — ca thường gặp nhất khi PO đang gõ" }
1652
+ ],
1653
+ "no_false_positive": "PO sửa VÀ bump version → `version != version_at_audit` → KHÔNG bật cờ. Chỉ đúng ca \"đổi nội dung, giữ nguyên nhãn\" mới bật.",
1654
+ "degraded": "Không phải git repo, hoặc `sha_at_audit` không còn (history bị rewrite) → BỎ QUA check này và in một dòng ⚠️ nói rõ là đang mù, KHÔNG bịa cờ và KHÔNG im lặng.",
1655
+ "written_by": ["validate-traces"],
1656
+ "read_by": ["validate-traces", "sync"],
1657
+ "$read_by_comment": [
1658
+ "`sync` đọc mốc này để trả lời câu KHÁC với câu của diff pull-to-pull (G56).",
1659
+ "Diff `{old_sha}..{new_sha}` trả lời \"đổi gì kể từ lần PULL trước\" — mốc đó RESET mỗi lần",
1660
+ "pull. Pull thứ Hai, thứ Ba, thứ Tư mà không audit lần nào thì đến thứ Năm cái thấy được chỉ",
1661
+ "là delta của MỘT ngày, không phải nợ đã tích. `sha_at_audit` trả lời \"đổi gì kể từ lần",
1662
+ "AUDIT gần nhất\" — con số đó tích luỹ đúng, không bao giờ reset sai."
1663
+ ]
1664
+ },
1665
+ "positive_assertion_guards": {
1666
+ "$comment": [
1667
+ "QUYỀN ĐƯỢC KHẲNG ĐỊNH `pass` (G55) — mệnh đề ĐỐI NGẪU của luật G28.",
1668
+ "",
1669
+ "rules/workflow.md §\"Làm mất hiệu lực ≠ ghi đè\" nói: ai làm một giá trị HẾT ĐÚNG thì BẮT",
1670
+ "BUỘC hạ nó xuống. Luật đó đúng và được thực thi tốt (/generate-bdd, /generate-code,",
1671
+ "/fix-bug đều làm). Nhưng nửa còn lại chưa được viết: AI KHẲNG ĐỊNH MỘT GIÁ TRỊ DƯƠNG THÌ",
1672
+ "PHẢI KIỂM RẰNG MÌNH ĐƯỢC PHÉP KHẲNG ĐỊNH.",
1673
+ "",
1674
+ "Thiếu nửa sau nên chuỗi thành: hạ xuống → dựng lại → hạ xuống → dựng lại.",
1675
+ " 1. PO đổi AC → /generate-bdd đặt status=DRIFT và HẠ dev_selftest→not_run, kèm cảnh báo",
1676
+ " \"{n} test của {sc_id} viết cho spec cũ — /dev-gen-test rà lại trước khi chạy\".",
1677
+ " 2. Sáng sau dev chạy /dev-run-test (thói quen bình thường), CHƯA /generate-code, CHƯA",
1678
+ " /dev-gen-test. Test cũ + code cũ ⇒ xanh hết.",
1679
+ " 3. /dev-run-test ghi dev_selftest=pass, dev_selftest_at=HÔM NAY.",
1680
+ "Row giờ mang status=DRIFT + dev_selftest=pass với ngày hôm nay. Lệnh kế tiếp trong vòng",
1681
+ "lặp dev BÌNH THƯỜNG vừa dựng lại đúng cái tín hiệu /generate-bdd hạ xuống.",
1682
+ "",
1683
+ "VÌ SAO `status` KHÔNG TRỰC GIAO Ở ĐÂY: hai file lệnh viết `dev_selftest`/`qc_status`",
1684
+ "\"trực giao với status\". Trực giao về KẾT QUẢ CHẠY thì đúng. Nhưng ô này không mang nghĩa",
1685
+ "\"test đã chạy và xanh\" — nó mang nghĩa \"scenario này đã được nghiệm thu theo spec HIỆN",
1686
+ "TẠI\". Trên row DRIFT, nghĩa thứ nhất đúng và nghĩa thứ hai SAI. `pass` ở đó không phải",
1687
+ "kết quả sai; nó là lời khẳng định sai về điều mà kết quả đó chứng minh.",
1688
+ "",
1689
+ "TIN XẤU LUÔN HỢP LỆ: `fail` VẪN được ghi trên row DRIFT. Đây là guard cho lời khẳng định",
1690
+ "DƯƠNG, không phải lệnh \"bỏ qua kết quả khi DRIFT\". Test đỏ trên row DRIFT vẫn là thông",
1691
+ "tin thật. Chỉ `pass` cần giấy phép.",
1692
+ "",
1693
+ "VÌ SAO CẦN CẢ HAI TẦNG:",
1694
+ " PA-1 (lệnh đọc status trước khi ghi) chặn NGUỒN.",
1695
+ " PA-2 (lint-trace T12) bắt TRẠNG THÁI, bất kể ai ghi — sổ sửa tay, sổ sinh bởi version",
1696
+ " framework cũ, hay một lệnh tương lai quên luật. T12 rẻ, xác định, chạy được trong",
1697
+ " CI mà không cần LLM.",
1698
+ "T1–T8 kiểm HÌNH DẠNG TỪNG Ô (đủ 24 cột, giá trị trong vocabulary, ngày parse được) —",
1699
+ "không rule nào kiểm NHẤT QUÁN GIỮA CÁC Ô. Nên `status=DRIFT` + `dev_selftest=pass` là một",
1700
+ "sổ HOÀN TOÀN HỢP LỆ với cả 11 rule cũ. T12 là rule đầu tiên nhìn nhiều ô cùng lúc.",
1701
+ "",
1702
+ "LỖ ĐÃ BIẾT, chấp nhận có chủ ý: ca \"spec đổi theo hướng THÊM hành vi mới\". Sau khi",
1703
+ "/generate-code bắt kịp, gen_ver=spec_ver ⇒ status=OK; test cũ vẫn PASS (chúng không sai,",
1704
+ "chỉ KHÔNG PHỦ phần mới); test_count>0 nên không có GAP ⇒ bảng xanh trên một hành vi chưa",
1705
+ "test lần nào. Đóng hẳn ca này cần một cột thứ 25 `test_spec_ver` — đổi contract + lint",
1706
+ "binding + backward-compat với mọi sổ 24 cột đang tồn tại. Hoãn có chủ ý; guard này vẫn",
1707
+ "thu hẹp đáng kể vì trong lúc row còn DRIFT thì dev_selftest bị giữ ở not_run, nên dev",
1708
+ "buộc phải làm gì đó và đường đúng là /dev-gen-test."
1709
+ ],
1710
+ "lint_rule": "T12",
1711
+ "blocked_when_status": ["DRIFT", "ORPHANED"],
1712
+ "negative_always_written": "`fail` được ghi bình thường trên MỌI status — guard này chỉ chặn giá trị KHẲNG ĐỊNH. Chặn cả `fail` là biến một guard chống-báo-cáo-sai thành một guard che-tin-xấu.",
1713
+ "guards": [
1714
+ {
1715
+ "column": "dev_selftest",
1716
+ "owner": "dev-run-test",
1717
+ "positive_values": ["pass"],
1718
+ "downgrade_to": "not_run",
1719
+ "companion": "dev_selftest_at",
1720
+ "companion_downgrade": "—",
1721
+ "why": "trên row DRIFT, test cũ đang nghiệm thu một hành vi không còn tồn tại; trên row ORPHANED, scenario đã bị xoá khỏi .feature nên không còn gì để nghiệm thu"
1722
+ },
1723
+ {
1724
+ "column": "qc_status",
1725
+ "owner": "qc-run-test",
1726
+ "positive_values": ["pass"],
1727
+ "downgrade_to": "not_run",
1728
+ "companion": "qc_run_at",
1729
+ "companion_downgrade": "—",
1730
+ "why": "đối xứng hoàn toàn với dev_selftest — `skip` KHÔNG bị chặn vì nó là giá trị trung tính, không phải lời khẳng định"
1731
+ }
1732
+ ]
1733
+ },
1123
1734
  "path_keys": {
1124
1735
  "$comment": "Key phải có trong templates/project-context.yaml VÀ được dùng bởi ít nhất một actor. Key khai mà không ai dùng = key chết (GAPS.md G17).",
1125
1736
  "config_file": "templates/project-context.yaml",