@educa-corp/sdd-framework 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/bin/lint-trace.js +64 -3
  2. package/bin/self-check.js +8 -5
  3. package/bin/trace-schema.json +44 -4
  4. package/core/FRAMEWORK_VERSION +1 -1
  5. package/core/commands/amend-prd.md +3 -2
  6. package/core/commands/dev-run-test.md +1 -1
  7. package/core/commands/generate-bdd.md +9 -6
  8. package/core/commands/generate-code.md +2 -2
  9. package/core/commands/generate-tech-docs.md +2 -2
  10. package/core/commands/map-testids.md +1 -1
  11. package/core/commands/validate-traces.md +6 -6
  12. package/core/steps/context-loader.md +1 -1
  13. package/core/templates/ci/trace-gate.gitlab-ci.yml +169 -0
  14. package/core/templates/project-context.yaml +3 -3
  15. package/core/templates/tech-design.template.md +2 -2
  16. package/docs/02-concepts/architecture.md +1 -1
  17. package/docs/02-concepts/overview.md +1 -1
  18. package/docs/02-concepts/pipeline-steps/02-specification.md +13 -7
  19. package/docs/02-concepts/pipeline-steps/07-dev-selftest.md +2 -0
  20. package/docs/02-concepts/pipeline-steps/08-qc-automation.md +1 -0
  21. package/docs/02-concepts/pipeline-steps/09-validate-traces.md +34 -3
  22. package/docs/02-concepts/pipeline-steps/10-feedback-loop.md +10 -1
  23. package/docs/02-concepts/traceability.md +187 -183
  24. package/docs/03-guides/architect.md +15 -5
  25. package/docs/03-guides/developer.md +1 -0
  26. package/docs/03-guides/product-owner.md +89 -72
  27. package/docs/03-guides/tester-qa.md +81 -81
  28. package/docs/04-reference/commands.md +1 -1
  29. package/docs/04-reference/trace-schema.md +8 -1
  30. package/docs/explain/02c-amend-prd.md +1 -1
  31. package/docs/explain/06-generate-bdd.md +1 -1
  32. package/docs/explain/13-dev-run-test.md +15 -1
  33. package/docs/explain/19-qc-run-test.md +91 -87
  34. package/docs/explain/21-validate-traces.md +79 -75
  35. package/package.json +1 -1
package/bin/lint-trace.js CHANGED
@@ -155,6 +155,63 @@ function readLines(file) {
155
155
  return fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n').split('\n');
156
156
  }
157
157
 
158
+ // ── T13 — hai điều kiện bảo vệ sổ có KIỂM ĐƯỢC không ─────────────────────────
159
+ //
160
+ // T10 (luật merge) và T11 (bị gitignore) đều hỏi `git`. Trước T13, khi không hỏi được
161
+ // thì cả hai `catch { return; }` — IM LẶNG. Và im lặng ở đây tệ hơn nó nghe: gate-trace
162
+ // G0 lọc warns theo rule id, không thấy gì thì in ra
163
+ // "✅ G0 sổ được bảo vệ — nằm trong git · git biết cách gộp (2/2 điều kiện)"
164
+ // tức KHẲNG ĐỊNH sổ được bảo vệ trên bằng chứng chưa bao giờ được thu. Cùng hình dạng
165
+ // G55 (`pass` không có quyền khẳng định).
166
+ //
167
+ // Ca thường gặp nhất KHÔNG phải hiếm: image CI tối giản. `node:20-alpine` và
168
+ // `node:20-slim` đều KHÔNG có git — tức chính môi trường mà cổng này tồn tại để chạy.
169
+ //
170
+ // Ba trạng thái, phân biệt có chủ ý:
171
+ // ok → hỏi được, T10/T11 chạy bình thường
172
+ // no-binary → không có `git` ⇒ KHÔNG BIẾT sổ có được bảo vệ hay không
173
+ // not-a-repo → có git nhưng sổ không nằm trong repo nào ⇒ nó KHÔNG được commit
174
+ // Hai trạng thái sau đều phát T13. Gate nâng T13 thành chặn (schema →
175
+ // gate.config_preconditions.promote_lint_rules) — pre-push NHẮC, CI CHẶN, y như T10/T11.
176
+ // Thụt lề cho dòng hint nhiều dòng — trùng khuôn của err()/warn().
177
+ const NL = String.fromCharCode(10) + " ";
178
+ let _gitState = null;
179
+ function gitState(sampleFile) {
180
+ if (_gitState) return _gitState;
181
+ const cp = require('child_process');
182
+ const q = (args, opts) => cp.spawnSync('git', args, { encoding: 'utf8', stdio: ['ignore', 'ignore', 'ignore'], ...opts });
183
+ const v = q(['--version']);
184
+ if (v.error || v.status !== 0) return (_gitState = 'no-binary');
185
+ const dir = sampleFile ? path.dirname(sampleFile) : '.';
186
+ const r = q(['rev-parse', '--git-dir'], { cwd: fs.existsSync(dir) ? dir : '.' });
187
+ if (r.error || r.status !== 0) return (_gitState = 'not-a-repo');
188
+ return (_gitState = 'ok');
189
+ }
190
+
191
+ function lintGitEvaluable(sampleFile) {
192
+ const st = gitState(sampleFile);
193
+ if (st === 'ok') return true;
194
+ const where = rel(sampleFile || TRACE_DIRS[0]);
195
+ if (st === 'no-binary') {
196
+ warn('T13', where,
197
+ 'KHÔNG kiểm được điều kiện bảo vệ sổ — môi trường này không có binary `git`',
198
+ ['T10 (luật merge) và T11 (bị gitignore) đều phải hỏi git. Không hỏi được thì hai điều',
199
+ 'kiện đó KHÔNG được đánh giá — và "không biết" KHÔNG PHẢI "sạch".',
200
+ 'Ca thường gặp nhất: image CI tối giản. node:*-alpine và node:*-slim đều KHÔNG có git.',
201
+ 'Sửa: image: node:20 (bản Debian — có git sẵn)',
202
+ ' hoặc apk add --no-cache git / apt-get install -y git'].join(NL));
203
+ return false;
204
+ }
205
+ warn('T13', where,
206
+ 'sổ trace KHÔNG nằm trong một repo git nào — nó không được version control',
207
+ ['Sổ trace phải commit: nó không regenerate được, và trace-history.jsonl là dữ liệu',
208
+ 'tích luỹ — mất là mất vĩnh viễn.',
209
+ 'Nếu đây là bản copy tạm để kiểm thì bỏ qua được. Trong CI thì KHÔNG: nghĩa là repo',
210
+ 'chưa được clone đúng, hoặc sổ nằm trong submodule chưa clone',
211
+ '(GitLab: GIT_SUBMODULE_STRATEGY=recursive · GitHub: submodules: recursive).'].join(NL));
212
+ return false;
213
+ }
214
+
158
215
  // ── T7 — marker conflict git ──────────────────────────────────────────────────
