# /generate-code — Sinh Implementation Code

## Gate
{{include:steps/gate.md}}

*Lưu ý: Với lệnh này, target ở Bước 1 là một file `.feature` hoặc UC-ID. Nếu `$ARGUMENTS` là UC-ID, tìm file feature khớp bằng cách glob `{paths.specs_dir}/{domain}/*/bdd/**/{UC-ID}*.feature` (wildcard `*` cho prd-slug chưa biết, `**` đệ quy để phủ các thư mục con platform `web/`·`app/`·`system/`); lấy `domain` + `prd_slug` từ path khớp. Cũng kiểm tra `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{@trace.platform}.tsv` tìm drift (new vs drifted vs synced).*

## Context
{{include:steps/context-loader.md}}

---

## Scope Lock

Lệnh này giới hạn nghiêm ngặt trong **một file feature** được truyền qua `$ARGUMENTS`:

- File feature: `{path chính xác từ $ARGUMENTS}`
- UC: `{UC-ID}` (đọc từ `@trace.id` trong header file đó)

**KHÔNG đọc hay implement scenario từ bất kỳ file `.feature` nào khác** trong cùng folder domain, kể cả khi chúng dùng chung entity, khái niệm domain, hay tên service.

> **⚠️ Scope Lock CẤM implement UC khác — KHÔNG cấp phép XOÁ code UC khác.** Nhiều UC của cùng nghiệp vụ thường **dùng chung file code** (cùng Controller/Service). Khi một file như vậy đã tồn tại, **mọi member có sẵn là BẤT KHẢ XÂM PHẠM** — kể cả method mang `@trace.implements` của UC khác. Bảo toàn chúng là **YÊU CẦU BẮT BUỘC**, KHÔNG phải vi phạm scope. Bạn ĐƯỢC đọc các tag `@trace.implements` có sẵn trong file code (để biết member nào thuộc UC khác mà giữ) — điều bị cấm chỉ là **implement scenario** của `.feature` khác. Không bao giờ tái tạo file chung "chỉ gồm scenario của UC này" — đó là cách xoá nhầm nghiệp vụ UC trước.

---

## Context Load (bổ sung)

Đọc:
1. Chỉ file `.feature` đã giới hạn scope
2. Tech-doc gộp của PRD tại `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md` (nếu tồn tại) — **tra §10 UC Coverage làm mục lục** để định vị scenario/section của UC đang sinh; **endpoint liên quan = §4.1 entries mà §5 lane của UC này gọi tới** (đừng lấy endpoint/section của UC khác). Từ đó đọc §4 API, §4.5 client, §5 flow của đúng UC này
3. CLAUDE.md §architecture + §coding_standards
4. **(chỉ FE/App)** Design Spec — nạp qua **Guard** bên dưới (gate approved/độ-tươi + sanity), là nguồn của màn hình, component inventory, và link Figma frame từng-màn.

---

## Guard — BDD & Design Spec đã sẵn sàng chưa *(cảnh báo MỀM — đồng bộ generate-bdd)*

**BDD (mọi platform) — DS1:** đọc `# @trace.status:` từ header `.feature` target.
- `approved` → tiếp tục. Khác `approved` → CHECKPOINT mềm:
  ```
  ⚠️  BDD {UC-ID} đang @trace.status: {status} (chưa duyệt). Code sinh từ BDD chưa chốt có thể phải làm lại.
     Vẫn sinh code bây giờ? (Y/N)
  ```
  Chỉ tiếp khi chọn Y.

**Design Spec (chỉ FE/App — `@trace.platform` = web/app) — DS2:** định vị `{paths.specs_dir}/{domain}/{prd-slug}/design-spec/{TICKET-ID}-design-spec-{@trace.platform}-{slug}.md`, đọc `| **Status** |` + `| **Built from PRD** |`:
- Không có file, HOẶC `Status ≠ approved`, HOẶC còn màn ❌ Missing, HOẶC `Built from PRD` ≠ `| **Version** |` PRD hiện tại (lỗi thời) → CHECKPOINT mềm (liệt kê lý do + "Vẫn dùng design-spec này? (Y/N)"). Nếu Y mà KHÔNG có design-spec → sinh UI từ BDD + Wireframe PRD (thiếu chi tiết visual).
- **Sanity-scan** (như generate-bdd bước 2.5): màn thiếu state loading/error/empty, AC-UI không testable, component `[NEW]`/`[TODO]` chưa chốt, còn ❌ Missing frame → cảnh báo trong cùng CHECKPOINT.
- `Status: approved` VÀ `Built from PRD` khớp PRD hiện tại VÀ sạch cờ đỏ → dùng làm nguồn màn hình / component / Figma.

**Tech-doc contract (chỉ backend/system) — DS3:** *áp dụng khi lần sinh này tạo code **backend** — `@trace.platform = system`, HOẶC chế độ default (không `--phase`) với `platform_type = backend`. Bỏ qua FE `--phase=ui`/`--phase=integration` (đã có nguồn shape riêng ở Phase Detection).*

