@educa-corp/fw 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/commands/bdd.md +5 -3
- package/commands/prd.md +9 -7
- package/docs/guide/02-khai-niem.md +1 -1
- package/docs/guide/README.md +7 -5
- package/docs/guide/lenh/bdd.md +2 -2
- package/docs/guide/lenh/prd-refine.md +7 -5
- package/docs/guide/lenh/prd-review.md +53 -0
- package/docs/guide/lenh/prd.md +3 -3
- package/docs/guide/vai-tro/po-ba.md +6 -5
- package/docs/guide/xu-ly-su-co.md +3 -0
- package/package.json +1 -1
- package/ref/bdd/writing.md +1 -0
- package/ref/common.md +1 -1
- package/ref/prd/change.md +2 -1
- package/ref/prd/refine.md +17 -4
- package/ref/prd/review.md +77 -0
- package/templates/prd.md +3 -2
- package/tools/spec_edit.py +111 -22
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
> `fw install` in các mục của những version mới hơn bản đang cài trong dự án.
|
|
4
4
|
> Mỗi lần sửa lệnh, khuôn, bộ câu hỏi hay công cụ đều phải tăng version và thêm một mục ở đây. Test sẽ báo lỗi nếu version chưa có mục.
|
|
5
5
|
|
|
6
|
+
## 0.11.0 — 2026-10-02
|
|
7
|
+
|
|
8
|
+
- **Chế độ mới `/prd <EPIC-ID> review`**: rà **hình thức** PRD (chính tả, câu chữ, thuật ngữ, đúng khuôn), chạy sau refine. **Bắt buộc**: PRD không duyệt được nếu chưa review đúng version (`reviewed` = `version`), và `/bdd` dừng nếu PRD chưa review. Review sửa **trong cùng version**, không tăng version, PRD đã `approved` vẫn giữ `approved`. Lỗi chính tả và định dạng thì AI sửa luôn. Câu chữ, thuật ngữ, dòng sai khuôn thì bạn quyết, được quyết theo nhóm. Chỗ muốn sửa mà làm đổi nghĩa thì AI chỉ ghi chú.
|
|
9
|
+
- PRD đã duyệt từ trước: chạy `/prd <EPIC-ID> review` một lần, rồi mới chạy `/bdd`.
|
|
10
|
+
- **`spec_edit edit --form`**: chặn mọi chỗ sửa làm đổi số, mã, dấu 🤖/✅, gạch ngang hoặc nguồn.
|
|
11
|
+
- **Refine rẻ hơn cho thay đổi nhỏ**: lượt 2, và lượt 1 sau `/prd change` chạm tối đa 3 UC, dùng **1 agent Opus gộp** cả 4 lăng kính thay vì 4 agent. Agent này vẫn đọc toàn bộ PRD. Lượt không có finding nào thì không gọi agent kiểm chứng. Lần đo trên LMS: thêm một dòng Nền tảng tốn 6,5M, ước tính còn khoảng 2,8M.
|
|
12
|
+
- **Nền tảng chỉ nhận mã `web` · `app` · `system`**. `system` dành cho luồng tự động, không có màn hình. Giải thích ghi sau dấu ` — `. Ghi bằng lời thì `bddcheck` báo lỗi và PRD không duyệt được. Trước bản này, dòng `Nền tảng: không có màn hình …` bị đọc thành tag rác mà vẫn cho duyệt.
|
|
13
|
+
- `spec_edit confirm` xác nhận được dòng không có mã, theo số dòng mà `pending` in ra (`L24`). `spec_edit context` ghi sẵn `reviewed` và nền tảng của UC ở dòng đầu.
|
|
14
|
+
- `/prd change` và `/prd refine` ghi rõ khuôn dòng Nền tảng và mã mục `history`, để AI không phải dò bằng `grep`.
|
|
15
|
+
|
|
6
16
|
## 0.10.0 — 2026-10-02
|
|
7
17
|
|
|
8
18
|
- **Lệnh mới `/bdd <UC-ID>`**: viết kịch bản BDD cho một UC từ PRD đã duyệt, mỗi UC một file `{domain}/{slug}/bdd/UC-001.feature`. Có 2 lượt: lượt 1 trình dàn ý (kèm bảng nhánh của từng BR và các điểm giao nhận với UC khác), lượt 2 ghi file rồi kiểm độ phủ. Chỉ đọc đúng phần PRD của UC đó: với UC-001 của LMS là 15 KB thay vì 91 KB.
|
package/commands/bdd.md
CHANGED
|
@@ -23,13 +23,15 @@ Tham số: `$ARGUMENTS`
|
|
|
23
23
|
|
|
24
24
|
## Bước 2 — Chọn chế độ
|
|
25
25
|
|
|
26
|
-
Xét **từ trên xuống**, gặp dòng nào khớp trước thì theo dòng đó.
|
|
26
|
+
Xét **từ trên xuống**, gặp dòng nào khớp trước thì theo dòng đó. Dòng đầu của `SE context` đã ghi sẵn `status`, `reviewed` và nền tảng của UC.
|
|
27
27
|
|
|
28
28
|
| Tình huống | Làm gì |
|
|
29
29
|
|---|---|
|
|
30
30
|
| UC đã bỏ trong PRD (tiêu đề gạch ngang) | Dừng: *"UC-xxx đã bỏ trong PRD (v…). Không viết BDD cho UC đã bỏ."* |
|
|
31
|
-
|
|
|
31
|
+
| Nền tảng `chưa khai` | Dừng: *"PRD chưa khai nền tảng. Chạy `/prd EP-xx change khai nền tảng: web …` trước. UC nào chạy khác mặc định thì nêu riêng."* |
|
|
32
|
+
| Nền tảng `sai khuôn` (ghi bằng lời, không phải mã `web` · `app` · `system`) | Dừng: *"Dòng Nền tảng của UC-xxx sai khuôn. Chạy `/prd EP-xx review` để sửa."* |
|
|
32
33
|
| PRD chưa `approved`, **không** có `--force` | Dừng: *"PRD chưa duyệt. Duyệt PRD trước, hoặc chạy `/bdd UC-xxx --force` để viết sớm (BDD sẽ không duyệt được cho tới khi PRD được duyệt)."* |
|
|
34
|
+
| PRD `approved` nhưng `reviewed` khác version, **không** có `--force` | Dừng: *"PRD v… chưa rà hình thức. Chạy `/prd EP-xx review` trước."* |
|
|
33
35
|
| Không có `change`, chưa có `F` | Chế độ **tạo**: đọc `.fw/core/ref/bdd/writing.md` và `.fw/core/ref/bdd/new.md` |
|
|
34
36
|
| Không có `change`, **đã có** `F` | Chế độ **bù**: đọc `.fw/core/ref/bdd/writing.md` và `.fw/core/ref/bdd/change.md` |
|
|
35
37
|
| Có `change`, đã có `F` | Chế độ **đổi**: đọc `.fw/core/ref/bdd/writing.md` và `.fw/core/ref/bdd/change.md` |
|
|
@@ -52,7 +54,7 @@ Ngoài luật chung (`common.md`) và luật viết (`writing.md`):
|
|
|
52
54
|
- `SE scenario F <mã SC | mã AC/BR>`: đọc kịch bản theo mã.
|
|
53
55
|
- `SE sc F`: thay, thêm, bỏ kịch bản theo mã. Chỉ viết khối mới, không chép khối cũ.
|
|
54
56
|
- `SE bddcheck F`: kiểm khuôn, tag và độ phủ.
|
|
55
|
-
5. **Duyệt.** `status=approved` chỉ đặt được khi `bddcheck` sạch, không còn `🤖`, PRD đang `approved
|
|
57
|
+
5. **Duyệt.** `status=approved` chỉ đặt được khi `bddcheck` sạch, không còn `🤖`, PRD đang `approved` và đã rà hình thức đúng version, `prd_version` của `F` bằng version của PRD, và có người duyệt. Hỏi *"Ai duyệt BDD này?"* (mặc định là người đã duyệt PRD), rồi đặt trong **một** lệnh: `SE set F status=approved approved_by=<tên> approved_at=YYYY-MM-DD`. `SE` tự chặn nếu chưa đủ.
|
|
56
58
|
|
|
57
59
|
## Bước 4 — Kết thúc
|
|
58
60
|
|
package/commands/prd.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Viết PRD từ epic đã làm rõ (EPIC-ID), rà nội dung
|
|
3
|
-
argument-hint: "<EPIC-ID> [refine | change <mô tả thay đổi>] [--force]"
|
|
2
|
+
description: Viết PRD từ epic đã làm rõ (EPIC-ID), rà nội dung (EPIC-ID refine), rà hình thức (EPIC-ID review), hoặc đổi PRD (EPIC-ID change)
|
|
3
|
+
argument-hint: "<EPIC-ID> [refine | review | change <mô tả thay đổi>] [--force | --full]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# /prd
|
|
@@ -11,6 +11,7 @@ Mục đích: biến epic đã làm rõ (`/product`) thành **PRD chính thức*
|
|
|
11
11
|
|
|
12
12
|
- `/prd <EPIC-ID>`: tạo PRD từ epic `ready`.
|
|
13
13
|
- `/prd <EPIC-ID> refine`: rà nội dung PRD qua 4 lăng kính. **Bắt buộc** trước khi duyệt.
|
|
14
|
+
- `/prd <EPIC-ID> review`: rà hình thức (chính tả, câu chữ, thuật ngữ, khuôn), sau refine. **Bắt buộc** trước khi duyệt.
|
|
14
15
|
- `/prd <EPIC-ID> change <mô tả>`: thêm, sửa hoặc bỏ nội dung trong PRD đã có.
|
|
15
16
|
|
|
16
17
|
Tham số: `$ARGUMENTS`
|
|
@@ -28,10 +29,11 @@ Tham số: `$ARGUMENTS`
|
|
|
28
29
|
|---|---|
|
|
29
30
|
| Không có `change`, chưa có `D`, epic `ready` | Chế độ **tạo**: đọc `.fw/core/ref/prd/new.md` |
|
|
30
31
|
| Không có `change`, chưa có `D`, epic **chưa** `ready` | Dừng: *"EP-xx chưa làm rõ xong. Chạy `/product EP-xx` trước."* Có `--force` thì làm tiếp, nhưng in cảnh báo, ghi vào mục Câu hỏi còn mở của PRD dòng *"Tạo khi epic chưa ready (--force)"*, và không cho `approved` khi epic còn câu hỏi mở |
|
|
31
|
-
| Không có `change` / `refine`, **đã có** `D` | Dừng: *"PRD đã có. Rà nội dung: `/prd EP-xx refine` · Đổi: `/prd EP-xx change <mô tả>`."* Không ghi đè |
|
|
32
|
+
| Không có `change` / `refine` / `review`, **đã có** `D` | Dừng: *"PRD đã có. Rà nội dung: `/prd EP-xx refine` · Rà hình thức: `/prd EP-xx review` · Đổi: `/prd EP-xx change <mô tả>`."* Không ghi đè |
|
|
32
33
|
| Có `refine`, đã có `D` | Chế độ **rà nội dung**: đọc `.fw/core/ref/prd/refine.md` |
|
|
34
|
+
| Có `review`, đã có `D` | Chế độ **rà hình thức**: đọc `.fw/core/ref/prd/review.md` |
|
|
33
35
|
| Có `change`, đã có `D` | Chế độ **đổi**: đọc `.fw/core/ref/prd/change.md` |
|
|
34
|
-
| Có `change` hoặc `
|
|
36
|
+
| Có `change`, `refine` hoặc `review`, chưa có `D` | Dừng: *"Chưa có PRD. Chạy `/prd EP-xx` trước."* |
|
|
35
37
|
|
|
36
38
|
## Bước 3 — Luật riêng của PRD
|
|
37
39
|
|
|
@@ -39,8 +41,8 @@ Ngoài luật chung (`common.md`):
|
|
|
39
41
|
1. **Mã ổn định.** `UC-NNN` đánh số trên toàn sản phẩm. `UC-NNN-BRnn`, `UC-NNN-ACnn` đánh số trong từng UC. Mã đã cấp thì **không bao giờ đổi, không dùng lại**. Thêm mới thì lấy số kế tiếp trong UC đó. Mục bỏ đi thì giữ dòng, gạch ngang nội dung, và ghi *"Đã bỏ (v…)"*.
|
|
40
42
|
2. **Nguồn.** Mọi BR và AC phải ghi `(nguồn: …)`, và **chỉ ghi mã**: mã epic (`BR3`, `AC1`), ràng buộc (`CON-02`), hoặc `PRD` (thêm khi viết PRD, PO đã duyệt). Không ghi lời giải thích. `SE` chặn nếu sai.
|
|
41
43
|
3. **Dấu.** Nội dung chuyển nguyên ý từ mục `✅` của epic thì giữ `✅`. Chỗ nào AI **tự thêm, tách hoặc suy ra** thì gắn `🤖`.
|
|
42
|
-
4. **Lệnh `SE` riêng của PRD:** `SE next-uc {specs}` (mã UC kế tiếp) · `SE coverage <epic> <prd>` (không rơi BR/AC nào của epic) · `SE flowcheck <prd>` (mọi UC có mặt trong User flow).
|
|
43
|
-
5. **Duyệt.** `status=approved` chỉ đặt được khi không còn `🤖`, `open_questions=0`, User flow đủ, **đã refine đúng version này** (`refined` = `version`, không còn critical/major chưa quyết), và có người duyệt. Chưa refine thì báo *"Chạy `/prd EP-xx refine` trước
|
|
44
|
+
4. **Lệnh `SE` riêng của PRD:** `SE next-uc {specs}` (mã UC kế tiếp) · `SE coverage <epic> <prd>` (không rơi BR/AC nào của epic) · `SE flowcheck <prd>` (mọi UC có mặt trong User flow) · `SE edit --form <prd>` (sửa hình thức, chặn mọi chỗ đổi số, mã, dấu, nguồn).
|
|
45
|
+
5. **Duyệt.** `status=approved` chỉ đặt được khi không còn `🤖`, `open_questions=0`, User flow đủ, **đã refine đúng version này** (`refined` = `version`, không còn critical/major chưa quyết), **đã rà hình thức đúng version này** (`reviewed` = `version`), mọi UC có **Nền tảng** đúng khuôn, và có người duyệt. Chưa refine thì báo *"Chạy `/prd EP-xx refine` trước."* Chưa review thì báo *"Chạy `/prd EP-xx review` trước."* Hỏi PO *"Ai duyệt PRD này?"* (mặc định là PO của epic), rồi đặt trong **một** lệnh: `SE set D status=approved approved_by=<tên> approved_at=YYYY-MM-DD`. `SE` tự chặn nếu chưa đủ.
|
|
44
46
|
|
|
45
47
|
## Bước 4 — Kết thúc
|
|
46
48
|
|
|
@@ -52,5 +54,5 @@ Trạng thái : {✅ Đã duyệt v{version} | 🟡 Nháp v{version} — còn {n
|
|
|
52
54
|
Đã ghi : {D} {· epic → handed-off · product.md nếu có}
|
|
53
55
|
Kiểm : coverage {đủ | thiếu …} · user flow {đủ | thiếu …} · {số UC} UC · {số BR} BR · {số AC} AC
|
|
54
56
|
Luồng : Product → [PRD ◀ bạn ở đây] → BDD → TDD · Design-spec → Code → Test → QC
|
|
55
|
-
Bước tiếp : {`/clear` rồi `/prd {id} refine` | /bdd UC-xxx | trả lời / xác nhận các mục trên}
|
|
57
|
+
Bước tiếp : {`/clear` rồi `/prd {id} refine` | `/clear` rồi `/prd {id} review` | /bdd UC-xxx | trả lời / xác nhận các mục trên}
|
|
56
58
|
```
|
|
@@ -27,7 +27,7 @@ Chỉ những khái niệm bạn **gặp khi dùng** các lệnh hiện có.
|
|
|
27
27
|
| **Ràng buộc** | Điều kiện áp cho **mọi epic**, mã `CON-01`… | CON-01: Audit log không được xoá hay sửa |
|
|
28
28
|
| **User flow** (hành trình) | Đường đi của một actor qua nhiều UC, có rẽ nhánh và nhánh lỗi, vẽ bằng sơ đồ trong PRD. Mã `J1`, `J2`… | J1 Nhân sự mới vào hệ thống: UC-001 → UC-003 → UC-005 |
|
|
29
29
|
| **Kịch bản BDD** (scenario) | Một hành vi cụ thể của hệ thống, viết dạng *Given / When / Then*, dev viết code và test theo đó. Mỗi UC có một file `.feature` | UC-001-SC01: Import có dòng trùng email với tài khoản đã có |
|
|
30
|
-
| **Nền tảng** | Nơi người dùng dùng tính năng: `web`, `app
|
|
30
|
+
| **Nền tảng** | Nơi người dùng dùng tính năng: `web`, `app`, hoặc `system` cho luồng tự động không có màn hình. Khai ở Tổng quan của PRD, UC nào khác thì ghi riêng | Nền tảng: web · app. UC-009: system — nhận đơn từ EP-03 |
|
|
31
31
|
| **Checkpoint** | Một điểm dừng để bạn chốt. `/product` có 2 (sản phẩm) hoặc 3 (epic) checkpoint | — |
|
|
32
32
|
|
|
33
33
|
## Mã ổn định
|
package/docs/guide/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Hướng dẫn sử dụng `@educa-corp/fw`
|
|
2
2
|
|
|
3
3
|
> Framework làm việc với Claude Code: đưa một tính năng đi từ **ý tưởng → làm rõ yêu cầu → PRD → …**, AI hỏi và viết, con người xác nhận ở mỗi bước.
|
|
4
|
-
> Hướng dẫn này viết cho **bản 0.
|
|
4
|
+
> Hướng dẫn này viết cho **bản 0.11.0**. Chỉ mô tả những lệnh **đã có**. Lệnh mới có thì hướng dẫn mới được bổ sung.
|
|
5
5
|
> Trong dự án đã cài framework, bản hướng dẫn đúng với version đang dùng nằm ở **`.fw/guide/`**. Có thể hỏi Claude: *"Đọc .fw/guide và cho biết cách đổi PRD"*.
|
|
6
6
|
|
|
7
7
|
---
|
|
@@ -22,10 +22,11 @@
|
|
|
22
22
|
## Pipeline hiện có
|
|
23
23
|
|
|
24
24
|
```
|
|
25
|
-
cài framework ──► /product ──► /product EP-xx ──► /prd EP-xx ──►
|
|
26
|
-
tầng làm rõ một viết PRD rà nội
|
|
27
|
-
sản phẩm tính năng ▲
|
|
28
|
-
|
|
25
|
+
cài framework ──► /product ──► /product EP-xx ──► /prd EP-xx ──► refine ──► review ──► duyệt ──► /bdd UC-xxx ──► duyệt
|
|
26
|
+
tầng làm rõ một viết PRD rà nội rà hình mỗi UC một
|
|
27
|
+
sản phẩm tính năng ▲ dung thức file kịch bản
|
|
28
|
+
│ (cả hai bắt buộc)
|
|
29
|
+
└── /prd EP-xx change: refine, review lại, duyệt, rồi /bdd UC-xxx để bù
|
|
29
30
|
```
|
|
30
31
|
|
|
31
32
|
Mỗi lệnh kết thúc bằng một khối giống nhau. Bạn chỉ cần đọc dòng **Bước tiếp**:
|
|
@@ -50,6 +51,7 @@ Bước tiếp : `/clear` rồi `/product EP-01`
|
|
|
50
51
|
| `/product <EPIC-ID>` | PO / BA | **Làm rõ yêu cầu** một tính năng trước khi viết PRD | [/product](lenh/product.md) |
|
|
51
52
|
| `/prd <EPIC-ID>` | PO / BA | Viết **PRD chính thức** từ epic đã làm rõ | [/prd](lenh/prd.md) |
|
|
52
53
|
| `/prd <EPIC-ID> refine` | PO / BA | **Rà nội dung** PRD qua 4 lăng kính. **Bắt buộc** trước khi duyệt | [/prd refine](lenh/prd-refine.md) |
|
|
54
|
+
| `/prd <EPIC-ID> review` | PO / BA | **Rà hình thức**: chính tả, câu chữ, thuật ngữ, đúng khuôn. Chạy sau refine. **Bắt buộc** trước khi duyệt | [/prd review](lenh/prd-review.md) |
|
|
53
55
|
| `/prd <EPIC-ID> change <mô tả>` | PO / BA | Thêm, sửa hoặc bỏ nội dung PRD | [/prd](lenh/prd.md) |
|
|
54
56
|
| `/bdd <UC-ID>` | PO / BA | Viết **kịch bản BDD** cho một UC. Đã có thì **bù** phần PRD mới đổi | [/bdd](lenh/bdd.md) |
|
|
55
57
|
| `/bdd <UC-ID> change <mô tả>` | PO / BA · QC · Dev | Thêm, sửa, bỏ kịch bản khi chưa đủ ý | [/bdd](lenh/bdd.md#đổi-bdd-uc-001-change-mô-tả) |
|
package/docs/guide/lenh/bdd.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
| **Gõ** | `/bdd UC-001` | `/bdd UC-001` | `/bdd UC-001 change <mô tả>` |
|
|
10
10
|
| **Khi nào** | Chưa có BDD cho UC này | Đã có BDD, PRD vừa đổi | Kịch bản có nhưng **chưa đủ ý** |
|
|
11
11
|
| **Ai** | PO / BA | PO / BA | PO / BA, hoặc QC / dev báo thiếu |
|
|
12
|
-
| **Cần có trước** | PRD `approved`, có dòng **Nền tảng** | như bên trái | BDD đã có |
|
|
12
|
+
| **Cần có trước** | PRD `approved`, đã [review](prd-review.md) đúng version, có dòng **Nền tảng** | như bên trái | BDD đã có |
|
|
13
13
|
| **Số lượt** | 2 | 2 | 2 |
|
|
14
14
|
| **Ghi ra** | `specs/{domain}/{slug}/bdd/UC-001.feature` | cùng file, version tăng | cùng file, version tăng |
|
|
15
15
|
|
|
@@ -54,7 +54,7 @@ Dòng `@…` là **tag**. Mỗi kịch bản có đủ bốn loại tag:
|
|
|
54
54
|
| Tag | Nghĩa |
|
|
55
55
|
|---|---|
|
|
56
56
|
| `@UC-001-SC01` | Mã kịch bản. Không bao giờ đổi. Code và test trỏ về kịch bản bằng mã này |
|
|
57
|
-
| `@web` `@app` | Nền tảng. Hành vi giống nhau thì một kịch bản mang cả hai tag
|
|
57
|
+
| `@web` `@app` `@system` | Nền tảng. Hành vi giống nhau thì một kịch bản mang cả hai tag, hành vi khác nhau thì tách thành hai kịch bản. `@system` là luồng tự động, không có màn hình, ví dụ UC-009 nhận đơn từ EP-03 |
|
|
58
58
|
| `@happy` `@alternative` `@edge` `@negative` | Loại: thành công · luồng khác vẫn thành công · biên, giới hạn · bị từ chối, lỗi |
|
|
59
59
|
| `@UC-001-AC01` `@UC-001-BR04` | AC và BR mà kịch bản này kiểm |
|
|
60
60
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
| **Cần có trước** | PRD đã có |
|
|
13
13
|
| **Số lượt rà** | Tối đa **2** cho mỗi version PRD |
|
|
14
14
|
| **Ghi ra** | `specs/{domain}/{slug}/review/prd-refine.md` · PRD nhận các bản sửa bạn đã chấp nhận |
|
|
15
|
-
| **Bước tiếp** |
|
|
15
|
+
| **Bước tiếp** | [`/prd EP-01 review`](prd-review.md) (rà hình thức) → duyệt PRD |
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
*(Các ví dụ trên là ví dụ minh hoạ.)*
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
Lần rà toàn bộ PRD: mỗi lăng kính là một agent riêng, chạy song song, **bắt buộc dùng model Opus**, và đọc **toàn bộ PRD**. Lượt 2, và lượt 1 sau một thay đổi nhỏ (chạm tối đa 3 UC), chỉ dùng **một agent Opus soi cả 4 lăng kính**. Agent này vẫn đọc toàn bộ PRD, nên vẫn thấy được ảnh hưởng lan sang UC khác. Sau đó có thêm **một agent kiểm chứng**: nó **cố chứng minh từng finding là sai** bằng cách tra lại PRD, rồi mới gộp trùng và sắp xếp. Finding nào không đứng vững thì bị bỏ, finding nào được trả lời một phần thì được thu hẹp. Đây là lớp chặn chính đối với các finding do AI ảo giác.
|
|
31
31
|
|
|
32
32
|
Khi bạn quyết xong, bảng Tóm tắt trong `prd-refine.md` ghi số **nhận · sửa · bác · hoãn**. **Tỉ lệ bác cao** nghĩa là lớp kiểm chứng còn lỏng. Hãy báo người phụ trách framework nếu bạn thấy vậy.
|
|
33
33
|
|
|
@@ -71,14 +71,16 @@ Sau mỗi lượt có sửa, lệnh sẽ nhắc *"`/clear` rồi `/prd EP-01 ref
|
|
|
71
71
|
|
|
72
72
|
## Sau khi đổi PRD
|
|
73
73
|
|
|
74
|
-
`/prd EP-01 change …` tăng version, nên bạn phải refine lại trước khi duyệt. Lần đó **chỉ
|
|
74
|
+
`/prd EP-01 change …` tăng version, nên bạn phải refine lại trước khi duyệt. Lần đó **chỉ soi các mục vừa đổi** (theo dòng Lịch sử thay đổi), nhưng agent vẫn đọc cả PRD. Vì thế PRD càng lớn thì mỗi lần đổi càng đắt: với PRD 91 KB của LMS, thêm một dòng tốn khoảng 6M token trước bản 0.11.0, và ước tính khoảng 3M từ bản 0.11.0. Lượt nào không có finding nào thì không cần agent kiểm chứng. Cách giảm chi phí hiệu quả nhất vẫn là **tách epic** để PRD nhỏ lại.
|
|
75
75
|
|
|
76
76
|
## Duyệt PRD
|
|
77
77
|
|
|
78
|
-
PRD chỉ được đặt `approved` khi đủ cả
|
|
78
|
+
PRD chỉ được đặt `approved` khi đủ cả 6 điều kiện:
|
|
79
79
|
1. Không còn 🤖.
|
|
80
80
|
2. Không còn câu hỏi mở.
|
|
81
81
|
3. **Đã refine đúng version này** (`refined` = `version`), và không còn critical/major chưa quyết.
|
|
82
|
-
4.
|
|
82
|
+
4. **Đã rà hình thức đúng version này** (`reviewed` = `version`), xem [/prd review](prd-review.md).
|
|
83
|
+
5. Mọi UC có **Nền tảng** đúng khuôn.
|
|
84
|
+
6. Đã ghi người duyệt.
|
|
83
85
|
|
|
84
86
|
Công cụ tự chặn nếu thiếu điều kiện nào.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[← Hướng dẫn](../README.md) · [Bảng lệnh](../README.md#bảng-lệnh)
|
|
2
|
+
|
|
3
|
+
# `/prd EP-01 review` — rà hình thức PRD
|
|
4
|
+
|
|
5
|
+
> AI rà **câu chữ, chính tả, thuật ngữ, trình bày và đúng khuôn**, để người và AI ở các bước sau (BDD, thiết kế, code) đọc đúng ý. **Bắt buộc**: chưa review thì không duyệt được PRD, và `/bdd` không chạy.
|
|
6
|
+
> Không rà nội dung nghiệp vụ, vì đó là việc của [refine](prd-refine.md).
|
|
7
|
+
|
|
8
|
+
| | |
|
|
9
|
+
|---|---|
|
|
10
|
+
| **Gõ** | `/prd EP-01 review` · rà lại toàn bộ: `/prd EP-01 review --full` |
|
|
11
|
+
| **Ai** | PO / BA |
|
|
12
|
+
| **Cần có trước** | Đã [refine](prd-refine.md) xong đúng version này. **Review luôn đi sau refine**, vì refine làm đổi câu chữ |
|
|
13
|
+
| **Số lượt** | 1–2: xem đề xuất → quyết |
|
|
14
|
+
| **Ghi ra** | Sửa trong `prd.md`, **giữ nguyên version** · trường `reviewed` · một dòng Lịch sử thay đổi |
|
|
15
|
+
| **Bước tiếp** | Duyệt PRD → [`/bdd UC-xxx`](bdd.md) |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## AI tìm ra ba loại
|
|
20
|
+
|
|
21
|
+
| Loại | Gồm | Ai quyết |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| **A. Tự sửa** | Chính tả, dấu tiếng Việt, dấu câu, bảng vỡ cột, danh sách lệch | AI sửa luôn, báo lại cho bạn số chỗ đã sửa |
|
|
24
|
+
| **B. Đề xuất** | Câu khó hiểu (quá dài, đại từ không rõ chỉ ai, phủ định kép) · một khái niệm gọi hai tên, hoặc sai glossary · dòng sai khuôn, ví dụ dòng Nền tảng ghi bằng lời | **Bạn quyết** |
|
|
25
|
+
| **C. Ghi chú** | Muốn sửa thì phải **đổi nghĩa**: đổi số, điều kiện, phạm vi | Không sửa. Muốn đổi thì dùng `/prd EP-01 change` |
|
|
26
|
+
|
|
27
|
+
Bạn quyết loại B theo mã, hoặc theo nhóm:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
nhận hết
|
|
31
|
+
nhận hết trừ R03
|
|
32
|
+
R04 sửa thành: "Admin mở lại tài khoản thì đếm lại 7 ngày"
|
|
33
|
+
bác R05
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Review không bao giờ làm đổi nghĩa
|
|
37
|
+
|
|
38
|
+
- **Không tăng version.** PRD đang `approved` thì vẫn giữ `approved`.
|
|
39
|
+
- Công cụ **tự chặn** mọi chỗ sửa làm thay đổi **số, mã** (UC, BR, AC…), **dấu 🤖 / ✅, gạch ngang hoặc nguồn**. Ví dụ AI định sửa *"tối đa 1.000 dòng"* thành *"tối đa 2.000 dòng"* thì công cụ chặn. Chỗ đó được chuyển thành ghi chú C.
|
|
40
|
+
- Nhờ vậy, review không cần refine lại sau đó.
|
|
41
|
+
|
|
42
|
+
## Phạm vi
|
|
43
|
+
|
|
44
|
+
| Khi nào | Rà gì |
|
|
45
|
+
|---|---|
|
|
46
|
+
| PRD chưa từng review | Toàn bộ PRD |
|
|
47
|
+
| Sau `/prd EP-01 change` → refine | Chỉ các mục vừa đổi, theo Lịch sử thay đổi |
|
|
48
|
+
|
|
49
|
+
Thứ tự sau mỗi lần đổi PRD: `change` → `refine` → `review` → duyệt.
|
|
50
|
+
|
|
51
|
+
## PRD đã duyệt trước bản 0.11.0
|
|
52
|
+
|
|
53
|
+
PRD vẫn ở trạng thái `approved`, nhưng `/bdd` sẽ dừng và báo *"PRD chưa rà hình thức"*. Bạn chạy `/prd EP-01 review` một lần, rà toàn bộ PRD. Lần đó cũng sửa luôn dòng Nền tảng ghi sai khuôn nếu có, ví dụ dòng của UC-009 trong LMS.
|
package/docs/guide/lenh/prd.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
| **Cần có trước** | Epic `ready` (xem [/product](product.md)) | PRD đã có |
|
|
12
12
|
| **Số lượt** | 2 | 2 |
|
|
13
13
|
| **Ghi ra** | `specs/{domain}/{slug}/prd.md` · epic chuyển `handed-off` | `prd.md`, version tăng (1.0 → 1.1) |
|
|
14
|
-
| **Bước tiếp** | [
|
|
14
|
+
| **Bước tiếp** | [`refine`](prd-refine.md) → [`review`](prd-review.md) → duyệt PRD → [`/bdd UC-xxx`](bdd.md) | [`refine`](prd-refine.md) → [`review`](prd-review.md) (chỉ phần vừa đổi) → duyệt lại → [`/bdd UC-xxx`](bdd.md) để bù |
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -34,7 +34,7 @@ Bạn đồng ý, gộp hoặc tách UC.
|
|
|
34
34
|
2. Công cụ **kiểm độ phủ**: mọi BR và AC của epic đều phải có trong PRD. Thiếu thì AI bổ sung cho tới khi đủ. AI không được tự bỏ mục nào; muốn bỏ thì phải hỏi bạn.
|
|
35
35
|
3. AI **chỉ in các mục 🤖**, tức những chỗ AI tự thêm, tách hoặc suy ra khi viết PRD. Không in lại cả PRD.
|
|
36
36
|
4. Bạn mở file để đọc toàn bộ, rồi trả lời hoặc xác nhận theo mã: *"OK UC-003-BR04, UC-003-AC02"*.
|
|
37
|
-
5. Tiếp theo **bắt buộc rà nội dung**: `/clear` rồi [`/prd EP-01 refine`](prd-refine.md).
|
|
37
|
+
5. Tiếp theo **bắt buộc rà nội dung**: `/clear` rồi [`/prd EP-01 refine`](prd-refine.md). Sau đó **bắt buộc rà hình thức**: `/clear` rồi [`/prd EP-01 review`](prd-review.md). Xong cả hai, hết 🤖 và hết câu hỏi mở thì mới duyệt được. AI hỏi *"Ai duyệt PRD này?"* rồi đặt `status: approved`.
|
|
38
38
|
|
|
39
39
|
## PRD trông như thế nào
|
|
40
40
|
|
|
@@ -91,7 +91,7 @@ Dùng cho **mọi** thay đổi: thêm, sửa, bỏ.
|
|
|
91
91
|
|
|
92
92
|
## Lưu ý
|
|
93
93
|
|
|
94
|
-
- **Nền tảng**
|
|
94
|
+
- **Nền tảng** chỉ ghi mã `web`, `app` hoặc `system` (luồng tự động, không có màn hình), giải thích để sau dấu ` — `. Dòng này ghi ở Tổng quan và là mặc định cho mọi UC. UC nào chạy khác thì ghi dòng riêng trong UC. Thiếu dòng này thì PRD không duyệt được, vì BDD cần nó để tách kịch bản theo nền tảng. PRD tạo bằng bản trước 0.10.0: `/prd EP-01 change khai nền tảng: web`.
|
|
95
95
|
- **Bạn được tự sửa tay PRD.** Nhưng giữ nguyên mã, dấu ✅ / 🤖 và đoạn `<!-- sec:… -->`. Sửa nhiều thì nên dùng `change`, để AI tìm giúp các mục bị kéo theo.
|
|
96
96
|
- **Không sửa epic sau khi đã có PRD.** Epic lúc này chỉ còn là lịch sử.
|
|
97
97
|
- PRD tạo bằng bản framework cũ (trước 0.6.1) có thể còn ghi nguồn bằng lời, ví dụ *"(nguồn: PO chốt khi viết PRD)"*. Lần sửa đầu tiên AI sẽ được yêu cầu đổi các chỗ đó thành mã, thường là `PRD`.
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
## Đường đi của bạn
|
|
8
8
|
|
|
9
9
|
```
|
|
10
|
-
/product ──► /product EP-xx ──► /prd EP-xx ──►
|
|
11
|
-
▲
|
|
12
|
-
└─ /prd EP-xx change
|
|
10
|
+
/product ──► /product EP-xx ──► /prd EP-xx ──► refine ──► review ──► duyệt PRD ──► /bdd UC-xxx ──► duyệt BDD
|
|
11
|
+
▲ (từng UC)
|
|
12
|
+
└─ /prd EP-xx change: refine, review lại, duyệt lại, rồi /bdd UC-xxx để bù
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
| Bước | Lệnh | Bạn làm gì | Hướng dẫn |
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
| 1 | `/product` | Trả lời về sản phẩm, nhóm người dùng, epic, ràng buộc. Chốt từng mục | [/product](../lenh/product.md) |
|
|
18
18
|
| 2 | `/product EP-xx` | Trả lời AI hỏi về tính năng. Đây là chỗ **quan trọng nhất**: câu trả lời càng rõ thì PRD càng ít phải sửa | [/product](../lenh/product.md#một-epic-product-ep-01) |
|
|
19
19
|
| 3 | `/prd EP-xx` | Duyệt cách chia UC, xác nhận các mục 🤖 | [/prd](../lenh/prd.md) |
|
|
20
|
-
| 4 | `/prd EP-xx refine` | Quyết từng finding: nhận, sửa, bác hay hoãn. Tối đa 2 lượt
|
|
20
|
+
| 4 | `/prd EP-xx refine` | Quyết từng finding: nhận, sửa, bác hay hoãn. Tối đa 2 lượt | [/prd refine](../lenh/prd-refine.md) |
|
|
21
|
+
| 4b | `/prd EP-xx review` | Xem các đề xuất sửa câu chữ, quyết theo nhóm (*"nhận hết trừ R03"*). Xong thì duyệt PRD | [/prd review](../lenh/prd-review.md) |
|
|
21
22
|
| 5 | `/bdd UC-xxx` | Với từng UC: duyệt dàn ý kịch bản (soát xem có sót nhánh nào không), xác nhận các mục 🤖, duyệt BDD | [/bdd](../lenh/bdd.md) |
|
|
22
|
-
| 6 | `/prd EP-xx change …` | Khi yêu cầu đổi. Duyệt kế hoạch thay đổi, refine lại phần vừa đổi, duyệt lại PRD, rồi `/bdd UC-xxx` để bù | [/prd](../lenh/prd.md#đổi-prd-prd-ep-01-change-mô-tả) |
|
|
23
|
+
| 6 | `/prd EP-xx change …` | Khi yêu cầu đổi. Duyệt kế hoạch thay đổi, refine và review lại phần vừa đổi, duyệt lại PRD, rồi `/bdd UC-xxx` để bù | [/prd](../lenh/prd.md#đổi-prd-prd-ep-01-change-mô-tả) |
|
|
23
24
|
|
|
24
25
|
## Cách làm thường gặp
|
|
25
26
|
|
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
| `Bản v1.1 đã refine xong` | Gọi refine lại khi PRD chưa đổi | Không cần làm gì. Thật sự muốn rà lại từ đầu thì thêm `--full` |
|
|
29
29
|
| `chưa được PRD dùng tới: BR7, AC12` | Có BR hoặc AC của epic chưa được đưa vào PRD | AI sẽ bổ sung. Nếu bạn muốn bỏ thì nói rõ |
|
|
30
30
|
| `(nguồn: …) chỉ được ghi mã …` | PRD đang ghi nguồn bằng lời (thường là PRD tạo bằng bản trước 0.6.1) | AI sẽ đổi thành mã, thường là `PRD` |
|
|
31
|
+
| `… chưa rà hình thức (reviewed=…)` | PRD chưa qua `/prd … review` ở version này | Chạy `/prd EP-xx review`. PRD đã duyệt trước bản 0.11.0 vẫn giữ `approved`, nhưng `/bdd` sẽ dừng cho tới khi review xong |
|
|
32
|
+
| `Nền tảng ghi sai khuôn ở UC-009` | Dòng Nền tảng ghi bằng lời thay vì mã | `/prd EP-xx review` sẽ đề xuất sửa thành `system — …` (hoặc `web`, `app`) |
|
|
33
|
+
| `chỗ sửa #2 không phải sửa hình thức: mất 1.000 · thêm 2.000` | Lúc review, có một chỗ sửa làm đổi số, mã, dấu hoặc nguồn | Đó là đổi nghĩa. AI bỏ chỗ đó và ghi thành ghi chú. Muốn đổi thật thì dùng `/prd EP-xx change` |
|
|
31
34
|
| `chưa khai Nền tảng cho UC-001, UC-002` | PRD chưa có dòng Nền tảng (thường là PRD tạo bằng bản trước 0.10.0) | Chạy `/prd EP-xx change khai nền tảng: web` (hoặc `web · app`) |
|
|
32
35
|
| `AC/BR của UC-001 chưa có kịch bản nào: …` | BDD còn sót AC hoặc BR | AI sẽ bổ sung. Đã duyệt BDD rồi mà PRD mới thêm mục thì chạy `/bdd UC-001` để bù |
|
|
33
36
|
| `@UC-001-BR08 đã bỏ trong PRD` | Kịch bản trỏ tới mục PRD vừa bỏ | Chạy `/bdd UC-001`: AI đề xuất sửa hoặc bỏ kịch bản đó |
|
package/package.json
CHANGED
package/ref/bdd/writing.md
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
2. Hành vi **khác nhau**, tức kết quả khác nhau theo PRD: tách thành kịch bản riêng, mỗi kịch bản một mã. Ví dụ `UC-003-SC08 @web` và `UC-003-SC09 @app`.
|
|
27
27
|
3. Khác nhau chỉ ở giao diện hay thao tác (bố cục, cử chỉ) thì **không** tách, vì đó là việc của design-spec.
|
|
28
28
|
4. Chỉ dùng nền tảng mà UC khai (dòng `Nền tảng` trong UC, không có thì lấy ở Tổng quan).
|
|
29
|
+
5. **`@system`** dành cho luồng tự động, không có màn hình: job chạy định kỳ, tích hợp với hệ thống khác. `When` là một sự kiện, ví dụ *"EP-03 chuyển sang đơn hàng của học sinh "Lan""* hoặc *"đã đủ 7×24 giờ kể từ lúc tạo"*. `Then` là kết quả nhìn thấy được trong dữ liệu, trong email gửi đi, hoặc trong thông báo trả về hệ thống kia.
|
|
29
30
|
|
|
30
31
|
## 4. Điểm giao nhận với UC khác
|
|
31
32
|
|
package/ref/common.md
CHANGED
|
@@ -36,7 +36,7 @@ Gọi là `SE` = `python .fw/core/tools/spec_edit.py` (hoặc `python3` / `py -3
|
|
|
36
36
|
| Tạo file mới (lỗi nếu đã có) | `SE create <file> <<'EOF'` … nội dung … `EOF` |
|
|
37
37
|
| Sửa nội dung | `SE edit <file> <<'EOF'` `[{"old": "…", "new": "…"}]` `EOF`. Gom mọi chỗ sửa của một lượt vào **một** lần gọi. Đoạn lặp giống nhau thì thêm `"all": true` |
|
|
38
38
|
| Frontmatter | `SE set <file> key=value …` (giá trị được kiểm trước khi ghi) |
|
|
39
|
-
| Mục còn chờ xác nhận · xác nhận theo mã | `SE pending <file>` · `SE confirm <file> <m
|
|
39
|
+
| Mục còn chờ xác nhận · xác nhận theo mã | `SE pending <file>` · `SE confirm <file> <mã…>`. Dòng không có mã thì xác nhận theo số dòng mà `pending` in ra: `SE confirm <file> L24` |
|
|
40
40
|
| Đọc **một mục** | `SE section <file> <mã>`, mã là `<!-- sec:… -->` ở tiêu đề |
|
|
41
41
|
| Đọc **một UC** | `SE uc <file> UC-003 [UC-005 …]` |
|
|
42
42
|
| Sửa / thêm **dòng có mã** (BR, AC) | `SE item <file> <<'EOF'` `[{"id": "UC-003-BR10", "new": "<dòng mới>"}, {"after": "UC-003-BR10", "new": "<dòng thêm>"}]` `EOF`. Chỉ viết dòng mới. Mã không đổi, không dùng lại |
|
package/ref/prd/change.md
CHANGED
|
@@ -29,5 +29,6 @@ Khi PO đồng ý kế hoạch:
|
|
|
29
29
|
- Mục bỏ: giữ dòng, gạch ngang nội dung, ghi *"Đã bỏ (v{mới})"*.
|
|
30
30
|
- Thay đổi làm đổi luồng (thêm UC, đổi thứ tự, thêm nhánh lỗi) thì sửa luôn mục **User flow**. Yêu cầu là *"thêm User flow"* (PRD tạo bằng bản cũ) thì vẽ mục này từ checkpoint 2 của epic, theo luật trong `new.md`.
|
|
31
31
|
- Thêm dòng vào mục **Lịch sử thay đổi**, dạng *"v1.1 — UC-003: khoá sau 3 lần sai (BR02 sửa, AC05 thêm, BR04 bỏ)"*.
|
|
32
|
+
- Dòng **Nền tảng** (ở Tổng quan, hoặc trong UC chạy khác mặc định) chỉ ghi mã: `web`, `app` (nơi người dùng dùng tính năng), `system` (luồng tự động, không có màn hình). Giải thích để sau dấu ` — `. Ví dụ: `- ✅ **Nền tảng:** system — luồng tự động nhận đơn từ EP-03`.
|
|
32
33
|
2. Chạy `SE flowcheck D`. Còn UC chưa có trong hành trình nào thì sửa cho đủ. Sau đó tăng version phụ (1.0 → 1.1): `SE set D version=1.1 updated=…`. PRD đang `approved` thì đặt luôn `status=draft approved_by=— approved_at=—`.
|
|
33
|
-
3. Báo lại cho PO. Không in lại cả PRD, chỉ tóm tắt những gì đã đổi.
|
|
34
|
+
3. Báo lại cho PO. Không in lại cả PRD, chỉ tóm tắt những gì đã đổi. Còn `🤖` thì xác nhận trước. Bản mới chưa duyệt được ngay, vì phải refine rồi review lại phần vừa đổi. Báo: *"Bước tiếp: `/clear` rồi `/prd {id} refine`, sau đó `/prd {id} review`, rồi duyệt."* Bản mới cần người duyệt mới, nên khi duyệt phải đặt lại `approved_by` và `approved_at`.
|
package/ref/prd/refine.md
CHANGED
|
@@ -19,7 +19,7 @@ Xét **từ trên xuống**, gặp dòng nào khớp trước thì theo dòng đ
|
|
|
19
19
|
| `refined` = `version` | **Dừng.** Báo *"Bản v{version} đã refine xong."* Có `--full` thì chạy lượt 1 trên toàn PRD | — |
|
|
20
20
|
| Chưa có `R`, hoặc chưa từng refine | **1** | Toàn bộ PRD |
|
|
21
21
|
| `R` có `round: 1` **đã áp** bản sửa, `prd_version` < `version` | **2** | Chỉ các mục mà lượt 1 đã sửa hoặc thêm (đọc từ quyết định `nhận` / `sửa` trong `R`), cộng các mục tham chiếu tới chúng |
|
|
22
|
-
| `refined` có giá trị nhưng < `version` (PRD đã qua `/prd change`) | **1** | Chỉ các mục trong dòng Lịch sử thay đổi
|
|
22
|
+
| `refined` có giá trị nhưng < `version` (PRD đã qua `/prd change`) | **1** | Chỉ các mục trong các dòng Lịch sử thay đổi (`SE section D history`) có version lớn hơn `refined` |
|
|
23
23
|
|
|
24
24
|
**Không có lượt 3.** Sau lượt 2 thì luôn đặt `refined` (Bước D).
|
|
25
25
|
|
|
@@ -27,11 +27,24 @@ Xét **từ trên xuống**, gặp dòng nào khớp trước thì theo dòng đ
|
|
|
27
27
|
|
|
28
28
|
Phiên chính **không đọc** kết quả chi tiết của các agent. Agent ghi ra file, phiên chính chỉ đọc bảng tóm tắt cuối cùng.
|
|
29
29
|
|
|
30
|
-
1.
|
|
30
|
+
1. **Chọn số agent rà** theo lượt và phạm vi ở Bước A. Đếm UC bằng các mã `UC-…` nêu trong phạm vi. Thay đổi chỉ ở Tổng quan, User flow hoặc Màn hình tính là 0 UC.
|
|
31
|
+
|
|
32
|
+
| Lượt và phạm vi | Agent rà |
|
|
33
|
+
|---|---|
|
|
34
|
+
| Lượt 1, toàn bộ PRD | **4 agent**: QA · DEV · SA · Phạm vi |
|
|
35
|
+
| Lượt 1 sau `/prd change`, phạm vi chạm **≤ 3 UC** | **1 agent gộp** |
|
|
36
|
+
| Lượt 1 sau `/prd change`, phạm vi chạm > 3 UC | **4 agent** |
|
|
37
|
+
| Lượt 2 | **1 agent gộp** |
|
|
38
|
+
|
|
39
|
+
**Agent gộp** soi lần lượt cả 4 phần QA, DEV, SA, Phạm vi trong `lenses.md`. Mỗi finding ghi đúng `lens:` của phần đã phát hiện ra nó. Agent vẫn **đọc toàn bộ `D`**, để bắt được lỗi lan sang UC khác. Agent này nhận đủ tài liệu phụ của Phạm vi, và ghi vào `W/round-{n}-all.md`. Lý do: phạm vi hẹp thì một agent soi đủ 4 góc nhìn, nên không cần 4 agent cùng đọc lại cả PRD.
|
|
40
|
+
|
|
41
|
+
Dùng công cụ **Agent**, `model: "opus"`. Có nhiều agent thì gọi **song song trong cùng một lượt**. Mỗi agent nhận:
|
|
31
42
|
- đường dẫn `D` (**đọc toàn bộ**), `R` (nếu có, để đọc sổ quyết định), `.fw/core/ref/prd/lenses.md`, tên lăng kính, **phạm vi** và **ngưỡng báo** của lượt (lượt 1: mọi mức · lượt 2: chỉ critical hoặc lỗi do chính bản sửa gây ra);
|
|
32
43
|
- riêng **Phạm vi**: thêm các lệnh để đọc tài liệu phụ, **chỉ đúng phần cần**: `SE section <epic> cp1`, `SE section {specs}/product/product.md constraints`, `SE section {specs}/product/product.md epics`;
|
|
33
44
|
- file kết quả: `W/round-{n}-{lens}.md`. Agent ghi bằng `SE create --replace` (chỉ dùng được trong `.fw/tmp/`) và **chỉ trả về một dòng**: *"{lens}: {k} finding → {file}"*.
|
|
34
|
-
2.
|
|
45
|
+
2. **Tổng finding thô = 0** (mọi agent đều trả *"0 finding"*) và đã có `R`: **không gọi agent kiểm chứng**. Phiên chính tự ghi vào `R` bằng **một** lần `SE edit`: thêm mục `## Lượt {n} — v{version} <!-- sec:round-{k} -->` ở cuối (`k` = số `round-…` lớn nhất trong `R` cộng một), với một dòng *"Phạm vi: … · 0 finding thô"*, và thêm một dòng vào bảng Tóm tắt (các cột số đều 0, Kết luận `APPROVED`). Sau đó `SE set R prd_version={version} round={n}`, rồi sang thẳng **Bước D, bước 8**.
|
|
46
|
+
|
|
47
|
+
Còn lại: khi đủ dòng trả về của các agent rà, gọi **1 agent kiểm chứng** (`model: "opus"`), giao: `D`, `R`, `W`, số lượt `n`, mã F kế tiếp (lớn nhất trong `R` cộng một, hoặc F01), và phần **"Kiểm chứng và ghi file"** trong `lenses.md`. Agent này kiểm chứng, gộp, sắp xếp, đánh mã, ghi vào `R`, rồi trả về **một dòng** số liệu. File kết quả trong `W` có thể là 4 file theo lăng kính, hoặc 1 file `round-{n}-all.md`.
|
|
35
48
|
|
|
36
49
|
## Bước C — Trình
|
|
37
50
|
|
|
@@ -58,4 +71,4 @@ Phiên chính **không đọc** kết quả chi tiết của các agent. Agent g
|
|
|
58
71
|
- chạy `SE flowcheck D`, rồi `SE set D version=… status=draft approved_by=— approved_at=—` và `SE set R status=applied`;
|
|
59
72
|
- **chỉ trả về một dòng**: *"áp {k} finding → v{version}; {số BR/AC sửa · thêm · bỏ}"*.
|
|
60
73
|
7. Lượt 1 có áp bản sửa: báo *"`/clear` rồi `/prd {id} refine` để chạy lượt 2, chỉ soi phần vừa sửa."*
|
|
61
|
-
8. Không áp gì (mọi finding đều bác / hoãn), **hoặc** vừa xong **lượt 2**: `SE set D refined={version hiện tại}` và `SE set R status=done`. Finding `hoãn` thì ghi thêm vào mục **Câu hỏi còn mở** của `D`, kèm chú thích *"(để bước QC)"*.
|
|
74
|
+
8. Không áp gì (mọi finding đều bác / hoãn), **hoặc** vừa xong **lượt 2**: `SE set D refined={version hiện tại}` và `SE set R status=done`. Finding `hoãn` thì ghi thêm vào mục **Câu hỏi còn mở** của `D`, kèm chú thích *"(để bước QC)"*. Rồi báo: *"Refine xong v{version}. Bước tiếp: `/clear` rồi `/prd {id} review` để rà hình thức, bắt buộc trước khi duyệt."*
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# /prd — chế độ rà hình thức (review)
|
|
2
|
+
|
|
3
|
+
Mục đích: rà **câu chữ, chính tả, thuật ngữ, trình bày, đúng khuôn**, để người và AI ở các bước sau đọc đúng ý. **Không** rà nội dung nghiệp vụ, vì đó là việc của `refine` và đã làm xong.
|
|
4
|
+
|
|
5
|
+
- **Review đi sau refine.** Refine làm đổi câu chữ, nên rà hình thức trước thì phải rà lại.
|
|
6
|
+
- **Sửa trong cùng version, không được đổi nghĩa.** Không tăng version. PRD đang `approved` thì vẫn giữ `approved`. `SE edit --form` tự chặn mọi chỗ sửa làm đổi số, mã, dấu `🤖`/`✅`, gạch ngang hoặc nguồn.
|
|
7
|
+
- Thư mục làm việc: `W = .fw/tmp/review/{slug}/`, không commit. File kết quả: `W/review-{version}.md`.
|
|
8
|
+
|
|
9
|
+
## Bước A — Xác định phạm vi
|
|
10
|
+
|
|
11
|
+
Chạy `SE upgrade D` trước, để PRD tạo bằng bản cũ có trường `reviewed`. Đọc frontmatter của `D` (`version`, `refined`, `reviewed`, `status`).
|
|
12
|
+
|
|
13
|
+
Xét **từ trên xuống**, gặp dòng nào khớp trước thì theo dòng đó.
|
|
14
|
+
|
|
15
|
+
| Tình huống | Làm gì |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `refined` ≠ `version` | **Dừng.** Báo *"Bản v{version} chưa refine. Chạy `/prd {id} refine` trước. Review đi sau refine."* |
|
|
18
|
+
| `reviewed` = `version`, không có `--full` | **Dừng.** Báo *"Bản v{version} đã rà hình thức xong."* |
|
|
19
|
+
| Đã có `W/review-{version}.md` (rà xong, đang chờ PO quyết) | **Không rà lại.** Sang thẳng Bước C |
|
|
20
|
+
| `reviewed` là `—` (chưa từng review), hoặc có `--full` | Phạm vi: **toàn bộ PRD** |
|
|
21
|
+
| `reviewed` < `version` | Phạm vi: chỉ các mục nêu trong các dòng Lịch sử thay đổi (`SE section D history`) có version **lớn hơn** `reviewed` |
|
|
22
|
+
|
|
23
|
+
## Bước B — Rà (1 sub-agent)
|
|
24
|
+
|
|
25
|
+
Dùng công cụ **Agent**, `model: "sonnet"`, gọi **1** agent. Giao cho agent: đường dẫn `D`, phạm vi, `{specs}/product/glossary.md` (nếu có), file kết quả `W/review-{version}.md`, và phần **"Dành cho agent rà"** ở cuối file này. Agent **chỉ trả về một dòng**. Phiên chính không đọc PRD.
|
|
26
|
+
|
|
27
|
+
## Bước C — Trình
|
|
28
|
+
|
|
29
|
+
1. Đọc `W/review-{version}.md` (file nhỏ).
|
|
30
|
+
2. Trình theo thứ tự:
|
|
31
|
+
- **Đã tự sửa (A):** chỉ báo số chỗ, kèm 2–3 ví dụ. Danh sách đầy đủ nằm trong file.
|
|
32
|
+
- **Bảng đề xuất (B):** mã · loại · mục · *trước → sau*, rút gọn mỗi bên khoảng 60 ký tự.
|
|
33
|
+
- **Ghi chú (C):** những chỗ muốn sửa thì phải đổi nghĩa. Mỗi chỗ một dòng, kèm gợi ý `/prd {id} change …`.
|
|
34
|
+
3. Nhắc PO cách quyết: *"Quyết theo mã: 'nhận hết' · 'nhận hết trừ R03' · 'R04 sửa thành …' · 'bác R05'."* Không có đề xuất nào thì sang thẳng Bước D.
|
|
35
|
+
|
|
36
|
+
## Bước D — Áp
|
|
37
|
+
|
|
38
|
+
1. Áp các đề xuất được nhận bằng **một** lần `SE edit --form D`, với `old` là "Trước" và `new` là "Sau" (hoặc bản PO sửa). `SE` chặn chỗ nào thì chỗ đó làm đổi nghĩa: bỏ chỗ đó ra khỏi lần sửa, báo PO, và chuyển nó thành ghi chú C.
|
|
39
|
+
2. Có chỗ nào được sửa (A hoặc B) thì thêm **một** dòng vào cuối bảng Lịch sử thay đổi bằng `SE edit D`, **giữ nguyên version**: `| {version} | {ngày} | Rà hình thức: sửa {k} chỗ (chính tả, câu chữ, thuật ngữ, khuôn). Không đổi nội dung |`.
|
|
40
|
+
3. `SE set D reviewed={version} updated={ngày}`.
|
|
41
|
+
- PRD đang `approved` thì `SE` kiểm lại mọi điều kiện duyệt. Báo lỗi thì sửa theo lỗi, thường là một dòng sai khuôn còn sót, rồi chạy lại.
|
|
42
|
+
- PRD đang `draft`: không còn `🤖`, không còn câu hỏi mở thì hỏi *"Duyệt bản v{version} không?"* và duyệt theo luật 5 (Duyệt) trong Bước 3 của lệnh.
|
|
43
|
+
4. Còn ghi chú C thì nhắc PO một dòng. Ghi chú **không chặn** duyệt.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Dành cho agent rà
|
|
48
|
+
|
|
49
|
+
Bạn rà **hình thức** của PRD trong đúng phạm vi được giao. Phạm vi là toàn bộ PRD thì đọc cả file. Phạm vi hẹp thì đọc `SE uc D UC-…` và `SE section D <mã mục>`, cộng mục Tổng quan để biết thuật ngữ trong phần Khái niệm. `SE` = `python .fw/core/tools/spec_edit.py`.
|
|
50
|
+
|
|
51
|
+
**Ba loại:**
|
|
52
|
+
|
|
53
|
+
| Loại | Gồm | Làm gì |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| **A. Tự sửa** | Chính tả, dấu tiếng Việt, dấu câu, khoảng trắng thừa, bảng vỡ cột, danh sách lệch | Sửa luôn bằng **một** lần `SE edit --form D`. `SE` chặn chỗ nào thì chuyển chỗ đó sang B hoặc C |
|
|
56
|
+
| **B. Đề xuất** | Câu khó hiểu (quá dài, nhiều mệnh đề lồng nhau, đại từ không rõ chỉ ai, phủ định kép) · một khái niệm gọi hai tên, hoặc sai so với glossary và phần Khái niệm · dòng sai khuôn, ví dụ dòng Nền tảng ghi bằng lời thay vì mã `web` · `app` · `system` | **Không sửa.** Ghi vào file để PO quyết |
|
|
57
|
+
| **C. Ghi chú** | Chỗ chỉ sửa được nếu đổi nghĩa: số, điều kiện, phạm vi, thêm hoặc bớt ý | **Không sửa.** Ghi một dòng |
|
|
58
|
+
|
|
59
|
+
**Không làm:** rà nội dung nghiệp vụ (đó là việc của refine), đổi mã, đổi số, đổi dấu `🤖`/`✅`, đổi `(nguồn: …)`, sửa phần ngoài phạm vi. Viết lại câu thì giữ đúng mọi ý và mọi giá trị của câu cũ.
|
|
60
|
+
|
|
61
|
+
**Ghi file** `W/review-{version}.md` bằng `SE create --replace`, đúng khuôn sau:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
## Đã sửa (A)
|
|
65
|
+
- {mã mục}: "{trước}" → "{sau}"
|
|
66
|
+
|
|
67
|
+
## Đề xuất (B)
|
|
68
|
+
### R01 · {câu chữ | thuật ngữ | khuôn} · {mã mục}
|
|
69
|
+
- Trước: "{nguyên văn trong PRD, đủ dài để chỉ xuất hiện một lần}"
|
|
70
|
+
- Sau: "{…}"
|
|
71
|
+
- Lý do: {…}
|
|
72
|
+
|
|
73
|
+
## Ghi chú (C)
|
|
74
|
+
- {mã mục}: {vấn đề} → cần `/prd … change`
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Mục nào không có gì thì ghi `Không có`. Sau đó **chỉ trả về một dòng**: *"sửa {a} · đề xuất {b} · ghi chú {c} → {file}"*.
|
package/templates/prd.md
CHANGED
|
@@ -7,6 +7,7 @@ open_questions: 0
|
|
|
7
7
|
approved_by: — # người duyệt PRD; bắt buộc có khi status: approved
|
|
8
8
|
approved_at: — # YYYY-MM-DD
|
|
9
9
|
refined: — # version đã refine xong (/prd <EPIC-ID> refine); bắt buộc = version khi duyệt
|
|
10
|
+
reviewed: — # version đã rà hình thức xong (/prd <EPIC-ID> review, sau refine); bắt buộc = version khi duyệt
|
|
10
11
|
updated: {YYYY-MM-DD}
|
|
11
12
|
---
|
|
12
13
|
|
|
@@ -21,7 +22,7 @@ updated: {YYYY-MM-DD}
|
|
|
21
22
|
|
|
22
23
|
- **Mục tiêu:** {…}
|
|
23
24
|
- **Actor:** {ACT-xx (chính) · ACT-yy (phụ)}
|
|
24
|
-
- **Nền tảng:** {web · app — nơi người dùng dùng tính năng;
|
|
25
|
+
- **Nền tảng:** {web · app · system — chỉ ghi mã: web, app (nơi người dùng dùng tính năng), system (luồng tự động, không có màn hình); giải thích sau dấu —. Là mặc định cho mọi UC}
|
|
25
26
|
- **Trong phạm vi:**
|
|
26
27
|
- {…}
|
|
27
28
|
- **Ngoài phạm vi:**
|
|
@@ -53,7 +54,7 @@ flowchart TD
|
|
|
53
54
|
|
|
54
55
|
- **Jira:** {key hoặc —}
|
|
55
56
|
- **Actor:** {ACT-xx}
|
|
56
|
-
- **Nền tảng:** {chỉ ghi khi UC này chạy khác Tổng quan, ví dụ
|
|
57
|
+
- **Nền tảng:** {chỉ ghi khi UC này chạy khác Tổng quan, ví dụ `app` hoặc `system — luồng tự động nhận đơn`; giống thì bỏ dòng này}
|
|
57
58
|
- **Điều kiện trước:** {…}
|
|
58
59
|
- **Kết quả sau:** {…}
|
|
59
60
|
|
package/tools/spec_edit.py
CHANGED
|
@@ -11,9 +11,10 @@ Cách dùng (nội dung truyền qua stdin):
|
|
|
11
11
|
spec_edit.py --check
|
|
12
12
|
spec_edit.py create <file> stdin = toàn bộ nội dung; lỗi nếu file đã có
|
|
13
13
|
spec_edit.py edit <file> stdin = JSON [{"old": "...", "new": "...", "all": false}]
|
|
14
|
+
spec_edit.py edit --form <file> như edit, nhưng chặn mọi chỗ sửa làm đổi số, mã, dấu, nguồn (/prd review)
|
|
14
15
|
spec_edit.py set <file> key=value… sửa frontmatter
|
|
15
16
|
spec_edit.py pending <file> liệt kê các dòng còn dấu 🤖 (kèm số dòng)
|
|
16
|
-
spec_edit.py confirm <file> <mã>… đổi 🤖 → ✅ trên dòng chứa mã (vd AC2 BR8b ACT-03)
|
|
17
|
+
spec_edit.py confirm <file> <mã>… đổi 🤖 → ✅ trên dòng chứa mã (vd AC2 BR8b ACT-03), hoặc theo số dòng L24
|
|
17
18
|
spec_edit.py section <file> <mã> in đúng một mục theo mã `<!-- sec:… -->` (vd constraints)
|
|
18
19
|
spec_edit.py upgrade <file> gắn mã mục cho file theo khuôn cũ (chạy một lần)
|
|
19
20
|
spec_edit.py next-uc <thư mục specs> in mã UC kế tiếp (quét mọi prd.md)
|
|
@@ -110,7 +111,20 @@ def cmd_create(path, replace=False):
|
|
|
110
111
|
print("đã tạo " + path)
|
|
111
112
|
|
|
112
113
|
|
|
113
|
-
|
|
114
|
+
# Sửa hình thức (/prd review): không được đổi số, mã, dấu 🤖/✅, gạch ngang, nguồn — những thứ mang nghĩa.
|
|
115
|
+
FORM_KEEP = re.compile("UC-\\d{3}(?:-(?:BR|AC|SC)\\d+)?|(?:CON|ACT)-\\d+|\\b(?:BR|AC|F|J)\\d+[a-z]?\\b|\\d+(?:[.,]\\d+)*"
|
|
116
|
+
"|[\U0001F916✅]|~~|\\(nguồn:[^)]*\\)")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def form_changes(old, new):
|
|
120
|
+
"""Những thứ mang nghĩa bị thêm / mất khi đổi old → new. Rỗng = chỉ đổi hình thức."""
|
|
121
|
+
a, b = sorted(FORM_KEEP.findall(old)), sorted(FORM_KEEP.findall(new))
|
|
122
|
+
lost = [x for x in a if a.count(x) > b.count(x)]
|
|
123
|
+
added = [x for x in b if b.count(x) > a.count(x)]
|
|
124
|
+
return sorted(set(lost)), sorted(set(added))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def cmd_edit(path, form=False):
|
|
114
128
|
try:
|
|
115
129
|
edits = json.loads(stdin_text())
|
|
116
130
|
except json.JSONDecodeError as e:
|
|
@@ -131,6 +145,12 @@ def cmd_edit(path):
|
|
|
131
145
|
if n > 1 and not replace_all:
|
|
132
146
|
fail("chỗ sửa #%d: đoạn cần sửa xuất hiện %d lần. Thêm ngữ cảnh cho đoạn `old` đủ duy nhất, "
|
|
133
147
|
"hoặc đặt \"all\": true.\n old = %r" % (i, n, old[:120]))
|
|
148
|
+
if form:
|
|
149
|
+
lost, added = form_changes(old, e["new"])
|
|
150
|
+
if lost or added:
|
|
151
|
+
fail("chỗ sửa #%d không phải sửa hình thức: %s%s. Số, mã, dấu 🤖/✅, gạch ngang và nguồn phải giữ nguyên. "
|
|
152
|
+
"Sửa làm đổi nghĩa thì ghi thành ghi chú, đi qua `/prd … change`.\n old = %r"
|
|
153
|
+
% (i, ("mất " + " ".join(lost)) if lost else "", (" · thêm " + " ".join(added)) if added else "", old[:120]))
|
|
134
154
|
before = sec_keys(text)
|
|
135
155
|
for e in edits:
|
|
136
156
|
text = text.replace(e["old"], e["new"]) if e.get("all") else text.replace(e["old"], e["new"], 1)
|
|
@@ -163,8 +183,9 @@ def validate(ftype, key, value):
|
|
|
163
183
|
fail("approved_at=%s không hợp lệ. Định dạng YYYY-MM-DD, hoặc — nếu chưa duyệt" % value)
|
|
164
184
|
if key in ("version", "prd_version") and not VERSION.match(value):
|
|
165
185
|
fail("%s=%s không hợp lệ. Định dạng số.số, ví dụ 1.0, 1.1" % (key, value))
|
|
166
|
-
if key
|
|
167
|
-
fail("
|
|
186
|
+
if key in ("refined", "reviewed") and value != "—" and not VERSION.match(value):
|
|
187
|
+
fail("%s=%s không hợp lệ. Là version PRD đã %s xong (vd 1.1), hoặc —"
|
|
188
|
+
% (key, value, "refine" if key == "refined" else "rà hình thức"))
|
|
168
189
|
|
|
169
190
|
|
|
170
191
|
# ── Refine: finding critical / major chưa có quyết định thì chưa được đi tiếp ──────────────
|
|
@@ -240,6 +261,15 @@ def guard_refined(path, block):
|
|
|
240
261
|
% (", ".join(pending), rp))
|
|
241
262
|
|
|
242
263
|
|
|
264
|
+
def guard_reviewed(block):
|
|
265
|
+
"""PRD chỉ được duyệt khi đã rà hình thức (/prd review) đúng version đang có. Review đi sau refine."""
|
|
266
|
+
ver = re.search(r"^version:\s*(\S+)", block, re.M)
|
|
267
|
+
rev = re.search(r"^reviewed:\s*(\S+)", block, re.M)
|
|
268
|
+
if not rev or not ver or rev.group(1) != ver.group(1):
|
|
269
|
+
fail("không đặt được status=approved: bản v%s chưa rà hình thức (reviewed=%s). Chạy `/prd <EPIC-ID> review` trước."
|
|
270
|
+
% (ver.group(1) if ver else "?", rev.group(1) if rev else "chưa có"))
|
|
271
|
+
|
|
272
|
+
|
|
243
273
|
def guard_done(ftype, block, body, path):
|
|
244
274
|
"""Chặn đặt trạng thái 'đã xong' khi còn mục chờ PO — AI không được tự khẳng định."""
|
|
245
275
|
done = DONE_STATUS.get(ftype)
|
|
@@ -260,12 +290,11 @@ def guard_done(ftype, block, body, path):
|
|
|
260
290
|
fail("không đặt được status=%s: chưa có %s. Đặt cùng lệnh, ví dụ: "
|
|
261
291
|
"set <file> status=%s approved_by=<tên> approved_at=YYYY-MM-DD" % (done, field, done))
|
|
262
292
|
if ftype == "prd":
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
fail("không đặt được status=approved: chưa khai Nền tảng cho %s. Thêm dòng `- **Nền tảng:** web · app` "
|
|
267
|
-
"ở Tổng quan (mặc định cho cả PRD), hoặc trong UC chạy khác mặc định." % ", ".join(no_platform))
|
|
293
|
+
problems = platform_problems(read(path))
|
|
294
|
+
if problems:
|
|
295
|
+
fail("không đặt được status=approved:\n - " + "\n - ".join(problems))
|
|
268
296
|
guard_refined(path, block)
|
|
297
|
+
guard_reviewed(block)
|
|
269
298
|
if ftype == "bdd":
|
|
270
299
|
guard_bdd(path, block)
|
|
271
300
|
if ftype == "prd-refine" and undecided_findings(body):
|
|
@@ -685,26 +714,62 @@ def prd_items(text):
|
|
|
685
714
|
return out
|
|
686
715
|
|
|
687
716
|
|
|
717
|
+
# web · app: nơi người dùng dùng tính năng · system: luồng tự động, không có màn hình (job, tích hợp).
|
|
718
|
+
PLATFORMS = ("web", "app", "system")
|
|
719
|
+
PLATFORM_RULE = ("dòng Nền tảng chỉ ghi mã `web` · `app` · `system` (luồng tự động, không có màn hình), "
|
|
720
|
+
"giải thích để sau dấu ` — `. Ví dụ: `- **Nền tảng:** system — luồng tự động nhận đơn từ EP-03`")
|
|
721
|
+
|
|
722
|
+
|
|
688
723
|
def platform_values(value):
|
|
689
|
-
|
|
690
|
-
|
|
724
|
+
"""(các mã nền tảng, các chữ không phải mã). Phần sau ` — ` hoặc `(` là lời giải thích."""
|
|
725
|
+
head = re.split(r"\(| — | – | - ", value.replace("✅", "").replace("\U0001F916", ""))[0]
|
|
726
|
+
words = [w for w in re.split(r"[·,/\s]+", head.strip()) if w]
|
|
727
|
+
return [w for w in words if w in PLATFORMS], [w for w in words if w not in PLATFORMS]
|
|
691
728
|
|
|
692
729
|
|
|
693
|
-
def
|
|
694
|
-
"""
|
|
730
|
+
def uc_platform_line(text, uc):
|
|
731
|
+
"""Giá trị dòng `Nền tảng` áp cho UC: trong UC, không có thì ở Tổng quan. None = chưa khai."""
|
|
695
732
|
lines = text.split("\n")
|
|
696
733
|
span = uc_span(lines, uc)
|
|
697
734
|
if span:
|
|
698
735
|
for l in lines[span[0]:span[1]]:
|
|
699
736
|
m = PLATFORM_LINE.match(l)
|
|
700
737
|
if m:
|
|
701
|
-
return
|
|
702
|
-
|
|
703
|
-
for l in overview.split("\n"):
|
|
738
|
+
return m.group(1)
|
|
739
|
+
for l in (section_text(text, "overview") or "").split("\n"):
|
|
704
740
|
m = PLATFORM_LINE.match(l)
|
|
705
741
|
if m:
|
|
706
|
-
return
|
|
707
|
-
return
|
|
742
|
+
return m.group(1)
|
|
743
|
+
return None
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
def uc_platforms(text, uc):
|
|
747
|
+
"""Mã nền tảng của UC. [] = chưa khai, hoặc khai sai khuôn (xem platform_problems)."""
|
|
748
|
+
value = uc_platform_line(text, uc)
|
|
749
|
+
if value is None:
|
|
750
|
+
return []
|
|
751
|
+
good, bad = platform_values(value)
|
|
752
|
+
return [] if bad else good
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def platform_problems(text):
|
|
756
|
+
"""Mỗi UC còn dùng phải có nền tảng đúng khuôn. Trả về danh sách lỗi (rỗng = ổn)."""
|
|
757
|
+
missing, wrong = [], []
|
|
758
|
+
for uc in re.findall(r"^###\s+(UC-\d{3})(?![\d-])", text, re.M):
|
|
759
|
+
value = uc_platform_line(text, uc)
|
|
760
|
+
if value is None:
|
|
761
|
+
missing.append(uc)
|
|
762
|
+
continue
|
|
763
|
+
good, bad = platform_values(value)
|
|
764
|
+
if bad or not good:
|
|
765
|
+
wrong.append('%s ("%s")' % (uc, value.strip()[:50]))
|
|
766
|
+
out = []
|
|
767
|
+
if missing:
|
|
768
|
+
out.append("chưa khai Nền tảng cho %s. Thêm dòng `- **Nền tảng:** web · app` ở Tổng quan (mặc định cho cả PRD), "
|
|
769
|
+
"hoặc trong UC chạy khác mặc định." % ", ".join(missing))
|
|
770
|
+
if wrong:
|
|
771
|
+
out.append("Nền tảng ghi sai khuôn ở %s: %s" % (", ".join(wrong), PLATFORM_RULE))
|
|
772
|
+
return out
|
|
708
773
|
|
|
709
774
|
|
|
710
775
|
def prd_version(text):
|
|
@@ -790,9 +855,11 @@ def bdd_report(feature, prd=None):
|
|
|
790
855
|
items = prd_items(ptext)
|
|
791
856
|
own = [c for c, alive in items.items() if alive and c.startswith(uc + "-")]
|
|
792
857
|
platforms = uc_platforms(ptext, uc)
|
|
793
|
-
if
|
|
858
|
+
if uc_platform_line(ptext, uc) is None:
|
|
794
859
|
errors.append("PRD chưa khai Nền tảng cho %s (dòng `- **Nền tảng:** web · app` ở Tổng quan, hoặc trong UC). "
|
|
795
860
|
"Chạy `/prd <EPIC-ID> change khai nền tảng …` trước." % uc)
|
|
861
|
+
elif not platforms:
|
|
862
|
+
errors.append("Nền tảng của %s trong PRD ghi sai khuôn: %s. Sửa PRD bằng `/prd <EPIC-ID> review`." % (uc, PLATFORM_RULE))
|
|
796
863
|
scenarios, removed = parse_feature(text)
|
|
797
864
|
if not scenarios:
|
|
798
865
|
errors.append("file chưa có kịch bản nào")
|
|
@@ -860,7 +927,7 @@ def cmd_bddcheck(feature, prd=None):
|
|
|
860
927
|
if st:
|
|
861
928
|
print("%s: %d kịch bản (%d đã bỏ) · AC %d/%d · BR %d/%d · %s" % (
|
|
862
929
|
uc_of(feature), st["scenarios"], st["removed"], st["ac"][0], st["ac"][1], st["br"][0], st["br"][1],
|
|
863
|
-
" · ".join("%s %d" % kv for kv in st["platforms"].items()) or "chưa
|
|
930
|
+
" · ".join("%s %d" % kv for kv in st["platforms"].items()) or "chưa có nền tảng hợp lệ"), flush=True)
|
|
864
931
|
ptext = read(prd or prd_of(feature))
|
|
865
932
|
todo = [u for u in re.findall(r"^###\s+(UC-\d{3})(?![\d-])", ptext, re.M)
|
|
866
933
|
if not os.path.isfile(os.path.join(os.path.dirname(os.path.abspath(feature)), u + ".feature"))]
|
|
@@ -881,6 +948,10 @@ def guard_bdd(path, block):
|
|
|
881
948
|
ps = re.search(r"^status:\s*(\S+)", ptext, re.M)
|
|
882
949
|
if not ps or ps.group(1) != "approved":
|
|
883
950
|
fail("không đặt được status=approved: PRD chưa approved. Duyệt PRD trước.")
|
|
951
|
+
rv = re.search(r"^reviewed:\s*(\S+)", ptext, re.M)
|
|
952
|
+
if not rv or rv.group(1) != prd_version(ptext):
|
|
953
|
+
fail("không đặt được status=approved: PRD v%s chưa rà hình thức. Chạy `/prd <EPIC-ID> review` trước."
|
|
954
|
+
% prd_version(ptext))
|
|
884
955
|
bv = re.search(r"^prd_version:\s*(\S+)", block, re.M)
|
|
885
956
|
if not bv or bv.group(1) != prd_version(ptext):
|
|
886
957
|
fail("không đặt được status=approved: BDD viết theo PRD v%s, PRD đang ở v%s. Chạy `/bdd <UC>` để bù trước."
|
|
@@ -908,7 +979,12 @@ def cmd_context(path, ucs):
|
|
|
908
979
|
if not span:
|
|
909
980
|
fail("không có %s trong %s" % (uc, path))
|
|
910
981
|
mention = re.compile(r"%s(?![\d-])" % re.escape(uc))
|
|
911
|
-
|
|
982
|
+
fm = lambda k: (re.search(r"^%s:\s*(\S+)" % k, text, re.M) or [None, "—"])[1]
|
|
983
|
+
value = uc_platform_line(text, uc)
|
|
984
|
+
plat = "chưa khai" if value is None else (" · ".join(uc_platforms(text, uc)) or "sai khuôn")
|
|
985
|
+
# Dòng đầu đủ để /bdd chọn chế độ (Bước 2) mà không phải đọc frontmatter riêng.
|
|
986
|
+
out = ["# PRD %s — v%s · status %s · reviewed %s · nền tảng của %s: %s"
|
|
987
|
+
% (path, prd_version(text), fm("status"), fm("reviewed"), uc, plat)]
|
|
912
988
|
out.append(section_text(text, "overview") or "(PRD chưa có mục Tổng quan)")
|
|
913
989
|
flow = (section_text(text, "userflow") or "").split("\n")
|
|
914
990
|
rows = [l for l in flow if l.startswith("| J") and mention.search(l)]
|
|
@@ -1073,6 +1149,14 @@ def cmd_confirm(path, codes):
|
|
|
1073
1149
|
# Mã khớp nguyên từ: AC2 không được khớp nhầm AC20.
|
|
1074
1150
|
targets = {}
|
|
1075
1151
|
for code in codes:
|
|
1152
|
+
# Dòng không có mã (vd dòng Nền tảng): xác nhận theo số dòng mà `pending` in ra, viết L24.
|
|
1153
|
+
ln = re.match(r"^L(\d+)$", code)
|
|
1154
|
+
if ln:
|
|
1155
|
+
i = int(ln.group(1)) - 1
|
|
1156
|
+
if not 0 <= i < len(lines) or not is_pending(lines[i]):
|
|
1157
|
+
fail("dòng %s không phải dòng 🤖. Chạy `pending` để xem số dòng các mục còn chờ." % ln.group(1))
|
|
1158
|
+
targets[i] = code
|
|
1159
|
+
continue
|
|
1076
1160
|
pat = re.compile(r"(?<![\w-])%s(?![\w])" % re.escape(code))
|
|
1077
1161
|
hits = [i for i, line in enumerate(lines) if is_pending(line) and pat.search(line)]
|
|
1078
1162
|
if not hits:
|
|
@@ -1108,7 +1192,12 @@ def main(argv):
|
|
|
1108
1192
|
else:
|
|
1109
1193
|
cmd_create(path)
|
|
1110
1194
|
elif cmd == "edit":
|
|
1111
|
-
|
|
1195
|
+
if path == "--form":
|
|
1196
|
+
if not rest:
|
|
1197
|
+
fail("cách dùng: edit --form <file>", 2)
|
|
1198
|
+
cmd_edit(rest[0], form=True)
|
|
1199
|
+
else:
|
|
1200
|
+
cmd_edit(path)
|
|
1112
1201
|
elif cmd == "pending":
|
|
1113
1202
|
cmd_pending(path)
|
|
1114
1203
|
elif cmd == "confirm":
|