159
216
  //
160
217
  // Chạy TRƯỚC mọi rule khác và trên MỌI file: một marker lọt vào TSV làm T1/T2 báo
@@ -587,8 +644,12 @@ for (const dir of TRACE_DIRS) {
587
644
  }
588
645
  }
589
646
 
590
- if (sampleForAttr) lintMergeAttr(sampleForAttr);
591
- lintGitTracked(abs);
647
+ // T13 TRƯỚC T10/T11: nếu không hỏi được git thì hai rule kia không có gì để phán, và
648
+ // im lặng ở đó làm gate G0 khẳng định "sổ được bảo vệ" trên bằng chứng không tồn tại.
649
+ if (lintGitEvaluable(sampleForAttr || abs)) {
650
+ if (sampleForAttr) lintMergeAttr(sampleForAttr);
651
+ lintGitTracked(abs);
652
+ }
592
653
  }
593
654
 
594
655
  // ── Report ────────────────────────────────────────────────────────────────────
@@ -596,7 +657,7 @@ for (const dir of TRACE_DIRS) {
596
657
  if (AS_JSON) {
597
658
  console.log(JSON.stringify({
598
659
  ok: errors.length === 0,
599
- scanned, trace_dirs: foundDirs, errors, warns, infos,
660
+ scanned, trace_dirs: foundDirs, git_state: _gitState, errors, warns, infos,
600
661
  }, null, 2));
601
662
  process.exit(errors.length && !WARN_ONLY ? 1 : 0);
602
663
  }
package/bin/self-check.js CHANGED
@@ -695,13 +695,16 @@ for (const [vocab, def] of Object.entries(schema.vocabularies)) {
695
695
  {
696
696
  const inv = (schema.gate || {}).audit_invalidating_tags;
697
697
  if (inv) {
698
- const rel = inv.ci_template;
699
- if (!rel) {
700
- err('R10', '`gate.audit_invalidating_tags` thiếu `ci_template` không biết canh file nào');
701
- } else {
698
+ // MỖI template CI phải grep đủ 7 tag — không chỉ bản GitHub. Một bản dịch (GitLab…)
699
+ // không được canh mà tụt lại sau schema thì grep không khớp gì ⇒ job LUÔN XANH.
700
+ const rels = inv.ci_templates || (inv.ci_template ? [inv.ci_template] : []);
701
+ if (!rels.length) {
702
+ err('R10', '`gate.audit_invalidating_tags` thiếu `ci_templates` — không biết canh file nào');
703
+ }
704
+ for (const rel of rels) {
702
705
  const abs = path.join(ROOT, rel);
703
706
  if (!fs.existsSync(abs)) {
704
- err('R10', `\`audit_invalidating_tags.ci_template\` trỏ tới ${rel} — file không tồn tại`,
707
+ err('R10', `\`audit_invalidating_tags.ci_templates\` trỏ tới ${rel} — file không tồn tại`,
705
708
  'Template đổi tên/bị xoá? Không có nó thì lỗ "sửa code mà không audit lại" hở lại.');
706
709
  } else {
707
710
  const yml = fs.readFileSync(abs, 'utf8');
@@ -1107,10 +1107,36 @@
1107
1107
  "column": "status"
1108
1108
  },
1109
1109
  "platform": {
1110
+ "$comment": [
1111
+ "PLATFORM = DELIVERY SURFACE, không phải tech stack. `webview` là surface thứ tư có thật:",
1112
+ "một bundle web nhúng trong app native — không phải `web` (browser) và không phải `app`",
1113
+ "(native). Nó có BDD riêng (tương tác khác), design-spec riêng, và repo riêng.",
1114
+ "",
1115
+ "VÌ SAO PHẢI THÊM (báo cáo từ project thật, v0.7.0): framework DẠY `webview` ở BỐN chỗ —",
1116
+ " templates/project-context.yaml FORM C lấy webview mini-game làm ví dụ",
1117
+ " steps/context-loader.md 2c ví dụ đầy đủ: bdd/webview/UC1.feature",
1118
+ " commands/setup-ai-first prompt tự đưa ra `learning:webview:dap-chuot:phaser-game`",
1119
+ " modules/phaser-game stack-profile có mục build cho webview",
1120
+ "— rồi TỪ CHỐI nó ở tầng kiểm. lint-trace T5 là `err`, và validate-traces Step 1.0 DỪNG",
1121
+ "NGAY khi lint exit 1 ⇒ project dùng đúng ví dụ của framework mất TOÀN BỘ audit.",
1122
+ "",
1123
+ "Đổi tên sổ KHÔNG phải đường ra: nó phá khoá routing `by_prd_slug`, lệch với",
1124
+ "@trace.platform trong .feature, và gộp hai surface vào một sổ làm sc_id đụng nhau (mỗi",
1125
+ "platform đánh SC từ 1).",
1126
+ "",
1127
+ "NỢ CÒN LẠI — danh sách này vẫn ĐÓNG: project cần `desktop`/`tv`/`mini-program`/`cli` sẽ",
1128
+ "bị chặn y như vậy và phải chờ một bản release. Nguyên nhân gốc là `platform` được xử lý",
1129
+ "như enum ĐÓNG trong contract nhưng MỌI code path sinh ra nó đều MỞ (setup-ai-first nhận",
1130
+ "chuỗi bất kỳ · context-loader 2b đọc sub-key bất kỳ · generate-bdd ghi bdd/{platform}/",
1131
+ "không kiểm gì). Cách sửa đúng là cho project TỰ KHAI thêm platform trong",
1132
+ "project-context.yaml và để lint-trace đọc — giữ được tính năng bắt typo của T5 mà không",
1133
+ "cần release cho từng surface mới. Đang hoãn có chủ ý, làm ở đợt sau."
1134
+ ],
1110
1135
  "values": [
1111
1136
  "web",
1112
1137
  "app",
1113
- "system"
1138
+ "system",
1139
+ "webview"
1114
1140
  ],
1115
1141
  "filename_segment": "{UC-ID}-{platform}.tsv",
1116
1142
  "$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)."
@@ -1233,6 +1259,10 @@
1233
1259
  "Cùng cách phân vai G39 đã chọn cho pre-push vs CI."
1234
1260
  ],
1235
1261
  "promote_lint_rules": [
1262
+ {
1263
+ "rule": "T13",
1264
+ "why": "KHÔNG KIỂM ĐƯỢC hai điều kiện kia — thiếu binary `git`, hoặc sổ không nằm trong repo git nào. Đây là loại THỨ TƯ, khác cả ba loại còn lại: không phải 'đang có cái sai' (🔴), không phải 'nợ' (🟠), cũng không phải 'bảo vệ đang tắt' (T10/T11) — mà là 'KHÔNG BIẾT bảo vệ có bật hay không'. Trước T13, ca này làm G0 in ra `sổ được bảo vệ — 2/2 điều kiện`: một lời KHẲNG ĐỊNH trên bằng chứng chưa bao giờ được thu, tệ hơn im lặng (cùng hình dạng G55). Ca thường gặp nhất là CI dùng image tối giản — node:*-alpine và node:*-slim đều KHÔNG có git — tức chính môi trường mà cổng này tồn tại để chạy."
1265
+ },
1236
1266
  {
1237
1267
  "rule": "T11",
1238
1268
  "why": "sổ trace đang bị gitignore — nó KHÔNG được lưu vào git, mất là mất toàn bộ (G29)"
@@ -1343,7 +1373,17 @@
1343
1373
  "self-check R10 canh templates/ci/trace-gate.yml có nhắc đủ 7 tag này. Tag đổi tên mà",
1344
1374
  "file CI không biết ⇒ grep không khớp gì ⇒ job LUÔN XANH ⇒ cổng mù im lặng."
1345
1375
  ],
1346
- "ci_template": "templates/ci/trace-gate.yml",
1376
+ "$ci_templates_comment": [
1377
+ "MỖI file CI phải grep đủ 7 tag. Trước v0.7.2 chỉ có bản GitHub Actions được canh —",
1378
+ "nên khi một team dịch sang GitLab, bản dịch KHÔNG được rule nào canh, và nếu nó tụt",
1379
+ "lại sau schema thì grep không khớp gì ⇒ job LUÔN XANH ⇒ cổng mù trong im lặng. Đúng",
1380
+ "lỗi R10 tồn tại để chặn, chỉ ở một file khác loại. Thêm template CI mới thì THÊM VÀO",
1381
+ "danh sách này ngay — R10 canh từng file."
1382
+ ],
1383
+ "ci_templates": [
1384
+ "templates/ci/trace-gate.yml",
1385
+ "templates/ci/trace-gate.gitlab-ci.yml"
1386
+ ],
1347
1387
  "tags": [
1348
1388
  {
1349
1389
  "tag": "@trace.implements",
@@ -1614,8 +1654,8 @@
1614
1654
  "khớp sổ ⇒ PASS · require-fresh-audit thấy PR không chạm tag ⇒ không đòi audit. Cả ba",
1615
1655
  "tầng xanh, và cả ba ĐÚNG theo định nghĩa của chính chúng.",
1616
1656
  "",
1617
- "Và G54 không chỉ CHO PHÉP sửa tay — nó ĐẨY người dùng về đó: /generate-prd chỉ 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ỉ",
1657
+ "Và G54 không chỉ CHO PHÉP sửa tay — nó ĐẨY người dùng về đó: /generate-prd DỪNG HẲN trên",
1658
+ "file đã (§Guard: 'Tồn tại DỪNG. KHÔNG ghi, KHÔNG hỏi Y/N') · /extend-prd chỉ",
1619
1659
  "add-only · /refine-prd chỉ áp được finding do chính nó tìm ra. Trước /amend-prd, hành",
1620
1660
  "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
1661
  "canh được.",
@@ -1 +1 @@
1
- 0.7.0
1
+ 0.7.2
@@ -10,8 +10,9 @@
10
10
  > | **PRD đã có, PO muốn ĐỔI một yêu cầu đang đúng cú pháp** | **`/amend-prd`** | **Edit tại chỗ** — output **KHÔNG** phải superset |
11
11
  >
12
12
  > **Vì sao phải là lệnh riêng (GAPS-v4 G54).** Ba lệnh kia đều **từ chối đúng việc này**:
13
- > `/generate-prd` chỉ một cổng chặn-cứng rồi vẫn ghi đè (mất changelog, **đánh số lại BR**,
14
- > phá `@trace.business_rules` trong mọi `.feature` đã sinh) · `/extend-prd` chỉ **add-only**, luật
13
+ > `/generate-prd` **DỪNG HẲN** trên file đã (§Guard: *"Tồn tại DỪNG. KHÔNG ghi, KHÔNG hỏi
14
+ > Y/N"* ghi đè mất changelog, **đánh số lại BR**, phá `@trace.business_rules` trong mọi
15
+ > `.feature` đã sinh, cả ba không hoàn tác được) · `/extend-prd` chỉ **add-only**, luật
15
16
  > Bước 5 §3 đòi output là *"superset chặt"* · `/refine-prd` tự cấm đụng section nào không được một
16
17
  > finding trỏ tới, và findings sinh từ việc soi PRD hiện có nên **không có đường nào để một ý định
17
18
  > MỚI của PO đi vào**.
@@ -338,7 +338,7 @@ Sau khi chạy, lưu kết quả vào **TSV authoritative** trong service để
338
338
  report Living Docs ở spec module (qua `/sync` + `/validate-traces`). Các file test
339
339
  ở lại trong service — chỉ *status* của lần chạy được report.
340
340
 
341
- Cập nhật **sổ của platform đang test** `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv` (`{platform}` = platform của code/`.feature` đang test — `system` cho backend, `web`/`app` cho FE/App; nếu `domain`/`prd_slug` không phân giải được từ spec target, định vị TSV bằng cách glob `{paths.trace_dir}/**/{UC-ID}-{platform}.tsv` — nó được tạo trước đó bởi `/generate-bdd`) — cho mỗi scenario row (khớp `sc_id` qua tag
341
+ Cập nhật **sổ của platform đang test** `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv` (`{platform}` = platform của code/`.feature` đang test — `system` cho backend, mọi platform khác (`web`/`app`/`webview`/…) cho client; nếu `domain`/`prd_slug` không phân giải được từ spec target, định vị TSV bằng cách glob `{paths.trace_dir}/**/{UC-ID}-{platform}.tsv` — nó được tạo trước đó bởi `/generate-bdd`) — cho mỗi scenario row (khớp `sc_id` qua tag
342
342
  `@trace.verifies={UC-ID}-SC{N}` của test). *(Umbrella + `spec_source`: `trace_dir` là `{spec_source}/.trace` — test chạy từ `service_root` nhưng update `dev_selftest` ghi vào **spec repo**; commit/push spec submodule cho nó.)*
343
343
 
344
344
  ### ĐỌC cột `status` của row TRƯỚC KHI GHI *(bắt buộc)*
@@ -222,11 +222,14 @@ Hỏi người dùng chọn platform target:
222
222
 
223
223
  ```
224
224
  BDD này dành cho platform nào?
225
- 1. web — FE/Web (React, Next.js, Angular, Vue, Nuxt)
226
- 2. app — Mobile (Flutter, React Native, iOS, Android)
227
- 3. systemSystem/BE BDD (tổng hợp từ web + app BDD có sẵn)
225
+ 1. web — FE/Web trong browser (React, Next.js, Angular, Vue, Nuxt)
226
+ 2. app — Mobile native (Flutter, React Native, iOS, Android)
227
+ 3. webviewBundle web NHÚNG trong app native (Phaser game, mini-app)
228
+ 4. system — System/BE BDD (tổng hợp từ BDD client có sẵn)
228
229
  ```
229
230
 
231
+ *Chỉ hiện những platform project thực sự dùng: nếu `services.{domain}` là map-theo-platform (context-loader 2b) thì lấy đúng các sub-key của nó làm danh sách; ngược lại hiện đủ bốn. `webview` là một **delivery surface** riêng — không phải `web` (browser) và không phải `app` (native) — nên nó có BDD, design-spec và sổ trace riêng.*
232
+
230
233
  Chờ người dùng chọn. Set `active_platform` = giá trị đã chọn.
231
234
 
232
235
  **Output path (spec repo mode):**
@@ -244,7 +247,7 @@ Chờ người dùng chọn. Set `active_platform` = giá trị đã chọn.
244
247
 
245
248
  ## System BDD Synthesis (active_platform = system)
246
249
 
247
- *Chỉ áp dụng khi platform = system. Bỏ qua với web app.*
250
+ *Chỉ áp dụng khi platform = `system`. Bỏ qua với mọi platform client (`web`/`app`/`webview`/…).*
248
251
 
249
252
  ### Step S0 — Brownfield Check
250
253
 
@@ -439,7 +442,7 @@ Umbrella mode không hỏi platform (khác spec repo mode) — nó **suy** từ
439
442
 
440
443
  ## Design Spec — Gate & Load (chỉ FE/App)
441
444
 
442
- *Chỉ chạy khi target platform là FE/App — spec mode: `active_platform {web, app, app-ios, app-android}`; umbrella mode: `active_module` là module FE/App (react/nextjs/vue/nuxt/angular/flutter/react-native/ios-swiftui/android-compose). Bỏ qua HOÀN TOÀN với `system` và backend/brownfield.*
445
+ *Chỉ chạy khi target platform là FE/App — spec mode: `active_platform` platform client (mọi giá trị **trừ** `system` — `web`, `app`, `webview`, `app-ios`, `app-android`, …); umbrella mode: `active_module` là module FE/App (react/nextjs/vue/nuxt/angular/flutter/react-native/ios-swiftui/android-compose). Bỏ qua HOÀN TOÀN với `system` và backend/brownfield.*
443
446
 
444
447
  **1. Định vị design-spec của platform:**
445
448
  `{paths.specs_dir}/{domain}/{prd-slug}/design-spec/{TICKET-ID}-design-spec-{active_platform}-{slug}.md`
@@ -795,7 +798,7 @@ In danh sách SC được bump vào report cuối để người dùng biết c
795
798
 
796
799
  ## Write Trace State
797
800
 
798
- Sau khi sinh tất cả file `.feature`, tạo hoặc cập nhật **sổ trace theo platform** `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{active_platform}.tsv` cho mỗi UC — một sổ riêng cho `system` / `web` / `app`. Vì `sc_id` = `{UC-ID}-SC{N}` chỉ độc nhất trong (UC × platform) (mỗi platform tự đánh số SC từ 1), **mỗi platform một file** để scenario platform này không đè/xoá platform khác. Lệnh luôn biết `active_platform` (từ Platform Selection / Service Detection) nên chỉ ghi đúng sổ của platform đang gen.
801
+ Sau khi sinh tất cả file `.feature`, tạo hoặc cập nhật **sổ trace theo platform** `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{active_platform}.tsv` cho mỗi UC — một sổ riêng cho mỗi platform (`system` / `web` / `app` / `webview` / …). Vì `sc_id` = `{UC-ID}-SC{N}` chỉ độc nhất trong (UC × platform) (mỗi platform tự đánh số SC từ 1), **mỗi platform một file** để scenario platform này không đè/xoá platform khác. Lệnh luôn biết `active_platform` (từ Platform Selection / Service Detection) nên chỉ ghi đúng sổ của platform đang gen.
799
802
 
800
803
  > **Umbrella + `spec_source`:** cả file `.feature` **và** trace `.tsv` đều ghi vào **spec repo** (`{spec_source}/specs/{domain}/{prd-slug}/bdd/…` và `{spec_source}/.trace/{domain}/{prd-slug}/…`, do context-loader phân giải) — một thao tác ghi **single-repo**, commit/push vào spec submodule. (Trace được gộp trong spec repo để PM quản lý mọi status ở một chỗ; các lệnh phía code cập nhật liên-repo sau.)
801
804
 
@@ -266,7 +266,7 @@ Lệnh này giới hạn nghiêm ngặt trong **một file feature** được tr
266
266
 
267
267
  ## Phase Detection
268
268
 
269
- > **Nguồn chuẩn quyết BE/FE = `@trace.platform` của FILE FEATURE** (`system` → BE · `web`/`app` FE). KHÔNG dùng `platform_type` (suy từ module) để quyết BE/FE — nó chỉ dùng cho **idiom stack/module** (cú pháp, layer, thư viện). Lý do: repo fullstack một-module (vd Next.js có API route) có `platform_type` cố định một giá trị, nhưng vẫn có cả feature `system` (BE) lẫn `web` (FE) — chỉ tag của chính feature mới đúng.
269
+ > **Nguồn chuẩn quyết BE/FE = `@trace.platform` của FILE FEATURE** — **`system` → BE · MỌI platform khác → FE** (`web`, `app`, `webview`, bất kỳ surface nào project khai thêm). Luật viết bằng **phủ định**, không phải liệt kê: liệt kê `web`/`app` làm platform thứ tư không khớp nhánh nào, và lệnh sẽ phải tự đoán — sinh sai loại code mà không cờ nào báo. KHÔNG dùng `platform_type` (suy từ module) để quyết BE/FE — nó chỉ dùng cho **idiom stack/module** (cú pháp, layer, thư viện). Lý do: repo fullstack một-module (vd Next.js có API route) có `platform_type` cố định một giá trị, nhưng vẫn có cả feature `system` (BE) lẫn `web` (FE) — chỉ tag của chính feature mới đúng.
270
270
 
271
271
  Parse `$ARGUMENTS` tìm flag `--phase` và `--force`:
272
272
 
@@ -275,7 +275,7 @@ Parse `$ARGUMENTS` tìm flag `--phase` và `--force`:
275
275
  | `--phase=ui` | FE Phase 1 — sinh UI + layer mock API từ System BDD contract |
276
276
  | `--phase=integration` | FE Phase 2 — thay mock adapter bằng lời gọi API thật từ tech docs |
277
277
  | `--force` | "Gen lại tường minh" — **CHỈ** bỏ qua guard status ở §Read Trace State (không skip row đang `OK`). Xem định nghĩa hẹp bên dưới. |
278
- | *(không có)* | Default — full: **BE/`system`** → full backend; **FE (`web`/`app`)** → **FE full** (sinh UI + wire API thật trong một lần, không qua bước mock) |
278
+ | *(không có)* | Default — full: **`system`** → full backend; **mọi platform khác** (`web`/`app`/`webview`/…) → **FE full** (sinh UI + wire API thật trong một lần, không qua bước mock) |
279
279
 
280
280
  > **`--force` có phạm vi HẸP — đây là ranh giới cứng, không phải khuyến nghị.**
281
281
  > Nó bỏ qua **đúng một** thứ: luật "row `OK` thì skip" ở §Read Trace State. **Mọi guard khác giữ nguyên hiệu lực:** Scope Lock (cấm implement scenario của `.feature` khác) · quy tắc EXTEND phi-phá-huỷ (đọc lại trước khi ghi · CẤM full Write trên file đã tồn tại · output phải là superset chặt) · Guard sau-ghi · Fill-before-create · Build Verify.
@@ -421,7 +421,7 @@ Ghi/mở rộng `{output_path}` dùng template dưới đây, chỉ sinh **nội
421
421
  @trace.ucs: {TICKET-ID}-UC1, {TICKET-ID}-UC2{, …}
422
422
  @trace.service: {service — từ header BDD @trace.service}
423
423
  @trace.module: {module liên quan — vd dotnet, angular}
424
- @trace.platforms: {system | web | app — tuỳ thư mục BDD nào tồn tại}
424
+ @trace.platforms: {system | web | app | webview | … — tuỳ thư mục BDD nào tồn tại}
425
425
  @trace.bdd_versions: {MAP theo từng platform — số nhiều, KHÁC @trace.bdd_version (scalar) của .feature — vd system=1.5, web=1.9, app=1.7; chỉ platform có mặt. Mỗi feature mang bdd_version riêng; đừng gộp về một số.}
426
426
  @trace.api_source: {existing | —}
427
427
  @trace.revision: 1
@@ -794,7 +794,7 @@ sequenceDiagram
794
794
 
795
795
  | UC | Feature | Platforms | Section phủ | Trạng thái |
796
796
  |----|---------|-----------|------------------|--------|
797
- | {TICKET-ID}-UC1 | {title} | {system, web, app} | §… | ✅ Covered |
797
+ | {TICKET-ID}-UC1 | {title} | {system, web, app, webview…} | §… | ✅ Covered |
798
798
 
799
799
  ### Độ phủ Scenario UC1
800
800
 
@@ -180,7 +180,7 @@ placeholder bên dưới sẽ rỗng và lệnh sẽ đọc/ghi sai chỗ.
180
180
 
181
181
  Phân giải `platform` từ `@trace.platform` / `platform_type`. Test-id là chuyện của **FE/App** — nếu `system` / backend → HALT:
182
182
  ```
183
- ❌ /map-testids chỉ áp dụng cho FE/App (web/app). BE không có UI test-id.
183
+ ❌ /map-testids chỉ áp dụng cho platform CLIENT (mọi platform trừ `system` — web/app/webview/…). BE không có UI test-id.
184
184
  ```
185
185
  Phân giải attribute test-id từ `@trace.testid_attr` (hoặc theo module): web `data-testid` · React Native `testID` · Flutter `Key`/`Semantics(identifier:)` · native iOS `accessibilityIdentifier`.
186
186
 
@@ -828,7 +828,7 @@ Schema:
828
828
  }
829
829
  },
830
830
  "by_platform": {
831
- "<web | app | system CHỈ platform thực sự có scenario>": {
831
+ "<platform — web | app | system | webview | … ; CHỈ platform thực sự có scenario>": {
832
832
  "total_scs": 0, "coded_scs": 0, "tested_scs": 0, "drift_count": 0
833
833
  }
834
834
  }
@@ -850,7 +850,7 @@ Schema:
850
850
  "scenarios": [
851
851
  {
852
852
  "sc_id": "<e.g. PAY-UC01-SC1>",
853
- "platform": "web | app | system",
853
+ "platform": "web | app | system | webview | …",
854
854
  "sc_title": "<title>",
855
855
  "spec_ver": "<current version from .feature>",
856
856
  "gen_ver": "<version at codegen time>",
@@ -956,7 +956,7 @@ Schema:
956
956
  "bdd_drift": [
957
957
  {
958
958
  "uc_id": "<UC-ID>",
959
- "platform": "web | app | system",
959
+ "platform": "web | app | system | webview | …",
960
960
  "code_bdd_version": "<@trace.bdd_version trong code>",
961
961
  "current_bdd_version": "<@trace.bdd_version của .feature>",
962
962
  "drifted_scs": ["<SC đang DRIFT của UC này>"],
@@ -966,7 +966,7 @@ Schema:
966
966
  "techdoc_stale_vs_bdd": [
967
967
  {
968
968
  "uc_id": "<UC-ID>",
969
- "platform": "web | app | system",
969
+ "platform": "web | app | system | webview | …",
970
970
  "techdoc_bdd_version": "<entry trong map @trace.bdd_version của tech-doc>",
971
971
  "current_bdd_version": "<@trace.bdd_version của .feature>",
972
972
  "fix": "/generate-tech-docs <feature-file> then /review-tech-docs"
@@ -993,7 +993,7 @@ Schema:
993
993
  "orphaned": [
994
994
  {
995
995
  "sc_id": "<SC-ID đã bị xoá khỏi .feature>",
996
- "platform": "web | app | system",
996
+ "platform": "web | app | system | webview | …",
997
997
  "implemented_by": "<ClassName.method còn tồn tại>",
998
998
  "test_classes": ["<test còn trỏ vào SC này>"],
999
999
  "fix": "xoá code + test, HOẶC đưa scenario trở lại .feature"
@@ -1037,7 +1037,7 @@ Schema:
1037
1037
  "service_unrouted": [
1038
1038
  {
1039
1039
  "sc_id": "<SC-ID>",
1040
- "platform": "web | app | system",
1040
+ "platform": "web | app | system | webview | …",
1041
1041
  "domain": "<domain của PRD>",
1042
1042
  "value": "unrouted | unresolved",
1043
1043
  "reason": "<lý do context-loader đã ghi — vd: domain chưa có entry trong services:>",
@@ -403,7 +403,7 @@ Dict : {loaded — N canonical terms, M banned terms | missing}
403
403
  Entities : {loaded — EntityA, EntityB, EntityC | missing}
404
404
  Lessons : {loaded — {n} active cho lệnh này ({tổng} tổng) | chưa có}
405
405
  {⚠️ CHỈ IN khi tổng ≥ 40: "{tổng} guardrail đang hoạt động — /learn --review để rà"}
406
- Platform : {active_platform: system | web | app | — nếu chưa xác định}
406
+ Platform : {active_platform: system | web | app | webview | … | — nếu chưa xác định}
407
407
  Service : {active_service} ({active_service_module}) [← domain{/platform}{/prd_slug} nếu route qua by_prd_slug] | multi (map-theo-platform hoặc map-theo-prd_slug, chốt khi target đủ platform/prd_slug) | single-service
408
408
  Svc Root : {service_root} — đã nạp conventions + trace_dir từ config service | —
409
409
  Status : {FULL | PARTIAL — thiếu: CLAUDE.md / business-dict / core-entities | MINIMAL}
@@ -0,0 +1,169 @@
1
+ # ─────────────────────────────────────────────────────────────────────────────
2
+ # SDD Framework — Trace Gate (GitLab CI)
3
+ #
4
+ # COPY nội dung này vào `.gitlab-ci.yml` ở gốc repo. Nó KHÔNG tự chạy từ
5
+ # `.agent/templates/` — mọi thứ trong `.agent/` là bản sinh ra, bị ghi đè mỗi lần
6
+ # `/update-framework`.
7
+ #
8
+ # ⚠️ Repo chưa có `.gitlab-ci.yml`? Thêm file này = mọi push/MR từ nay sẽ kích hoạt
9
+ # pipeline. Đó là mục đích — nhưng hãy biết trước.
10
+ #
11
+ # GIỚI HẠN — đọc trước khi tin:
12
+ # Job này chứng minh "report khớp SỔ, và sổ không có cờ 🔴".
13
+ # Nó KHÔNG chứng minh "sổ khớp CODE" — việc đó cần quét tag trong source, đọc
14
+ # .feature, so version, tức cần `/validate-traces` (một lệnh LLM, không chạy được
15
+ # trong CI thường). Nên nó bắt ca phổ biến "quên chạy lại /validate-traces",
16
+ # nhưng KHÔNG bắt ca "sửa code mà không đụng sổ".
17
+ #
18
+ # ─────────────────────────────────────────────────────────────────────────────
19
+ # BỐN BẪY KHI DỊCH TỪ GITHUB ACTIONS — file này đã vá cả bốn.
20
+ #
21
+ # Bản GitHub (`trace-gate.yml`) dựa vào ba thứ `ubuntu-latest` cho sẵn mà GitLab
22
+ # KHÔNG có. Hai bẫy đầu ỒN (job đỏ, thấy ngay). Hai bẫy sau IM LẶNG — job XANH
23
+ # trên một phép kiểm chưa bao giờ chạy, tức đúng thứ cổng này tồn tại để chống.
24
+ #
25
+ # 1. ỒN thiếu `tags:` → job stuck "no active runners that can run this job",
26
+ # dù runner Online. Group/shared runner thường có
27
+ # `run_untagged = false`, nên job KHÔNG tag sẽ không
28
+ # bao giờ được nhận. Điền đúng tag của runner bạn.
29
+ # 2. ỒN thiếu Node → `npx: not found`. GitHub có bước
30
+ # `actions/setup-node@v4`; GitLab không có tương
31
+ # đương, và bước đó là chỗ cấp `npx`.
32
+ # 3. IM LẶNG thiếu `GIT_DEPTH: 0` → GitLab clone SHALLOW (mặc định 20). `git diff
33
+ # BASE...HEAD` không có merge-base ⇒ ra RỖNG ⇒ job
34
+ # `require-fresh-audit` in "MR không chạm tag nào"
35
+ # và PASS, trong khi bạn vừa đổi tag. XANH GIẢ.
36
+ # 4. IM LẶNG image không có git → `node:20-alpine` và `node:20-slim` đều KHÔNG có
37
+ # git. Mà lint-trace cần git cho T10 (luật merge) và
38
+ # T11 (bị gitignore) — hai điều kiện `gate-trace` G0
39
+ # NÂNG thành chặn. Không hỏi được git thì (trước T13)
40
+ # G0 in "✅ sổ được bảo vệ — 2/2 điều kiện": một lời
41
+ # KHẲNG ĐỊNH trên bằng chứng chưa bao giờ được thu.
42
+ # Từ v0.7.2 lint phát **T13** và gate FAIL đúng ca đó
43
+ # — nhưng cách sửa vẫn là dùng image CÓ git.
44
+ #
45
+ # `before_script` dưới đây in `node -v` và `git --version` để bẫy 2 và 4 lộ ra ở
46
+ # dòng log đầu tiên, thay vì lộ ra dưới dạng một dấu xanh sai.
47
+ # ─────────────────────────────────────────────────────────────────────────────
48
+
49
+ stages:
50
+ - trace
51
+
52
+ variables:
53
+ # ── BẪY 3 ── BẮT BUỘC. Không có dòng này thì `require-fresh-audit` XANH GIẢ.
54
+ GIT_DEPTH: 0
55
+
56
+ # PIN VERSION, đừng `@latest`. Cổng này quyết định PR nào được merge — một bản
57
+ # release framework đổi hành vi cổng giữa sprint là thứ bạn muốn tự chọn thời điểm.
58
+ SDD_VERSION: "0.7.2"
59
+
60
+ # Sổ/spec nằm trong submodule (umbrella + spec_source)? Bỏ comment dòng dưới.
61
+ # Không có nó, lint thấy 0 sổ → gate FAIL ở G2 ("chưa audit ≠ sạch"). Ồn, không im.
62
+ # GIT_SUBMODULE_STRATEGY: recursive
63
+
64
+ .sdd:
65
+ # ── BẪY 2 + 4 ── `node:20` là bản Debian: có SẴN cả node lẫn git.
66
+ # KHÔNG dùng `node:20-alpine` / `node:20-slim` — chúng thiếu git (xem bẫy 4).
67
+ # Buộc phải dùng alpine? Thêm: `- apk add --no-cache git`
68
+ image: node:20
69
+
70
+ # ── BẪY 1 ── ĐỔI thành đúng tag của runner bạn. Kiểm ở Settings → CI/CD → Runners.
71
+ # Runner Online mà job vẫn stuck ⇒ hoặc lệch tag, hoặc runner không bật
72
+ # "Run untagged jobs", hoặc runner chỉ nhận protected ref (MR pipeline chạy trên
73
+ # `refs/merge-requests/N/head` — KHÔNG phải protected ref, kể cả khi đích là main).
74
+ tags:
75
+ - CHANGE_ME
76
+
77
+ before_script:
78
+ # Hai dòng này tồn tại để bẫy 2 và 4 lộ ra NGAY, chứ không lộ ra dưới dạng dấu xanh sai.
79
+ - node -v
80
+ - git --version
81
+
82
+ # ── 1+2. Cấu trúc sổ + cổng chặn cờ 🔴 ───────────────────────────────────────
83
+ # Sổ trace 24 cột do LLM ghi tay: một dấu tab thiếu dồn mọi ô sang trái, ô `status`
84
+ # nhận một ngày tháng, và không cờ nào bật. lint bắt việc đó (T1/T2/T3), bắt marker
85
+ # conflict git lọt vào sổ (T7), sổ thiếu luật merge (T10), sổ bị gitignore (T11),
86
+ # row vừa DRIFT vừa mang `pass` (T12), và ca không hỏi được git (T13).
87
+ #
88
+ # `--gate-trace` tự chạy lại lint ở tầng G1, nên bước lint riêng ở trên là để có log
89
+ # dễ đọc khi đỏ. Muốn gọn thì bỏ nó và chỉ giữ `--gate-trace`.
90
+ trace-gate:
91
+ extends: .sdd
92
+ stage: trace
93
+ script:
94
+ - npx -y @educa-corp/sdd-framework@${SDD_VERSION} --lint-trace
95
+ - npx -y @educa-corp/sdd-framework@${SDD_VERSION} --gate-trace
96
+ artifacts:
97
+ when: always
98
+ paths:
99
+ - .trace/trace-report.json
100
+ expire_in: 1 week
101
+ rules:
102
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
103
+ - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
104
+
105
+ # ── 3. Ép audit phải TƯƠI khi thứ report đang KHẲNG ĐỊNH bị đổi ──────────────
106
+ # ĐO BẰNG TAG, KHÔNG BẰNG `src/**`: framework có luật boundary-only tagging, nên
107
+ # "MR có sửa src/ không?" chặn oan cả MR chỉ thêm một field vào DTO — một file không
108
+ # 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 cần chặn.
109
+ # Câu hỏi đúng: "MR có chạm dòng nào mang tag mà report đang khẳng định không?"
110
+ # sửa log trong Controller → cho qua
111
+ # thêm field vào DTO → cho qua
112
+ # thêm method + @implements → CHẶN
113
+ # lấp một @trace.stub → CHẶN
114
+ #
115
+ # 7 tag dưới đây khai ở `bin/trace-schema.json` → `gate.audit_invalidating_tags`;
116
+ # `self-check` R10 canh danh sách này khớp schema. Tag đổi tên mà file CI không biết
117
+ # ⇒ grep không khớp gì ⇒ job LUÔN XANH ⇒ cổng mù trong im lặng.
118
+ #
119
+ # GIỚI HẠN ĐÃ BIẾT: đổi tên class (AuthService → AuthenticationService) không chạm
120
+ # dòng tag ⇒ job này bỏ lọt, dù cột `implemented_by` giờ trỏ vào tên không còn. Chấp
121
+ # nhận có chủ ý: ca đó ít gặp và KHÔNG im lặng (lần `/validate-traces` sau báo ngay),
122
+ # còn báo oan thì xảy ra mỗi ngày và hậu quả là CỔNG BỊ TẮT.
123
+ require-fresh-audit:
124
+ extends: .sdd
125
+ stage: trace
126
+ rules:
127
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
128
+ script:
129
+ - |
130
+ # ── BẪY 3, phần thứ hai ── GitHub có `github.base_ref` sẵn trong checkout.
131
+ # GitLab phải FETCH branch đích TRƯỚC — nếu không thì "$BASE" không tồn tại,
132
+ # `git diff` ra rỗng, và job PASS sai. Refspec tường minh để chắc chắn
133
+ # `refs/remotes/origin/<branch>` được cập nhật.
134
+ BR="$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
135
+ git fetch --no-tags origin "+refs/heads/${BR}:refs/remotes/origin/${BR}"
136
+ BASE="origin/${BR}"
137
+
138
+ # Fail-fast: chứng minh merge-base tồn tại. Không có nó, mọi kết luận bên dưới
139
+ # dựa trên một diff rỗng — và "rỗng" ở đây nghĩa là "không biết", không phải "sạch".
140
+ if ! git merge-base "$BASE" HEAD >/dev/null 2>&1; then
141
+ echo "🔴 Không tìm được merge-base giữa $BASE và HEAD."
142
+ echo " Gần như chắc chắn là clone SHALLOW — kiểm biến GIT_DEPTH: 0 ở đầu file."
143
+ echo " Không có merge-base thì git diff ra rỗng và job này sẽ PASS SAI."
144
+ exit 1
145
+ fi
146
+
147
+ TAGS='@trace\.(implements|verifies|seam_port|seam_pending|stub|stub_owner|stub_for)'
148
+ touched=$(git diff -U0 "$BASE"...HEAD | grep -E "^[+-].*${TAGS}" | head -5 || true)
149
+ audit=$(git diff --name-only "$BASE"...HEAD | grep -E 'trace-report\.json$' | head -1 || true)
150
+
151
+ if [ -n "$touched" ] && [ -z "$audit" ]; then
152
+ echo "🔴 MR đổi tag trace nhưng không kèm trace-report.json được sinh lại."
153
+ echo ""
154
+ echo "Những dòng này đã đổi:"
155
+ echo "$touched" | sed 's/^/ /'
156
+ echo ""
157
+ echo "Trace gate chỉ chứng minh 'report khớp SỔ'. Tag vừa đổi mà chưa audit lại"
158
+ echo "thì mọi cờ 🔴 trong report nói về trạng thái TRƯỚC khi bạn sửa."
159
+ echo ""
160
+ echo "Chạy trong Claude Code: /validate-traces"
161
+ echo "Rồi commit: .trace/trace-report.json + *.tsv"
162
+ exit 1
163
+ fi
164
+
165
+ if [ -n "$touched" ]; then
166
+ echo "✅ Tag trace có đổi, và audit đã được sinh lại cùng MR."
167
+ else
168
+ echo "✅ MR không chạm tag nào mà report đang khẳng định — audit vẫn còn đúng."
169
+ fi
@@ -26,7 +26,7 @@ project:
26
26
  paths:
27
27
  # Feature-Package Layout:
28
28
  # specs/{domain}/{prd-slug}/{TICKET-ID}-{prd-slug}.md — PRD document
29
- # specs/{domain}/{prd-slug}/bdd/{platform}/ — BDD .feature ({platform} = web | app | system)
29
+ # specs/{domain}/{prd-slug}/bdd/{platform}/ — BDD .feature ({platform} = web | app | system | webview)
30
30
  # specs/{domain}/{prd-slug}/tech-docs/ — Technical design (ONE merged doc per PRD: {TICKET-ID}-tech-design.md)
31
31
  # specs/{domain}/{prd-slug}/design-spec/ — Design specs (FE/App only)
32
32
  # specs/{domain}/{prd-slug}/changelog/ — PRD changelog overflow (created once history exceeds 5 versions)
@@ -97,7 +97,7 @@ paths:
97
97
  # {spec_source}/specs so FE/App read the contract via the spec submodule.
98
98
  tech_docs_dir: "specs"
99
99
 
100
- # Design Specs (FE/App platforms only — web, app).
100
+ # Design Specs (client platforms only — every platform except `system`: web, app, webview).
101
101
  # In the feature-package layout, design-specs live at specs/{domain}/{prd-slug}/design-spec/.
102
102
  # This variable is no longer needed as a separate path — derived from specs_dir.
103
103
  # design_spec_dir: "specs/design-spec" ← removed; use specs_dir instead
@@ -163,7 +163,7 @@ domains:
163
163
  # #
164
164
  # # FORM B — PER-PLATFORM MAP (one business-domain implemented on several platforms /
165
165
  # # submodules — a merged monorepo/workspace). No direct `path`; instead one
166
- # # sub-key per platform (system | web | app). context-loader routes by the
166
+ # # sub-key per platform (system | web | app | webview). context-loader routes by the
167
167
  # # target .feature's @trace.platform → picks {path, module} for that platform.
168
168
  # # The PRD keeps a SINGLE business @trace.domain (do NOT invent onboarding-web).
169
169
  # {{DOMAIN_2}}:
@@ -39,7 +39,7 @@
39
39
  @trace.ucs: {TICKET-ID}-UC1, {TICKET-ID}-UC2{, …}
40
40
  @trace.service: {service — từ header BDD @trace.service}
41
41
  @trace.module: {module liên quan — vd dotnet, angular}
42
- @trace.platforms: {system | web | app — tuỳ thư mục BDD nào tồn tại}
42
+ @trace.platforms: {system | web | app | webview | … — tuỳ thư mục BDD nào tồn tại}
43
43
  @trace.bdd_versions: {MAP theo từng platform — số nhiều, KHÁC @trace.bdd_version (scalar) của .feature — vd system=1.5, web=1.9, app=1.7; chỉ platform có mặt. Mỗi feature mang bdd_version riêng; đừng gộp về một số.}
44
44
  @trace.api_source: {existing | —}
45
45
  @trace.revision: 1
@@ -412,7 +412,7 @@ sequenceDiagram
412
412
 
413
413
  | UC | Feature | Platforms | Section phủ | Trạng thái |
414
414
  |----|---------|-----------|------------------|--------|
415
- | {TICKET-ID}-UC1 | {title} | {system, web, app} | §… | ✅ Covered |
415
+ | {TICKET-ID}-UC1 | {title} | {system, web, app, webview…} | §… | ✅ Covered |
416
416
 
417
417
  ### Độ phủ Scenario UC1
418
418
 
@@ -66,7 +66,7 @@ Spec-driven thành/bại phụ thuộc **~80%** vào việc context được n
66
66
  bin/self-check.js (fail build) --init cài vào đây
67
67
  ```
68
68
 
69
- **Vì sao slim (G45):** build inline `{{include:}}` vào **từng** file lệnh. Với 32 lệnh, kết quả là
69
+ **Vì sao slim (G45):** build inline `{{include:}}` vào **từng** file lệnh. Đo lúc đó (32 lệnh), kết quả là
70
70
  2069 KB mà chỉ 580 KB là nội dung riêng của chúng — **72% là vài step giống hệt nhau, chép 30 lần**.
71
71
  `/generate-code` từng nặng 108 KB (≈27k token đọc **trước** khi làm gì), gần một nửa không nói gì về
72
72
  việc sinh code. Cái giá thật không phải tiền: trên PRD nhiều UC nó làm tăng rủi ro **cạn context
@@ -11,7 +11,7 @@
11
11
  ```mermaid
12
12
  flowchart TD
13
13
  S["0 · Setup<br/>/setup-ai-first"] --> D["1 · Discovery<br/>/define-product"]
14
- D --> SP["2 · Specification<br/>/generate-prd · /refine-prd · /review-context"]
14
+ D --> SP["2 · Specification<br/>/generate-prd · /extend-prd · /amend-prd<br/>/refine-prd · /review-context"]
15
15
  SP --> DS["3 · Design-Spec<br/>(chỉ FE/App)"]
16
16
  SP --> B["4 · BDD<br/>/generate-bdd · /review-context"]
17
17
  DS --> B