Định vị tech-doc gộp `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md`, kiểm phần backend của UC này (§4.1 Endpoints / §4.2 Request-Response / §4.3 Error):
- **Brownfield** — nếu PRD Metadata `API Source: existing`: contract nằm ở Appendix "Existing API Contract" của PRD → nguồn hợp lệ, **bỏ qua DS3** (không cảnh báo).
- **Thiếu tech-doc, HOẶC thiếu §4 contract cho UC này** → CHECKPOINT chặn mềm:
  ```
  ⚠️  Sinh code BACKEND cho {UC-ID} mà chưa có contract chốt (thiếu tech-doc §4 API).
     Code BE không có contract sẽ phải TỰ CHẾ shape API/DTO/error → rủi ro bịa, rework nặng.
     Khuyến nghị: /generate-tech-docs {system .feature} → /review-tech-docs (approved) rồi sinh code.
     Vẫn sinh bây giờ? (Y/N)
  ```
  Chỉ tiếp khi Y. *(Khác FE: FE degrade êm để prototype qua mock; BE thì contract là sản phẩm chính → chặn mềm.)*
- **Có §4 nhưng `@trace.status = draft/in-review`, HOẶC §12 GAP Register còn 🔴 blocker `open` chạm UC này** → WARN (không chặn): "contract chưa chốt / còn {n} blocker-GAP open — có thể phải rework khi contract đổi."
- **Có §4 + `@trace.status: approved` + 0 blocker-GAP** → dùng §4 làm nguồn contract (shape DTO/endpoint/error lấy nguyên văn từ đây, KHÔNG tự chế).

---

## Phase Detection

Parse `$ARGUMENTS` tìm flag `--phase`:

| Flag | Ý nghĩa |
|---|---|
| `--phase=ui` | FE Phase 1 — sinh UI + layer mock API từ System BDD contract |
| `--phase=integration` | FE Phase 2 — thay mock adapter bằng lời gọi API thật từ tech docs |
| *(không có)* | Default — full implementation (BE hoặc full-stack không tách mock) |

**Nếu `--phase` được set — xác nhận platform:**
Đọc `@trace.platform` từ header file feature.
- Nếu `system` → cảnh báo: "Flag `--phase` không áp dụng cho system BDD (hướng BE). Tiếp tục với chế độ default." Coi như không có flag.
- Nếu `web` hoặc `app` → tiếp tục logic phase bên dưới.

**Nếu `--phase=ui`:**
Phân giải **nguồn shape của mock** (hybrid — ưu tiên contract thật, fallback về System BDD):
- **BE contract** — tech-doc gộp `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md`, phần backend của UC này (§4.1 Endpoints / §4.2 Request-Response / §4.3 Error). Nếu tồn tại, **shape** port/DTO của mock adapter (field request/response, type, error code) lấy từ đây → `mock_source = contract`. Chính xác nhất; không phải rework shape lúc integration.
- **System BDD** `{paths.specs_dir}/{domain}/{prd-slug}/bdd/system/{TICKET-ID}*.feature` — luôn nạp mệnh đề `Then` để lấy **behavior + giá trị fixture**. Nếu không có §4 API contract, shape cũng được infer từ đây → `mock_source = system-bdd`, và WARN:
  ```
  ⚠ Không tìm thấy §4 API contract — shape mock được infer chỉ từ System BDD.
    System BDD mô tả behavior, không phải full request/response shape — mock
    có thể khác API thật; dự kiến điều chỉnh ở --phase=integration.
    (Khuyến nghị: để BE publish {prd-slug}/tech-docs/{TICKET-ID}-tech-design.md (§4) trước để có mock chính xác.)
  ```
- Nếu System BDD cũng thiếu → cảnh báo "Không tìm thấy System BDD — layer mock sẽ dùng fixture placeholder." Tiếp tục.

Lưu `mock_source` (`contract` | `system-bdd`) cho các tag mock bên dưới.

**Rồi chạy Figma Dev Mode MCP Check bên dưới** trước khi sinh UI — link frame của Design
Spec là visual contract, và MCP local đọc chúng với độ trung thực cao hơn nhiều so với link web trần.

---

## Figma Dev Mode MCP Check *(chỉ sinh UI FE/App)*

*Chỉ chạy khi `platform` là `web`/`app` VÀ đang sinh UI (`--phase=ui`, hoặc chế độ default
cho feature FE/App). Bỏ qua hoàn toàn với BE / platform `system`.*

PO viết Design Spec từ **link web Figma** (read-only, giới hạn). Để codegen,
**Figma Dev Mode MCP server local** (tích hợp trong **app desktop** Figma) cho nhiều hơn
nhiều: layout chính xác, **variable/token** design, mapping component **Code Connect**,
selection context, và code snippet — những thứ một URL web đơn không trả về được.

**Step 1 — Phát hiện MCP local.** Kiểm tra xem Figma Dev Mode MCP server có kết nối không
(một Figma tool kiểu `get_design_context` / `get_code` sẵn có qua MCP).

**Step 2 — Nếu CHƯA kết nối → gợi ý dev bật nó, rồi chờ:**

```
🎨 Không phát hiện Figma Dev Mode MCP.
   Để có code FE chính xác (token, component, Code Connect thật), dùng server LOCAL:

   1. Mở app Figma DESKTOP (không phải browser)
   2. Mở file/frame của feature này
   3. Bật Dev Mode MCP server:
        Menu Figma → Preferences → "Enable Dev Mode MCP Server"
        (cần Dev hoặc Full seat; server chạy ở http://127.0.0.1:3845)
   4. Đảm bảo MCP server này đã được thêm vào config MCP của Claude Code
   5. Chọn frame của màn bạn đang implement, rồi tiếp tục

   Gõ C để tiếp tục khi đã bật, hoặc S để skip (fallback về link web + text spec).
```

- `C` → phát hiện lại; nếu giờ đã kết nối → tiếp tục dùng MCP local.
- `S` → tiếp tục ở **fallback mode**: chỉ dùng link frame web + text spec của Design Spec;
  thêm note ⚠️ trong report cuối rằng UI được sinh mà không có độ trung thực Figma local.

