@educa-corp/sdd-framework 0.2.1 → 0.2.4

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 (65) hide show
  1. package/bin/index.js +59 -29
  2. package/commands/debug.md +21 -8
  3. package/commands/define-product.md +21 -8
  4. package/commands/dev-gen-test.md +21 -8
  5. package/commands/dev-run-test.md +21 -8
  6. package/commands/dev-smoke-test.md +21 -8
  7. package/commands/fix-bug.md +21 -8
  8. package/commands/generate-bdd.md +24 -10
  9. package/commands/generate-bdd.tmpl +3 -2
  10. package/commands/generate-code.md +21 -8
  11. package/commands/generate-design-spec.md +21 -8
  12. package/commands/generate-prd.md +21 -8
  13. package/commands/generate-spec-manifest.md +21 -8
  14. package/commands/generate-tech-docs.md +21 -8
  15. package/commands/learn.md +21 -8
  16. package/commands/map-testids.md +21 -8
  17. package/commands/propose-scenario.md +21 -8
  18. package/commands/qc-analyze.md +21 -8
  19. package/commands/qc-design-test.md +21 -8
  20. package/commands/qc-plan.md +21 -8
  21. package/commands/qc-report.md +21 -8
  22. package/commands/qc-review.md +21 -8
  23. package/commands/qc-run-test.md +21 -8
  24. package/commands/refine-prd.md +21 -8
  25. package/commands/report-bug.md +21 -8
  26. package/commands/review-code.md +21 -8
  27. package/commands/review-context.md +21 -8
  28. package/commands/review-tech-docs.md +21 -8
  29. package/commands/setup-ai-first.md +14 -7
  30. package/commands/setup-ai-first.tmpl +14 -7
  31. package/commands/validate-traces.md +21 -8
  32. package/core/FRAMEWORK_VERSION +1 -1
  33. package/core/commands/debug.md +21 -8
  34. package/core/commands/define-product.md +21 -8
  35. package/core/commands/dev-gen-test.md +21 -8
  36. package/core/commands/dev-run-test.md +21 -8
  37. package/core/commands/dev-smoke-test.md +21 -8
  38. package/core/commands/fix-bug.md +21 -8
  39. package/core/commands/generate-bdd.md +24 -10
  40. package/core/commands/generate-code.md +21 -8
  41. package/core/commands/generate-design-spec.md +21 -8
  42. package/core/commands/generate-prd.md +21 -8
  43. package/core/commands/generate-spec-manifest.md +21 -8
  44. package/core/commands/generate-tech-docs.md +21 -8
  45. package/core/commands/learn.md +21 -8
  46. package/core/commands/map-testids.md +21 -8
  47. package/core/commands/propose-scenario.md +21 -8
  48. package/core/commands/qc-analyze.md +21 -8
  49. package/core/commands/qc-design-test.md +21 -8
  50. package/core/commands/qc-plan.md +21 -8
  51. package/core/commands/qc-report.md +21 -8
  52. package/core/commands/qc-review.md +21 -8
  53. package/core/commands/qc-run-test.md +21 -8
  54. package/core/commands/refine-prd.md +21 -8
  55. package/core/commands/report-bug.md +21 -8
  56. package/core/commands/review-code.md +21 -8
  57. package/core/commands/review-context.md +21 -8
  58. package/core/commands/review-tech-docs.md +21 -8
  59. package/core/commands/setup-ai-first.md +14 -7
  60. package/core/commands/validate-traces.md +21 -8
  61. package/core/steps/context-loader.md +21 -8
  62. package/core/templates/project-context.yaml +23 -10
  63. package/package.json +1 -1
  64. package/steps/context-loader.md +21 -8
  65. package/templates/project-context.yaml +23 -10
package/bin/index.js CHANGED
@@ -21,13 +21,20 @@ const showHelp = args.includes('--help') || args.includes('-h');
21
21
  const moduleIdx = args.indexOf('--module');
22
22
  const moduleName = moduleIdx !== -1 ? args[moduleIdx + 1] : null;
23
23
 
24
- // --services backend:java-spring,web-admin:react,app-mobile:flutter
24
+ // FORM A (flat): --services backend:java-spring,web-admin:react,app-mobile:flutter
25
+ // FORM B (per-platform): --services onboarding:system:java-spring,onboarding:web:nextjs,onboarding:app:flutter
26
+ // (same domain repeated with a platform segment → grouped into a per-platform map)
25
27
  const servicesIdx = args.indexOf('--services');
26
28
  const servicesRaw = servicesIdx !== -1 ? args[servicesIdx + 1] : null;
27
29
  const serviceList = servicesRaw
28
30
  ? servicesRaw.split(',').map(s => {
29
- const [name, mod] = s.trim().split(':');
30
- return { name: name.trim(), module: (mod || '').trim() };
31
+ const parts = s.trim().split(':').map(p => p.trim());
32
+ if (parts.length >= 3) {
33
+ // domain:platform:module → FORM B entry
34
+ return { name: parts[0], platform: parts[1], module: parts[2] };
35
+ }
36
+ const [name, mod] = parts;
37
+ return { name, module: (mod || '') };
31
38
  }).filter(s => s.name)
32
39
  : [];
33
40
 
