@educa-corp/sdd-framework 0.9.6 → 0.9.7

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 (93) hide show
  1. package/bin/lint-trace.js +4 -4
  2. package/bin/self-check.js +448 -12
  3. package/bin/trace-schema.json +643 -112
  4. package/core/FRAMEWORK_VERSION +1 -1
  5. package/core/commands/amend-prd.md +7 -1
  6. package/core/commands/debug.md +8 -2
  7. package/core/commands/define-product.md +38 -1
  8. package/core/commands/dev-gen-test.md +9 -3
  9. package/core/commands/dev-run-test.md +8 -2
  10. package/core/commands/dev-smoke-test.md +7 -1
  11. package/core/commands/extend-prd.md +7 -1
  12. package/core/commands/fix-bug.md +11 -5
  13. package/core/commands/generate-architecture.md +9 -1
  14. package/core/commands/generate-bdd.md +45 -5
  15. package/core/commands/generate-code.md +43 -4
  16. package/core/commands/generate-design-spec.md +7 -1
  17. package/core/commands/generate-prd.md +9 -1
  18. package/core/commands/generate-spec-manifest.md +7 -1
  19. package/core/commands/generate-tech-docs.md +41 -1
  20. package/core/commands/learn.md +7 -1
  21. package/core/commands/map-testids.md +11 -5
  22. package/core/commands/propose-scenario.md +7 -1
  23. package/core/commands/qc-analyze.md +12 -6
  24. package/core/commands/qc-automation-assess.md +356 -0
  25. package/core/commands/qc-design-script.md +400 -0
  26. package/core/commands/qc-design-test.md +21 -15
  27. package/core/commands/qc-plan.md +9 -3
  28. package/core/commands/qc-report.md +9 -3
  29. package/core/commands/qc-review-script.md +333 -0
  30. package/core/commands/{qc-review.md → qc-review-testcase.md} +84 -53
  31. package/core/commands/qc-run-manualtest.md +401 -0
  32. package/core/commands/qc-run-script.md +414 -0
  33. package/core/commands/refine-prd.md +7 -1
  34. package/core/commands/report-bug.md +9 -3
  35. package/core/commands/review-code.md +9 -3
  36. package/core/commands/review-context.md +11 -3
  37. package/core/commands/review-tech-docs.md +11 -3
  38. package/core/commands/setup-ai-first.md +7 -1
  39. package/core/commands/validate-traces.md +10 -4
  40. package/core/modules/qc-playwright/stack-profile.yaml +1 -1
  41. package/core/rules/workflow.md +2 -2
  42. package/core/skills/qc/_shared/self-review-principles.md +2 -2
  43. package/core/skills/qc/qa-analyst/DOC_GAP.template.md +1 -1
  44. package/core/skills/qc/qa-analyst/spec-issue-reporter.md +1 -1
  45. package/core/skills/qc/qa-automation-assess/matrix.md +120 -0
  46. package/core/skills/qc/qa-designer/e2e/journey.md +1 -1
  47. package/core/skills/qc/qa-designer/exploratory/explore-to-functional.md +1 -1
  48. package/core/skills/qc/qa-designer/functional/api.md +1 -1
  49. package/core/skills/qc/qa-designer/functional/gui-feature.md +1 -1
  50. package/core/skills/qc/qa-designer/functional/gui-screen.md +1 -1
  51. package/core/skills/qc/qa-designer/integration/api.md +1 -1
  52. package/core/skills/qc/qa-designer/integration/db.md +1 -1
  53. package/core/skills/qc/qa-designer/integration/gui.md +1 -1
  54. package/core/skills/qc/qa-designer/integration/kafka.md +1 -1
  55. package/core/skills/qc/qa-designer/non-functional.md +1 -1
  56. package/core/skills/qc/qa-designer/shared/tc-metadata-format.md +11 -6
  57. package/core/skills/qc/qa-reviewer/script/e2e.md +1 -1
  58. package/core/skills/qc/qa-reviewer/script/exploratory.md +1 -1
  59. package/core/skills/qc/qa-reviewer/script/functional.md +1 -1
  60. package/core/skills/qc/qa-reviewer/script/integration.md +1 -1
  61. package/core/skills/qc/qa-reviewer/script/non-functional.md +1 -1
  62. package/core/skills/qc/qa-reviewer/shared/review-file-template.md +3 -3
  63. package/core/skills/qc/qa-reviewer/test-case/e2e.md +1 -1
  64. package/core/skills/qc/qa-reviewer/test-case/functional.md +1 -1
  65. package/core/skills/qc/qa-reviewer/test-case/integration.md +1 -1
  66. package/core/skills/qc/qa-reviewer/test-case/non-functional.md +1 -1
  67. package/core/skills/qc/qa-runner/e2e.md +2 -2
  68. package/core/skills/qc/qa-runner/functional/gui-feature.md +3 -3
  69. package/core/skills/qc/qa-runner/functional/gui-screen.md +3 -3
  70. package/core/skills/qc/qa-runner/integration.md +1 -1
  71. package/core/skills/qc/qa-runner/non-functional.md +1 -1
  72. package/core/steps/context-loader.md +1 -1
  73. package/core/steps/gate.md +7 -1
  74. package/core/steps/qc-scope.md +2 -2
  75. package/core/steps/qc-stamp.md +4 -4
  76. package/core/steps/report-footer.md +10 -9
  77. package/docs/02-concepts/pipeline-steps/07-dev-selftest.md +1 -1
  78. package/docs/02-concepts/pipeline-steps/08-qc-automation.md +9 -8
  79. package/docs/02-concepts/pipeline-steps/10-feedback-loop.md +3 -3
  80. package/docs/02-concepts/traceability.md +1 -1
  81. package/docs/03-guides/developer.md +1 -1
  82. package/docs/03-guides/tester-qa.md +8 -7
  83. package/docs/04-reference/commands.md +4 -2
  84. package/docs/04-reference/trace-schema.md +4 -4
  85. package/docs/explain/17-qc-design-test.md +5 -5
  86. package/docs/explain/18-qc-review.md +42 -20
  87. package/docs/explain/19-qc-run-test.md +9 -6
  88. package/docs/explain/20-qc-report.md +3 -3
  89. package/docs/explain/23-fix-bug.md +2 -2
  90. package/docs/explain/README.md +2 -2
  91. package/docs/plans/qc-surgery/01-checklist.md +70 -17
  92. package/package.json +1 -1
  93. package/core/commands/qc-run-test.md +0 -561
@@ -28,7 +28,9 @@
28
28
  "dev-gen-test": "commands/dev-gen-test.tmpl",
29
29
  "dev-run-test": "commands/dev-run-test.tmpl",
30
30
  "dev-smoke-test": "commands/dev-smoke-test.tmpl",
31
- "qc-run-test": "commands/qc-run-test.tmpl",
31
+ "qc-design-script": "commands/qc-design-script.tmpl",
32
+ "qc-run-script": "commands/qc-run-script.tmpl",
33
+ "qc-run-manualtest": "commands/qc-run-manualtest.tmpl",
32
34
  "report-bug": "commands/report-bug.tmpl",
33
35
  "propose-scenario": "commands/propose-scenario.tmpl",
34
36
  "fix-bug": "commands/fix-bug.tmpl",
@@ -194,14 +196,14 @@
194
196
  "shape": "{UC-ID}-SC{N}",
195
197
  "written_by": [
196
198
  "dev-gen-test",
197
- "qc-run-test",
199
+ "qc-design-script",
198
200
  "fix-bug"
199
201
  ],
200
202
  "read_by": [
201
203
  "validate-traces",
202
204
  "review-code",
203
205
  "dev-run-test",
204
- "qc-run-test"
206
+ "qc-run-script"
205
207
  ],
206
208
  "why": "Khớp test → sc_id khi ghi test_count/dev_selftest/qc_status."
207
209
  },
@@ -477,11 +479,11 @@
477
479
  ],
478
480
  "read_by": [
479
481
  "generate-code",
480
- "qc-run-test",
482
+ "qc-design-script",
481
483
  "qc-design-test"
482
484
  ],
483
- "$history_note": "G30 lô 3 phát hiện field này TỰ MÔ TẢ là contract FE↔QC nhưng chỉ nửa FE (generate-code) đọc — QC tự suy tên thuộc tính từ platform. G37 nối nửa QC: qc-run-test đọc để CẤU HÌNH test-id attribute (Playwright get_by_test_id mặc định dò `data-testid`; dự án dùng `data-test`/`data-qa` sẽ trượt 100% locator nếu không set), qc-design-test ghi lại vào metadata .Test.md để reviewer đối chiếu.",
484
- "why": "Tên thuộc tính test-id của stack — contract FE↔QC. FE emit theo nó (generate-code), QC cấu hình locator theo nó (qc-run-test). Suy từ platform thay vì đọc field = phát biểu lại sự thật đã ghi ở nơi khác, và hỏng IM LẶNG: test fail 'element not found' trông y hệt bug sản phẩm."
485
+ "$history_note": "G30 lô 3 phát hiện field này TỰ MÔ TẢ là contract FE↔QC nhưng chỉ nửa FE (generate-code) đọc — QC tự suy tên thuộc tính từ platform. G37 nối nửa QC: qc-design-script đọc để CẤU HÌNH test-id attribute (Playwright get_by_test_id mặc định dò `data-testid`; dự án dùng `data-test`/`data-qa` sẽ trượt 100% locator nếu không set), qc-design-test ghi lại vào metadata .Test.md để reviewer đối chiếu.",
486
+ "why": "Tên thuộc tính test-id của stack — contract FE↔QC. FE emit theo nó (generate-code), QC cấu hình locator theo nó (qc-design-script). Suy từ platform thay vì đọc field = phát biểu lại sự thật đã ghi ở nơi khác, và hỏng IM LẶNG: test fail 'element not found' trông y hệt bug sản phẩm."
485
487
  },
486
488
  {
487
489
  "name": "@trace.domain",
@@ -663,7 +665,8 @@
663
665
  "generate-code",
664
666
  "dev-gen-test",
665
667
  "dev-run-test",
666
- "qc-run-test",
668
+ "qc-run-script",
669
+ "qc-run-manualtest",
667
670
  "report-bug",
668
671
  "fix-bug"
669
672
  ]
@@ -761,7 +764,8 @@
761
764
  ],
762
765
  "read_by": [
763
766
  "validate-traces",
764
- "qc-run-test"
767
+ "qc-run-script",
768
+ "qc-run-manualtest"
765
769
  ]
766
770
  },
767
771
  {
@@ -776,15 +780,17 @@
776
780
  ],
777
781
  "read_by": [
778
782
  "validate-traces",
779
- "qc-run-test"
783
+ "qc-run-script",
784
+ "qc-run-manualtest"
780
785
  ]
781
786
  },