**Step 3 — Khi MCP local ĐÃ kết nối:** với mỗi màn đang implement, pull frame được chọn
qua Figma MCP và ground UI trên layout, variable, và mapping Code Connect trả về. Ưu tiên
component được map Code-Connect hơn là bịa markup; dùng tên token thật, không phải giá trị hardcode.

**Nếu `--phase=integration`:**
Phân giải design điều khiển adapter từ **tech-doc gộp của PRD** `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md`:
- **Mapping port→endpoint→DTO→error** (ưu tiên): §4.5.4 (API Integration Layer của platform này) — mỗi client method → endpoint có thật.
- **Nguồn endpoint/shape**: §4.1 Endpoints + §4.2 Request-Response + §4.3 Error của cùng doc.

Đọc `@trace.status` của doc. Nếu `draft` hoặc `in-review` → cảnh báo:
```
⚠ Tech design {TICKET-ID} (UC {UC-ID} / {platform}) đang {status}.
  Contract / mapping adapter còn có thể đổi.
  Tiếp tục — đảm bảo BE endpoint đã deploy hoặc confirm mapping thủ công.
```
Nếu doc **thiếu §4.5.4** (client integration chưa được vẽ cho platform này) → cảnh báo: "Chưa có §4.5.4 cho {platform} — fallback map trực tiếp từ §4.1 endpoint (mapping adapter được infer). Khuyến nghị: chạy `/generate-tech-docs {web|app .feature}` để bổ sung §4.5 trước."
Định vị mock adapter có sẵn từ lần chạy `--phase=ui` (tìm `{UC-ID}MockApiAdapter` trong `{paths.src_dir}/{domain}/`).
Nếu không tìm thấy → cảnh báo: "Không tìm thấy mock adapter — sinh real API adapter từ đầu dùng contract tech-doc."

---

## Read Trace State

Đọc `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{@trace.platform}.tsv` nếu tồn tại. Với mỗi scenario row, ghi nhận `status` hiện tại:

| Status | Ý nghĩa | Hành động trong lần chạy này |
|--------|---------|-------------------|
| `UNTRACKED` | `implemented_by == —` | Generate — scenario chưa có code |
| `DRIFT` | `spec_ver != gen_ver` | Sửa **tại chỗ đúng method** của scenario đó (Edit) — KHÔNG viết lại cả file (file chung sẽ mất method UC khác) |
| `OK` | đã implement + test | Skip trừ khi gen lại tường minh |
| `GAP` | đã implement, chưa test | Skip codegen — đã code rồi; chạy `/dev-gen-test` thay vì |

Dùng các status này để điền số **Scenarios** trong plan CHECKPOINT (`{X} new, {Y} drifted, {Z} synced-skip`).
Nếu `.tsv` không tồn tại → coi mọi scenario là `UNTRACKED`.

---

## Package Placement *(đặt code đúng convention — chống phân mảnh package)*

Trước khi định vị file, chốt **package đích** theo **Package Layout của CLAUDE.md §2** (context-loader đã trích: `code_base_package` + `package_strategy`):

- **Path code = `{code_base_package}.{layer}`** — vd `vn.edupia.segment.service`, `.repository`, `.facade`. `{layer}` theo thứ tự layer §2 (dto/entity/repository/service/facade/controller…).
- **CẤM đưa `{prd-slug}` / tên feature / `{UC-ID}` vào package** (đây là lỗi hay gặp: map bố cục spec `specs/{domain}/{prd-slug}/` thành package code → đẻ `…{service}.{feature}.{layer}`, nhân bản layer). Feature/UC **chỉ** phân biệt ở **TÊN CLASS**: `EventCollectionService`, `IdentityResolutionService` **cùng nằm trong** `…segment.service`, KHÔNG phải `…segment.eventcollection.service`.
- *(Chỉ khi §2 khai `package_strategy = by-feature` mới đặt sub-package theo feature — mặc định là **by-layer**.)*

**Quét module TÁI DÙNG trước khi tạo:** với mỗi layer, kiểm `{code_base_package}.{layer}` đã tồn tại trên disk chưa. Nếu đã có class phục vụ cùng entity/nghiệp vụ → **đặt member mới vào class đó (EXTEND)**, KHÔNG dựng cây layer / class song song. Chỉ tạo mới khi thực sự chưa có.

---

## Seam & Stub Ledger — nối/lấp chỗ chưa implement *(chống mồ côi)*

*Chạy TRƯỚC khi tạo bất kỳ chỗ giả lập nào. Đây là chỗ vá hai lỗi kinh điển khi gen từng BDD: (a) **hàm trắng mồ côi** — callee để trắng vì logic thuộc BDD khác, ghép luồng thì chạy vào no-op; (b) **hàm thật mồ côi** — BDD sau đẻ hàm mới thay vì lấp hàm trắng cũ, thành ra hàm thật không ai gọi.*

Có **hai loại chỗ chưa implement**, ghi chung vào **một sổ** — cột `kind` phân biệt:

| kind | Là gì | Ví dụ |
|---|---|---|
| `seam` | Gọi ra một **port cross-UC** do UC khác sở hữu | `A` gọi `ScoreAccumulationPort` (UC scoring sở hữu) |
| `stub` | Một **method/hàm trắng nội-feature** — callee mà logic thuộc BDD **khác của chính feature này** | `SegmentService.calculateSegment()` để trắng vì logic thuộc BDD2 |