@@ -222,25 +229,48 @@ if (isInit) {
222
229
  if (!fs.existsSync(rootCtxPath)) {
223
230
  const specSrc = specSource || 'TODO-spec-submodule';
224
231
 
232
+ // Group serviceList by domain name. Entries carrying a `platform` become a
233
+ // per-platform map (FORM B); plain entries stay flat (FORM A).
234
+ const byDomain = new Map();
235
+ for (const s of serviceList) {
236
+ if (!byDomain.has(s.name)) byDomain.set(s.name, []);
237
+ byDomain.get(s.name).push(s);
238
+ }
239
+
225
240
  let servicesBlock = '';
226
- if (serviceList.length > 0) {
227
- servicesBlock = serviceList.map(s => [
228
- ` ${s.name}:`,
229
- ` path: "${s.name}"`,
230
- ` module: "${s.module || 'TODO'}"`,
231
- ` specs_dir: "${s.name}/specs/bdd"`,
232
- ].join('\n')).join('\n');
241
+ if (byDomain.size > 0) {
242
+ servicesBlock = [...byDomain.entries()].map(([domain, entries]) => {
243
+ const hasPlatform = entries.some(e => e.platform);
244
+ if (hasPlatform) {
245
+ // FORM B — per-platform map. path defaults to a TODO placeholder because the
246
+ // submodule dir usually differs from the domain name (review after generate).
247
+ const rows = entries.map(e =>
248
+ ` ${e.platform}: { path: "TODO-${e.platform}-submodule-dir", module: "${e.module || 'TODO'}" }`);
249
+ return [` ${domain}:`, ...rows].join('\n');
250
+ }
251
+ const e = entries[0];
252
+ return [
253
+ ` ${domain}:`,
254
+ ` path: "TODO-submodule-dir" # dir name usually differs from domain "${domain}"`,
255
+ ` module: "${e.module || 'TODO'}"`,
256
+ ].join('\n');
257
+ }).join('\n');
233
258
  } else {
234
259
  servicesBlock = [
260
+ ` # FORM A (one domain ↔ one submodule):`,
235
261
  ` # {domain}:`,
236
262
  ` # path: "{service-submodule-dir}"`,
237
263
  ` # module: "{stack-module}"`,
238
- ` # specs_dir: "{service-dir}/specs/bdd"`,
264
+ ` # FORM B (one domain across platforms — routed by @trace.platform):`,
265
+ ` # {domain}:`,
266
+ ` # system: { path: "{be-dir}", module: "java-spring" }`,
267
+ ` # web: { path: "{web-dir}", module: "nextjs" }`,
268
+ ` # app: { path: "{app-dir}", module: "flutter" }`,
239
269
  ].join('\n');
240
270
  }
241
271
 
242
- const domainsBlock = serviceList.length > 0
243
- ? serviceList.map(s => ` - "${s.name}"`).join('\n')
272
+ const domainsBlock = byDomain.size > 0
273
+ ? [...byDomain.keys()].map(d => ` - "${d}"`).join('\n')
244
274
  : ` - "TODO"`;
245
275
 
246
276
  const umbrellaYaml = [
@@ -252,21 +282,16 @@ if (isInit) {
252
282
  ` mode: umbrella`,
253
283
  ` spec_source: "${specSrc}"`,
254
284
  ``,
255
- `# Paths auto-derived from spec_source by context-loader.`,
256
- `# Override here only if your structure differs.`,
285
+ `# With spec_source set, context-loader auto-derives specs_dir, tech_docs_dir,`,
286
+ `# domain_knowledge_dir, business_dictionary, core_entities, trace_dir, refinement_dir`,
287
+ `# and feedback/* under {spec_source} (feature-package layout). Do NOT set those here —`,
288
+ `# an explicit value would override the shared-repo route. Only product_definitions_dir`,
289
+ `# (not auto-derived) is pinned to the spec repo:`,
257
290
  `paths:`,
258
- ` prd_dir: "${specSrc}/specs/prd"`,
259
- ` design_spec_dir: "${specSrc}/specs/design-spec"`,
260
- ` tech_docs_dir: "${specSrc}/specs/tech-docs" # API contract — shared (BE pushes here, FE/App read via /sync)`,
261
- ` domain_knowledge_dir: "${specSrc}/specs/domain-knowledge"`,
262
- ` business_dictionary: "${specSrc}/specs/domain-knowledge/business-dictionary.md"`,
263
- ` core_entities: "${specSrc}/specs/domain-knowledge/core-entities.md"`,
264
291
  ` product_definitions_dir: "${specSrc}/specs/product-definition"`,
265
- ` refinement_dir: ".agent/review"`,
266
- ` trace_dir: ".trace"`,
267
292
  ``,
268
- `# Domain → service submodule routing.`,
269
- `# Each key must match the PRD's Domain (Metadata row) / folder path segment.`,
293
+ `# Domain → service submodule routing (see two forms below). Each key must match the`,
294
+ `# PRD's Domain (Metadata row) / folder path segment. Review the TODO paths.`,
270
295
  `services:`,
271
296
  servicesBlock,
272
297
  ``,
@@ -292,12 +317,17 @@ if (isInit) {
292
317
  }
293
318
  }
294
319
 
295
- // 5. Monorepo: install into each service subfolder
296
- if (serviceList.length > 0) {
320
+ // 5. Monorepo: install into each service subfolder.
321
+ // Skip FORM B entries (per-platform, from an umbrella of existing submodules) — their
322
+ // real dirs differ from the domain name and must not be scaffolded here. Also skip the
323
+ // whole loop in umbrella mode: --umbrella installs the framework at the umbrella root
324
+ // only; each existing submodule is set up by opening it and running /setup-ai-first.
325
+ const scaffoldList = isUmbrella ? [] : serviceList.filter(s => !s.platform);
326
+ if (scaffoldList.length > 0) {
297
327
  console.log('');
298
- console.log(`Setting up ${serviceList.length} service workspace(s)...`);
328
+ console.log(`Setting up ${scaffoldList.length} service workspace(s)...`);
299
329
 
300
- for (const svc of serviceList) {
330
+ for (const svc of scaffoldList) {
301
331
  const svcDir = path.join(projectRoot, svc.name);
302
332
  const svcAgentDir = path.join(svcDir, '.agent');
303
333
  const svcClaudeCmds = path.join(svcDir, '.claude', 'commands');
package/commands/debug.md CHANGED
@@ -182,16 +182,28 @@ Nếu có section `services`:
182
182
  *(vd: `specs/user/create-account/USR01-create-account.md` **và** `specs/user/create-account/bdd/system/UC1.feature` đều → domain = `user`, prd_slug = `create-account`)*
183
183
  - Nếu `$ARGUMENTS` chứa một path, trích xuất segment domain sau `specs_dir`
184
184
 
185
- **2. Route tới service** nếu active domain khớp với một key trong `services`:
186
- - Override `paths.specs_dir` `services.{domain}.specs_dir` **chỉ khi `setup.spec_source` KHÔNG được đặt.** Khi `spec_source` ĐƯỢC đặt, MỌI BDD (web/app/**system**) artifact dùng chung liên team → để bước 4 route sang spec repo; KHÔNG pin theo service ở đây.
187
- - Override `paths.tech_docs_dir` `services.{domain}.tech_docs_dir` — **chỉ khi `setup.spec_source` KHÔNG được đặt.** Khi `spec_source` ĐƯỢC đặt, tech-design (API contract)artifact liên team và phải nằm trong spec repo dùng chung (xử lý ở bước 4), nên để bước 4 route `tech_docs_dir` KHÔNG pin theo service ở đây.
185
+ **1b. Phát hiện active platform** (chỉ cần khi service dạng map-theo-platform bước 2b):
186
+ - Đọc `@trace.platform` từ header của target `.feature` (`system` | `web` | `app`) Gate đã resolve target trước bước này.
187
+ - Nếu target không mang `@trace.platform` (vd targetPRD `.md`), thử suy từ segment `bdd/{platform}/` trong path target.
188
+ - Nếu vẫn không xác định được → `active_platform = null`.
189
+
190
+ **2. Route tới service** — nếu active domain khớp một key trong `services`. Giá trị `services.{domain}` có **hai dạng**; nhận dạng bằng việc có `path` trực tiếp hay không:
191
+
192
+ **2a. Dạng phẳng** — `services.{domain}` có **trực tiếp** `path`/`module` (một domain ↔ một service, mọi platform về cùng submodule). Route như cũ:
188
193
  - Lưu `active_service` = `services.{domain}.path`
189
194
  - Lưu `active_service_module` = `services.{domain}.module`
190
195
  - Nếu service có `module` riêng → dùng nó làm `active_module` (override `tech_stack.module`)
191
196
 
192
- **3. Fallback** — nếu không phát hiện được domain hoặc không service key khớp:
193
- - Giữ path mặc định từ Bước 1
194
- - Đặt `active_service = unresolved`
197
+ **2b. Dạng map-theo-platform** — `services.{domain}` **KHÔNG** `path` trực tiếp mà chứa các sub-key platform (`system` / `web` / `app`), mỗi cái là `{ path, module }` (một business-domain trải trên nhiều platform/submodule). Route theo `active_platform` (bước 1b):
198
+ - Nếu `active_platform` khớp một sub-key → `entry = services.{domain}.{active_platform}`; lưu `active_service = entry.path`, `active_service_module = entry.module` (→ `active_module`, override `tech_stack.module`).
199
+ - Nếu `active_platform = null` (chưa xác định platform, vd đang thao tác cấp PRD) → **KHÔNG** chốt một service; đặt `active_service = multi` và lưu `service_candidates = services.{domain}` (toàn map platform→{path,module}). Lệnh cần một service cụ thể (`/generate-code`, `/dev-*`, `/fix-bug`) luôn chạy trên target `.feature` có platform nên sẽ resolve được ở lần chạy đó; lệnh cấp PRD (`/generate-prd`, `/refine-prd`) không cần service cụ thể.
200
+ - Nếu `active_platform` xác định nhưng không có sub-key tương ứng → `active_service = unresolved` (xem Fallback) với lý do "domain `{domain}` chưa cấu hình platform `{active_platform}`".
201
+
202
+ *(Cả 2a/2b: override `paths.specs_dir`/`paths.tech_docs_dir` per-service CHỈ khi `setup.spec_source` KHÔNG được đặt. Khi `spec_source` ĐƯỢC đặt, MỌI BDD/tech-doc là artifact liên team → để bước 4 route sang spec repo; KHÔNG pin per-service ở đây.)*
203
+
204
+ **3. Fallback**:
205
+ - Không phát hiện được domain, hoặc domain không khớp key nào trong `services` → giữ path mặc định từ Bước 1, đặt `active_service = unresolved`.
206
+ - Domain khớp một map-theo-platform (2b) nhưng `active_platform` xác định mà thiếu sub-key tương ứng → `active_service = unresolved`, ghi lý do rõ để lệnh DỪNG báo lỗi cấu hình (không tự đoán platform).
195
207
 
196
208
  **4. Tự động override theo spec source** — nếu `setup.spec_source` được đặt VÀ path tương ứng chưa được set tường minh trong `paths:`:
197
209
  - Override `paths.specs_dir` → `{spec_source}/specs` — **luôn khi `spec_source` được đặt.** Mọi spec artifact (PRD, BDD, tech-docs, design-spec) nằm dưới gốc spec thống nhất trong spec repo dùng chung theo bố cục feature-package: `{spec_source}/specs/{domain}/{prd-slug}/`. Mọi umbrella (FE/App/BE) đều đọc từ đây. *(`specs/` theo service chỉ khi không có `spec_source`.)*
@@ -211,7 +223,7 @@ Nếu có section `services`:
211
223
 
212
224
  ## Bước 1.6 — [SERVICE CONVENTIONS] Nạp convention riêng của service (chế độ umbrella)
213
225
 
214
- *Bỏ qua hoàn toàn bước này nếu `active_service` là `"unresolved"` hoặc context ở chế độ single-service.*
226
+ *Bỏ qua hoàn toàn bước này nếu `active_service` là `"unresolved"` hoặc `"multi"` (chưa chốt một service — dạng map-theo-platform ở cấp PRD) hoặc context ở chế độ single-service.*
215
227
 
216
228
  Khi `active_service` đã được phân giải thành một path thật ở Bước 1.5 (vd: `user-service/`):
217
229
 
@@ -387,7 +399,8 @@ Ticket : {ticket_prefix}-
387
399
  Dict : {loaded — N canonical terms, M banned terms | missing}
388
400
  Entities : {loaded — EntityA, EntityB, EntityC | missing}
389
401
  Lessons : {loaded — N guardrails | chưa có}
390
- Service : {active_service} ({active_service_module}) | single-service
402
+ Platform : {active_platform: system | web | app | — nếu chưa xác định}
403
+ Service : {active_service} ({active_service_module}) | multi (map-theo-platform, chốt khi target có platform) | single-service
391
404
  Svc Root : {service_root} — đã nạp conventions + trace_dir từ config service | —
392
405
  Status : {FULL | PARTIAL — thiếu: CLAUDE.md / business-dict / core-entities | MINIMAL}
393
406
  ```
@@ -179,16 +179,28 @@ Nếu có section `services`:
179
179
  *(vd: `specs/user/create-account/USR01-create-account.md` **và** `specs/user/create-account/bdd/system/UC1.feature` đều → domain = `user`, prd_slug = `create-account`)*
180
180
  - Nếu `$ARGUMENTS` chứa một path, trích xuất segment domain sau `specs_dir`
181
181
 
182
- **2. Route tới service** nếu active domain khớp với một key trong `services`:
183
- - Override `paths.specs_dir` `services.{domain}.specs_dir` **chỉ khi `setup.spec_source` KHÔNG được đặt.** Khi `spec_source` ĐƯỢC đặt, MỌI BDD (web/app/**system**) artifact dùng chung liên team → để bước 4 route sang spec repo; KHÔNG pin theo service ở đây.
184
- - Override `paths.tech_docs_dir` `services.{domain}.tech_docs_dir` — **chỉ khi `setup.spec_source` KHÔNG được đặt.** Khi `spec_source` ĐƯỢC đặt, tech-design (API contract)artifact liên team và phải nằm trong spec repo dùng chung (xử lý ở bước 4), nên để bước 4 route `tech_docs_dir` KHÔNG pin theo service ở đây.
182
+ **1b. Phát hiện active platform** (chỉ cần khi service dạng map-theo-platform bước 2b):
183
+ - Đọc `@trace.platform` từ header của target `.feature` (`system` | `web` | `app`) Gate đã resolve target trước bước này.
184
+ - Nếu target không mang `@trace.platform` (vd targetPRD `.md`), thử suy từ segment `bdd/{platform}/` trong path target.
185
+ - Nếu vẫn không xác định được → `active_platform = null`.
186
+
187
+ **2. Route tới service** — nếu active domain khớp một key trong `services`. Giá trị `services.{domain}` có **hai dạng**; nhận dạng bằng việc có `path` trực tiếp hay không:
188
+
189
+ **2a. Dạng phẳng** — `services.{domain}` có **trực tiếp** `path`/`module` (một domain ↔ một service, mọi platform về cùng submodule). Route như cũ:
185
190
  - Lưu `active_service` = `services.{domain}.path`
186
191
  - Lưu `active_service_module` = `services.{domain}.module`
187
192
  - Nếu service có `module` riêng → dùng nó làm `active_module` (override `tech_stack.module`)
188
193
 
189
- **3. Fallback** — nếu không phát hiện được domain hoặc không service key khớp:
190
- - Giữ path mặc định từ Bước 1
191
- - Đặt `active_service = unresolved`
194
+ **2b. Dạng map-theo-platform** — `services.{domain}` **KHÔNG** `path` trực tiếp mà chứa các sub-key platform (`system` / `web` / `app`), mỗi cái là `{ path, module }` (một business-domain trải trên nhiều platform/submodule). Route theo `active_platform` (bước 1b):
195
+ - Nếu `active_platform` khớp một sub-key → `entry = services.{domain}.{active_platform}`; lưu `active_service = entry.path`, `active_service_module = entry.module` (→ `active_module`, override `tech_stack.module`).
196
+ - Nếu `active_platform = null` (chưa xác định platform, vd đang thao tác cấp PRD) → **KHÔNG** chốt một service; đặt `active_service = multi` và lưu `service_candidates = services.{domain}` (toàn map platform→{path,module}). Lệnh cần một service cụ thể (`/generate-code`, `/dev-*`, `/fix-bug`) luôn chạy trên target `.feature` có platform nên sẽ resolve được ở lần chạy đó; lệnh cấp PRD (`/generate-prd`, `/refine-prd`) không cần service cụ thể.
197
+ - Nếu `active_platform` xác định nhưng không có sub-key tương ứng → `active_service = unresolved` (xem Fallback) với lý do "domain `{domain}` chưa cấu hình platform `{active_platform}`".
198
+
199
+ *(Cả 2a/2b: override `paths.specs_dir`/`paths.tech_docs_dir` per-service CHỈ khi `setup.spec_source` KHÔNG được đặt. Khi `spec_source` ĐƯỢC đặt, MỌI BDD/tech-doc là artifact liên team → để bước 4 route sang spec repo; KHÔNG pin per-service ở đây.)*
200
+
201
+ **3. Fallback**:
202
+ - Không phát hiện được domain, hoặc domain không khớp key nào trong `services` → giữ path mặc định từ Bước 1, đặt `active_service = unresolved`.
203
+ - Domain khớp một map-theo-platform (2b) nhưng `active_platform` xác định mà thiếu sub-key tương ứng → `active_service = unresolved`, ghi lý do rõ để lệnh DỪNG báo lỗi cấu hình (không tự đoán platform).
192
204
 
193
205
  **4. Tự động override theo spec source** — nếu `setup.spec_source` được đặt VÀ path tương ứng chưa được set tường minh trong `paths:`:
194
206
  - Override `paths.specs_dir` → `{spec_source}/specs` — **luôn khi `spec_source` được đặt.** Mọi spec artifact (PRD, BDD, tech-docs, design-spec) nằm dưới gốc spec thống nhất trong spec repo dùng chung theo bố cục feature-package: `{spec_source}/specs/{domain}/{prd-slug}/`. Mọi umbrella (FE/App/BE) đều đọc từ đây. *(`specs/` theo service chỉ khi không có `spec_source`.)*
@@ -208,7 +220,7 @@ Nếu có section `services`:
208
220
 
209
221
  ## Bước 1.6 — [SERVICE CONVENTIONS] Nạp convention riêng của service (chế độ umbrella)
210
222
 
211
- *Bỏ qua hoàn toàn bước này nếu `active_service` là `"unresolved"` hoặc context ở chế độ single-service.*
223
+ *Bỏ qua hoàn toàn bước này nếu `active_service` là `"unresolved"` hoặc `"multi"` (chưa chốt một service — dạng map-theo-platform ở cấp PRD) hoặc context ở chế độ single-service.*
212
224
 
213
225
  Khi `active_service` đã được phân giải thành một path thật ở Bước 1.5 (vd: `user-service/`):
214
226
 
@@ -384,7 +396,8 @@ Ticket : {ticket_prefix}-
384
396
  Dict : {loaded — N canonical terms, M banned terms | missing}
385
397
  Entities : {loaded — EntityA, EntityB, EntityC | missing}
386
398
  Lessons : {loaded — N guardrails | chưa có}
387
- Service : {active_service} ({active_service_module}) | single-service
399
+ Platform : {active_platform: system | web | app | — nếu chưa xác định}
400
+ Service : {active_service} ({active_service_module}) | multi (map-theo-platform, chốt khi target có platform) | single-service
388
401
  Svc Root : {service_root} — đã nạp conventions + trace_dir từ config service | —
389
402
  Status : {FULL | PARTIAL — thiếu: CLAUDE.md / business-dict / core-entities | MINIMAL}
390
403
  ```
@@ -185,16 +185,28 @@ Nếu có section `services`:
185
185
  *(vd: `specs/user/create-account/USR01-create-account.md` **và** `specs/user/create-account/bdd/system/UC1.feature` đều → domain = `user`, prd_slug = `create-account`)*
186
186
  - Nếu `$ARGUMENTS` chứa một path, trích xuất segment domain sau `specs_dir`
187
187
 
188
- **2. Route tới service** nếu active domain khớp với một key trong `services`:
189
- - Override `paths.specs_dir` `services.{domain}.specs_dir` **chỉ khi `setup.spec_source` KHÔNG được đặt.** Khi `spec_source` ĐƯỢC đặt, MỌI BDD (web/app/**system**) artifact dùng chung liên team → để bước 4 route sang spec repo; KHÔNG pin theo service ở đây.
190
- - Override `paths.tech_docs_dir` `services.{domain}.tech_docs_dir` — **chỉ khi `setup.spec_source` KHÔNG được đặt.** Khi `spec_source` ĐƯỢC đặt, tech-design (API contract)artifact liên team và phải nằm trong spec repo dùng chung (xử lý ở bước 4), nên để bước 4 route `tech_docs_dir` KHÔNG pin theo service ở đây.
188
+ **1b. Phát hiện active platform** (chỉ cần khi service dạng map-theo-platform bước 2b):
189
+ - Đọc `@trace.platform` từ header của target `.feature` (`system` | `web` | `app`) Gate đã resolve target trước bước này.
190
+ - Nếu target không mang `@trace.platform` (vd targetPRD `.md`), thử suy từ segment `bdd/{platform}/` trong path target.
191
+ - Nếu vẫn không xác định được → `active_platform = null`.
192
+
193
+ **2. Route tới service** — nếu active domain khớp một key trong `services`. Giá trị `services.{domain}` có **hai dạng**; nhận dạng bằng việc có `path` trực tiếp hay không:
194
+
195
+ **2a. Dạng phẳng** — `services.{domain}` có **trực tiếp** `path`/`module` (một domain ↔ một service, mọi platform về cùng submodule). Route như cũ:
191
196
  - Lưu `active_service` = `services.{domain}.path`
192
197
  - Lưu `active_service_module` = `services.{domain}.module`
193
198
  - Nếu service có `module` riêng → dùng nó làm `active_module` (override `tech_stack.module`)
194
199
 
195
- **3. Fallback** — nếu không phát hiện được domain hoặc không service key khớp:
196
- - Giữ path mặc định từ Bước 1
197
- - Đặt `active_service = unresolved`
200
+ **2b. Dạng map-theo-platform** — `services.{domain}` **KHÔNG** `path` trực tiếp mà chứa các sub-key platform (`system` / `web` / `app`), mỗi cái là `{ path, module }` (một business-domain trải trên nhiều platform/submodule). Route theo `active_platform` (bước 1b):
201
+ - Nếu `active_platform` khớp một sub-key → `entry = services.{domain}.{active_platform}`; lưu `active_service = entry.path`, `active_service_module = entry.module` (→ `active_module`, override `tech_stack.module`).
202
+ - Nếu `active_platform = null` (chưa xác định platform, vd đang thao tác cấp PRD) → **KHÔNG** chốt một service; đặt `active_service = multi` và lưu `service_candidates = services.{domain}` (toàn map platform→{path,module}). Lệnh cần một service cụ thể (`/generate-code`, `/dev-*`, `/fix-bug`) luôn chạy trên target `.feature` có platform nên sẽ resolve được ở lần chạy đó; lệnh cấp PRD (`/generate-prd`, `/refine-prd`) không cần service cụ thể.
203
+ - Nếu `active_platform` xác định nhưng không có sub-key tương ứng → `active_service = unresolved` (xem Fallback) với lý do "domain `{domain}` chưa cấu hình platform `{active_platform}`".
204
+
205
+ *(Cả 2a/2b: override `paths.specs_dir`/`paths.tech_docs_dir` per-service CHỈ khi `setup.spec_source` KHÔNG được đặt. Khi `spec_source` ĐƯỢC đặt, MỌI BDD/tech-doc là artifact liên team → để bước 4 route sang spec repo; KHÔNG pin per-service ở đây.)*
206
+
207
+ **3. Fallback**:
208
+ - Không phát hiện được domain, hoặc domain không khớp key nào trong `services` → giữ path mặc định từ Bước 1, đặt `active_service = unresolved`.
209
+ - Domain khớp một map-theo-platform (2b) nhưng `active_platform` xác định mà thiếu sub-key tương ứng → `active_service = unresolved`, ghi lý do rõ để lệnh DỪNG báo lỗi cấu hình (không tự đoán platform).
198
210
 
199
211
  **4. Tự động override theo spec source** — nếu `setup.spec_source` được đặt VÀ path tương ứng chưa được set tường minh trong `paths:`:
200
212
  - Override `paths.specs_dir` → `{spec_source}/specs` — **luôn khi `spec_source` được đặt.** Mọi spec artifact (PRD, BDD, tech-docs, design-spec) nằm dưới gốc spec thống nhất trong spec repo dùng chung theo bố cục feature-package: `{spec_source}/specs/{domain}/{prd-slug}/`. Mọi umbrella (FE/App/BE) đều đọc từ đây. *(`specs/` theo service chỉ khi không có `spec_source`.)*
@@ -214,7 +226,7 @@ Nếu có section `services`:
214
226
 
215
227
  ## Bước 1.6 — [SERVICE CONVENTIONS] Nạp convention riêng của service (chế độ umbrella)
216
228
 
217
- *Bỏ qua hoàn toàn bước này nếu `active_service` là `"unresolved"` hoặc context ở chế độ single-service.*
229
+ *Bỏ qua hoàn toàn bước này nếu `active_service` là `"unresolved"` hoặc `"multi"` (chưa chốt một service — dạng map-theo-platform ở cấp PRD) hoặc context ở chế độ single-service.*
218
230
 
219
231
  Khi `active_service` đã được phân giải thành một path thật ở Bước 1.5 (vd: `user-service/`):
220
232
 
@@ -390,7 +402,8 @@ Ticket : {ticket_prefix}-
390
402
  Dict : {loaded — N canonical terms, M banned terms | missing}
391
403
  Entities : {loaded — EntityA, EntityB, EntityC | missing}
392
404
  Lessons : {loaded — N guardrails | chưa có}
393
- Service : {active_service} ({active_service_module}) | single-service
405
+ Platform : {active_platform: system | web | app | — nếu chưa xác định}
406
+ Service : {active_service} ({active_service_module}) | multi (map-theo-platform, chốt khi target có platform) | single-service
394
407
  Svc Root : {service_root} — đã nạp conventions + trace_dir từ config service | —
395
408
  Status : {FULL | PARTIAL — thiếu: CLAUDE.md / business-dict / core-entities | MINIMAL}
396
409
  ```
@@ -185,16 +185,28 @@ Nếu có section `services`:
185
185
  *(vd: `specs/user/create-account/USR01-create-account.md` **và** `specs/user/create-account/bdd/system/UC1.feature` đều → domain = `user`, prd_slug = `create-account`)*
186
186
  - Nếu `$ARGUMENTS` chứa một path, trích xuất segment domain sau `specs_dir`
187
187
 
188
- **2. Route tới service** nếu active domain khớp với một key trong `services`:
189
- - Override `paths.specs_dir` `services.{domain}.specs_dir` **chỉ khi `setup.spec_source` KHÔNG được đặt.** Khi `spec_source` ĐƯỢC đặt, MỌI BDD (web/app/**system**) artifact dùng chung liên team → để bước 4 route sang spec repo; KHÔNG pin theo service ở đây.
190
- - Override `paths.tech_docs_dir` `services.{domain}.tech_docs_dir` — **chỉ khi `setup.spec_source` KHÔNG được đặt.** Khi `spec_source` ĐƯỢC đặt, tech-design (API contract)artifact liên team và phải nằm trong spec repo dùng chung (xử lý ở bước 4), nên để bước 4 route `tech_docs_dir` KHÔNG pin theo service ở đây.
188
+ **1b. Phát hiện active platform** (chỉ cần khi service dạng map-theo-platform bước 2b):
189
+ - Đọc `@trace.platform` từ header của target `.feature` (`system` | `web` | `app`) Gate đã resolve target trước bước này.
190
+ - Nếu target không mang `@trace.platform` (vd targetPRD `.md`), thử suy từ segment `bdd/{platform}/` trong path target.
191
+ - Nếu vẫn không xác định được → `active_platform = null`.
192
+
193
+ **2. Route tới service** — nếu active domain khớp một key trong `services`. Giá trị `services.{domain}` có **hai dạng**; nhận dạng bằng việc có `path` trực tiếp hay không:
194
+
195
+ **2a. Dạng phẳng** — `services.{domain}` có **trực tiếp** `path`/`module` (một domain ↔ một service, mọi platform về cùng submodule). Route như cũ:
191
196
  - Lưu `active_service` = `services.{domain}.path`
192
197
  - Lưu `active_service_module` = `services.{domain}.module`
193
198
  - Nếu service có `module` riêng → dùng nó làm `active_module` (override `tech_stack.module`)
194
199
 
195
- **3. Fallback** — nếu không phát hiện được domain hoặc không service key khớp:
196
- - Giữ path mặc định từ Bước 1
197
- - Đặt `active_service = unresolved`
200
+ **2b. Dạng map-theo-platform** — `services.{domain}` **KHÔNG** `path` trực tiếp mà chứa các sub-key platform (`system` / `web` / `app`), mỗi cái là `{ path, module }` (một business-domain trải trên nhiều platform/submodule). Route theo `active_platform` (bước 1b):
201
+ - Nếu `active_platform` khớp một sub-key → `entry = services.{domain}.{active_platform}`; lưu `active_service = entry.path`, `active_service_module = entry.module` (→ `active_module`, override `tech_stack.module`).
202
+ - Nếu `active_platform = null` (chưa xác định platform, vd đang thao tác cấp PRD) → **KHÔNG** chốt một service; đặt `active_service = multi` và lưu `service_candidates = services.{domain}` (toàn map platform→{path,module}). Lệnh cần một service cụ thể (`/generate-code`, `/dev-*`, `/fix-bug`) luôn chạy trên target `.feature` có platform nên sẽ resolve được ở lần chạy đó; lệnh cấp PRD (`/generate-prd`, `/refine-prd`) không cần service cụ thể.
203
+ - Nếu `active_platform` xác định nhưng không có sub-key tương ứng → `active_service = unresolved` (xem Fallback) với lý do "domain `{domain}` chưa cấu hình platform `{active_platform}`".
204
+
205
+ *(Cả 2a/2b: override `paths.specs_dir`/`paths.tech_docs_dir` per-service CHỈ khi `setup.spec_source` KHÔNG được đặt. Khi `spec_source` ĐƯỢC đặt, MỌI BDD/tech-doc là artifact liên team → để bước 4 route sang spec repo; KHÔNG pin per-service ở đây.)*
206
+
207
+ **3. Fallback**:
208
+ - Không phát hiện được domain, hoặc domain không khớp key nào trong `services` → giữ path mặc định từ Bước 1, đặt `active_service = unresolved`.
209
+ - Domain khớp một map-theo-platform (2b) nhưng `active_platform` xác định mà thiếu sub-key tương ứng → `active_service = unresolved`, ghi lý do rõ để lệnh DỪNG báo lỗi cấu hình (không tự đoán platform).
198
210
 
199
211
  **4. Tự động override theo spec source** — nếu `setup.spec_source` được đặt VÀ path tương ứng chưa được set tường minh trong `paths:`:
200
212
  - Override `paths.specs_dir` → `{spec_source}/specs` — **luôn khi `spec_source` được đặt.** Mọi spec artifact (PRD, BDD, tech-docs, design-spec) nằm dưới gốc spec thống nhất trong spec repo dùng chung theo bố cục feature-package: `{spec_source}/specs/{domain}/{prd-slug}/`. Mọi umbrella (FE/App/BE) đều đọc từ đây. *(`specs/` theo service chỉ khi không có `spec_source`.)*
@@ -214,7 +226,7 @@ Nếu có section `services`:
214
226
 
215
227
  ## Bước 1.6 — [SERVICE CONVENTIONS] Nạp convention riêng của service (chế độ umbrella)
216
228
 
217
- *Bỏ qua hoàn toàn bước này nếu `active_service` là `"unresolved"` hoặc context ở chế độ single-service.*
229
+ *Bỏ qua hoàn toàn bước này nếu `active_service` là `"unresolved"` hoặc `"multi"` (chưa chốt một service — dạng map-theo-platform ở cấp PRD) hoặc context ở chế độ single-service.*
218
230
 
219
231
  Khi `active_service` đã được phân giải thành một path thật ở Bước 1.5 (vd: `user-service/`):
220
232
 
@@ -390,7 +402,8 @@ Ticket : {ticket_prefix}-
390
402
  Dict : {loaded — N canonical terms, M banned terms | missing}
391
403
  Entities : {loaded — EntityA, EntityB, EntityC | missing}
392
404
  Lessons : {loaded — N guardrails | chưa có}
393
- Service : {active_service} ({active_service_module}) | single-service
405
+ Platform : {active_platform: system | web | app | — nếu chưa xác định}
406
+ Service : {active_service} ({active_service_module}) | multi (map-theo-platform, chốt khi target có platform) | single-service
394
407
  Svc Root : {service_root} — đã nạp conventions + trace_dir từ config service | —
395
408
  Status : {FULL | PARTIAL — thiếu: CLAUDE.md / business-dict / core-entities | MINIMAL}
396
409
  ```
@@ -181,16 +181,28 @@ Nếu có section `services`:
181
181
  *(vd: `specs/user/create-account/USR01-create-account.md` **và** `specs/user/create-account/bdd/system/UC1.feature` đều → domain = `user`, prd_slug = `create-account`)*
182
182
  - Nếu `$ARGUMENTS` chứa một path, trích xuất segment domain sau `specs_dir`
183
183
 
184
- **2. Route tới service** nếu active domain khớp với một key trong `services`:
185
- - Override `paths.specs_dir` `services.{domain}.specs_dir` **chỉ khi `setup.spec_source` KHÔNG được đặt.** Khi `spec_source` ĐƯỢC đặt, MỌI BDD (web/app/**system**) artifact dùng chung liên team → để bước 4 route sang spec repo; KHÔNG pin theo service ở đây.
186
- - Override `paths.tech_docs_dir` `services.{domain}.tech_docs_dir` — **chỉ khi `setup.spec_source` KHÔNG được đặt.** Khi `spec_source` ĐƯỢC đặt, tech-design (API contract)artifact liên team và phải nằm trong spec repo dùng chung (xử lý ở bước 4), nên để bước 4 route `tech_docs_dir` KHÔNG pin theo service ở đây.
184
+ **1b. Phát hiện active platform** (chỉ cần khi service dạng map-theo-platform bước 2b):
185
+ - Đọc `@trace.platform` từ header của target `.feature` (`system` | `web` | `app`) Gate đã resolve target trước bước này.
186
+ - Nếu target không mang `@trace.platform` (vd targetPRD `.md`), thử suy từ segment `bdd/{platform}/` trong path target.
187
+ - Nếu vẫn không xác định được → `active_platform = null`.
188
+
189
+ **2. Route tới service** — nếu active domain khớp một key trong `services`. Giá trị `services.{domain}` có **hai dạng**; nhận dạng bằng việc có `path` trực tiếp hay không:
190
+
191
+ **2a. Dạng phẳng** — `services.{domain}` có **trực tiếp** `path`/`module` (một domain ↔ một service, mọi platform về cùng submodule). Route như cũ:
187
192
  - Lưu `active_service` = `services.{domain}.path`
188
193
  - Lưu `active_service_module` = `services.{domain}.module`
189
194
  - Nếu service có `module` riêng → dùng nó làm `active_module` (override `tech_stack.module`)
190
195
 
191
- **3. Fallback** — nếu không phát hiện được domain hoặc không service key khớp:
192
- - Giữ path mặc định từ Bước 1
193
- - Đặt `active_service = unresolved`
196
+ **2b. Dạng map-theo-platform** — `services.{domain}` **KHÔNG** `path` trực tiếp mà chứa các sub-key platform (`system` / `web` / `app`), mỗi cái là `{ path, module }` (một business-domain trải trên nhiều platform/submodule). Route theo `active_platform` (bước 1b):
197
+ - Nếu `active_platform` khớp một sub-key → `entry = services.{domain}.{active_platform}`; lưu `active_service = entry.path`, `active_service_module = entry.module` (→ `active_module`, override `tech_stack.module`).
198
+ - Nếu `active_platform = null` (chưa xác định platform, vd đang thao tác cấp PRD) → **KHÔNG** chốt một service; đặt `active_service = multi` và lưu `service_candidates = services.{domain}` (toàn map platform→{path,module}). Lệnh cần một service cụ thể (`/generate-code`, `/dev-*`, `/fix-bug`) luôn chạy trên target `.feature` có platform nên sẽ resolve được ở lần chạy đó; lệnh cấp PRD (`/generate-prd`, `/refine-prd`) không cần service cụ thể.
199
+ - Nếu `active_platform` xác định nhưng không có sub-key tương ứng → `active_service = unresolved` (xem Fallback) với lý do "domain `{domain}` chưa cấu hình platform `{active_platform}`".
200
+
201
+ *(Cả 2a/2b: override `paths.specs_dir`/`paths.tech_docs_dir` per-service CHỈ khi `setup.spec_source` KHÔNG được đặt. Khi `spec_source` ĐƯỢC đặt, MỌI BDD/tech-doc là artifact liên team → để bước 4 route sang spec repo; KHÔNG pin per-service ở đây.)*
202
+
203
+ **3. Fallback**:
204
+ - Không phát hiện được domain, hoặc domain không khớp key nào trong `services` → giữ path mặc định từ Bước 1, đặt `active_service = unresolved`.
205
+ - Domain khớp một map-theo-platform (2b) nhưng `active_platform` xác định mà thiếu sub-key tương ứng → `active_service = unresolved`, ghi lý do rõ để lệnh DỪNG báo lỗi cấu hình (không tự đoán platform).
194
206
 
195
207
  **4. Tự động override theo spec source** — nếu `setup.spec_source` được đặt VÀ path tương ứng chưa được set tường minh trong `paths:`:
196
208
  - Override `paths.specs_dir` → `{spec_source}/specs` — **luôn khi `spec_source` được đặt.** Mọi spec artifact (PRD, BDD, tech-docs, design-spec) nằm dưới gốc spec thống nhất trong spec repo dùng chung theo bố cục feature-package: `{spec_source}/specs/{domain}/{prd-slug}/`. Mọi umbrella (FE/App/BE) đều đọc từ đây. *(`specs/` theo service chỉ khi không có `spec_source`.)*
@@ -210,7 +222,7 @@ Nếu có section `services`:
210
222
 
211
223
  ## Bước 1.6 — [SERVICE CONVENTIONS] Nạp convention riêng của service (chế độ umbrella)
212
224
 
213
- *Bỏ qua hoàn toàn bước này nếu `active_service` là `"unresolved"` hoặc context ở chế độ single-service.*
225
+ *Bỏ qua hoàn toàn bước này nếu `active_service` là `"unresolved"` hoặc `"multi"` (chưa chốt một service — dạng map-theo-platform ở cấp PRD) hoặc context ở chế độ single-service.*
214
226
 
215
227
  Khi `active_service` đã được phân giải thành một path thật ở Bước 1.5 (vd: `user-service/`):
216
228
 
@@ -386,7 +398,8 @@ Ticket : {ticket_prefix}-
386
398
  Dict : {loaded — N canonical terms, M banned terms | missing}
387
399
  Entities : {loaded — EntityA, EntityB, EntityC | missing}
388
400
  Lessons : {loaded — N guardrails | chưa có}
389
- Service : {active_service} ({active_service_module}) | single-service
401
+ Platform : {active_platform: system | web | app | — nếu chưa xác định}
402
+ Service : {active_service} ({active_service_module}) | multi (map-theo-platform, chốt khi target có platform) | single-service
390
403
  Svc Root : {service_root} — đã nạp conventions + trace_dir từ config service | —
391
404
  Status : {FULL | PARTIAL — thiếu: CLAUDE.md / business-dict / core-entities | MINIMAL}
392
405
  ```
@@ -179,16 +179,28 @@ Nếu có section `services`:
179
179
  *(vd: `specs/user/create-account/USR01-create-account.md` **và** `specs/user/create-account/bdd/system/UC1.feature` đều → domain = `user`, prd_slug = `create-account`)*
180
180
  - Nếu `$ARGUMENTS` chứa một path, trích xuất segment domain sau `specs_dir`
181
181
 
182
- **2. Route tới service** nếu active domain khớp với một key trong `services`:
183
- - Override `paths.specs_dir` `services.{domain}.specs_dir` **chỉ khi `setup.spec_source` KHÔNG được đặt.** Khi `spec_source` ĐƯỢC đặt, MỌI BDD (web/app/**system**) artifact dùng chung liên team → để bước 4 route sang spec repo; KHÔNG pin theo service ở đây.
184
- - Override `paths.tech_docs_dir` `services.{domain}.tech_docs_dir` — **chỉ khi `setup.spec_source` KHÔNG được đặt.** Khi `spec_source` ĐƯỢC đặt, tech-design (API contract)artifact liên team và phải nằm trong spec repo dùng chung (xử lý ở bước 4), nên để bước 4 route `tech_docs_dir` KHÔNG pin theo service ở đây.
182
+ **1b. Phát hiện active platform** (chỉ cần khi service dạng map-theo-platform bước 2b):
183
+ - Đọc `@trace.platform` từ header của target `.feature` (`system` | `web` | `app`) Gate đã resolve target trước bước này.
184
+ - Nếu target không mang `@trace.platform` (vd targetPRD `.md`), thử suy từ segment `bdd/{platform}/` trong path target.
185
+ - Nếu vẫn không xác định được → `active_platform = null`.
186
+
187
+ **2. Route tới service** — nếu active domain khớp một key trong `services`. Giá trị `services.{domain}` có **hai dạng**; nhận dạng bằng việc có `path` trực tiếp hay không:
188
+
189
+ **2a. Dạng phẳng** — `services.{domain}` có **trực tiếp** `path`/`module` (một domain ↔ một service, mọi platform về cùng submodule). Route như cũ:
185
190
  - Lưu `active_service` = `services.{domain}.path`
186
191
  - Lưu `active_service_module` = `services.{domain}.module`
187
192
  - Nếu service có `module` riêng → dùng nó làm `active_module` (override `tech_stack.module`)
188
193
 
189
- **3. Fallback** — nếu không phát hiện được domain hoặc không service key khớp:
190
- - Giữ path mặc định từ Bước 1
191
- - Đặt `active_service = unresolved`
194
+ **2b. Dạng map-theo-platform** — `services.{domain}` **KHÔNG** `path` trực tiếp mà chứa các sub-key platform (`system` / `web` / `app`), mỗi cái là `{ path, module }` (một business-domain trải trên nhiều platform/submodule). Route theo `active_platform` (bước 1b):
195
+ - Nếu `active_platform` khớp một sub-key → `entry = services.{domain}.{active_platform}`; lưu `active_service = entry.path`, `active_service_module = entry.module` (→ `active_module`, override `tech_stack.module`).
196
+ - Nếu `active_platform = null` (chưa xác định platform, vd đang thao tác cấp PRD) → **KHÔNG** chốt một service; đặt `active_service = multi` và lưu `service_candidates = services.{domain}` (toàn map platform→{path,module}). Lệnh cần một service cụ thể (`/generate-code`, `/dev-*`, `/fix-bug`) luôn chạy trên target `.feature` có platform nên sẽ resolve được ở lần chạy đó; lệnh cấp PRD (`/generate-prd`, `/refine-prd`) không cần service cụ thể.
197
+ - Nếu `active_platform` xác định nhưng không có sub-key tương ứng → `active_service = unresolved` (xem Fallback) với lý do "domain `{domain}` chưa cấu hình platform `{active_platform}`".
198
+
199
+ *(Cả 2a/2b: override `paths.specs_dir`/`paths.tech_docs_dir` per-service CHỈ khi `setup.spec_source` KHÔNG được đặt. Khi `spec_source` ĐƯỢC đặt, MỌI BDD/tech-doc là artifact liên team → để bước 4 route sang spec repo; KHÔNG pin per-service ở đây.)*
200
+
201
+ **3. Fallback**:
202
+ - Không phát hiện được domain, hoặc domain không khớp key nào trong `services` → giữ path mặc định từ Bước 1, đặt `active_service = unresolved`.
203
+ - Domain khớp một map-theo-platform (2b) nhưng `active_platform` xác định mà thiếu sub-key tương ứng → `active_service = unresolved`, ghi lý do rõ để lệnh DỪNG báo lỗi cấu hình (không tự đoán platform).
192
204
 
193
205
  **4. Tự động override theo spec source** — nếu `setup.spec_source` được đặt VÀ path tương ứng chưa được set tường minh trong `paths:`:
194
206
  - Override `paths.specs_dir` → `{spec_source}/specs` — **luôn khi `spec_source` được đặt.** Mọi spec artifact (PRD, BDD, tech-docs, design-spec) nằm dưới gốc spec thống nhất trong spec repo dùng chung theo bố cục feature-package: `{spec_source}/specs/{domain}/{prd-slug}/`. Mọi umbrella (FE/App/BE) đều đọc từ đây. *(`specs/` theo service chỉ khi không có `spec_source`.)*
@@ -208,7 +220,7 @@ Nếu có section `services`:
208
220
 
209
221
  ## Bước 1.6 — [SERVICE CONVENTIONS] Nạp convention riêng của service (chế độ umbrella)
210
222
 
211
- *Bỏ qua hoàn toàn bước này nếu `active_service` là `"unresolved"` hoặc context ở chế độ single-service.*
223
+ *Bỏ qua hoàn toàn bước này nếu `active_service` là `"unresolved"` hoặc `"multi"` (chưa chốt một service — dạng map-theo-platform ở cấp PRD) hoặc context ở chế độ single-service.*
212
224
 
213
225
  Khi `active_service` đã được phân giải thành một path thật ở Bước 1.5 (vd: `user-service/`):
214
226
 
@@ -384,7 +396,8 @@ Ticket : {ticket_prefix}-
384
396
  Dict : {loaded — N canonical terms, M banned terms | missing}
385
397
  Entities : {loaded — EntityA, EntityB, EntityC | missing}
386
398
  Lessons : {loaded — N guardrails | chưa có}
387
- Service : {active_service} ({active_service_module}) | single-service
399
+ Platform : {active_platform: system | web | app | — nếu chưa xác định}
400
+ Service : {active_service} ({active_service_module}) | multi (map-theo-platform, chốt khi target có platform) | single-service
388
401
  Svc Root : {service_root} — đã nạp conventions + trace_dir từ config service | —
389
402
  Status : {FULL | PARTIAL — thiếu: CLAUDE.md / business-dict / core-entities | MINIMAL}
390
403
  ```
@@ -177,16 +177,28 @@ Nếu có section `services`:
177
177
  *(vd: `specs/user/create-account/USR01-create-account.md` **và** `specs/user/create-account/bdd/system/UC1.feature` đều → domain = `user`, prd_slug = `create-account`)*
178
178
  - Nếu `$ARGUMENTS` chứa một path, trích xuất segment domain sau `specs_dir`
179
179
 
180
- **2. Route tới service** nếu active domain khớp với một key trong `services`:
181
- - Override `paths.specs_dir` `services.{domain}.specs_dir` **chỉ khi `setup.spec_source` KHÔNG được đặt.** Khi `spec_source` ĐƯỢC đặt, MỌI BDD (web/app/**system**) artifact dùng chung liên team → để bước 4 route sang spec repo; KHÔNG pin theo service ở đây.
182
- - Override `paths.tech_docs_dir` `services.{domain}.tech_docs_dir` — **chỉ khi `setup.spec_source` KHÔNG được đặt.** Khi `spec_source` ĐƯỢC đặt, tech-design (API contract)artifact liên team và phải nằm trong spec repo dùng chung (xử lý ở bước 4), nên để bước 4 route `tech_docs_dir` KHÔNG pin theo service ở đây.
180
+ **1b. Phát hiện active platform** (chỉ cần khi service dạng map-theo-platform bước 2b):
181
+ - Đọc `@trace.platform` từ header của target `.feature` (`system` | `web` | `app`) Gate đã resolve target trước bước này.
182
+ - Nếu target không mang `@trace.platform` (vd targetPRD `.md`), thử suy từ segment `bdd/{platform}/` trong path target.
183
+ - Nếu vẫn không xác định được → `active_platform = null`.
184
+
185
+ **2. Route tới service** — nếu active domain khớp một key trong `services`. Giá trị `services.{domain}` có **hai dạng**; nhận dạng bằng việc có `path` trực tiếp hay không:
186
+
187
+ **2a. Dạng phẳng** — `services.{domain}` có **trực tiếp** `path`/`module` (một domain ↔ một service, mọi platform về cùng submodule). Route như cũ:
183
188
  - Lưu `active_service` = `services.{domain}.path`
184
189
  - Lưu `active_service_module` = `services.{domain}.module`
185
190
  - Nếu service có `module` riêng → dùng nó làm `active_module` (override `tech_stack.module`)
186
191
 
187
- **3. Fallback** — nếu không phát hiện được domain hoặc không service key khớp:
188
- - Giữ path mặc định từ Bước 1
189
- - Đặt `active_service = unresolved`
192
+ **2b. Dạng map-theo-platform** — `services.{domain}` **KHÔNG** `path` trực tiếp mà chứa các sub-key platform (`system` / `web` / `app`), mỗi cái là `{ path, module }` (một business-domain trải trên nhiều platform/submodule). Route theo `active_platform` (bước 1b):
193
+ - Nếu `active_platform` khớp một sub-key → `entry = services.{domain}.{active_platform}`; lưu `active_service = entry.path`, `active_service_module = entry.module` (→ `active_module`, override `tech_stack.module`).
194
+ - Nếu `active_platform = null` (chưa xác định platform, vd đang thao tác cấp PRD) → **KHÔNG** chốt một service; đặt `active_service = multi` và lưu `service_candidates = services.{domain}` (toàn map platform→{path,module}). Lệnh cần một service cụ thể (`/generate-code`, `/dev-*`, `/fix-bug`) luôn chạy trên target `.feature` có platform nên sẽ resolve được ở lần chạy đó; lệnh cấp PRD (`/generate-prd`, `/refine-prd`) không cần service cụ thể.
195
+ - Nếu `active_platform` xác định nhưng không có sub-key tương ứng → `active_service = unresolved` (xem Fallback) với lý do "domain `{domain}` chưa cấu hình platform `{active_platform}`".
196
+
197
+ *(Cả 2a/2b: override `paths.specs_dir`/`paths.tech_docs_dir` per-service CHỈ khi `setup.spec_source` KHÔNG được đặt. Khi `spec_source` ĐƯỢC đặt, MỌI BDD/tech-doc là artifact liên team → để bước 4 route sang spec repo; KHÔNG pin per-service ở đây.)*
198
+
199
+ **3. Fallback**:
200
+ - Không phát hiện được domain, hoặc domain không khớp key nào trong `services` → giữ path mặc định từ Bước 1, đặt `active_service = unresolved`.
201
+ - Domain khớp một map-theo-platform (2b) nhưng `active_platform` xác định mà thiếu sub-key tương ứng → `active_service = unresolved`, ghi lý do rõ để lệnh DỪNG báo lỗi cấu hình (không tự đoán platform).
190
202
 
191
203
  **4. Tự động override theo spec source** — nếu `setup.spec_source` được đặt VÀ path tương ứng chưa được set tường minh trong `paths:`:
192
204
  - Override `paths.specs_dir` → `{spec_source}/specs` — **luôn khi `spec_source` được đặt.** Mọi spec artifact (PRD, BDD, tech-docs, design-spec) nằm dưới gốc spec thống nhất trong spec repo dùng chung theo bố cục feature-package: `{spec_source}/specs/{domain}/{prd-slug}/`. Mọi umbrella (FE/App/BE) đều đọc từ đây. *(`specs/` theo service chỉ khi không có `spec_source`.)*
@@ -206,7 +218,7 @@ Nếu có section `services`:
206
218
 
207
219
  ## Bước 1.6 — [SERVICE CONVENTIONS] Nạp convention riêng của service (chế độ umbrella)
208
220
 
209
- *Bỏ qua hoàn toàn bước này nếu `active_service` là `"unresolved"` hoặc context ở chế độ single-service.*
221
+ *Bỏ qua hoàn toàn bước này nếu `active_service` là `"unresolved"` hoặc `"multi"` (chưa chốt một service — dạng map-theo-platform ở cấp PRD) hoặc context ở chế độ single-service.*
210
222
 
211
223
  Khi `active_service` đã được phân giải thành một path thật ở Bước 1.5 (vd: `user-service/`):
212
224
 
@@ -382,7 +394,8 @@ Ticket : {ticket_prefix}-
382
394
  Dict : {loaded — N canonical terms, M banned terms | missing}
383
395
  Entities : {loaded — EntityA, EntityB, EntityC | missing}
384
396
  Lessons : {loaded — N guardrails | chưa có}
385
- Service : {active_service} ({active_service_module}) | single-service
397
+ Platform : {active_platform: system | web | app | — nếu chưa xác định}
398
+ Service : {active_service} ({active_service_module}) | multi (map-theo-platform, chốt khi target có platform) | single-service
386
399
  Svc Root : {service_root} — đã nạp conventions + trace_dir từ config service | —
387
400
  Status : {FULL | PARTIAL — thiếu: CLAUDE.md / business-dict / core-entities | MINIMAL}
388
401
  ```
@@ -599,14 +612,15 @@ Từ vựng step của System BDD (luôn dùng — bất kể từ vựng FE/App
599
612
  *Bỏ qua section này nếu đang chạy spec repo mode.*
600
613
 
601
614
  Routing service là **domain-keyed** và **context-loader (Bước 1.5) đã phân giải sẵn** từ `@trace.domain`/Domain của PRD — KHÔNG re-resolve ở đây, chỉ dùng lại các biến đã set:
602
- - `active_service` = `services.{domain}.path` (path submodule, vd `user-service/`) — hoặc `"unresolved"` nếu domain không khớp entry nào, hoặc bỏ trống ở single-service.
603
- - `active_module` = module của service (`services.{domain}.module`, đã override `tech_stack.module` ở Bước 1.5) — dùng cho từ vựng bên dưới.
615
+ - `active_service` = path submodule đã phân giải (dạng phẳng), hoặc `"multi"` (dạng map-theo-platform ở cấp PRD chưa chốt 1 platform), hoặc `"unresolved"` nếu domain không khớp entry nào, hoặc bỏ trống ở single-service.
616
+ - `active_module` = module của service (`services.{domain}.module`, đã override `tech_stack.module` ở Bước 1.5) — dùng cho từ vựng bên dưới. Với `"multi"`, lấy module theo từng platform từ `service_candidates` khi sinh `bdd/{platform}/`.
604
617
 
605
618
  Chỉ cần kiểm tra trạng thái đã phân giải:
606
619
 
607
620
  | Trạng thái (từ context-loader) | Hành động |
608
621
  |---|---|
609
622
  | `active_service` đã phân giải thành path service | Tiếp tục với `active_module` đã set. |
623
+ | `active_service = "multi"` (domain map-theo-platform, target PRD chưa gắn 1 platform) | Tiếp tục — BDD là artifact liên team, platform-split. Sinh `bdd/{platform}/` cho các platform có trong `service_candidates`; từ vựng lấy theo `service_candidates.{platform}.module`. KHÔNG cần chốt 1 service. |
610
624
  | `active_service = "unresolved"` (có section `services` nhưng domain PRD không khớp entry nào) | **DỪNG**, báo: "Domain `{domain}` của PRD không khớp service nào trong `services:` của project-context.yaml — bổ sung mapping rồi chạy lại." (Không đoán/hỏi tay — domain là khoá định danh, lệch là lỗi cấu hình cần sửa ở SoT.) |
611
625
  | Single-service (không có section `services`) | `active_module = tech_stack.module` (đã set ở Bước 6.5). Tiếp tục. |
612
626
 
@@ -200,14 +200,15 @@ Từ vựng step của System BDD (luôn dùng — bất kể từ vựng FE/App
200
200
  *Bỏ qua section này nếu đang chạy spec repo mode.*
201
201
 
202
202
  Routing service là **domain-keyed** và **context-loader (Bước 1.5) đã phân giải sẵn** từ `@trace.domain`/Domain của PRD — KHÔNG re-resolve ở đây, chỉ dùng lại các biến đã set:
203
- - `active_service` = `services.{domain}.path` (path submodule, vd `user-service/`) — hoặc `"unresolved"` nếu domain không khớp entry nào, hoặc bỏ trống ở single-service.
204
- - `active_module` = module của service (`services.{domain}.module`, đã override `tech_stack.module` ở Bước 1.5) — dùng cho từ vựng bên dưới.
203
+ - `active_service` = path submodule đã phân giải (dạng phẳng), hoặc `"multi"` (dạng map-theo-platform ở cấp PRD chưa chốt 1 platform), hoặc `"unresolved"` nếu domain không khớp entry nào, hoặc bỏ trống ở single-service.
204
+ - `active_module` = module của service (`services.{domain}.module`, đã override `tech_stack.module` ở Bước 1.5) — dùng cho từ vựng bên dưới. Với `"multi"`, lấy module theo từng platform từ `service_candidates` khi sinh `bdd/{platform}/`.
205
205
 
206
206
  Chỉ cần kiểm tra trạng thái đã phân giải:
207
207
 
208
208
  | Trạng thái (từ context-loader) | Hành động |
209
209
  |---|---|
210
210
  | `active_service` đã phân giải thành path service | Tiếp tục với `active_module` đã set. |
211
+ | `active_service = "multi"` (domain map-theo-platform, target PRD chưa gắn 1 platform) | Tiếp tục — BDD là artifact liên team, platform-split. Sinh `bdd/{platform}/` cho các platform có trong `service_candidates`; từ vựng lấy theo `service_candidates.{platform}.module`. KHÔNG cần chốt 1 service. |
211
212
  | `active_service = "unresolved"` (có section `services` nhưng domain PRD không khớp entry nào) | **DỪNG**, báo: "Domain `{domain}` của PRD không khớp service nào trong `services:` của project-context.yaml — bổ sung mapping rồi chạy lại." (Không đoán/hỏi tay — domain là khoá định danh, lệch là lỗi cấu hình cần sửa ở SoT.) |
212
213
  | Single-service (không có section `services`) | `active_module = tech_stack.module` (đã set ở Bước 6.5). Tiếp tục. |
213
214