782
787
  {
783
788
  "n": 10,
784
789
  "name": "qc_status",
785
- "$comment": "Chủ (ghi pass/fail/skip): qc-run-testDUY NHẤT. Invalidator (chỉ hạ về not_run khi spec/code/HỢP ĐỒNG TEST-ID vừa đổi): generate-bdd · generate-code · map-testids. Xem dev_selftest + rules/workflow.md 'Làm mất hiệu lực ≠ ghi đè'. map-testids vào danh sách này vì đổi một test-id trong §4.5.6 làm script QC bám id cũ HẾT ĐÚNG: nó định vị một element không còn mang id đó, nên `pass` cũ không còn nghĩa 'scenario đã được nghiệm thu theo spec hiện tại'. Cột 'Serves SC' của row chính là chỉ mục ngược để biết SC nào bị ảnh hưởng.",
790
+ "$comment": "Chủ (ghi pass/fail/skip): qc-run-script + qc-run-manualtest HAI chủ kể từ Đợt 2 · b2, và KHÔNG có chủ thứ ba. Invalidator (chỉ hạ về not_run khi spec/code/HỢP ĐỒNG TEST-ID vừa đổi): generate-bdd · generate-code · map-testids. Xem dev_selftest + rules/workflow.md 'Làm mất hiệu lực ≠ ghi đè'. map-testids vào danh sách này vì đổi một test-id trong §4.5.6 làm script QC bám id cũ HẾT ĐÚNG: nó định vị một element không còn mang id đó, nên `pass` cũ không còn nghĩa 'scenario đã được nghiệm thu theo spec hiện tại'. Cột 'Serves SC' của row chính là chỉ mục ngược để biết SC nào bị ảnh hưởng.",
786
791
  "written_by": [
787
- "qc-run-test",
792
+ "qc-run-script",
793
+ "qc-run-manualtest",
788
794
  "generate-bdd",
789
795
  "generate-code",
790
796
  "map-testids"
@@ -800,9 +806,10 @@
800
806
  {
801
807
  "n": 11,
802
808
  "name": "qc_run_at",
803
- "$comment": "Chủ: qc-run-test. Invalidator (hạ về —): generate-bdd · generate-code · map-testids. Xem qc_status.",
809
+ "$comment": "Chủ: qc-run-script + qc-run-manualtest. Invalidator (hạ về —): generate-bdd · generate-code · map-testids. Xem qc_status.",
804
810
  "written_by": [
805
- "qc-run-test",
811
+ "qc-run-script",
812
+ "qc-run-manualtest",
806
813
  "generate-bdd",
807
814
  "generate-code",
808
815
  "map-testids"
@@ -817,7 +824,8 @@
817
824
  "n": 12,
818
825
  "name": "qc_owner",
819
826
  "written_by": [
820
- "qc-run-test",
827
+ "qc-run-script",
828
+ "qc-run-manualtest",
821
829
  "report-bug"
822
830
  ],
823
831
  "read_by": [
@@ -830,13 +838,15 @@
830
838
  "n": 13,
831
839
  "name": "qc_blocked_by",
832
840
  "written_by": [
833
- "qc-run-test",
841
+ "qc-run-script",
842
+ "qc-run-manualtest",
834
843
  "report-bug"
835
844
  ],
836
845
  "read_by": [
837
846
  "validate-traces",
838
847
  "generate-code",
839
- "qc-run-test"
848
+ "qc-run-script",
849
+ "qc-run-manualtest"
840
850
  ]
841
851
  },
842
852
  {
@@ -950,7 +960,8 @@
950
960
  "validate-traces",
951
961
  "generate-code",
952
962
  "dev-run-test",
953
- "qc-run-test"
963
+ "qc-run-script",
964
+ "qc-run-manualtest"
954
965
  ]
955
966
  },
956
967
  {
@@ -1021,7 +1032,8 @@
1021
1032
  "generate-code",
1022
1033
  "dev-gen-test",
1023
1034
  "dev-run-test",
1024
- "qc-run-test",
1035
+ "qc-run-script",
1036
+ "qc-run-manualtest",
1025
1037
  "review-context",
1026
1038
  "review-tech-docs",
1027
1039
  "fix-bug"
@@ -1077,7 +1089,7 @@
1077
1089
  ],
1078
1090
  "read_by": [
1079
1091
  "fix-bug",
1080
- "qc-run-test"
1092
+ "qc-run-script"
1081
1093
  ]
1082
1094
  },
1083
1095
  {
@@ -1332,33 +1344,97 @@
1332
1344
  "sinh ra bản đang có?"
1333
1345
  ],
1334
1346
  "label_marker": "Checkpoint:",
1347
+ "unconditional_hard_budget": {
1348
+ "max": 3,
1349
+ "current": [
1350
+ "extend-prd",
1351
+ "amend-prd",
1352
+ "refine-prd"
1353
+ ],
1354
+ "why": [
1355
+ "SỐ CỔNG CỨNG NỔ Ở MỌI LẦN CHẠY — không phải tổng số lệnh `hard`.",
1356
+ "",
1357
+ "Đo 2026-09-16: 12 lệnh ở `hard`, nhưng 9 trong đó khai `narrowing` nên chỉ nổ khi ĐÃ",
1358
+ "có thứ để mất (file đã tồn tại / bảng đã có row / findings đã có quyết định). Chạy một",
1359
+ "lượt đầu trọn vẹn: 0 cổng cứng nổ. Con số gây lo lắng là 12; con số ĐÚNG là 3.",
1360
+ "",
1361
+ "VÌ SAO PHẢI CÓ HẠN MỨC: `--yes` KHÔNG bỏ qua được `hard`. Nên khi `hard` trở nên phiền,",
1362
+ "người dùng không thêm `--yes` — họ GỠ LỆNH KHỎI `hard`, và mất luôn những cổng đáng",
1363
+ "giữ. Một cơ chế cổng chặn quá dày không làm hệ thống an toàn hơn rồi chậm hơn; nó làm",
1364
+ "hệ thống KÉM an toàn hơn, vì nó tiêu huỷ chính cơ chế nó thuộc về. gate.md Bước 3b tồn",
1365
+ "tại vì đúng rủi ro đó: Y thành phản xạ và cổng hỏng âm thầm.",
1366
+ "",
1367
+ "BA LỆNH HIỆN TẠI đều là sửa một PRD ĐÃ DUYỆT — không tồn tại lần chạy vô hại, nên cổng",
1368
+ "nổ mọi lần là ĐÚNG BẢN CHẤT.",
1369
+ "",
1370
+ "TĂNG SỐ NÀY LÀ MỘT QUYẾT ĐỊNH, KHÔNG PHẢI MỘT HỆ QUẢ. R20 chặn build khi vượt, nên việc",
1371
+ "vượt phải là một lần sửa contract NHÌN THẤY TRONG DIFF. Nếu đang định nâng vì N3: kiểm",
1372
+ "xem lệnh đó có THẬT SỰ không có lần chạy vô hại không, hay chỉ là chưa ai ngồi nghĩ ra",
1373
+ "điều kiện cho nó."
1374
+ ]
1375
+ },
1335
1376
  "hard": [
1377
+ {
1378
+ "cmd": "qc-automation-assess",
1379
+ "narrowing": "chỉ nổ khi `AUTOMATION_ASSESSMENT.md` ĐÃ TỒN TẠI. Lần đánh giá đầu không ghi đè gì, đi thẳng.",
1380
+ "why": "ghi đè bảng đánh giá đã có → mất quyết định `Automatable: Y/N` + nhãn lý do đã chốt, và XOÁ cột `Script file` do /qc-design-script điền — chỉ mục ngược duy nhất từ TC sang file code",
1381
+ "$narrowing_note": "Khác /qc-review-testcase (cũng ghi một file QC nhưng ở mức `none`): biên bản soát là KẾT QUẢ CỦA MỘT LƯỢT ĐỌC, soát lại là đúng bản chất. Bảng này là QUYẾT ĐỊNH TÍCH LUỸ, và một nửa của nó do LỆNH KHÁC điền."
1382
+ },
1383
+ {
1384
+ "cmd": "generate-bdd",
1385
+ "narrowing": "chỉ nổ khi Version Check chọn `F` (gen lại TOÀN BỘ scenario) — nhánh DUY NHẤT đánh số lại `sc_id`. Chọn `Y` (cập nhật hẹp) không mất gì và đi thẳng; lần gen đầu cũng đi thẳng. Xem §Chạy lại.",
1386
+ "why": "ghi đè `.feature` đã có — mode F gen lại TOÀN BỘ scenario, ĐÁNH SỐ LẠI `sc_id` mà sổ TSV, tag `@trace.verifies` trong code và `.Test.md` đều đang trỏ vào"
1387
+ },
1388
+ {
1389
+ "cmd": "generate-tech-docs",
1390
+ "narrowing": "chỉ nổ khi trả lời `Y` (merge/mở rộng) cho một UC ĐÃ CÓ nội dung trong doc. UC mới và `N` (bỏ qua UC) đi thẳng; doc chưa tồn tại cũng đi thẳng. Xem §Chạy lại.",
1391
+ "why": "ghi đè/mở rộng tech-doc gộp đã có — doc chứa §4.5.6 Test Selectors do SA VIẾT TAY và §12 GAP Register, cả hai không suy lại được từ BDD"
1392
+ },
1393
+ {
1394
+ "cmd": "generate-code",
1395
+ "narrowing": "chỉ nổ khi trạng thái của UC là `drifted` hoặc `synced` (Bước 1 phân biệt qua sổ TSV). Trạng thái `new` — chưa có code — đi thẳng, gồm cả ca headless `claude -p \"/generate-code UC1 --yes\"` mà gate.md nêu làm ví dụ. Xem §Chạy lại.",
1396
+ "why": "ghi đè mã nguồn đã có — dev SỬA TAY chính các file này; trước 2026-09-16 lệnh đứng ở mức `normal`, tức `--yes` ghi đè bản sửa tay TRONG IM LẶNG"
1397
+ },
1398
+ {
1399
+ "cmd": "define-product",
1400
+ "narrowing": "chỉ nổ khi file product-definition ĐÃ TỒN TẠI và có ≥1 mục mang `✅ PO xác nhận`. Buổi discovery đầu đi thẳng, và resume khi chưa mục nào được chốt cũng đi thẳng. Xem §Chạy lại.",
1401
+ "why": "ghi đè product-definition đã có — từng mục mang dấu `✅ PO xác nhận`, và `Completed Phase` cho phép resume; ghi đè là xoá cả vòng xác nhận của PO"
1402
+ },
1336
1403
  {
1337
1404
  "cmd": "generate-prd",
1405
+ "narrowing": "chỉ chạm tới khi PRD ĐÃ TỒN TẠI — và khi đó lệnh DỪNG HẲN, không cả hỏi Y/N (Bước 1: \"Tồn tại → DỪNG. KHÔNG ghi, KHÔNG hỏi Y/N\"). PRD chưa có: đi thẳng.",
1338
1406
  "why": "ghi đè PRD đã có → mất changelog, ĐÁNH SỐ LẠI BR, phá @trace.business_rules trong mọi .feature đã sinh"
1339
1407
  },
1340
1408
  {
1341
1409
  "cmd": "extend-prd",
1410
+ "narrowing": null,
1411
+ "narrowing_why": "KHÔNG có lần chạy vô hại — lệnh chỉ chạy được trên PRD ĐÃ DUYỆT (chưa có PRD thì dùng /generate-prd). Cổng nổ mọi lần là đúng bản chất, không phải chỗ sót.",
1342
1412
  "why": "sửa PRD đã DUYỆT"
1343
1413
  },
1344
1414
  {
1345
1415
  "cmd": "amend-prd",
1416
+ "narrowing": null,
1417
+ "narrowing_why": "KHÔNG có lần chạy vô hại — đây là 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ũ. Mọi lần chạy đều làm mất nội dung đã duyệt, CÓ CHỦ Ý.",
1346
1418
  "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ũ"
1347
1419
  },
1348
1420
  {
1349
1421
  "cmd": "refine-prd",
1422
+ "narrowing": null,
1423
+ "narrowing_why": "KHÔNG có lần chạy vô hại ở nhánh `--resume`: nó áp findings thẳng vào PRD đã duyệt rồi bump version. (Nhánh phân tích tự DỪNG khi PRD chưa đổi từ lần review trước — đó là guard của lệnh, không phải điều kiện của cổng.)",
1350
1424
  "why": "--resume áp findings trực tiếp vào PRD"
1351
1425
  },
1352
1426
  {
1353
1427
  "cmd": "generate-architecture",
1428
+ "narrowing": "chỉ nổ khi architecture.md đã có VÀ `verified_by` là `AI-draft`/trống. `verified_by: {người thật}` → KHÔNG đè, nhảy Bước 6 refresh có kiểm soát. File chưa có: đi thẳng.",
1354
1429
  "why": "ghi đè architecture.md đã verified_by một người thật"
1355
1430
  },
1356
1431
  {
1357
1432
  "cmd": "dev-gen-test",
1433
+ "narrowing": "chỉ nổ khi file test ĐÃ TỒN TẠI. Lần sinh đầu đi thẳng.",
1358
1434
  "why": "ghi đè file test đã có → XOÁ bản sửa tay của dev (mock, fixture, expectation đã chỉnh) mà chính /dev-run-test bảo họ làm",
1359
1435
  "$comment": [
1360
1436
  "G84 — CÙNG KHUÔN G77, khác lane. Bốn trên bốn giống nhau:",
1361
- " /qc-run-test (G77) /dev-gen-test (G84)",
1437
+ " /qc-design-script (G77) /dev-gen-test (G84)",
1362
1438
  " ai sửa tay QC — 'script-bug' Dev — 'fix test (sai expectation)'",
1363
1439
  " luật chạy lại ✅ đã thêm ❌ không có",
1364
1440
  " nhãn checkpoint ✅ hard ❌ normal",
@@ -1382,6 +1458,7 @@
1382
1458
  },
1383
1459
  {
1384
1460
  "cmd": "map-testids",
1461
+ "narrowing": "chỉ nổ khi §4.5.6 ĐÃ CÓ ROW. Tech-doc chưa có bảng — brownfield thật, ca lệnh này sinh ra để phục vụ — đi thẳng.",
1385
1462
  "why": "ghi đè §4.5.6 đã có → ĐỔI id hợp đồng mà SA viết tay, và kéo theo Step 5b hạ qc_status + bump tech_doc_revision cho một hợp đồng CHƯA HỎNG",
1386
1463
  "$comment": [
1387
1464
  "G83 — lệnh này TỪNG được miễn `hard` có chủ ý, xem $comment của checkpoint_levels.",
@@ -1395,7 +1472,8 @@
1395
1472
  ]
1396
1473
  },
1397
1474
  {
1398
- "cmd": "qc-run-test",
1475
+ "cmd": "qc-design-script",
1476
+ "narrowing": "chỉ nổ khi SINH LẠI file script đã tồn tại. Chạy lại test trên script không đổi: đi thẳng.",
1399
1477
  "why": "ghi đè script Python đã có → XOÁ bản sửa `script-bug` mà chính lệnh này bảo QC tự sửa bằng tay",
1400
1478
  "$comment": [
1401
1479
  "G77 — NẶNG HƠN G67, và lý do nằm ở TRIỆU CHỨNG.",
@@ -1417,9 +1495,10 @@
1417
1495
  },
1418
1496
  {
1419
1497
  "cmd": "qc-design-test",
1420
- "why": "ghi đè *.Test.md đã qua cổng HITL /qc-review → mất `Status` đã duyệt, `Expected Result` đã sửa theo finding, dấu 🚫 Block vừa gỡ; ĐÁNH SỐ LẠI TC_<FEATURE>_NNN mà REVIEW_<FEATURE>.md đang trỏ vào",
1498
+ "narrowing": "chỉ nổ khi file `.Test.md` ĐÃ TỒN TẠI. Lần thiết kế đầu không ghi đè gì, đi thẳng.",
1499
+ "why": "ghi đè *.Test.md đã qua cổng HITL /qc-review-testcase → mất `Status` đã duyệt, `Expected Result` đã sửa theo finding, dấu 🚫 Block vừa gỡ; và ĐÁNH SỐ LẠI TC_<FEATURE>_NNN mà REVIEW_<FEATURE>.md đang trỏ vào",
1421
1500
  "$comment": [
1422
- "G78 — cùng khuôn G67/G77 nhưng 🟠, vì /qc-review chạy lại được và bắt lại phần lớn finding.",
1501
+ "G78 — cùng khuôn G67/G77 nhưng 🟠, vì /qc-review-testcase chạy lại được và bắt lại phần lớn finding.",
1423
1502
  "Cái mất là MỘT VÒNG REVIEW ĐẦY ĐỦ, không phải một sự thật sai — nhưng nó mất mà không ai",
1424
1503
  "biết vì sao phải soát lại.",
1425
1504
  "",
@@ -1433,6 +1512,7 @@
1433
1512
  },
1434
1513
  {
1435
1514
  "cmd": "qc-analyze",
1515
+ "narrowing": "chỉ nổ khi `DOC_GAP.md` ĐÃ TỒN TẠI. Lần chạy đầu không ghi đè gì, đi thẳng.",
1436
1516
  "why": "ghi đè DOC_GAP.md đã có → mất cột Trạng thái/Câu trả lời (PO điền TAY, KHÔNG sinh lại được), ĐÁNH SỐ LẠI GAP-ID, phá 🚫 Block trong mọi .Test.md đã sinh",
1437
1517
  "$comment": [
1438
1518
  "G67 — KHỚP BA TRÊN BA với lý do của generate-prd, và đó chính là lập luận:",
@@ -1456,28 +1536,30 @@
1456
1536
  "",
1457
1537
  "Đường lập lại từ trắng: --force (gate.bypass_flags), và report PHẢI khai số câu trả lời bị xoá."
1458
1538
  ]
1539
+ },
1540
+ {
1541
+ "cmd": "review-context",
1542
+ "why": "ghi đè quyết định reviewer — `--full` BỎ QUA findings cũ (mất status accepted/modified/rejected của cả một vòng Review Board), và `--resume` áp fix THẲNG vào PRD/.feature rồi bump version. Cùng hình dạng /refine-prd, lệnh đó đã `hard` từ trước. CHUYỂN TỪ `none` 2026-09-16 (N2)",
1543
+ "narrowing": "Mức cứng chỉ áp khi file findings ĐÃ TỒN TẠI và có ≥1 finding mang status accepted/modified/rejected (đã qua Review Board), HOẶC khi chạy với --full/--resume. Lần phân tích đầu không ghi đè gì — bắt CHECKPOINT cứng ở đó là ồn vô cớ. Cùng khuôn map-testids."
1544
+ },
1545
+ {
1546
+ "cmd": "review-tech-docs",
1547
+ "why": "ghi đè quyết định reviewer (`--full`), và `--resume` Phase 2–3 ghi vào THÂN tech-doc, HEADER (@trace.bdd_versions · @trace.sign_off — sign_off là xác nhận của NGƯỜI), và sổ TSV của MỌI UC mà doc phủ. CHUYỂN TỪ `none` 2026-09-16 (N2)",
1548
+ "narrowing": "Như /review-context — chỉ áp khi findings đã có quyết định reviewer, hoặc khi chạy --full/--resume."
1459
1549
  }
1460
1550
  ],
1461
1551
  "none": [
1462
1552
  {
1463
1553
  "cmd": "review-code",
1464
- "why": "read-only"
1554
+ "why": "ghi {lessons_path} qua steps/capture-lesson.md, nhưng CHỈ sau khi người trả lời Y cho câu hỏi tường minh (tmpl:140) — điều kiện đó ĐÃ là một cổng; thêm CHECKPOINT nữa là hỏi hai lần cho một hành động, và hỏi hai lần dạy người ta bấm Y theo phản xạ. KHÔNG phải read-only"
1465
1555
  },
1466
1556
  {
1467
1557
  "cmd": "validate-traces",
1468
- "why": "read-only (ghi trace-report.json + TSV status, không đụng spec/code)"
1558
+ "why": "mọi thao tác ghi `when: always` đều hoặc dựng lại được (trace-report.json, .trace-mirror/) hoặc `append` (trace-history.jsonl — AUTHORITATIVE, KHÔNG regenerate được, nên nó an toàn CHỈ vì là append). Hai thao tác nguy hơn — row TSV và _seams.tsv — đều nằm sau `--reconcile-code`. KHÔNG phải read-only: tmpl:58 tự phân biệt chế độ đọc với `--reconcile-code (có GHI)`"
1469
1559
  },
1470
1560
  {
1471
1561
  "cmd": "debug",
1472
- "why": "read-only"
1473
- },
1474
- {
1475
- "cmd": "review-context",
1476
- "why": "read-only (ghi findings vào .agent/review/)"
1477
- },
1478
- {
1479
- "cmd": "review-tech-docs",
1480
- "why": "read-only (ghi findings vào .agent/review/)"
1562
+ "why": "như /review-code — ghi {lessons_path} sau câu hỏi (Y/N) ở tmpl:254. KHÔNG phải read-only"
1481
1563
  }
1482
1564
  ]
1483
1565
  },
@@ -2059,7 +2141,10 @@
2059
2141
  },
2060
2142
  {
2061
2143
  "column": "qc_status",
2062
- "owner": "qc-run-test",
2144
+ "owner": [
2145
+ "qc-run-script",
2146
+ "qc-run-manualtest"
2147
+ ],
2063
2148
  "positive_values": [
2064
2149
  "pass"
2065
2150
  ],
@@ -2079,7 +2164,7 @@
2079
2164
  " bảng §4.5.6 (thân, theo nền) → GIÁ TRỊ test-id từng element, N dòng",
2080
2165
  "Consumer per-UC lọc row của mình qua cột 'Serves SC (UC · SC)'.",
2081
2166
  "",
2082
- "VÌ SAO CẦN CANH: trước khối này, bảng §4.5.6 được 3 lệnh ĐỌC (generate-code, qc-run-test,",
2167
+ "VÌ SAO CẦN CANH: trước khối này, bảng §4.5.6 được 3 lệnh ĐỌC (generate-code, qc-design-script,",
2083
2168
  "qc-design-test) và 2 lệnh GHI (generate-tech-docs, map-testids) — mà 0 nơi kiểm. Luật chống",
2084
2169
  "giẫm chân giữa hai người ghi là một CÂU VĂN XUÔI ở map-testids Step 5. Đây đúng hình dạng",
2085
2170
  "đã gặp bốn lần trong loạt GAP: luật ĐÚNG, viết RÕ, và KHÔNG AI CANH (G1 · G28 · G41 · G55).",
@@ -2106,7 +2191,7 @@
2106
2191
  {
2107
2192
  "rule": "T16",
2108
2193
  "level": "warn+error",
2109
- "why": "Doc có §4.5 client mà header KHÔNG CÓ DÒNG @trace.testid_attr → WARN (G86): đó là doc LEGACY, sinh trước khi field vào template — trạng thái HỢP LỆ trong quy trình, y như placeholder. Lập luận giữ `error` ('QC đoán theo nền ⇒ trượt 100% locator TRONG IM LẶNG') đã HẾT ĐÚNG: qc-run-test và generate-code đều 'Cảnh báo mềm, KHÔNG im lặng hardcode'. Giữ error là dựng cổng thứ hai cho chuyện đã có người canh — và cổng đó CHẶN NHẦM NGƯỜI (30 doc của 2 domain khác chặn /validate-traces --prd FEAT-01-1). Cùng lập luận đã dùng cho T14. | Dòng CÓ MẶT mà giá trị RỖNG → vẫn ERROR: đó là 'ai đó xoá' khỏi một hợp đồng ĐÃ CHỐT. Dòng-không-tồn-tại vs dòng-rỗng là chỗ DUY NHẤT máy phân biệt được legacy với bị-xoá. | Còn placeholder → WARN."
2194
+ "why": "Doc có §4.5 client mà header KHÔNG CÓ DÒNG @trace.testid_attr → WARN (G86): đó là doc LEGACY, sinh trước khi field vào template — trạng thái HỢP LỆ trong quy trình, y như placeholder. Lập luận giữ `error` ('QC đoán theo nền ⇒ trượt 100% locator TRONG IM LẶNG') đã HẾT ĐÚNG: qc-design-script và generate-code đều 'Cảnh báo mềm, KHÔNG im lặng hardcode'. Giữ error là dựng cổng thứ hai cho chuyện đã có người canh — và cổng đó CHẶN NHẦM NGƯỜI (30 doc của 2 domain khác chặn /validate-traces --prd FEAT-01-1). Cùng lập luận đã dùng cho T14. | Dòng CÓ MẶT mà giá trị RỖNG → vẫn ERROR: đó là 'ai đó xoá' khỏi một hợp đồng ĐÃ CHỐT. Dòng-không-tồn-tại vs dòng-rỗng là chỗ DUY NHẤT máy phân biệt được legacy với bị-xoá. | Còn placeholder → WARN."
2110
2195
  },
2111
2196
  {
2112
2197
  "rule": "T17",
@@ -2146,7 +2231,8 @@
2146
2231
  },
2147
2232
  "artifact_writers": {
2148
2233
  "$comment": [
2149
- "AI GHI ARTIFACT NÀO, ARTIFACT ĐÓ CÓ PHẦN NGƯỜI NHẬP TAY KHÔNG (G74 · R18).",
2234
+ "AI GHI ARTIFACT NÀO, KHI NÀO GHI, ARTIFACT ĐÓ CÓ PHẦN NGƯỜI NHẬP TAY KHÔNG, ĐÈ MẤT THÌ",
2235
+ "DỰNG LẠI ĐƯỢC KHÔNG (G74 · R18).",
2150
2236
  "",
2151
2237
  "VÌ SAO KHỐI NÀY TỒN TẠI — ĐẢO CHIỀU MỘT ALLOWLIST:",
2152
2238
  "gate.checkpoint_levels là allowlist với mặc định `normal` (mức LỎNG NHẤT). Lệnh không khai",
@@ -2155,116 +2241,552 @@
2155
2241
  "checkpoint_levels: 'thêm lệnh mới mà quên thêm vào đó thì lại sai lần nữa'. Nó đã sai đúng",
2156
2242
  "như vậy BỐN lần: G67 (đã sửa) · G77 · G78 · G79.",
2157
2243
  "",
2158
- "R18 duyệt CHIỀU NGƯỢC LẠI: mọi lệnh trong `enrolled` phải khai đủ, khai",
2159
- "has_human_content:true thì BẮT BUỘC nằm trong checkpoint_levels.hard. Quên = BUILD ĐỎ,",
2160
- "thay vì quên = mở cửa.",
2244
+ "R18 duyệt CHIỀU NGƯỢC LẠI: mọi lệnh trong `enrolled` phải khai đủ. Quên = BUILD ĐỎ, thay vì",
2245
+ "quên = mở cửa. `why` BẮT BUỘC cả khi has_human_content:false đó chính là G79.",
2246
+ "",
2247
+ "BA TRƯỜNG Ở CẤP ARTIFACT, KHÔNG Ở CẤP LỆNH — `mode` · `regenerable` · `when`:",
2161
2248
  "",
2162
- "`why` BẮT BUỘC CẢ KHI has_human_content:false đó chính G79. /qc-plan/qc-review",
2163
- "ghi đè AN TOÀN, nhưng không ai khai an toàn, nên một ngoại lệ CHỦ Ý trông y hệt một chỗ",
2164
- "sót và sẽ bị copy đi. Bắt khai `why` biến im lặng thành một khẳng định KIỂM CHỨNG ĐƯỢC.",
2249
+ " (D1 · mode + regenerable) `has_human_content` cờ PHẲNG CẤP LỆNHkhông nói được:",
2250
+ " loại file KHÔNG ai tay vào nhưng VẪN không được đè DỒN THÊM qua từng lần chạy",
2251
+ " `trace-history.jsonl`, chính /validate-traces gọi AUTHORITATIVE, không regenerate",
2252
+ " được. Và một lệnh đụng NHIỀU artifact khác tính chất: /validate-traces GHI ĐÈ",
2253
+ " trace-report.json (dựng lại được) NHƯNG APPEND trace-history.jsonl (không dựng lại được).",
2254
+ " Một cờ phẳng cho cả lệnh thì BẮT BUỘC phải chọn một nửa để nói dối.",
2165
2255
  "",
2166
- "PHẠM VI CHỦ Ý `enrolled` hiện chỉ phủ 6 lệnh QC (phạm vi đợt GAPS-v5). 27 lệnh còn",
2167
- "lại nằm `pending_enrollment`: nợ được KHAI RA, không phải nợ hình. R18 không ép chúng,",
2168
- "nhưng danh sách đó phải RỖNG DẦN — và nó nằm trong schema nên không ai quên được nó tồn tại.",
2169
- "Khai một phạm vi hẹp nói hẹp thì trung thực hơn khai đầy đủ mà chưa rà."
2256
+ " (D4 · when) Cùng lỗ hổng đó MỘT TẦNG CAO HƠN: hành vi ghi thuộc về CHẾ ĐỘ, không thuộc",
2257
+ " về lệnh. Đo 2026-09-15: NĂM lệnh tự khai 'read-only' cả năm đều ghi ",
2258
+ " /review-context, /review-tech-docs, /validate-traces, /review-code, /debug. Nhãn được đặt",
2259
+ " theo chế độ MẶC ĐỊNH, rồi chế độ --resume/--reconcile-code thêm vào sau không ai quay",
2260
+ " lại sửa nhãn. Chính validate-traces.tmpl:58 đã viết sẵn phân biệt đó: 'Với một lệnh CHỈ ĐỌC",
2261
+ " thì đó là đốt thời gian. Với --reconcile-code (CÓ GHI) thì...'. Lệnh biết; registry trước",
2262
+ " đây không có ô để chứa.",
2263
+ "",
2264
+ "PHẠM VI — ĐỦ 33/33 LỆNH kể từ N3 (2026-09-16): `enrolled` 32 · `non_writers` 1",
2265
+ "(`dev-smoke-test` — rà rồi, không ghi gì) · `pending_enrollment` RỖNG.",
2266
+ "",
2267
+ "Đi qua bốn đợt: 8 (GAPS-v5) → +5 (N1: đã ở `hard` sẵn, khai thêm không đổi hành vi lệnh",
2268
+ "nào) → +5 (N2: nhóm tự khai 'read-only' mà cả năm đều CÓ ghi) → +14 (N3: phần còn lại).",
2269
+ "",
2270
+ "GIỮ `pending_enrollment` DÙ NÓ RỖNG: lệnh thêm về sau mà chưa rà phải có chỗ để KHAI RA,",
2271
+ "thay vì rơi vào mặc định `normal` trong im lặng. Khai một phạm vi hẹp và nói rõ là hẹp thì",
2272
+ "trung thực hơn là khai đầy đủ mà chưa rà."
2170
2273
  ],
2274
+ "write_modes": {
2275
+ "$comment": [
2276
+ "TỪ VỰNG `mode` — ba giá trị, phân biệt theo DUY NHẤT một câu hỏi: thao tác này có làm MẤT",
2277
+ "nội dung đang có không?",
2278
+ "",
2279
+ "CHỈ `append` LÀ AN TOÀN VÔ ĐIỀU KIỆN. `merge` KHÔNG an toàn: /amend-prd là merge, và nó tự",
2280
+ "khai là 'thao tác ghi DUY NHẤT trong framework được phép làm output KHÔNG phải superset của",
2281
+ "bản cũ'. Gộp merge chung với append là mở lại đúng cánh cửa vừa đóng.",
2282
+ "",
2283
+ "`create` THÊM Ở N3 (2026-09-16): /setup-ai-first ghi CLAUDE.md và project-context.yaml —",
2284
+ "hai artifact người sửa tay nhiều nhất — nhưng nó CHỈ TẠO KHI CHƯA CÓ ('skip nếu đã tồn",
2285
+ "tại'). Không có `create` thì phải khai `overwrite` (nói dối) hoặc `append` (cũng sai).",
2286
+ "RỦI RO NẰM Ở THAO TÁC, KHÔNG Ở ARTIFACT — đây là ca chứng minh điều đó."
2287
+ ],
2288
+ "overwrite": "Viết đè toàn bộ file. Mọi nội dung cũ mất.",
2289
+ "merge": "Sửa/thêm một phần, phần còn lại giữ nguyên. VẪN có thể mất phần bị đụng.",
2290
+ "append": "Chỉ thêm bản ghi mới, KHÔNG sửa và KHÔNG xoá bản ghi cũ. Không mất gì.",
2291
+ "create": "CHỈ tạo khi chưa có; file đã tồn tại thì BỎ QUA hoàn toàn. Không mất gì.",
2292
+ "$creates_flag": "Cờ `creates: true` đặt Ở TỪNG ARTIFACT, đi KÈM `mode` chứ không thay nó (R22). Nó nói: thao tác này là `merge` khi file đã có, VÀ sinh ra file khi chưa có. Cần vì `merge` một mình không hứa file tồn tại — nếu MỌI lệnh ghi một artifact đều chỉ merge thì không ai tạo nó, và cả chuỗi trạm sau đứng chờ một file không bao giờ xuất hiện. Ép chúng khai `overwrite` thay vì dùng cờ này là NÓI DỐI: chế độ delta của file findings GIỮ `status` của reviewer."
2293
+ },
2294
+ "safe_modes": {
2295
+ "$comment": [
2296
+ "MODE NÀO ĐƯỢC MIỄN LUẬT (B) — tức mode nào KHÔNG THỂ làm mất nội dung đang có.",
2297
+ "",
2298
+ "Khai ở đây thay vì hard-code trong checker: thêm một mode mới mà quên dạy checker là",
2299
+ "đúng kiểu lỗi R18 sinh ra để bắt. Và để danh sách này NGẮN là có chủ ý — `merge` KHÔNG",
2300
+ "nằm trong đây, vì /amend-prd là merge và nó tự khai là thao tác ghi DUY NHẤT được phép",
2301
+ "làm output KHÔNG phải superset của bản cũ."
2302
+ ],
2303
+ "modes": [
2304
+ "append",
2305
+ "create"
2306
+ ]
2307
+ },
2308
+ "write_conditions": {
2309
+ "$comment": [
2310
+ "TỪ VỰNG `when` — thao tác ghi này xảy ra Ở CHẾ ĐỘ NÀO (D4 · 2026-09-15).",
2311
+ "",
2312
+ "LUẬT (B) CỦA R18 CHỈ ÉP `hard` KHI `when: always`. Ghi có điều kiện thì người dùng đã phải",
2313
+ "gõ THÊM một cờ, hoặc đã phải trả lời Y cho một câu hỏi — bản thân việc đó đã là một cổng.",
2314
+ "Ép `hard` cho cả lệnh vì một nhánh hiếm là phạt nhầm chế độ đọc: /validate-traces chạy hàng",
2315
+ "ngày ở chế độ đọc, và dựng cổng chặn lên nó vì nhánh --reconcile-code sẽ dạy người ta thêm",
2316
+ "`--yes` vào script — rồi `--yes` đó nằm lại vĩnh viễn, kể cả cho ngày họ chạy --reconcile-code.",
2317
+ "",
2318
+ "ĐỔI LẠI, R18 ĐÒI LỜI KHAI PHẢI KIỂM CHỨNG ĐƯỢC: `when` là tên cờ thì tmpl PHẢI nhắc đúng cờ",
2319
+ "đó; `when: confirm` thì tmpl PHẢI có câu hỏi (Y/N). Khai một điều kiện không tồn tại thì luật",
2320
+ "trông như đã được áp trong khi không ai áp — đúng bài học R17."
2321
+ ],
2322
+ "always": "Ghi ở MỌI lần chạy, không cần cờ và không hỏi thêm.",
2323
+ "confirm": "Chỉ ghi sau khi người trả lời Y cho một câu hỏi tường minh (Y/N) trong lệnh.",
2324
+ "--<flag>": "Chỉ ghi khi người dùng gõ thêm đúng cờ đó. Giá trị khai phải là tên cờ thật, có mặt trong tmpl."
2325
+ },
2171
2326
  "enrolled": {
2172
- "qc-analyze": {
2327
+ "generate-prd": {
2173
2328
  "writes": [
2174
- "DOC_GAP.md",
2175
- "REQUIREMENT_ANALYSIS.md",
2176
- "{refinement_dir}/{TICKET-ID}-qa-findings.yaml"
2329
+ { "path": "{specs_dir}/{domain}/{prd-slug}/{TICKET-ID}-{prd-slug}.md",
2330
+ "mode": "overwrite", "regenerable": false, "when": "always",
2331
+ "note": "Mất toàn bộ `# Change Log` (và file changelog/ đã rollover), ĐÁNH SỐ LẠI BR, phá `@trace.business_rules` trong mọi .feature đã sinh. Cả ba không hoàn tác được từ trong lệnh, và cái thứ ba lan RA NGOÀI file." }
2177
2332
  ],
2178
2333
  "has_human_content": true,
2179
- "why": "PO điền TAY cột `Trạng thái` (Open/Answered) cột `Câu trả lời` của DOC_GAP.mdkhông sinh lại được. GAP-UC{N}-{nnn} còn bị .Test.md tham chiếu qua 🚫 Block."
2334
+ "why": "PRD là tài liệu PO viết DUYỆT. Lệnh DỪNG HẲN chứ không hỏi Y/N khi PRD đã có tmpl:167 nói sao: 'ghi đè một PRD đã duyệt không phải thứ nên nằm sau một phím bấm'."
2180
2335
  },
2181
- "qc-run-test": {
2336
+ "extend-prd": {
2182
2337
  "writes": [
2183
- "test Python pytest-playwright",
2184
- "Page Object"
2338
+ { "path": "{specs_dir}/{domain}/{prd-slug}/{TICKET-ID}-{prd-slug}.md",
2339
+ "mode": "merge", "regenerable": false, "when": "always",
2340
+ "note": "ADD-ONLY: Bước 5 §3 đòi output là SUPERSET CHẶT — mọi UC/AC/BR/row bảng/dòng changelog/cross-ref cũ còn nguyên si, và có guard sau-ghi tự chặn clobber. Nhưng nội dung PRD do người viết và duyệt, nên phần bị đụng mất là mất hẳn." },
2341
+ { "path": "{specs_dir}/{domain}/{prd-slug}/changelog/{TICKET-ID}-{prd-slug}.changelog.md",
2342
+ "mode": "append", "regenerable": false, "when": "always",
2343
+ "note": "Kho changelog. Rollover đẩy row vượt 5 từ PRD sang ĐẦU bảng file này, không sửa và không xoá row cũ. Dựng lại KHÔNG được: PRD chỉ còn giữ 5 row gần nhất, phần cũ chỉ tồn tại ở đây." }
2185
2344
  ],
2186
2345
  "has_human_content": true,
2187
- "why": "QC SỬA TAY chính các file này §Phân loại FAIL khai tường minh: `script-bug` 'QC tự sửa. Không mở bug'. Sinh lạixoá công sửa lỗi, đúng lúc người ta chạy lại để kiểm bản sửa."
2346
+ "why": "Sửa PRD đã DUYỆT. add-only, phần bị đụng (`Status` draft, bảng Change Log, §1c Phụ thuộc liên service) nằm trong tài liệu PO sở hữu. Dòng changelog còn CONTRACT: /generate-bdd đọc để quyết cập nhật hẹp hay gen lại, /validate-traces đọc nó để lọc PRD_DRIFT vs PRD_STALE_REF."
2347
+ },
2348
+ "amend-prd": {
2349
+ "writes": [
2350
+ { "path": "{specs_dir}/{domain}/{prd-slug}/{TICKET-ID}-{prd-slug}.md",
2351
+ "mode": "merge", "regenerable": false, "when": "always",
2352
+ "note": "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ũ (tmpl:30). Sửa TẠI CHỖ một AC/BR/UC đã duyệt — nội dung cũ mất CÓ CHỦ Ý, và guard chỉ giới hạn PHẠM VI mất trong `amend_targets`, không ngăn việc mất." },
2353
+ { "path": "{specs_dir}/{domain}/{prd-slug}/changelog/{TICKET-ID}-{prd-slug}.changelog.md",
2354
+ "mode": "append", "regenerable": false, "when": "always",
2355
+ "note": "Như /extend-prd — rollover, không đụng row cũ." }
2356
+ ],
2357
+ "has_human_content": true,
2358
+ "why": "Đây là lệnh DUY NHẤT được phép làm mất nội dung PRD đã duyệt. Chính vì thế nó phải chặn cứng: guard `amend_targets` canh chiều GIỚI HẠN (không sửa ngoài danh sách) chứ không canh được chiều BẢO TOÀN — cái mất trong phạm vi là cái PO cố ý yêu cầu mất, và chỉ PO xác nhận được."
2359
+ },
2360
+ "refine-prd": {
2361
+ "writes": [
2362
+ { "path": "{refinement_dir}/{prd-slug}-findings.yaml",
2363
+ "mode": "merge", "creates": true, "regenerable": false, "when": "always",
2364
+ "note": "CHỨA QUYẾT ĐỊNH CỦA NGƯỜI: `status` mỗi finding là accepted|modified|rejected|needs_discussion, và `modified` nghĩa là reviewer ĐÃ SỬA suggestion ngay trong Review Board. Sinh lại = xoá sạch một vòng review đã làm." },
2365
+ { "path": "{specs_dir}/{domain}/{prd-slug}/{TICKET-ID}-{prd-slug}.md",
2366
+ "mode": "merge", "regenerable": false, "when": "--resume",
2367
+ "note": "Áp findings đã duyệt THẲNG vào PRD, bump version, ghi dòng changelog." },
2368
+ { "path": "{specs_dir}/{domain}/{prd-slug}/changelog/{TICKET-ID}-{prd-slug}.changelog.md",
2369
+ "mode": "append", "regenerable": false, "when": "--resume",
2370
+ "note": "Rollover — Phase 3 của lệnh này chính là bản gốc mà /extend-prd và /amend-prd tái dùng." }
2371
+ ],
2372
+ "has_human_content": true,
2373
+ "why": "HAI lớp người, không phải một: reviewer quyết accepted/modified/rejected ngay trong file findings (ghi ở MỌI lần chạy), và PRD là tài liệu PO duyệt (chỉ ghi khi --resume). Cờ `has_human_content` ở cấp lệnh không phân biệt được hai lớp đó."
2374
+ },
2375
+ "generate-architecture": {
2376
+ "writes": [
2377
+ { "path": "{specs_dir}/architecture.md · umbrella: {service}/specs/architecture.md",
2378
+ "mode": "overwrite", "regenerable": false, "when": "always",
2379
+ "note": "CHỈ đè khi `verified_by: AI-draft` hoặc trống. `verified_by: {người thật}` → KHÔNG đè, nhảy Bước 6 refresh có kiểm soát: chỉ ĐỀ XUẤT diff để SA tự áp, và KHÔNG tự đổi `verified_by`." }
2380
+ ],
2381
+ "has_human_content": true,
2382
+ "why": "File mang chữ ký một người thật ở `verified_by`, và chữ ký đó có hiệu lực RA NGOÀI file: /generate-tech-docs Bước 0.5 [ARCH] dùng nó để quyết doc là RÀNG BUỘC KIẾN TRÚC CHÍNH THỨC hay chỉ là AI-draft bị gắn ⚠️ và xuống hạng sau CLAUDE.md/BDD. Đè lên một file đã verified là xoá chữ ký của người khác."
2383
+ },
2384
+ "qc-analyze": {
2385
+ "writes": [
2386
+ { "path": "{qc_artifact_dir}DOC_GAP.md",
2387
+ "mode": "overwrite", "regenerable": false, "when": "always",
2388
+ "note": "PO điền TAY cột `Trạng thái` (Open/Answered) và cột `Câu trả lời`. Ghi đè còn ĐÁNH SỐ LẠI mã GAP-UC{N}-{nnn} mà mọi .Test.md đang tham chiếu qua dấu 🚫 Block." },
2389
+ { "path": "{qc_artifact_dir}REQUIREMENT_ANALYSIS.md",
2390
+ "mode": "overwrite", "regenerable": true, "when": "always",
2391
+ "note": "Bản phân tích hợp nhất, suy hoàn toàn từ PRD + §12 tech-doc. Không ô nào cho người điền." },
2392
+ { "path": "{refinement_dir}/{TICKET-ID}-qa-findings.yaml",
2393
+ "mode": "overwrite", "regenerable": true, "when": "always",
2394
+ "note": "CÙNG dữ liệu gap của DOC_GAP.md, đổi sang định dạng Review Board đọc được — dựng lại từ DOC_GAP." }
2395
+ ],
2396
+ "has_human_content": true,
2397
+ "why": "PO điền TAY vào DOC_GAP.md — không sinh lại được. Hai artifact còn lại thì sinh lại được; chính sự khác nhau đó là lý do `regenerable` phải nằm ở cấp artifact chứ không ở cấp lệnh."
2188
2398
  },
2189
2399
  "qc-design-test": {
2190
2400
  "writes": [
2191
- "test-cases/*.Test.md"
2401
+ { "path": "{qc_artifact_dir}test-cases/TC_<FEATURE>.Test.md",
2402
+ "mode": "overwrite", "regenerable": false, "when": "always",
2403
+ "note": "File đi qua cổng HITL /qc-review-testcase: `Status` đổi sau review, `Expected Result` sửa theo finding, dấu 🚫 Block gỡ bằng tay — đều là kết luận của NGƯỜI, không suy lại từ spec. Ghi đè còn ĐÁNH SỐ LẠI TC_<FEATURE>_NNN mà REVIEW_<FEATURE>.md đang trỏ vào." }
2404
+ ],
2405
+ "has_human_content": true,
2406
+ "why": "Chân thứ tư của hợp đồng test-id nằm ở file này (G64): giá trị id bị CHÉP CỨNG vào một artifact BỀN rồi nằm lại. Mất bản đã review là mất cả vòng HITL lẫn điểm neo của hợp đồng."
2407
+ },
2408
+ "qc-automation-assess": {
2409
+ "writes": [
2410
+ { "path": "{qc_artifact_dir}AUTOMATION_ASSESSMENT.md",
2411
+ "mode": "merge", "creates": true, "regenerable": false, "when": "always", "human_content": true,
2412
+ "note": "TẠO bảng ở lượt đầu, MERGE ở lượt sau (giữ quyết định Y/N + nhãn lý do của TC không DRIFT). Hai phần KHÔNG sinh lại được: (1) quyết định Y/N + nhãn lý do — người chốt sau khi cân nhắc ROI; (2) cột `Script file` do /qc-design-script điền, là CHỈ MỤC NGƯỢC DUY NHẤT từ TC → file code. Ghi đè là xoá công của hai bên cùng lúc." }
2192
2413
  ],
2193
2414
  "has_human_content": true,
2194
- "why": "File đi qua cổng HITL /qc-review: `Status` đổi sau review, `Expected Result` sửa theo finding, dấu 🚫 Block gỡ bằng tay đều kết luận của NGƯỜI, không suy lại từ spec. REVIEW_<FEATURE>.md đang trỏ vào số TC_<FEATURE>_NNN."
2415
+ "why": "Bảng này NGUỒN DUY NHẤT cho ba trạm sau: /qc-design-script lọc `Automatable: Y`, /qc-run-script đọc cột `Script file` để biết chạy file nào, /qc-run-manualtest lọc `Automatable: N` để biết hỏi những TC nào. Mất thì cả ba đứt, mắt xích /qc-run-script hỏng IM LẶNG nhất — suy sai đường dẫn chạy 0 test vẫn báo xanh."
2416
+ },
2417
+ "qc-design-script": {
2418
+ "writes": [
2419
+ { "path": "tests/<project>/test_<feature>.py (module qc-playwright)",
2420
+ "mode": "overwrite", "regenerable": false, "when": "always", "human_content": true,
2421
+ "note": "QC SỬA TAY chính các file này — /qc-run-script §Phân loại FAIL khai tường minh: `script-bug` ⇒ 'QC tự sửa. Không mở bug'. Sinh lại là xoá công sửa lỗi ĐÚNG LÚC người ta chạy lại để kiểm bản sửa." },
2422
+ { "path": "pages/<feature>_page.py — Page Object",
2423
+ "mode": "overwrite", "regenerable": false, "when": "always", "human_content": true,
2424
+ "note": "Cùng lý do — QC sửa tay locator ở đây khi §4.5.6 đổi." },
2425
+ { "path": "{qc_artifact_dir}AUTOMATION_ASSESSMENT.md — cột `Script file`",
2426
+ "mode": "merge", "regenerable": true, "when": "always",
2427
+ "note": "Chỉ điền đường dẫn script cho từng TC; suy lại được từ cây file script." },
2428
+ { "path": "{qc_artifact_dir}TESTABILITY_IMPROVEMENTS.md",
2429
+ "mode": "append", "regenerable": false, "when": "always",
2430
+ "note": "Mỗi rào cản kỹ thuật một mục IMPROVE-{date}-{NN}. CHỈ THÊM, không sửa mục cũ — nó là sổ đề xuất gửi FE/BE, và trạng thái 🟡 Proposed do người đổi." }
2431
+ ],
2432
+ "has_human_content": true,
2433
+ "why": "KẾ THỪA vai ghi script của /qc-run-test (G77): lệnh bảo QC sửa tay, rồi lần sinh sau tự xoá bản sửa đó. Sau Đợt 2 · b2 lệnh này KHÔNG còn chạy test và KHÔNG ghi sổ trace — hai việc đó về /qc-run-script và /qc-run-manualtest."
2434
+ },
2435
+ "qc-run-script": {
2436
+ "writes": [
2437
+ { "path": "{trace_dir}/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv — cột qc_status · qc_run_at · qc_owner · qc_blocked_by · last_updated",
2438
+ "mode": "merge", "regenerable": true, "when": "always",
2439
+ "note": "Chỉ đụng cột nó SỞ HỮU, giữ nguyên cột của /dev-gen-test và /dev-run-test. Chạy lại test là có lại." },
2440
+ { "path": "{bug_reports_dir}/{BUG-ID}.md — chuyển 🟡 Fixed → 🟢 Closed",
2441
+ "mode": "merge", "regenerable": true, "when": "always",
2442
+ "note": "Chỉ LẬT field State và THÊM một dòng Verified — cả hai suy ra từ kết quả lần chạy, nên dựng lại được bằng cách chạy lại, và đóng nhầm thì mở lại được. Nội dung bug do /report-bug viết, lệnh này không đụng. Lệnh là CHỦ SỞ HỮU bước đóng bug. Đóng sai thì defect biến mất khỏi tầm nhìn — nên nó có guard riêng: bug đang 🟢 Open mà test pass thì KHÔNG đóng, chỉ ghi chú cảnh báo." },
2443
+ { "path": "./.trace-mirror/ (panel mirror, qua steps/trace-mirror.md)",
2444
+ "mode": "overwrite", "regenerable": true, "when": "always",
2445
+ "note": "Sinh lại được bất cứ lúc nào, luôn gitignore." }
2446
+ ],
2447
+ "has_human_content": false,
2448
+ "why": "KHÔNG sinh và KHÔNG sửa file script — nó chỉ CHẠY. Mọi thứ nó ghi đều dựng lại được bằng cách chạy lại, trừ trạng thái bug (merge, có guard riêng). Vì thế mức `normal` là đúng, dù nó là một trong hai chủ của cột qc_status."
2449
+ },
2450
+ "qc-run-manualtest": {
2451
+ "writes": [
2452
+ { "path": "{trace_dir}/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv — cột qc_status · qc_run_at · qc_owner · qc_blocked_by · last_updated",
2453
+ "mode": "merge", "regenerable": true, "when": "always",
2454
+ "note": "GHI NGAY sau mỗi câu trả lời, không gom cuối phiên — mất phiên giữa chừng không mất gì đã nhập, và chạy lại chỉ hỏi SC còn not_run (tự resume). Sổ trace là nguồn DUY NHẤT: không artifact trung gian nào để trôi." },
2455
+ { "path": "{qc_artifact_dir}MANUAL_EVIDENCE.md",
2456
+ "mode": "append", "regenerable": false, "when": "always", "human_content": true,
2457
+ "note": "Một dòng mỗi lần ghi: ngày · sc_id · kết quả · NGƯỜI CHẠY · bằng chứng. CHỈ THÊM nên không trôi được. Đây là mức tương đương của 'output runner' ở /qc-run-script — `pass` không được phép là 'vì tôi nói vậy'." },
2458
+ { "path": "./.trace-mirror/ (panel mirror, qua steps/trace-mirror.md)",
2459
+ "mode": "overwrite", "regenerable": true, "when": "always",
2460
+ "note": "Sinh lại được bất cứ lúc nào, luôn gitignore." }
2461
+ ],
2462
+ "has_human_content": true,
2463
+ "why": "LỆNH THỨ BA, không có trong đề xuất gốc. Không có nó thì TC mang `Automatable: N` nằm not_run vĩnh viễn và /qc-report chấm cả PRD là FAIL — rồi đội QC sẽ hạ ngưỡng về 0 để verdict xanh, và cả Đợt 3 thành trang trí. Cách nhập CHỐT VỚI CHỊ QC 2026-09-16: hỏi từng TC, KHÔNG đọc file checklist — nguyên văn \"chị sợ đọc checklist mà lệch thông tin lại ghi kết quả sai\". Artifact có người nhập tay (MANUAL_EVIDENCE.md) là `append`, nên luật (A) không ép hard."
2195
2464
  },
2196
2465
  "qc-plan": {
2197
2466
  "writes": [
2198
- "TEST_PLAN.md"
2467
+ { "path": "{qc_artifact_dir}TEST_PLAN.md",
2468
+ "mode": "overwrite", "regenerable": true, "when": "always",
2469
+ "note": "questions-for-dev là danh sách GỬI ĐI; câu trả lời quay về qua DOC_GAP.md hoặc kênh chat, không ai điền ngược vào file này. Stamp phiên bản và cột Ready/Blocked đều do lệnh tự tính." }
2470
+ ],
2471
+ "has_human_content": false,
2472
+ "why": "Ghi đè AN TOÀN — và khai điều đó ra là có chủ đích. KHAI LẠI NẾU về sau có ai điền tay vào đây."
2473
+ },
2474
+ "qc-review-testcase": {
2475
+ "writes": [
2476
+ { "path": "{qc_artifact_dir}test-cases/REVIEW_<FEATURE>.md",
2477
+ "mode": "overwrite", "regenerable": true, "when": "always",
2478
+ "note": "Là BIÊN BẢN của một lượt soát test case. Sinh lại = soát lại, đúng bản chất của nó — không có trạng thái nào tích luỹ qua các lần chạy." }
2199
2479
  ],
2200
2480
  "has_human_content": false,
2201
- "why": "questions-for-dev danh sách GỬI ĐI; câu trả lời quay về qua DOC_GAP.md hoặc kênh chat, không ai điền ngược vào file này. Stamp phiên bảncột Ready/Blocked đều do lệnh tự tính. KHAI LẠI NẾU về sau ai điền tay vào đây."
2481
+ "why": "Ghi đè AN TOÀN. Kết luận của người soát KHÔNG nằm file này mà nằm ở *.Test.md (Status, Expected Result, 🚫 Block) nên chính /qc-design-test mới là lệnh phải chặn cứng, không phải lệnh này. Lệnh cũng canh CHÂN THỨ TƯ của hợp đồng test-id (testid_fourth_leg.checked_by) ĐỌC stamp trên *.Test.md (qc_artifact_stamp.checked_by) cả hai vai đó chạy trên .Test.md nên chúng về vai TEST CASE khi /qc-review tách đôi (Đợt 2 · b1)."
2202
2482
  },
2203
- "qc-review": {
2483
+ "qc-review-script": {
2204
2484
  "writes": [
2205
- "test-cases/REVIEW_<FEATURE>.md"
2485
+ { "path": "{qc_artifact_dir}test-cases/REVIEW_SCRIPT_<FEATURE>.md",
2486
+ "mode": "overwrite", "regenerable": true, "when": "always",
2487
+ "note": "Biên bản một lượt soát CODE TEST. File RIÊNG, không chung với REVIEW_<FEATURE>.md: hai lượt soát cách nhau vài trạm, chung file thì lượt sau đè bảng chi tiết của lượt trước, và trạm tiêu thụ verdict lại phải đoán nó thuộc vai nào — đúng vấn đề mà việc tách lệnh này giải quyết." }
2206
2488
  ],
2207
2489
  "has_human_content": false,
2208
- "why": " BIÊN BẢN của một lần soát. Sinh lại = soát lại, đúng bản chất của không trạng thái nào tích luỹ qua các lần chạy."
2490
+ "why": "Ghi đè AN TOÀN, cùng do với /qc-review-testcase. Phần người sửa tay code test lệnh này ĐỌC trạm sinh script đã mức hard đúng lý do đó (G77). An toàn ở phần nó GHI, không phải phần nó ĐỌC."
2209
2491
  },
2210
2492
  "qc-report": {
2211
2493
  "writes": [
2212
- "reports/<feature>/report.html",
2213
- "test-results/<nodeid>/trace.zip"
2494
+ { "path": "reports/<feature>/report.html",
2495
+ "mode": "overwrite", "regenerable": true, "when": "always",
2496
+ "note": "pytest-html self-contained, sinh từ lần chạy /qc-run-script gần nhất." },
2497
+ { "path": "test-results/<nodeid>/trace.zip",
2498
+ "mode": "overwrite", "regenerable": true, "when": "always",
2499
+ "note": "Playwright Trace của cùng lần chạy đó." }
2214
2500
  ],
2215
2501
  "has_human_content": false,
2216
- "why": "Sinh hoàn toàn từ lần chạy gần nhất (pytest-html + Playwright Trace). Không có ô nào cho người điền."
2502
+ "why": "Sinh hoàn toàn từ lần chạy gần nhất. Không có ô nào cho người điền, và không có trạng thái nào tích luỹ qua các lần chạy."
2217
2503
  },
2218
2504
  "map-testids": {
2219
2505
  "writes": [
2220
- "§4.5.6 Test Selectors (tech-doc gộp)",
2221
- "@trace.testid_attr (header)",
2222
- "figma-components catalogTest-ID Forwarding",
2223
- "usage site FE (--from-code)"
2506
+ { "path": "tech-doc gộp — §4.5.6 Test Selectors",
2507
+ "mode": "merge", "regenerable": false, "when": "always",
2508
+ "note": "HỢP ĐỒNG do SA viết tay. 28/28 tech-doc thật dùng quy ước riêng (uc{n}-z2-region, ô đa-giá-trị) mà lệnh này KHÔNG tái tạo được quy ước của nó là {uc-lower}-{screen}-{element}-{type}. Mức cứng CHỈ áp khi §4.5.6 ĐÃ có row; bảng chưa có (brownfield thật) thì đi thẳng." },
2509
+ { "path": "tech-doc header — @trace.testid_attr",
2510
+ "mode": "merge", "regenerable": true, "when": "always",
2511
+ "note": "Một field scalar, suy từ stack FE." },
2512
+ { "path": "figma-components catalog — Test-ID Forwarding",
2513
+ "mode": "merge", "regenerable": true, "when": "--from-code",
2514
+ "note": "Step 3. Chế độ mặc định BỎ QUA bước này — tmpl:20." },
2515
+ { "path": "usage site FE",
2516
+ "mode": "merge", "regenerable": true, "when": "--from-code",
2517
+ "note": "Step 4. Chèn thuộc tính test-id vào đúng usage site, suy từ bảng §4.5.6 — không đụng logic quanh nó. Chế độ mặc định BỎ QUA." }
2224
2518
  ],
2225
2519
  "has_human_content": true,
2226
- "why": "§4.5.6 là HỢP ĐỒNG do SA viết tay 28/28 tech-doc thật dùng quy ước riêng (uc{n}-z2-region, ô đa-giá-trị) lệnh này KHÔNG tái tạo được (quy ước của nó: {uc-lower}-{screen}-{element}-{type}). Ghi đè đổi hợp đồng đang được cả FE lẫn QC bám vào, không ai yêu cầu."
2520
+ "why": "Ghi đè §4.5.6 là ĐỔI hợp đồng cả FE lẫn QC đang bám vào, kéo theo Step 5b hạ `qc_status` + bump `tech_doc_revision` cho một hợp đồng CHƯA HỎNG. Ba artifact còn lại đều sinh lại được một mình §4.5.6 do lệnh này phải chặn cứng."
2227
2521
  },
2228
2522
  "dev-gen-test": {
2229
2523
  "writes": [
2230
- "file unit/integration/E2E test theo module dev",
2231
- "sổ trace: test_count · test_classes · dev_selftest"
2524
+ { "path": "file unit/integration/E2E test theo module dev",
2525
+ "mode": "overwrite", "regenerable": false, "when": "always",
2526
+ "note": "Dev SỬA TAY chính các file test này — /dev-run-test:260 nói thẳng: 'Test fail → /fix-bug (bug thật) hoặc FIX TEST (sai expectation)', và :137 'Thêm mock; kiểm tra test fixture'." },
2527
+ { "path": "{trace_dir}/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv — cột test_count · test_classes · dev_selftest",
2528
+ "mode": "merge", "regenerable": true, "when": "always",
2529
+ "note": "Khớp row theo `sc_id`, GIỮ NGUYÊN mọi cột khác — `dev_selftest_at` do /dev-run-test sở hữu, `qc_status`/`qc_run_at` do /qc-run-script + /qc-run-manualtest sở hữu." },
2530
+ { "path": "./.trace-mirror/ (panel mirror, qua steps/trace-mirror.md)",
2531
+ "mode": "overwrite", "regenerable": true, "when": "always",
2532
+ "note": "Sinh lại được bất cứ lúc nào, luôn gitignore. (Artifact này THIẾU trong bản khai N1 — phát hiện khi rà include ở N2.)" }
2533
+ ],
2534
+ "has_human_content": true,
2535
+ "why": "Cùng khuôn G77 ở lane QC, khác lane: lệnh bảo người ta sửa tay, rồi lần chạy sau tự xoá bản sửa đó. Cột sổ trace và mirror thì ngược lại — máy tính ra, sinh lại được."
2536
+ },
2537
+ "review-code": {
2538
+ "writes": [
2539
+ { "path": "{lessons_path} — mặc định specs/domain-knowledge/lessons-learned.md, umbrella: {service_root}/.agent/project-lessons.md",
2540
+ "mode": "merge", "regenerable": false, "when": "confirm",
2541
+ "note": "Qua {{include:steps/capture-lesson.md}}. Lesson là GUARDRAIL do người chốt, tích luỹ qua nhiều phiên — không suy lại được từ code. capture-lesson L-35: nếu đã có lesson cùng lỗi thì SỬA lesson đó, nên là `merge` chứ không phải `append`." }
2542
+ ],
2543
+ "has_human_content": true,
2544
+ "why": "GHI CÓ ĐIỀU KIỆN, và điều kiện ĐÃ LÀ một cổng: lệnh hỏi 'Ghi nó thành project lesson để /generate-code không lặp lại? (Y/N)' (tmpl:140). Thêm CHECKPOINT nữa là hỏi hai lần cho một hành động — cách nhanh nhất dạy người ta bấm Y theo phản xạ. Vì thế lệnh ở mức `none`, KHÔNG phải vì nó read-only. Dòng `Output Artifacts: none (read-only)` cũ là SAI — đã sửa 2026-09-16."
2545
+ },
2546
+ "debug": {
2547
+ "writes": [
2548
+ { "path": "{lessons_path} — mặc định specs/domain-knowledge/lessons-learned.md, umbrella: {service_root}/.agent/project-lessons.md",
2549
+ "mode": "merge", "regenerable": false, "when": "confirm",
2550
+ "note": "Qua {{include:steps/capture-lesson.md}}, cùng đường với /review-code và /fix-bug." }
2551
+ ],
2552
+ "has_human_content": true,
2553
+ "why": "Như /review-code: ghi nằm sau câu hỏi 'Cái này trông như một lỗi AI lặp lại. Ghi nó thành project lesson? (Y/N)' (tmpl:254). capture-lesson §G46 còn cảnh báo file này 'phình nhanh nhất đúng lúc dự án đang trục trặc' — tức nó tích luỹ thật, không phải sổ tạm."
2554
+ },
2555
+ "review-context": {
2556
+ "writes": [
2557
+ { "path": "{refinement_dir}/{prd-slug}-review-context-findings.yaml · BDD: {refinement_dir}/{uc-id}-{platform}-review-bdd-findings.yaml",
2558
+ "mode": "merge", "creates": true, "regenerable": false, "when": "always",
2559
+ "note": "CHỨA QUYẾT ĐỊNH CỦA NGƯỜI (status accepted|modified|rejected|needs_discussion, `applied_via`). Chế độ DELTA giữ finding cũ; nhưng `--full` BỎ QUA findings cũ (tmpl:64) ⇒ mất cả vòng Review Board. Tên file BẮT BUỘC có platform, thiếu là review platform này ĐÈ findings platform khác." },
2560
+ { "path": "PRD hoặc .feature target (áp fix + bump version)",
2561
+ "mode": "merge", "regenerable": false, "when": "--resume",
2562
+ "note": "Resume Mode áp các finding được chấp nhận THẲNG vào target và bump version — `applied_to_version` ghi lại version vừa bump tới (tmpl:290)." }
2563
+ ],
2564
+ "has_human_content": true,
2565
+ "why": "Tiêu đề tmpl khai 'READ-ONLY — ghi file findings, KHÔNG sửa target'. Đúng ở chế độ phân tích, SAI ở `--resume`. Và ngay chế độ phân tích cũng không vô hại: `--full` bỏ qua findings cũ, tức xoá quyết định reviewer. Cùng hình dạng với /refine-prd — lệnh đó đã ở `hard` từ trước, lệnh này thì không, dù cùng một loại file findings."
2566
+ },
2567
+ "review-tech-docs": {
2568
+ "writes": [
2569
+ { "path": "{refinement_dir}/{TICKET-ID}-tech-review-findings.yaml",
2570
+ "mode": "merge", "creates": true, "regenerable": false, "when": "always",
2571
+ "note": "Như /review-context: chứa quyết định reviewer, và mang thêm `sign_off` + `sign_off_gate` đọc từ @trace.sign_off của tech-doc." },
2572
+ { "path": "tech-doc header — @trace.bdd_versions · @trace.sign_off (+ áp fix vào thân doc)",
2573
+ "mode": "merge", "regenerable": false, "when": "--resume",
2574
+ "note": "Phase 2 áp fix, Phase 3 làm mới `@trace.bdd_versions` (CHỈ cho platform đã giải quyết T3b — platform còn finding open giữ số cũ CÓ CHỦ Ý, để cờ TECHDOC_STALE_VS_BDD của /validate-traces còn sáng) và thêm skeleton `@trace.sign_off` nếu vắng. `sign_off` là XÁC NHẬN CỦA NGƯỜI (be_team/fe_team/app_team/sa)." },
2575
+ { "path": "{trace_dir}/{domain}/{prd-slug}/{UC-ID}-*.tsv — cột tech_doc_revision · last_updated",
2576
+ "mode": "merge", "regenerable": true, "when": "--resume",
2577
+ "note": "Phase 3, cho MỌI UC trong `@trace.ucs` và MỌI sổ platform. KHÔNG đụng `fe_tech_doc_revision` — cột đó do /generate-code --phase=integration sở hữu." }
2578
+ ],
2579
+ "has_human_content": true,
2580
+ "why": "Cũng khai 'READ-ONLY — KHÔNG sửa target', nhưng `--resume` Phase 2–3 ghi vào THÂN tech-doc, HEADER tech-doc, và sổ TSV của mọi UC mà doc phủ. Đây là lệnh ghi rộng nhất trong nhóm tự-nhận-read-only."
2581
+ },
2582
+ "validate-traces": {
2583
+ "writes": [
2584
+ { "path": "{trace_dir}/trace-report.json · {living_docs_dir}/trace-report.json",
2585
+ "mode": "overwrite", "regenerable": true, "when": "always",
2586
+ "note": "Snapshot đầy đủ tại thời điểm chạy lần cuối; source of truth cho web dashboard. Dựng lại bằng cách chạy lại lệnh." },
2587
+ { "path": "{trace_dir}/trace-history.jsonl",
2588
+ "mode": "append", "regenerable": false, "when": "always",
2589
+ "note": "⭐ ĐÂY LÀ CA SINH RA TRƯỜNG `regenerable`. Tmpl tự khai: 'AUTHORITATIVE, không regenerate được' · 'PHẢI commit' · sync.tmpl gọi là 'dữ liệu tích luỹ, không phải thứ sinh lại được'. Không ai gõ tay vào nó nên `has_human_content` trả lời false — ĐÚNG CHỮ, và để lọt đúng thứ mất là mất vĩnh viễn. Nó AN TOÀN chỉ vì `append`: đổi sang overwrite là R18 đỏ ngay." },
2590
+ { "path": "./.trace-mirror/ (panel mirror)",
2591
+ "mode": "overwrite", "regenerable": true, "when": "always",
2592
+ "note": "Bỏ qua hoàn toàn nếu {trace_dir} đã nằm trong workspace hiện tại." },
2593
+ { "path": "{trace_dir}/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv — row trạng thái",
2594
+ "mode": "merge", "regenerable": true, "when": "--reconcile-code",
2595
+ "note": "Step 5f. Điền row theo kết quả quét tag code + `has_verifies`. Chạy lại quét là có lại." },
2596
+ { "path": "{trace_dir}/{domain}/{prd-slug}/_seams.tsv",
2597
+ "mode": "merge", "regenerable": true, "when": "--reconcile-code",
2598
+ "note": "Khớp dòng theo khoá (kind, name, consumer_uc) — KHÔNG khớp theo `artifact`, vì class/method đổi tên giữa hai lần chạy sẽ đẻ ra bản ghi ma. Giữ nguyên `owner_uc`/`binding` (do /generate-code sở hữu)." }
2599
+ ],
2600
+ "has_human_content": false,
2601
+ "why": "Không ô nào cho người điền — nhưng KHÔNG PHẢI vì thế mà an toàn. Lệnh ở mức `none` là ĐÚNG, và lý do đúng là: mọi thao tác ghi `when: always` đều hoặc dựng lại được (trace-report.json, mirror) hoặc `append` (trace-history.jsonl); hai thao tác nguy hơn đều nằm sau `--reconcile-code`. Tmpl:58 tự phân biệt: 'Với một lệnh CHỈ ĐỌC thì đó là đốt thời gian. Với --reconcile-code (CÓ GHI) thì...'. Lời khai cũ 'read-only ... không đụng spec/code' là SAI — đã sửa 2026-09-16."
2602
+ },
2603
+ "generate-bdd": {
2604
+ "writes": [
2605
+ { "path": "{specs_dir}/{domain}/{prd-slug}/bdd/{platform}/{TICKET-ID}-UC{N}-{slug}.feature",
2606
+ "mode": "overwrite", "regenerable": false, "when": "always", "human_content": true,
2607
+ "note": "Mode F (gen lại TOÀN BỘ scenario) ghi đè mọi scenario đã có. `.feature` là ĐIỂM NEO của sổ trace: `sc_id` = {UC-ID}-SC{N} bị chép vào TSV, vào tag `@trace.verifies` trong code, và vào `.Test.md` — đánh số lại là phá cả bốn cùng lúc." },
2608
+ { "path": "{trace_dir}/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv",
2609
+ "mode": "merge", "regenerable": true, "when": "always",
2610
+ "note": "Tạo hoặc cập nhật, MỘT sổ cho mỗi platform — `sc_id` chỉ độc nhất trong (UC × platform), nên gộp là scenario platform này đè platform khác. Dựng lại được từ `.feature`." }
2611
+ ],
2612
+ "has_human_content": true,
2613
+ "why": "BDD là spec ĐÃ DUYỆT, và mode F ghi đè toàn bộ. Sổ TSV thì ngược lại — suy từ `.feature`, dựng lại được."
2614
+ },
2615
+ "generate-tech-docs": {
2616
+ "writes": [
2617
+ { "path": "{tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md",
2618
+ "mode": "merge", "regenerable": false, "when": "always", "human_content": true,
2619
+ "note": "Ghi/mở rộng theo từng UC, có hỏi \"Cập nhật các section của UC này? (Y = merge/mở rộng · N = bỏ qua UC này)\". Doc chứa §4.5.6 Test Selectors do SA VIẾT TAY (xem /map-testids) và §12 GAP Register — cả hai không suy lại được từ BDD." }
2620
+ ],
2621
+ "has_human_content": true,
2622
+ "why": "Cùng một file mà /map-testids đã phải chặn cứng vì §4.5.6. Lệnh này ghi rộng hơn — cả thân doc — nên không thể lỏng hơn."
2623
+ },
2624
+ "generate-design-spec": {
2625
+ "writes": [
2626
+ { "path": "{specs_dir}/{domain}/{prd-slug}/design-spec/{TICKET-ID}-design-spec-{platform}-{slug}.md",
2627
+ "mode": "overwrite", "regenerable": true, "when": "always",
2628
+ "note": "Sinh từ PRD + figma/design source. Không có ô nào cho người điền, không trạng thái tích luỹ qua các lần chạy." }
2629
+ ],
2630
+ "has_human_content": false,
2631
+ "why": "Ghi đè AN TOÀN theo hiện trạng đo 2026-09-16 — KHAI LẠI NẾU về sau designer/PO bắt đầu sửa tay vào file này. Khác /generate-bdd (spec đã duyệt) và /generate-tech-docs (§4.5.6 do SA viết): design-spec chưa có phần nào người sở hữu."
2632
+ },
2633
+ "generate-code": {
2634
+ "writes": [
2635
+ { "path": "{src_dir}/… mã nguồn theo layer + {UC-ID}MockApiAdapter.{ext} + real API adapter",
2636
+ "mode": "merge", "regenerable": false, "when": "always", "human_content": true,
2637
+ "note": "Dev SỬA TAY chính các file này. Đây là artifact đắt nhất trong toàn framework — và trước 2026-09-16 lệnh đứng ở mức `normal`, tức `--yes` ghi đè mã nguồn đã sửa tay TRONG IM LẶNG." },
2638
+ { "path": "{trace_dir}/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv — cột gen/impl",
2639
+ "mode": "merge", "regenerable": true, "when": "always",
2640
+ "note": "Khớp row theo `sc_id`, chỉ đụng cột nó sở hữu. Liên-repo ở umbrella: trace_dir phân giải về {spec_source}/.trace." },
2641
+ { "path": "{trace_dir}/{domain}/{prd-slug}/_seams.tsv",
2642
+ "mode": "merge", "creates": true, "regenerable": true, "when": "always",
2643
+ "note": "SỔ CHUNG của seam/stub liên-UC. Lệnh tự khai \"tạo file + header nếu chưa có\" — nó là lệnh DUY NHẤT tạo sổ này; /validate-traces chỉ merge \"nếu tồn tại\". Sở hữu cột `owner_uc`/`binding`. (Artifact này SÓT hẳn khỏi registry cho tới khi R22 bắt được, 2026-09-16.)" },
2644
+ { "path": "./.trace-mirror/ (panel mirror, qua steps/trace-mirror.md)",
2645
+ "mode": "overwrite", "regenerable": true, "when": "always",
2646
+ "note": "Sinh lại được bất cứ lúc nào, luôn gitignore." }
2232
2647
  ],
2233
2648
  "has_human_content": true,
2234
- "why": "Dev SỬA TAY chính các file test này — /dev-run-test:260 nói thẳng: \"Test fail /fix-bug (bug thật) hoặc FIX TEST (sai expectation)\", :137 \"Thêm mock; kiểm tra test fixture\". Cùng khuôn G77 ở lane QC, khác lane."
2649
+ "why": "Ghi đè nguồn dev đã sửa tay. Cùng khuôn G77/G84 (lane QC lane dev), nhưng artifact giá trị cao nhất."
2650
+ },
2651
+ "define-product": {
2652
+ "writes": [
2653
+ { "path": "{product_definitions_dir}/{TICKET-ID}-{slug}.md",
2654
+ "mode": "merge", "regenerable": false, "when": "always", "human_content": true,
2655
+ "note": "Từng mục mang dấu `✅ PO xác nhận` hoặc `🤖 AI trích — chờ PO chốt`, và `Completed Phase` cho phép resume giữa chừng. Ghi đè là xoá cả vòng xác nhận của PO và đẩy discovery về phase 0." }
2656
+ ],
2657
+ "has_human_content": true,
2658
+ "why": "Dấu `✅ PO xác nhận` là kết luận của NGƯỜI trên từng mục, không suy lại được. Guard chống-blitz của chính lệnh nói rõ: một phase chỉ tính là chốt khi MỌI item mang dấu đó."
2659
+ },
2660
+ "fix-bug": {
2661
+ "writes": [
2662
+ { "path": "mã nguồn — áp fix trên nhánh `fix/{TICKET_ID}-{description}`",
2663
+ "mode": "merge", "regenerable": true, "when": "always",
2664
+ "note": "Sửa ĐIỂM (kèm `@trace.fixes`/`@trace.root_cause`), KHÔNG ghi đè file, và Phase 3 tự `git checkout -b` sang nhánh riêng trước khi sửa — nhánh đó là đường lùi. Fix suy được lại từ bug report + phân tích." },
2665
+ { "path": "{lessons_path}",
2666
+ "mode": "merge", "regenerable": false, "when": "confirm", "human_content": true,
2667
+ "note": "Qua steps/capture-lesson.md, sau câu hỏi \"Ghi nó thành project lesson để không bị sinh lại? (Y/N)\". Đây là artifact DUY NHẤT của lệnh có phần người nhập tay — và nó CÓ ĐIỀU KIỆN." },
2668
+ { "path": "{trace_dir}/…/{UC-ID}-{platform}.tsv — reset `dev_selftest`",
2669
+ "mode": "merge", "regenerable": true, "when": "always",
2670
+ "note": "Hạ cờ về `not_run` vì code vừa đổi. Chạy /dev-run-test là có lại." },
2671
+ { "path": "./.trace-mirror/", "mode": "overwrite", "regenerable": true, "when": "always",
2672
+ "note": "Sinh lại được, luôn gitignore." }
2673
+ ],
2674
+ "has_human_content": true,
2675
+ "why": "Ở mức `normal` CÓ CHỦ Ý. Lệnh sửa đúng thứ người dùng vừa yêu cầu nó sửa, trên một nhánh riêng, sau khi /dev-run-test đã báo đỏ — dựng cổng chặn ở đó là hỏi lại một việc vừa được yêu cầu. Artifact có phần người nhập tay (`{lessons_path}`) nằm sau `(Y/N)`, nên luật (A) không áp: xem `human_content` ở cấp artifact."
2676
+ },
2677
+ "report-bug": {
2678
+ "writes": [
2679
+ { "path": "{bug_reports_dir}/{BUG-ID}.md",
2680
+ "mode": "create", "regenerable": false, "when": "always",
2681
+ "note": "Lệnh tự khai `Output Artifacts: CREATED` — mỗi lần chạy một {BUG-ID} mới, rồi `git add` + `git commit` vào spec repo dùng chung. Không đè bản ghi cũ. Nội dung là quan sát của MỘT phiên, không dựng lại được — nhưng `create` nên không mất gì." }
2682
+ ],
2683
+ "has_human_content": false,
2684
+ "why": "Chỉ TẠO file mới. Không ô nào cho người điền lúc sinh (người sửa sau thì sửa trên file đã commit, lệnh không quay lại đè)."
2685
+ },
2686
+ "propose-scenario": {
2687
+ "writes": [
2688
+ { "path": "{bdd_proposals_dir}/{UC-ID}-{slug}.md · {prd_change_requests_dir}/{UC-ID}-{slug}.md",
2689
+ "mode": "create", "regenerable": false, "when": "always",
2690
+ "note": "Lệnh tự khai `created`. Case A → bdd-proposals (`/generate-bdd` tự chèn khi `Status: accepted`); Case B → prd-change-requests (PO phải đưa vào PRD trước). Cột `Status` do NGƯỜI đổi — nhưng lệnh không quay lại ghi đè file đã tạo." }
2691
+ ],
2692
+ "has_human_content": false,
2693
+ "why": "Chỉ TẠO đề xuất mới. `Status: accepted` do người đặt về sau, và không lệnh nào trong framework ghi đè lại file đó."
2694
+ },
2695
+ "setup-ai-first": {
2696
+ "writes": [
2697
+ { "path": ".agent/project-context.yaml · CLAUDE.md · specs/domain-knowledge/business-dictionary.md · core-entities.md · các base dir",
2698
+ "mode": "create", "regenerable": false, "when": "always",
2699
+ "note": "⭐ CHỈ TẠO KHI CHƯA CÓ — tmpl nói ba lần: \"skip nếu đã tồn tại\", \"nếu chưa tồn tại\". File đã có thì lệnh KHÔNG đụng. Đây là lý do nó ở `normal` chứ không phải `hard`, dù artifact của nó (CLAUDE.md, project-context.yaml) là thứ người sửa tay nhiều nhất." }
2700
+ ],
2701
+ "has_human_content": false,
2702
+ "why": "Artifact thì CÓ phần người nhập tay, nhưng lệnh KHÔNG BAO GIỜ ghi đè chúng — `mode: create`. Rủi ro nằm ở THAO TÁC, không ở artifact: đây chính là ca làm tôi dự đoán sai mức của lệnh này khi chưa đọc file."
2703
+ },
2704
+ "generate-spec-manifest": {
2705
+ "writes": [
2706
+ { "path": "{manifest_root}/spec-manifest.yaml",
2707
+ "mode": "overwrite", "regenerable": true, "when": "always",
2708
+ "note": "\"Ghi đè nếu tồn tại\" — và chính report của lệnh in: \"Sinh lại bất cứ lúc nào: /generate-spec-manifest\". Chỉ số ticket_id/domain/status/version/prd_path quét từ specs." }
2709
+ ],
2710
+ "has_human_content": false,
2711
+ "why": "Chỉ mục thuần, suy 100% từ cây specs. Không ô nào cho người điền."
2712
+ },
2713
+ "sync": {
2714
+ "writes": [
2715
+ { "path": "{service.path}/.agent/project-context.yaml",
2716
+ "mode": "overwrite", "regenerable": true, "when": "always",
2717
+ "note": "Sinh từ cấu hình umbrella — dựng lại bằng cách chạy lại /sync." },
2718
+ { "path": "{living_docs_dir}/trace-report.json (merged) + {panel_mirror}/",
2719
+ "mode": "overwrite", "regenerable": true, "when": "always",
2720
+ "note": "Gộp report từ các service. KHÔNG copy `trace-history.jsonl` — tmpl nói rõ nó là \"dữ liệu tích luỹ, không phải thứ sinh lại được\"." },
2721
+ { "path": "spec-manifest.yaml", "mode": "overwrite", "regenerable": true, "when": "always",
2722
+ "note": "Như /generate-spec-manifest." }
2723
+ ],
2724
+ "has_human_content": false,
2725
+ "why": "Mọi artifact đều suy từ cấu hình umbrella + report của các service. Điểm đáng chú ý: lệnh CỐ Ý không đụng `trace-history.jsonl` — nó biết file đó không dựng lại được."
2726
+ },
2727
+ "learn": {
2728
+ "writes": [
2729
+ { "path": "{lessons_path}",
2730
+ "mode": "merge", "regenerable": false, "when": "confirm", "human_content": true,
2731
+ "note": "Qua steps/capture-lesson.md. Lesson là GUARDRAIL do người chốt, tích luỹ qua nhiều phiên. Lệnh có 4 câu hỏi `(Y/N)` trước khi ghi." }
2732
+ ],
2733
+ "has_human_content": true,
2734
+ "why": "Cùng artifact và cùng đường với /review-code · /debug · /fix-bug. Ghi nằm sau `(Y/N)` nên `normal` là đúng mức — thêm CHECKPOINT nữa là hỏi hai lần cho một hành động."
2735
+ },
2736
+ "update-framework": {
2737
+ "writes": [
2738
+ { "path": ".agent/ (copy core/ → .agent/, vô điều kiện) + .claude/commands/ shortcuts",
2739
+ "mode": "overwrite", "regenerable": true, "when": "always",
2740
+ "note": "⭐ ĐÈ THẬT, nhưng CỨU ĐƯỢC: bản cũ lưu ở `.agent/.overwritten-{version}-{date}/` từ v0.4.2, và lệnh TỰ IN cảnh báo kèm lệnh `diff` khi thư mục đó xuất hiện. Nội dung `.agent/` là MIRROR sinh từ package — dựng lại bằng cách cài lại. Ba chỗ KHÔNG bị đè: CLAUDE.md · project-context.yaml · project-lessons.md." }
2741
+ ],
2742
+ "has_human_content": false,
2743
+ "why": "Ở mức `normal` là ĐÚNG dù nó ghi đè: mất có đường lùi (`.overwritten-*`), có báo, và `.agent/` là mirror dựng lại được. Đây là ca chứng minh `regenerable` phải hỏi \"dựng lại được không\", KHÔNG phải \"có đè không\"."
2744
+ },
2745
+ "dev-run-test": {
2746
+ "writes": [
2747
+ { "path": "{trace_dir}/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv — cột dev_selftest · dev_selftest_at",
2748
+ "mode": "merge", "regenerable": true, "when": "always",
2749
+ "note": "Khớp row theo `sc_id`, giữ nguyên cột của /dev-gen-test và /qc-run-script. Chạy lại test là có lại." },
2750
+ { "path": "./.trace-mirror/", "mode": "overwrite", "regenerable": true, "when": "always",
2751
+ "note": "Sinh lại được, luôn gitignore." }
2752
+ ],
2753
+ "has_human_content": false,
2754
+ "why": "Chỉ ghi kết quả chạy vào sổ. Việc SỬA test khi expectation sai là dev làm tay, không phải lệnh này ghi."
2235
2755
  }
2236
2756
  },
2237
2757
  "pending_enrollment": {
2238
- "$comment": "Lệnh CÓ ghi file nhưng CHƯA rà xem có phần người nhập tay không. R18 không ép, nhưng danh sách này phải rỗng dần. Nợ được KHAI, không phải nợ vô hình.",
2758
+ "$comment": [
2759
+ "RỖNG TỪ 2026-09-16 (N3) — `enrolled` phủ 32/33 lệnh; lệnh thứ 33 (`dev-smoke-test`) nằm",
2760
+ "ở `non_writers` vì nó KHÔNG ghi file nào. Đây là lần đầu danh sách nợ này rỗng kể từ khi",
2761
+ "khối artifact_writers ra đời.",
2762
+ "",
2763
+ "GIỮ KHOÁ NÀY LẠI, KHÔNG XOÁ: lệnh mới thêm về sau mà chưa rà phải có chỗ để KHAI RA, thay",
2764
+ "vì rơi vào mặc định `normal` trong im lặng — đúng lỗ hổng đã xảy ra bốn lần (G67·G77·G78·G79).",
2765
+ "",
2766
+ "⚠️ ĐÍNH CHÍNH (2026-09-16): bản N2 của $comment này khai `dev-smoke-test` 'Tạo PR và link",
2767
+ "tới ticket — một tác động RA NGOÀI repo'. SAI. Dòng đó ở steps/report-footer.md:113 nằm",
2768
+ "trong bảng LÀM GÌ TIẾP THEO — nó nói với NGƯỜI DÙNG, không mô tả hành vi lệnh. Bài học:",
2769
+ "một dòng trong tài liệu chỉ là bằng chứng về hành vi nếu nó nằm trong phần MÔ TẢ HÀNH VI."
2770
+ ],
2771
+ "cmds": []
2772
+ },
2773
+ "non_writers": {
2774
+ "$comment": [
2775
+ "LỆNH KHÔNG GHI FILE NÀO — rà rồi, kết luận là không có gì để khai (N3 · 2026-09-16).",
2776
+ "",
2777
+ "VÌ SAO CẦN DANH SÁCH NÀY, KHÔNG PHẢI BỎ TRỐNG: một lệnh vắng mặt ở CẢ `enrolled` LẪN",
2778
+ "`pending_enrollment` trông y hệt một lệnh bị quên. Ghi ra đây biến 'không có gì' thành một",
2779
+ "KẾT LUẬN kiểm chứng được — cùng lập luận đã bắt `why` phải khai cả khi false (G79).",
2780
+ "",
2781
+ "R18 kiểm: lệnh ở đây KHÔNG được đồng thời ở `enrolled`, và tmpl phải tồn tại."
2782
+ ],
2239
2783
  "cmds": [
2240
- "generate-prd",
2241
- "extend-prd",
2242
- "amend-prd",
2243
- "refine-prd",
2244
- "generate-bdd",
2245
- "generate-tech-docs",
2246
- "generate-design-spec",
2247
- "generate-architecture",
2248
- "generate-code",
2249
- "review-context",
2250
- "review-tech-docs",
2251
- "review-code",
2252
- "fix-bug",
2253
- "report-bug",
2254
- "propose-scenario",
2255
- "define-product",
2256
- "setup-ai-first",
2257
- "generate-spec-manifest",
2258
- "validate-traces",
2259
- "sync",
2260
- "learn",
2261
- "update-framework",
2262
- "dev-run-test",
2263
- "dev-smoke-test",
2264
- "debug"
2265
- ]
2266
- }
2267
- },
2784
+ "dev-smoke-test"
2785
+ ],
2786
+ "why": {
2787
+ "dev-smoke-test": "Rà bản ĐÃ ĐÚC (core/commands/dev-smoke-test.md): không có dòng ghi file nào, và không include step ghi-file nào (chỉ context-loader · gate · report-footer). Nó CHẠY test khói và in kết quả; việc tạo PR là BƯỚC TIẾP THEO gợi ý cho người dùng, không phải hành vi của lệnh."
2788
+ }
2789
+ } },
2268
2790
  "testid_fourth_leg": {
2269
2791
  "$comment": [
2270
2792
  "CHÂN THỨ TƯ CỦA HỢP ĐỒNG TEST-ID: §4.5.6 ↔ *.Test.md (G64).",
@@ -2273,7 +2795,7 @@
2273
2795
  "Chân thứ tư không ai canh, và nó là chân DUY NHẤT mà giá trị id bị CHÉP CỨNG vào một artifact",
2274
2796
  "BỀN rồi nằm lại:",
2275
2797
  " generate-code → bake vào code → T17/T18 canh",
2276
- " qc-run-test → dựng locator LÚC CHẠY → đọc bảng tươi, tự cứu",
2798
+ " qc-run-script → dựng locator LÚC CHẠY → đọc bảng tươi, tự cứu",
2277
2799
  " qc-design-test → chép vào .Test.md → KHÔNG AI CANH ← đây",
2278
2800
  "",
2279
2801
  "KIỂU HỎNG LÀ 'NÓI DỐI', KHÔNG PHẢI 'IM LẶNG'. Lỗi CÓ hiện ra, nhưng ĐỘI LỐT thứ khác:",
@@ -2295,7 +2817,7 @@
2295
2817
  "rule": "T19",
2296
2818
  "level": "warn",
2297
2819
  "needs": "--qc",
2298
- "why": "LỚP CI cho chân thứ tư (G76). Guard ở /qc-review + /qc-run-test phủ 100% đường đi thật nhưng do LLM thực thi — nó không chặn được TRƯỚC khi merge. T19 nông hơn (chỉ so chuỗi) nhưng chạy trong CI. rules/workflow.md: 'Canh contract ≠ canh dữ liệu. Cần CẢ HAI' — đúng cặp self-check ↔ lint-trace. Bất đối xứng 3/4 (ba chân có lint, một chân không) là hình dạng G33: người đọc contract thấy bốn chân 'đã được canh' mà không phân biệt được chân nào chặn được ở CI.",
2820
+ "why": "LỚP CI cho chân thứ tư (G76). Guard ở /qc-review-testcase + /qc-design-script phủ 100% đường đi thật nhưng do LLM thực thi — nó không chặn được TRƯỚC khi merge. T19 nông hơn (chỉ so chuỗi) nhưng chạy trong CI. rules/workflow.md: 'Canh contract ≠ canh dữ liệu. Cần CẢ HAI' — đúng cặp self-check ↔ lint-trace. Bất đối xứng 3/4 (ba chân có lint, một chân không) là hình dạng G33: người đọc contract thấy bốn chân 'đã được canh' mà không phân biệt được chân nào chặn được ở CI.",
2299
2821
  "skip_clauses": [
2300
2822
  "Thiếu --qc → BỎ QUA CÓ TIẾNG. Không default: gốc artifact QC không đoán được (qc_dir là path QC duy nhất không remap theo spec_source), và 'quét sai chỗ còn tệ hơn không quét'.",
2301
2823
  "Không có §4.5 client → im lặng HOÀN TOÀN. Điều khoản phạm vi của testid_contract; thiếu nó thì mọi dự án backend-only đỏ ngay ngày nâng version và việc đầu tiên người ta làm là thêm --warn-only vào CI.",
@@ -2304,11 +2826,11 @@
2304
2826
  },
2305
2827
  "checked_by": [
2306
2828
  {
2307
- "cmd": "qc-review",
2829
+ "cmd": "qc-review-testcase",
2308
2830
  "level": "warn"
2309
2831
  },
2310
2832
  {
2311
- "cmd": "qc-run-test",
2833
+ "cmd": "qc-design-script",
2312
2834
  "level": "warn",
2313
2835
  "$note": "warn, KHÔNG chặn `pass` như qc_artifact_stamp. Id sai làm test ĐỎ, không làm test xanh giả — nên nó không thuộc lớp 'báo cáo sai'. Chặn ở đây là chặn nhầm loại."
2314
2836
  }
@@ -2323,7 +2845,7 @@
2323
2845
  "VÌ SAO CẦN: /generate-bdd hạ qc_status → not_run khi spec đổi, rất đúng luật. Nhưng đó là",
2324
2846
  "nửa 'KẾT QUẢ CHẠY hết hiệu lực'. Nửa còn thiếu là 'TÀI LIỆU THIẾT KẾ TEST hết khớp' — và",
2325
2847
  "hai nửa dẫn tới HAI VIỆC KHÁC NHAU:",
2326
- " qc_status = not_run ⇒ CHẠY LẠI (/qc-run-test)",
2848
+ " qc_status = not_run ⇒ CHẠY LẠI (/qc-run-script)",
2327
2849
  " stamp lệch ⇒ VIẾT LẠI (/qc-design-test)",
2328
2850
  "Thiếu nửa sau thì người ta thấy not_run và chạy lại một bộ TC lỗi thời — ra `pass`, và `pass`",
2329
2851
  "đó HỢP LỆ theo mọi phép kiểm hiện có. rules/workflow.md: '`pass` … mang nghĩa scenario này",
@@ -2440,14 +2962,22 @@
2440
2962
  "on_stale": "warn"
2441
2963
  },
2442
2964
  {
2443
- "cmd": "qc-review",
2965
+ "cmd": "qc-review-testcase",
2444
2966
  "checks": [
2445
2967
  "test-cases/*.Test.md"
2446
2968
  ],
2447
2969
  "on_stale": "warn"
2970
+ },
2971
+ {
2972
+ "cmd": "qc-run-manualtest",
2973
+ "checks": [
2974
+ "test-cases/*.Test.md"
2975
+ ],
2976
+ "on_stale": "block_positive_assertion",
2977
+ "$note": "Cùng luật với /qc-run-script: TC lỗi thời thì kết quả chạy TAY cũng không được ghi `pass`. Ở test tay guard này còn quan trọng hơn — lời khẳng định của người chạy là bằng chứng duy nhất, nên lớp chặn cơ học là thứ duy nhất đứng giữa một cái gật đầu thiện chí và một dòng `pass` sai trong sổ."
2448
2978
  },
2449
2979
  {
2450
- "cmd": "qc-run-test",
2980
+ "cmd": "qc-design-script",
2451
2981
  "checks": [
2452
2982
  "test-cases/*.Test.md"
2453
2983
  ],
@@ -2537,7 +3067,8 @@
2537
3067
  "field": "status",
2538
3068
  "actors": [
2539
3069
  "dev-run-test",
2540
- "qc-run-test"
3070
+ "qc-run-script",
3071
+ "qc-run-manualtest"
2541
3072
  ],
2542
3073
  "why": "G55 — đây là ca đã xảy ra thật. Không rẽ nhánh theo `status` thì hai lệnh ghi `pass` trên row `DRIFT`, tức dựng lại đúng tín hiệu `/generate-bdd` vừa hạ. Cờ NÓI DỐI, không phải cờ im lặng."
2543
3074
  },