Scope Lock cấm **implement scenario** của `.feature` khác — nhưng KHÔNG cấm **nối vào / lấp code đã tồn tại** (đọc `@trace.implements` sẵn có; xem Scope Lock). Đó là chìa khoá để không đẻ mồ côi.

### Sổ chung `_seams.tsv`

`{paths.trace_dir}/{domain}/{prd-slug}/_seams.tsv` — tạo file + header nếu chưa có, tab-separated:

```
kind    name    consumer_uc    owner_uc    artifact    binding    status    last_updated
```

- `kind` = `seam` | `stub`
- `name` = tên port (seam) | trách nhiệm/tên logic ngắn (stub) — "cái gì đang thiếu"
- `consumer_uc` = UC có call-site / để trắng
- `owner_uc` = UC sở hữu logic thật (`?` nếu tech-doc chưa nói rõ)
- `artifact` = `StubClass` (seam) | `ClassName#method` (stub) — nơi tìm ra hàng giả
- `binding` = cách stub được wire, vd bean/config (seam) | `—` (stub lấp tại chỗ, không qua binding)
- `status` = `PENDING` (chưa có hàng thật — bình thường) · `READY` (hàng thật đã có nhưng chưa nối/lấp — 🔴) · `RESOLVED` (đã nối/lấp xong)

*(Sổ sống cạnh TSV trace — cùng `trace_dir`, cùng luật ghi liên-repo khi có `spec_source`.)*

### Loại A — Seam (port ra ngoài, cross-UC)

Khi scenario của `{UC-ID}` gọi qua một **port do UC khác sở hữu** (vd `ScoreAccumulationPort`):

**A1. Xác định port + chủ sở hữu.** Từ tech-doc gộp — §5 flow lane của UC này (participant nào bị gọi tới), §6.2 Cross-Service Dependencies, §11 Out-of-Scope Reference — lấy tên port và **UC nào sở hữu hàng thật**. Tech-doc không nói rõ chủ → `owner_uc = ?`.

**A2. Ngó vào kho TRƯỚC khi dán giấy nợ.** Quét disk tìm một class **thật** implement port đó dưới `{code_base_package}` — class KHÔNG phải `*Stub*`/`*Mock*` và mang `@trace.implements` của UC khác:
- **CÓ hàng thật → nối thẳng, KHÔNG tạo stub.** Wire DI binding của `{UC-ID}` vào class thật (constructor injection / `@Primary` bean / config hiện có). Đây là **cấu hình wiring của chính UC này** → không vi phạm Scope Lock/EXTEND.
- **CHƯA có hàng thật → tạo stub, NHƯNG ghi nợ** (A3).

**A3. Ghi sổ nợ (chỉ khi phải tạo stub).**
- Tag class stub: `@trace.seam_pending={owner_uc | ?}` + `@trace.seam_port={PortName}`.
- Append 1 dòng: `kind=seam` · `name={PortName}` · `consumer_uc={UC-ID}` · `owner_uc={owner|?}` · `artifact={StubClass}` · `binding={cách wire}` · `status=PENDING`.

**A4. Khi CHÍNH `{UC-ID}` là chủ nợ** (lần gen này sinh **hàng thật** của một port UC khác đã stub trước đó):
- Sinh class thật bình thường (đúng layer).
- Đọc sổ: mọi dòng `kind=seam` `status=PENDING` có `owner_uc == {UC-ID}` (hoặc `owner_uc == ?` mà `name` khớp port) → đổi `status → READY`.
- **KHÔNG tự sửa binding của UC consumer** (file/scope UC khác — tránh clobber). IN cảnh báo nổi bật ở report cuối:
  ```
  🔌 SEAM READY — {port}: hàng thật {RealClass} vừa sinh, nhưng {consumer_uc} còn wire vào {stub_class}.
     Nối: trỏ binding của {consumer_uc} sang {RealClass} (xoá/thay stub), rồi build lại.
     (/validate-traces giữ cờ SEAM_UNWIRED tới khi nối xong.)
  ```

### Loại B — Stub (method/hàm trắng nội-feature) 🆕

Khi một scenario buộc gọi tới một callee mà **logic thuộc BDD khác của chính feature này** → KHÔNG để trắng vô hình. Làm hàm trắng thành **hiện hình + có sổ**:

**B1. Class thật GIỮ NGUYÊN TÊN** (vd `SegmentService` — KHÔNG đổi thành `StubSegmentService`; class là thật, chỉ **method** là placeholder).

**B2. Chữ ký suy từ CALL-SITE** — đúng cái caller cần (tên method, tham số, kiểu trả về). Thân method là placeholder rõ ràng: `throw new UnsupportedOperationException("stub: {trách nhiệm}")` (hoặc tương đương theo stack). **KHÔNG bịa logic** — chỉ dựng chữ ký.

**B3. Tag lên method trắng:**
```
@trace.stub={UC-ID}                (ai để trắng — consumer)
@trace.stub_owner={owner_uc | ?}   (BDD/UC sẽ điền logic thật)
@trace.stub_for={trách nhiệm ngắn} (để owner nhận ra đây là chỗ cần lấp)
```

**B4. Ghi sổ:** append 1 dòng: `kind=stub` · `name={trách nhiệm}` · `consumer_uc={UC-ID}` · `owner_uc={owner|?}` · `artifact={ClassName#method}` · `binding=—` · `status=PENDING`.

> *Phần lấp stub (khi BDD owner chạy) + cổng kiểm mồ côi nằm ở "Fill-before-create" và `/validate-traces` — xem các mục sau.*

> **Mẹo vận hành (né hẳn):** gen BDD **sở hữu logic TRƯỚC**, BDD **gọi SAU** — khi đó không bao giờ cần để trắng, không phát sinh nợ. Cơ chế trên chỉ để chịu được thứ tự ngược.

---

## File Scan

Trước khi sinh, xác định file nào cần cho các scenario của UC này — **path theo Package Placement ở trên**. Kiểm tra mỗi file đã tồn tại trên disk chưa.

Phân loại mỗi file:

| Status | Ý nghĩa | Hành động |
|--------|---------|--------|
| `CREATE` | File chưa tồn tại | Sinh file mới đầy đủ |
| `EXTEND` | File tồn tại, cần method mới | Chỉ thêm method mới — KHÔNG viết lại code có sẵn |
| `FILL` | File tồn tại, chứa **method stub trắng** mà `{UC-ID}` là chủ logic | **Lấp logic thật vào chính method đó** (Edit) — KHÔNG đẻ method song song (xem Fill-before-create) |
| `SKIP` | File tồn tại và đã phủ tất cả scenario của UC | Để nguyên |

> **Quy tắc EXTEND (phi-phá-huỷ — BẮT BUỘC ở mức thao tác):**
> 1. **Đọc lại file trên disk NGAY TRƯỚC khi ghi** (không dựa vào trí nhớ từ đầu phiên — file có thể đã đổi).
> 2. **CHỈ dùng thao tác chèn/sửa từng phần (Edit) để THÊM member mới.** **CẤM tuyệt đối ghi đè cả file (full Write)** cho file đã tồn tại — đây là nguyên nhân số 1 xoá nghiệp vụ UC trước.
> 3. Output PHẢI là **superset chặt** của nội dung cũ: **mọi** method, field, annotation, import, và `@trace.implements` cũ (kể cả của UC khác) **còn nguyên si**. Chỉ được **thêm**, không xoá/sửa member không thuộc `{UC-ID}` này.
> 4. Gắn `@trace.implements={UC-ID}-SC{N}` lên mỗi method **mới**.
>
> **Guard sau-ghi (lưới an toàn — chạy sau khi ghi mỗi file EXTEND):** đọc lại file vừa ghi, đối chiếu với bản trước khi sửa: **mọi `@trace.implements` và member cũ phải vẫn còn**. Nếu **mất bất kỳ member/tag cũ nào** (đặc biệt của UC khác) → **DỪNG NGAY, khôi phục file về bản cũ** (`git checkout -- {file}` nếu đã commit, hoặc hoàn tác edit), báo lỗi: *"EXTEND làm mất {member/tag} của {UC khác} — đã chặn clobber. Sửa lại theo add-only rồi chạy lại."* KHÔNG tiếp tục sinh các file sau khi chưa khôi phục.

> **Fill-before-create (chống hàm thật mồ côi — BẮT BUỘC trước khi tạo BẤT KỲ method mới nào):**
>
> Trước khi sinh một method mới cho scenario của `{UC-ID}`, phải kiểm tra: nó có phải là **logic thật cần lấp vào một stub đã tồn tại** không — thay vì đẻ một method song song mồ côi (nửa B của bug: BDD sau tạo hàm mới thay vì lấp hàm trắng cũ → hàm cũ được caller gọi mà rỗng, hàm mới không ai gọi).
>
> 1. **Quét chỗ chờ lấp** (hai nguồn):
>    - Sổ `_seams.tsv`: dòng `kind=stub` `status=PENDING` có `owner_uc == {UC-ID}` (hoặc `owner_uc == ?` mà `name`/trách nhiệm khớp scenario đang làm).
>    - Scan disk dưới `{code_base_package}`: method mang `@trace.stub_owner == {UC-ID}` (hoặc `?`) và `@trace.stub_for` khớp trách nhiệm.
> 2. **KHỚP → lấp TẠI CHỖ (Edit), KHÔNG tạo mới.** Ghi logic thật vào **đúng thân method stub đó**, **giữ nguyên chữ ký** (caller đang gọi vào đó — đổi chữ ký = gãy call-site). Gỡ 3 tag `@trace.stub*`, thay bằng `@trace.implements={UC-ID}-SC{N}`. **CẤM tạo method mới cùng trách nhiệm** — đó chính là hàm mồ côi.
> 3. **Cập nhật sổ:** dòng tương ứng `status → RESOLVED`, `owner_uc = {UC-ID}`, `last_updated`. *(Caller ở UC khác KHÔNG cần đổi — vẫn gọi đúng method cũ, giờ có logic; đây là điểm khác với seam: stub lấp tại chỗ, không cần rewire binding.)*
> 4. **Không khớp stub nào → tạo method mới bình thường** (theo EXTEND/CREATE ở trên).
>
> **Guard sau-lấp:** sau khi lấp, KHÔNG được còn method trắng cùng trách nhiệm, cũng KHÔNG được có method song song mới cùng trách nhiệm. Lỡ tạo song song → gộp về một, xoá method thừa (chỉ khi chắc chắn cùng trách nhiệm).
>
> *Nghi ngờ khớp mà không chắc (tên/chữ ký lệch nhiều) → KHÔNG tự ý xoá/gộp; giữ nguyên cả hai + để `/validate-traces` gắn cờ cho người soát.*

---

## CHECKPOINT — Code Generation Plan

Trước khi sinh code, hiện:

```
Code Generation Plan — {UC-ID}
──────────────────────────────────────────────────────
Feature  : {name}
Ticket   : {TICKET_ID nếu biết}
Domain   : {domain}
UC       : chỉ {UC-ID}  ← các file feature khác trong folder này KHÔNG được đọc
Tech     : {language} / {framework}
Package  : {code_base_package}.{layer} · {by-layer | by-feature}  ← feature/UC ở TÊN CLASS, KHÔNG thành package (nếu by-layer)
Phase    : {UI — mock layer | Integration — real API | Default — full}   ← bỏ nếu không có flag --phase
Scenarios: {N} total ({X} new, {Y} drifted, {Z} synced-skip)
Layer    : {từ CLAUDE.md §2}

Files:
  CREATE  {N} file mới
    + {path/FileName.ext}
  EXTEND  {M} file có sẵn  (ADD-ONLY — chỉ Edit thêm, CẤM full Write)
    ~ {path/FileName.ext}  — thêm: {methodA}, {methodB}
        ↳ GIỮ NGUYÊN (member có sẵn, gồm UC khác): {methodX [UC-other], methodY …}   ← sẽ không bị đụng
  FILL    {F} stub trắng  (lấp logic TẠI CHỖ — KHÔNG đẻ method song song)
    ⟲ {path/FileName.ext}#{method}  — lấp: {trách nhiệm}  (stub của {consumer_uc})
  SKIP    {K} file  (không cần đổi)
    = {path/FileName.ext}
──────────────────────────────────────────────────────
Proceed? (Y/N)
```

Chờ "Y" rõ ràng trước khi sinh.

## Branch
```bash
git checkout -b feature/{TICKET_ID}-{slug}
```

## Generate (thứ tự layer từ CLAUDE.md §2)

Thứ tự mặc định (override từ CLAUDE.md nếu khác):
DTOs → Entity/Model → Repository → Service interface → Service impl → Facade (nếu áp dụng) → Controller

**Với file `CREATE`:** sinh file đầy đủ.

**Với file `EXTEND`:** áp **Quy tắc EXTEND phi-phá-huỷ** (xem File Scan) — đọc lại file trên disk → **chỉ Edit thêm** method mới cho `{UC-ID}` → **KHÔNG full Write**, không đụng member cũ → chạy **Guard sau-ghi** để chắc không mất member/tag của UC khác.

**Với file `FILL`:** áp **Fill-before-create** (xem File Scan) — Edit logic thật vào **đúng thân method stub** đã có, giữ nguyên chữ ký, gỡ `@trace.stub*` → `@trace.implements`, cập nhật sổ `RESOLVED`. **KHÔNG** đẻ method mới cùng trách nhiệm.

**Tag traceability trên controller/handler (theo cú pháp comment của ngôn ngữ bạn):**
```
@trace.implements={UC-ID}-SC{N}
@trace.prd_version={đọc @trace.prd_version từ header file .feature}
@trace.bdd_version={đọc @trace.bdd_version từ header file .feature}
@trace.tech_doc_revision={đọc @trace.revision từ header tech-doc, hoặc bỏ nếu không có tech-doc}
@trace.source={paths.specs_dir}/{domain}/{prd-slug}/bdd/{UC-ID}-{slug}.feature
```

`@trace.prd_version` ghi code này được viết theo version PRD nào.
`@trace.bdd_version` ghi code này được sinh từ version BDD nào.
`@trace.tech_doc_revision` ghi code này theo revision tech-design nào.
`/validate-traces` sẽ gắn cờ drift nếu bất kỳ artifact upstream nào được cập nhật lên version mới hơn.

> **Quy tắc entry-point:** `@trace.implements` phải xuất hiện ở **layer entry-point** như định nghĩa trong `CLAUDE.md §2`. Với REST API → Controller. Với module event-driven → event handler / consumer class. Với context-engineering → hàm orchestration prompt. Không bao giờ chỉ đặt ở layer trong.

> **Quy tắc nguồn giá trị (chống hard-code):** MỌI giá trị cụ thể (endpoint path, error code, tên field/DTO, enum, limit/timeout, header) phải lấy từ **nguồn đã chốt** — tech-doc §4 (contract) · `core-entities.md` (enum/field) · config/env — **KHÔNG bịa inline**. Nếu một hằng số nghiệp vụ lặp lại hoặc mang ý nghĩa (retry count, ngưỡng, key) → **đặt tên hằng số** (constant/config), không rải magic number/string trong code. Không có nguồn cho một giá trị → đây là GAP: dừng và hỏi, đừng chế bừa (đồng bộ Cổng 2 của generate-tech-docs). *(DS3 đã đảm bảo có §4 contract trước khi tới đây với BE.)*

### Test Selectors — emit element ID ổn định *(chỉ UI FE/App)*

*Áp dụng khi `platform` là `web`/`app` và đang sinh UI (`--phase=ui`, hoặc chế độ default FE/App). Bỏ qua với BE.*

Mỗi element **có action** (button, input, link, select, toggle, form-submit) PHẢI mang một **test-id ổn định** để QC định vị trực tiếp (không scan runtime):

1. **Nguồn id.** Nếu tech-doc gộp `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md` có **§4.5.6 Test Selectors** cho platform này, lấy id **nguyên văn** từ bảng đó (contract). Nếu chưa có (vd `--phase=ui` trước khi §4.5 được vẽ), **sinh id theo quy ước** `{uc-lower}-{screen}-{element}-{type}` (vd `ft001-login-submit-btn`) để QC vẫn có handle ổn định — chúng sẽ được đối chiếu với §4.5.6 của tech-design lúc integration.
2. **Emit qua attribute platform** (từ `@trace.testid_attr`, hoặc theo module):
   - web (`react`/`nextjs`/`vue`/`angular`) → `data-testid="..."`
   - React Native → `testID="..."`
   - Flutter → `Key('...')` (+ `Semantics(identifier: '...')` khi action cần)
   - native iOS → `accessibilityIdentifier = "..."`
3. Chỉ element có action; đừng spam id lên text tĩnh. Giữ id giống hệt map của tech-design để QC Page Object khớp ngay lần đầu.
4. **Component catalog tái dùng?** Truyền id qua **forwarding prop** của nó (xem section catalog `## Test-ID Forwarding` — vd `<Button testId="ft001-login-submit-btn">`), không phải attribute thô. Nếu component không forward test-id, hoặc bạn đang backfill màn **existing/brownfield** (không phải sinh mới ở đây), đó là việc của `/map-testids {UC-ID}` — chạy nó thay vì sửa component dùng chung inline.

## Mock API Layer (chỉ `--phase=ui`)

*Bỏ qua hoàn toàn section này nếu `--phase` không phải `ui`.*

Dựng mock từ `mock_source` đã phân giải ở Phase Detection — **shape** từ §4 API contract (tech-doc gộp) khi có, **giá trị fixture + behavior** luôn từ mệnh đề `Then` của System BDD:

1. **Định nghĩa shape port** `{UC-ID}ApiPort` (DTO request/response + error code):
   - `mock_source = contract` → tên field / type / error code lấy **nguyên văn từ tech-doc gộp** §4.1 (Endpoints) / §4.2 (Request-Response) / §3 (Data Model) — shape thật.
   - `mock_source = system-bdd` → shape **infer** từ mệnh đề `Then` của System BDD (tạm — xem cảnh báo ở trên).
2. **Trích dữ liệu fixture** theo từng scenario từ mệnh đề `Then` của System BDD — response success + error (BDD là source of truth cho *giá trị / behavior*, bất kể nguồn shape).
3. **Sinh mock adapter** tại `{paths.src_dir}/{domain}/{UC-ID}MockApiAdapter.{ext}`:
   - Implements interface `{UC-ID}ApiPort` (cùng interface mà real adapter sẽ implement)
   - Mỗi method trả về fixture data khớp mệnh đề `Then` của BDD, theo shape của port
   - Gồm cả trạng thái success và error (map sang các error scenario trong BDD)
   - Tag traceability:
     ```
     @trace.mock_for={UC-ID}
     @trace.mock_source={contract | system-bdd}
     @trace.system_bdd={paths.specs_dir}/{domain}/{prd-slug}/bdd/system/{UC-ID}*.feature
     {@trace.be_contract={TICKET-ID}-tech-design.md   # chỉ khi mock_source=contract}
     ```
4. **Wire vào layer service/hook** qua environment flag hoặc DI:
   ```
   const adapter = IS_MOCK ? new {UC-ID}MockApiAdapter() : new {UC-ID}ApiAdapter()
   ```
   - `IS_MOCK` mặc định `true` ở môi trường development/test cho tới khi real adapter được sinh.

> Tester dùng mock adapter để test mọi FE scenario mà không cần đợi BE **deploy**.
> Shape lấy từ §4 API contract (tech-doc gộp) khi có (chính xác, không rework integration); else từ System BDD (tạm — điều chỉnh ở `--phase=integration`). Giá trị fixture luôn từ System BDD — BDD là source of truth cho behavior.

---

## Integration Phase (chỉ `--phase=integration`)

*Bỏ qua hoàn toàn section này nếu `--phase` không phải `integration`.*

1. **Đọc integration design.** Trong tech-doc gộp `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md`: ưu tiên §4.5.4 (mapping port→endpoint→DTO→error của platform), dùng §4.1/§4.2/§4.3 làm nguồn endpoint / request-response / error-code. Nếu doc chưa có §4.5.4 cho platform này, trích endpoint + shape + error code trực tiếp từ §4.1–§4.3.
2. **Đọc mock adapter có sẵn** interface (`{UC-ID}ApiPort`) từ output `--phase=ui`.
3. **Sinh real API adapter** tại `{paths.src_dir}/{domain}/{UC-ID}ApiAdapter.{ext}`:
   - Implements cùng interface `{UC-ID}ApiPort` như mock adapter
   - Gọi HTTP thật tới endpoint từ contract tech-doc
   - Map field response sang cùng shape mà mock adapter trả về
   - Tag traceability:
     ```
     @trace.implements={UC-ID}-SC{N}
     @trace.tech_doc_revision={đọc từ header tech-doc}
     ```
4. **Lật wire-up**: chuyển DI binding / env flag để service/hook dùng `{UC-ID}ApiAdapter` (thật) thay vì mock.
5. **KHÔNG xoá mock adapter** — giữ lại cho unit test.

---

## Self-Review (3 vòng)
- [ ] Mỗi scenario có endpoint tương ứng
- [ ] @trace.implements trên mọi endpoint
- [ ] Tôn trọng quy tắc layer kiến trúc (CLAUDE.md §2)
- [ ] Error handling khớp CLAUDE.md §5
- [ ] **Không hard-code:** giá trị cụ thể lấy từ tech-doc §4 / core-entities / config; hằng số nghiệp vụ được đặt tên; 0 magic number/string inline; không debug logging
- [ ] **Add-only cho file EXTEND:** đọc lại file trên disk; mọi member + `@trace.implements` cũ (gồm UC khác) còn nguyên; Guard sau-ghi đã chạy, không mất member nào
- [ ] **Seam/Stub ledger:** không tạo giả lập khi hàng thật đã tồn tại (đã nối/lấp thẳng); mọi seam mới có `@trace.seam_pending` + dòng `kind=seam PENDING`; mọi method trắng có `@trace.stub`/`@trace.stub_owner`/`@trace.stub_for` + dòng `kind=stub PENDING` trong `_seams.tsv`; nếu UC này là chủ nợ (seam) → đã set `READY` + in cảnh báo
- [ ] **Fill-before-create:** trước khi tạo method mới đã quét sổ + `@trace.stub` — có stub khớp thì **lấp tại chỗ** (giữ chữ ký, gỡ `@trace.stub*` → `@trace.implements`, sổ `RESOLVED`); KHÔNG còn method song song cùng trách nhiệm

## Build Verify
```bash
{conventions.build_command}   # từ project-context.yaml, tối đa 3 retry
```

## Write Trace State

Cập nhật `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{@trace.platform}.tsv` — với mỗi scenario đã implement, tìm row có sẵn theo `sc_id` và chỉ cập nhật các cột sau. *(Umbrella + `spec_source`: `trace_dir` phân giải về `{spec_source}/.trace` — lệnh này chạy từ `service_root` nhưng ghi trace row vào **spec repo** (liên-repo); commit/push spec submodule cho lần cập nhật trace, cùng với push code 2 tầng.)*

| Cột | Giá trị |
|--------|-------|
| `gen_ver` | copy `spec_ver` từ row `.tsv` hiện tại (= version scenario tại thời điểm codegen) |
| `implemented_by` | `{ControllerClass}.{methodName}` |
| `bdd_version` | `@trace.bdd_version` từ header `.feature` |
| `tech_doc_revision` | `@trace.revision` từ tech-doc gộp `{TICKET-ID}-tech-design.md` (§4 backend đã điều khiển codegen của UC này), hoặc `—` nếu chưa có doc |
| `fe_tech_doc_revision` | `@trace.revision` của cùng tech-doc gộp, ghi khi sinh FE với `--phase=integration` (adapter được wire theo §4.5.4); `—` cho BE, hoặc cho FE `--phase=ui` / chưa có §4.5.4 |
| `fe_phase` | `ui` nếu `--phase=ui` \| `integrated` nếu `--phase=integration` \| `—` nếu không có flag phase |
| `last_updated` | hôm nay `YYYY-MM-DD` |

Giữ nguyên mọi cột khác (`sc_title`, `spec_ver`, `prd_version`, `prd_status`, `uc_status`, `test_count`, `test_classes`, `dev_selftest`, `dev_selftest_at`, `qc_status`, `qc_run_at`, `qc_owner`, `qc_blocked_by`).
`status` được tính bởi `/validate-traces` — không set ở đây.

**Reset test khi lấp stub (Fill-before-create).** Nếu lần gen này **lấp** một/nhiều method stub (dòng sổ `→ RESOLVED`): logic vừa đổi thật → test cũ viết trên hàm trắng đã cũ (nó có thể "xanh" chỉ vì hàm trắng `throw`/trả rỗng). Với **mọi scenario chạy qua method vừa lấp** — gồm cả scenario của **consumer_uc** (UC đã để trắng, thường nằm ở file TSV khác `{consumer_uc}-{platform}.tsv`):
- `dev_selftest → not_run`, `dev_selftest_at → —` (ép chạy lại self-test).
- **CHỈ** đụng 2 cột test này — ngoại lệ có kiểm soát của luật "giữ nguyên cột khác"; là thao tác an-toàn (không sửa code UC khác, chỉ hạ cờ test đã cũ).
- Gom danh sách `{consumer_uc}` bị ảnh hưởng để in ở "Next".

## Refresh Panel Mirror
{{include:steps/trace-mirror.md}}

## Commit
```bash
git add {files}
git commit -m "{commit_format}: {description}"
```

## Output

{{include:steps/report-footer.md}}

```
/generate-code Hoàn tất — {UC-ID}
Files: created={N}, extended={M}, filled={F} stub, skipped={K} | Build: SUCCESS
Branch: feature/{TICKET_ID}-{slug}
Phase    : {UI (mock layer) | Integration (real API) | Default (full)}
fe_phase : {ui | integrated | —}
Figma    : {Dev Mode MCP local (grounded) | ⚠️ chỉ link web + text spec (không có MCP local) | n/a cho BE}   ← chỉ UI FE/App

Next:
  --phase=ui xong:
    → Báo tester: FE test được qua mock adapter
    → Thu sign-off BE → /review-tech-docs {tech-design-file}
    → Khi BE sẵn sàng → /generate-code {feature-file} --phase=integration

  --phase=integration xong:
    → /review-code {UC-ID}   ← cần code review
    → /dev-gen-test {UC-ID}  ← bộ integration test

  Default (không có flag phase):
    → /review-code {UC-ID}   ← cần code review trước khi test
    → /dev-gen-test {UC-ID}

  Nếu lần này LẤP stub (filled > 0):
    → /dev-gen-test {owner_uc}      ← test cho logic vừa lấp
    → /dev-gen-test {consumer_uc}   ← RE-GEN: test cũ của {consumer_uc} viết trên hàm trắng, giờ đã cũ
    (dev_selftest các scenario này đã bị reset → not_run; /validate-traces sẽ hiện chúng là GAP tới khi test lại)

📊 Living Docs: chạy /validate-traces (hoặc /sync) để push trace này lên dashboard spec-module.
```
