@educa-corp/fw 0.10.0 → 0.11.1
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 +19 -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 +55 -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/lenses.md +7 -3
- package/ref/prd/refine.md +18 -5
- package/ref/prd/review.md +85 -0
- package/templates/prd.md +3 -2
- package/tools/spec_edit.py +172 -23
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
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.1 — 2026-10-02
|
|
7
|
+
|
|
8
|
+
Sửa vòng lặp *review → change → refine → finding mới*. Lần chạy đầu trên LMS: review gợi ý đổi tên một trạng thái, dẫn tới `change` rồi refine ra 8 finding, tốn khoảng 4,4M token mà không có thay đổi nghiệp vụ nào.
|
|
9
|
+
|
|
10
|
+
- **`/prd review` không đưa ý kiến về nội dung**: không đề xuất đổi tên một khái niệm đang được gọi nhất quán, không gợi ý `/prd change`. Thuật ngữ chỉ đề xuất sửa khi cùng một khái niệm mà gọi bằng hai tên.
|
|
11
|
+
- **`--form` chặn một chỗ sửa thì AI hỏi bạn có đổi nghĩa không.** Không đổi nghĩa thì áp ngay, vẫn giữ version. Sửa chỗ trỏ "xem mục 5" → "mục 6" không còn bị chặn.
|
|
12
|
+
- **Refine sau `/prd change` chỉ báo lỗi do chính thay đổi gây ra**, cộng với critical. Vấn đề đã có ở bản đã duyệt được gom vào *"Có từ trước, để sau"*, không phải quyết, không chặn duyệt.
|
|
13
|
+
- **`spec_edit diff <prd>`**: in phần đã đổi so với bản `approved` gần nhất trong git, kèm số UC bị chạm. Refine và review sau change dùng lệnh này để lấy đúng phạm vi.
|
|
14
|
+
|
|
15
|
+
## 0.11.0 — 2026-10-02
|
|
16
|
+
|
|
17
|
+
- **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ú.
|
|
18
|
+
- PRD đã duyệt từ trước: chạy `/prd <EPIC-ID> review` một lần, rồi mới chạy `/bdd`.
|
|
19
|
+
- **`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.
|
|
20
|
+
- **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.
|
|
21
|
+
- **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.
|
|
22
|
+
- `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.
|
|
23
|
+
- `/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`.
|
|
24
|
+
|
|
6
25
|
## 0.10.0 — 2026-10-02
|
|
7
26
|
|
|
8
27
|
- **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.1**. 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 đúng phần vừa đổi** so với bản đã duyệt gần nhất, và **chỉ báo lỗi do chính thay đổi gây ra** (cộng với critical). Lỗi đã có từ trước trong mục bị chạm chỉ được ghi vào *"Có từ trước, để sau"*, không cần bạn quyết. Agent vẫn đọc cả PRD để thấy ảnh hưởng lan sang chỗ khác. 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,55 @@
|
|
|
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ú** | Lỗi hình thức mà sửa thì chắc chắn đổi nghĩa | Không sửa, không chặn duyệt. Chỉ để bạn biết |
|
|
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.
|
|
40
|
+
- Bị chặn mà thật ra không đổi nghĩa, ví dụ thêm mã `EP-03` vào tiêu đề cho khớp với bảng, thì **AI hỏi bạn**. Bạn trả lời *"không đổi nghĩa"* thì AI áp luôn, vẫn trong cùng version.
|
|
41
|
+
- **Review không đề xuất đổi tên khái niệm** đang được gọi nhất quán, và không gợi ý `/prd change`. Đặt tên là việc của nghiệp vụ. Lần đầu chạy ở LMS, review gợi ý đổi tên một trạng thái; lần đổi đó kéo theo refine 8 finding, tốn khoảng 4M token, và tên mới bị refine chỉ ra là sai nghĩa.
|
|
42
|
+
- Nhờ vậy, review không cần refine lại sau đó.
|
|
43
|
+
|
|
44
|
+
## Phạm vi
|
|
45
|
+
|
|
46
|
+
| Khi nào | Rà gì |
|
|
47
|
+
|---|---|
|
|
48
|
+
| PRD chưa từng review | Toàn bộ PRD |
|
|
49
|
+
| Sau `/prd EP-01 change` → refine | Chỉ phần vừa đổi so với bản đã duyệt gần nhất |
|
|
50
|
+
|
|
51
|
+
Thứ tự sau mỗi lần đổi PRD: `change` → `refine` → `review` → duyệt.
|
|
52
|
+
|
|
53
|
+
## PRD đã duyệt trước bản 0.11.0
|
|
54
|
+
|
|
55
|
+
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/lenses.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Bạn là **một** lăng kính. Chỉ soi đúng phần của lăng kính mình, trong đúng **phạm vi** và **ngưỡng báo** được giao. Tìm ít nhưng đúng còn hơn tìm nhiều mà sai.
|
|
4
4
|
|
|
5
|
+
**Lượt sau `/prd change`** (phạm vi là kết quả `SE diff`): **chỉ báo lỗi do chính thay đổi gây ra**. Ví dụ: mâu thuẫn mới giữa phần vừa đổi và phần khác, chỗ phải đổi theo mà chưa đổi, nghĩa mới làm hỏng một BR hay AC khác. Ngoài ra chỉ báo thêm critical. **Không báo** vấn đề đã có y nguyên ở bản đã duyệt, kể cả khi nó nằm trong mục vừa bị chạm, vì bản đó đã qua refine. Vẫn đọc toàn bộ PRD để thấy ảnh hưởng lan sang chỗ khác.
|
|
6
|
+
|
|
5
7
|
## Luật chung
|
|
6
8
|
|
|
7
9
|
1. **Viết bằng ngôn ngữ nghiệp vụ.** Mắt nhìn có thể là kỹ thuật, nhưng finding và đề xuất phải nói *nghiệp vụ còn thiếu hay chưa rõ điều gì*. Không đề xuất API, bảng dữ liệu, retry, timeout hay thư viện.
|
|
@@ -78,12 +80,14 @@ Bạn nhận: PRD `D`, file refine `R` (có thể chưa có), thư mục `W` ch
|
|
|
78
80
|
- Finding có nêu lại một điểm **đã có quyết định** trong `R` không?
|
|
79
81
|
- Mức độ có đúng định nghĩa không?
|
|
80
82
|
|
|
81
|
-
|
|
83
|
+
- **Lượt sau `/prd change`:** vấn đề này **đã có ở bản đã duyệt** chưa? Xem phần `-` trong `SE diff D`, và phần PRD không nằm trong diff. Đã có từ trước thì đây **không phải** lỗi do thay đổi.
|
|
84
|
+
|
|
85
|
+
Kết luận cho mỗi finding: `ĐÚNG` · `SAI` (bỏ) · `HẠ MỨC` (giữ, đổi mức) · `CÓ TỪ TRƯỚC` (không thành finding; ghi một dòng vào mục *Có từ trước, để sau* ở cuối lượt, không cần PO quyết, không chặn duyệt).
|
|
82
86
|
|
|
83
87
|
**2. Gộp** các finding ĐÚNG trùng nhau (cùng mục, cùng vấn đề), giữ bản rõ nhất. Hai đề xuất trái ngược nhau thì giữ cả hai trong **một** finding, và ghi rõ là cần PO chọn.
|
|
84
88
|
|
|
85
89
|
**3. Sắp xếp** theo mức độ (critical → major → minor), sau đó theo lăng kính (QA → DEV → SA → Phạm vi). **Đánh mã F theo đúng thứ tự đó**, bắt đầu từ mã F kế tiếp.
|
|
86
90
|
|
|
87
|
-
**4. Ghi vào `R`.** Chưa có `R` thì `SE create R` theo khuôn `.fw/core/templates/prd-refine.md`. Đã có thì `SE edit` thêm mục `## Lượt {n} — v{version} <!-- sec:round-{n} -->` ở **cuối**. **Không sửa các lượt cũ.** Thêm một dòng vào bảng Tóm tắt, rồi `SE set R prd_version={version} round={n} status=pending`. Mỗi finding theo đúng mẫu trong khuôn, `Quyết định: chờ`.
|
|
91
|
+
**4. Ghi vào `R`.** Chưa có `R` thì `SE create R` theo khuôn `.fw/core/templates/prd-refine.md`. Đã có thì `SE edit` thêm mục `## Lượt {n} — v{version} <!-- sec:round-{n} -->` ở **cuối**. **Không sửa các lượt cũ.** Thêm một dòng vào bảng Tóm tắt, rồi `SE set R prd_version={version} round={n} status=pending`. Mỗi finding theo đúng mẫu trong khuôn, `Quyết định: chờ`. Có finding `CÓ TỪ TRƯỚC` thì thêm `### Có từ trước, để sau` ở cuối lượt, mỗi vấn đề một dòng: *"- {mã mục}: {vấn đề}"*.
|
|
88
92
|
|
|
89
|
-
**5. Trả về một dòng:** *"thô {a} · bỏ {b} · hạ mức {c} · còn {d} (critical {x} · major {y} · minor {z}) → {R}"*.
|
|
93
|
+
**5. Trả về một dòng:** *"thô {a} · bỏ {b} · hạ mức {c} · có từ trước {e} · còn {d} (critical {x} · major {y} · minor {z}) → {R}"*.
|
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** |
|
|
22
|
+
| `refined` có giá trị nhưng < `version` (PRD đã qua `/prd change`) | **1** | **Đúng phần đã đổi**: `SE diff D` (so với bản `approved` gần nhất trong git, có sẵn số UC bị chạm). Không có bản đã duyệt trong git thì dùng các dòng Lịch sử thay đổi có version lớn hơn `refined`. Ngưỡng: **chỉ lỗi do chính thay đổi gây ra, hoặc critical** (xem đầu `lenses.md`) |
|
|
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,16 +27,29 @@ 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. Số UC bị chạm lấy ở dòng đầu của `SE diff D`. 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
|
|
|
38
51
|
1. Đọc `SE section R summary` và danh sách tiêu đề finding của lượt này. Danh sách đã được agent kiểm chứng **sắp xếp sẵn** theo mức độ, rồi theo lăng kính (QA → DEV → SA → Phạm vi).
|
|
39
|
-
2. Trình **một bảng tóm tắt** gồm mã · mức · lăng kính · mục · một dòng vấn đề, đúng theo thứ tự đó. Kèm số finding thô, số bị bỏ ở bước kiểm chứng,
|
|
52
|
+
2. Trình **một bảng tóm tắt** gồm mã · mức · lăng kính · mục · một dòng vấn đề, đúng theo thứ tự đó. Kèm số finding thô, số bị bỏ ở bước kiểm chứng, số bị hạ mức, và số vấn đề *có từ trước* (chỉ báo số, không cần PO quyết).
|
|
40
53
|
3. Nhắc PO cách quyết:
|
|
41
54
|
- *"Chi tiết xem {R}. Quyết theo mã: 'nhận F01, F03 · bác F02 vì … · F04 sửa thành … · hoãn F05'."*
|
|
42
55
|
- *"Quyết theo nhóm được, áp cho major và minor: 'nhận tất cả minor' · 'nhận hết UC-003 trừ F12' · 'bác F20–F24 vì …'. Critical phải quyết từng mã."*
|
|
@@ -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,85 @@
|
|
|
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: **đúng phần đã đổi**, lấy bằng `SE diff D` (so với bản `approved` gần nhất trong git). Không có bản đã duyệt trong git thì dùng các dòng Lịch sử thay đổi 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):** mỗi chỗ một dòng, để PO biết. **Không gợi ý `/prd … change`**, và nói rõ ghi chú không chặn duyệt. Đổi PRD là quyết định nghiệp vụ của PO, review không tự đề xuất.
|
|
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).
|
|
39
|
+
- `SE` chặn chỗ nào (vì chỗ đó đổi số, mã, dấu hoặc nguồn) thì **hỏi PO ngay**, kèm đúng thứ bị đổi. Ví dụ: *"R07 thêm mã EP-03 vào tiêu đề J7, cho khớp với tên trong bảng. Chỗ này có đổi nghĩa không?"*
|
|
40
|
+
- PO nói **không đổi nghĩa**: áp chỗ đó bằng `SE edit D` (không `--form`), vẫn trong cùng version.
|
|
41
|
+
- PO nói **có**: không sửa, chuyển thành ghi chú C.
|
|
42
|
+
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 |`.
|
|
43
|
+
3. `SE set D reviewed={version} updated={ngày}`.
|
|
44
|
+
- 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.
|
|
45
|
+
- 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.
|
|
46
|
+
4. Còn ghi chú C thì nhắc PO một dòng. Ghi chú **không chặn** duyệt.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Dành cho agent rà
|
|
51
|
+
|
|
52
|
+
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`.
|
|
53
|
+
|
|
54
|
+
**Ba loại:**
|
|
55
|
+
|
|
56
|
+
| Loại | Gồm | Làm gì |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| **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 |
|
|
59
|
+
| **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) · **cùng một khái niệm mà gọi bằng hai tên** ở hai chỗ, hoặc sai so với glossary và phần Khái niệm (đề xuất dùng tên **đang dùng nhiều hơn**) · tham chiếu trỏ sai ("xem mục 5" trong khi đúng là mục 6) · dòng sai khuôn, ví dụ dòng Nền tảng ghi bằng lời thay vì mã `web` · `app` · `system`. A bị `SE` chặn thì cũng đưa vào đây | **Không sửa.** Ghi vào file để PO quyết |
|
|
60
|
+
| **C. Ghi chú** | Lỗi hình thức mà sửa thì chắc chắn đổi nghĩa: số, điều kiện, phạm vi | **Không sửa.** Ghi một dòng |
|
|
61
|
+
|
|
62
|
+
**Không làm:**
|
|
63
|
+
- Rà nội dung nghiệp vụ (việc của refine). Đổi mã, đổi số, đổi dấu `🤖`/`✅`, đổi `(nguồn: …)`. Sửa phần ngoài phạm vi.
|
|
64
|
+
- **Đề xuất đổi tên một khái niệm đang được gọi nhất quán**, dù bạn thấy tên chưa hay hoặc đã cũ. Ví dụ: "đang dùng mật khẩu mặc định" được gọi đúng một tên ở mọi chỗ thì để nguyên, không đề xuất. Đặt tên khái niệm là quyết định nội dung. Lần đổi tên như vậy ở LMS v1.8 đã tốn khoảng 3M token refine, và tên mới bị refine chỉ ra là sai nghĩa.
|
|
65
|
+
- Ghi ý kiến về nghiệp vụ vào C, hoặc gợi ý `/prd … change`.
|
|
66
|
+
|
|
67
|
+
Viết lại câu thì giữ đúng mọi ý và mọi giá trị của câu cũ.
|
|
68
|
+
|
|
69
|
+
**Ghi file** `W/review-{version}.md` bằng `SE create --replace`, đúng khuôn sau:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
## Đã sửa (A)
|
|
73
|
+
- {mã mục}: "{trước}" → "{sau}"
|
|
74
|
+
|
|
75
|
+
## Đề xuất (B)
|
|
76
|
+
### R01 · {câu chữ | thuật ngữ | khuôn} · {mã mục}
|
|
77
|
+
- Trước: "{nguyên văn trong PRD, đủ dài để chỉ xuất hiện một lần}"
|
|
78
|
+
- Sau: "{…}"
|
|
79
|
+
- Lý do: {…}
|
|
80
|
+
|
|
81
|
+
## Ghi chú (C)
|
|
82
|
+
- {mã mục}: {lỗi hình thức không sửa được mà không đổi nghĩa}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
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)
|
|
@@ -30,6 +31,7 @@ Cách dùng (nội dung truyền qua stdin):
|
|
|
30
31
|
spec_edit.py scenario <feature> <SC…>… in đúng các kịch bản đó
|
|
31
32
|
spec_edit.py sc <feature> stdin = JSON [{"id": mã, "new": khối} | {"after": mã | "end", "new": khối} | {"remove": mã, "version": "1.1"}]
|
|
32
33
|
spec_edit.py bddcheck <feature> [prd] kiểm khuôn, tag, độ phủ AC / BR của file BDD
|
|
34
|
+
spec_edit.py diff <prd> phần đã đổi so với bản `approved` gần nhất trong git, kèm UC bị chạm
|
|
33
35
|
|
|
34
36
|
Mã thoát: 0 = thành công · 1 = lỗi dữ liệu (không ghi gì) · 2 = sai cách dùng.
|
|
35
37
|
"""
|
|
@@ -110,7 +112,22 @@ def cmd_create(path, replace=False):
|
|
|
110
112
|
print("đã tạo " + path)
|
|
111
113
|
|
|
112
114
|
|
|
113
|
-
|
|
115
|
+
# 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.
|
|
116
|
+
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+)*"
|
|
117
|
+
"|[\U0001F916✅]|~~|\\(nguồn:[^)]*\\)")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def form_changes(old, new):
|
|
121
|
+
"""Những thứ mang nghĩa bị thêm / mất khi đổi old → new. Rỗng = chỉ đổi hình thức."""
|
|
122
|
+
# "xem mục 5" → "mục 6" là sửa chỗ trỏ tới tiêu đề, không phải đổi số nghiệp vụ.
|
|
123
|
+
ref = lambda s: re.sub(r"\b([Mm]ục)\s+\d+", r"\1 #", s)
|
|
124
|
+
a, b = sorted(FORM_KEEP.findall(ref(old))), sorted(FORM_KEEP.findall(ref(new)))
|
|
125
|
+
lost = [x for x in a if a.count(x) > b.count(x)]
|
|
126
|
+
added = [x for x in b if b.count(x) > a.count(x)]
|
|
127
|
+
return sorted(set(lost)), sorted(set(added))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def cmd_edit(path, form=False):
|
|
114
131
|
try:
|
|
115
132
|
edits = json.loads(stdin_text())
|
|
116
133
|
except json.JSONDecodeError as e:
|
|
@@ -131,6 +148,12 @@ def cmd_edit(path):
|
|
|
131
148
|
if n > 1 and not replace_all:
|
|
132
149
|
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
150
|
"hoặc đặt \"all\": true.\n old = %r" % (i, n, old[:120]))
|
|
151
|
+
if form:
|
|
152
|
+
lost, added = form_changes(old, e["new"])
|
|
153
|
+
if lost or added:
|
|
154
|
+
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. "
|
|
155
|
+
"Sửa làm đổi nghĩa thì ghi thành ghi chú, đi qua `/prd … change`.\n old = %r"
|
|
156
|
+
% (i, ("mất " + " ".join(lost)) if lost else "", (" · thêm " + " ".join(added)) if added else "", old[:120]))
|
|
134
157
|
before = sec_keys(text)
|
|
135
158
|
for e in edits:
|
|
136
159
|
text = text.replace(e["old"], e["new"]) if e.get("all") else text.replace(e["old"], e["new"], 1)
|
|
@@ -163,8 +186,9 @@ def validate(ftype, key, value):
|
|
|
163
186
|
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
187
|
if key in ("version", "prd_version") and not VERSION.match(value):
|
|
165
188
|
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("
|
|
189
|
+
if key in ("refined", "reviewed") and value != "—" and not VERSION.match(value):
|
|
190
|
+
fail("%s=%s không hợp lệ. Là version PRD đã %s xong (vd 1.1), hoặc —"
|
|
191
|
+
% (key, value, "refine" if key == "refined" else "rà hình thức"))
|
|
168
192
|
|
|
169
193
|
|
|
170
194
|
# ── Refine: finding critical / major chưa có quyết định thì chưa được đi tiếp ──────────────
|
|
@@ -240,6 +264,15 @@ def guard_refined(path, block):
|
|
|
240
264
|
% (", ".join(pending), rp))
|
|
241
265
|
|
|
242
266
|
|
|
267
|
+
def guard_reviewed(block):
|
|
268
|
+
"""PRD chỉ được duyệt khi đã rà hình thức (/prd review) đúng version đang có. Review đi sau refine."""
|
|
269
|
+
ver = re.search(r"^version:\s*(\S+)", block, re.M)
|
|
270
|
+
rev = re.search(r"^reviewed:\s*(\S+)", block, re.M)
|
|
271
|
+
if not rev or not ver or rev.group(1) != ver.group(1):
|
|
272
|
+
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."
|
|
273
|
+
% (ver.group(1) if ver else "?", rev.group(1) if rev else "chưa có"))
|
|
274
|
+
|
|
275
|
+
|
|
243
276
|
def guard_done(ftype, block, body, path):
|
|
244
277
|
"""Chặn đặt trạng thái 'đã xong' khi còn mục chờ PO — AI không được tự khẳng định."""
|
|
245
278
|
done = DONE_STATUS.get(ftype)
|
|
@@ -260,12 +293,11 @@ def guard_done(ftype, block, body, path):
|
|
|
260
293
|
fail("không đặt được status=%s: chưa có %s. Đặt cùng lệnh, ví dụ: "
|
|
261
294
|
"set <file> status=%s approved_by=<tên> approved_at=YYYY-MM-DD" % (done, field, done))
|
|
262
295
|
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))
|
|
296
|
+
problems = platform_problems(read(path))
|
|
297
|
+
if problems:
|
|
298
|
+
fail("không đặt được status=approved:\n - " + "\n - ".join(problems))
|
|
268
299
|
guard_refined(path, block)
|
|
300
|
+
guard_reviewed(block)
|
|
269
301
|
if ftype == "bdd":
|
|
270
302
|
guard_bdd(path, block)
|
|
271
303
|
if ftype == "prd-refine" and undecided_findings(body):
|
|
@@ -589,6 +621,61 @@ def cmd_decide(path):
|
|
|
589
621
|
print("đã ghi %d quyết định vào %s" % (len(ops), path))
|
|
590
622
|
|
|
591
623
|
|
|
624
|
+
def git_show(rev_path):
|
|
625
|
+
import subprocess
|
|
626
|
+
r = subprocess.run(["git", "show", rev_path], capture_output=True)
|
|
627
|
+
return r.stdout.decode("utf-8", "replace") if r.returncode == 0 else None
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def last_approved(path):
|
|
631
|
+
"""(commit, nội dung) của bản gần nhất trong git mà file đang `status: approved`. None nếu không có."""
|
|
632
|
+
import subprocess
|
|
633
|
+
r = subprocess.run(["git", "log", "--format=%H", "--", path], capture_output=True)
|
|
634
|
+
if r.returncode != 0:
|
|
635
|
+
return None
|
|
636
|
+
rel = "./" + os.path.relpath(path).replace("\\", "/")
|
|
637
|
+
for h in r.stdout.decode().split():
|
|
638
|
+
text = git_show("%s:%s" % (h, rel))
|
|
639
|
+
if text and re.search(r"^status:\s*approved", text, re.M):
|
|
640
|
+
return h, text
|
|
641
|
+
return None
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def cmd_diff(path):
|
|
645
|
+
"""Phần PRD đã đổi so với bản đã duyệt gần nhất trong git — phạm vi chính xác cho refine / review sau change."""
|
|
646
|
+
import difflib
|
|
647
|
+
base = last_approved(path)
|
|
648
|
+
if not base:
|
|
649
|
+
fail("không tìm thấy bản `approved` nào của %s trong git (chưa commit bản đã duyệt?). "
|
|
650
|
+
"Dùng các dòng Lịch sử thay đổi làm phạm vi." % path)
|
|
651
|
+
h, old = base
|
|
652
|
+
new = read(path)
|
|
653
|
+
old_l, new_l = old.split("\n"), new.split("\n")
|
|
654
|
+
# Mỗi UC chỉ gồm khối của nó: từ tiêu đề ### UC-… tới tiêu đề ## / ### kế tiếp.
|
|
655
|
+
spans = []
|
|
656
|
+
for i, l in enumerate(new_l):
|
|
657
|
+
m = re.match(r"^###\s+(?:~~)?(UC-\d{3})", l)
|
|
658
|
+
if m:
|
|
659
|
+
end = next((j for j in range(i + 1, len(new_l)) if re.match(r"^#{2,3}\s", new_l[j])), len(new_l))
|
|
660
|
+
spans.append((i, end, m.group(1)))
|
|
661
|
+
|
|
662
|
+
def uc_at(i):
|
|
663
|
+
return next((u for s, e, u in spans if s <= i < e), None)
|
|
664
|
+
touched, body = [], []
|
|
665
|
+
sm = difflib.SequenceMatcher(None, old_l, new_l, autojunk=False)
|
|
666
|
+
for tag, i1, i2, j1, j2 in sm.get_opcodes():
|
|
667
|
+
if tag == "equal":
|
|
668
|
+
continue
|
|
669
|
+
for j in range(j1, max(j2, j1 + 1)):
|
|
670
|
+
u = uc_at(min(j, len(new_l) - 1))
|
|
671
|
+
if u and u not in touched:
|
|
672
|
+
touched.append(u)
|
|
673
|
+
body += ["@@ dòng %d" % (j1 + 1)] + ["- " + l for l in old_l[i1:i2]] + ["+ " + l for l in new_l[j1:j2]]
|
|
674
|
+
print("# So với bản đã duyệt v%s (commit %s) · UC bị chạm: %s (%d UC)"
|
|
675
|
+
% (prd_version(old), h[:7], ", ".join(touched) or "không có (chỉ Tổng quan / User flow / Màn hình / Lịch sử)", len(touched)))
|
|
676
|
+
print("\n".join(body) if body else "(không có gì đổi)")
|
|
677
|
+
|
|
678
|
+
|
|
592
679
|
PRD_SIZE_WARN = 60 * 1024
|
|
593
680
|
|
|
594
681
|
|
|
@@ -685,26 +772,62 @@ def prd_items(text):
|
|
|
685
772
|
return out
|
|
686
773
|
|
|
687
774
|
|
|
775
|
+
# 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).
|
|
776
|
+
PLATFORMS = ("web", "app", "system")
|
|
777
|
+
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), "
|
|
778
|
+
"giải thích để sau dấu ` — `. Ví dụ: `- **Nền tảng:** system — luồng tự động nhận đơn từ EP-03`")
|
|
779
|
+
|
|
780
|
+
|
|
688
781
|
def platform_values(value):
|
|
689
|
-
|
|
690
|
-
|
|
782
|
+
"""(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."""
|
|
783
|
+
head = re.split(r"\(| — | – | - ", value.replace("✅", "").replace("\U0001F916", ""))[0]
|
|
784
|
+
words = [w for w in re.split(r"[·,/\s]+", head.strip()) if w]
|
|
785
|
+
return [w for w in words if w in PLATFORMS], [w for w in words if w not in PLATFORMS]
|
|
691
786
|
|
|
692
787
|
|
|
693
|
-
def
|
|
694
|
-
"""
|
|
788
|
+
def uc_platform_line(text, uc):
|
|
789
|
+
"""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
790
|
lines = text.split("\n")
|
|
696
791
|
span = uc_span(lines, uc)
|
|
697
792
|
if span:
|
|
698
793
|
for l in lines[span[0]:span[1]]:
|
|
699
794
|
m = PLATFORM_LINE.match(l)
|
|
700
795
|
if m:
|
|
701
|
-
return
|
|
702
|
-
|
|
703
|
-
for l in overview.split("\n"):
|
|
796
|
+
return m.group(1)
|
|
797
|
+
for l in (section_text(text, "overview") or "").split("\n"):
|
|
704
798
|
m = PLATFORM_LINE.match(l)
|
|
705
799
|
if m:
|
|
706
|
-
return
|
|
707
|
-
return
|
|
800
|
+
return m.group(1)
|
|
801
|
+
return None
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
def uc_platforms(text, uc):
|
|
805
|
+
"""Mã nền tảng của UC. [] = chưa khai, hoặc khai sai khuôn (xem platform_problems)."""
|
|
806
|
+
value = uc_platform_line(text, uc)
|
|
807
|
+
if value is None:
|
|
808
|
+
return []
|
|
809
|
+
good, bad = platform_values(value)
|
|
810
|
+
return [] if bad else good
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
def platform_problems(text):
|
|
814
|
+
"""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)."""
|
|
815
|
+
missing, wrong = [], []
|
|
816
|
+
for uc in re.findall(r"^###\s+(UC-\d{3})(?![\d-])", text, re.M):
|
|
817
|
+
value = uc_platform_line(text, uc)
|
|
818
|
+
if value is None:
|
|
819
|
+
missing.append(uc)
|
|
820
|
+
continue
|
|
821
|
+
good, bad = platform_values(value)
|
|
822
|
+
if bad or not good:
|
|
823
|
+
wrong.append('%s ("%s")' % (uc, value.strip()[:50]))
|
|
824
|
+
out = []
|
|
825
|
+
if missing:
|
|
826
|
+
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), "
|
|
827
|
+
"hoặc trong UC chạy khác mặc định." % ", ".join(missing))
|
|
828
|
+
if wrong:
|
|
829
|
+
out.append("Nền tảng ghi sai khuôn ở %s: %s" % (", ".join(wrong), PLATFORM_RULE))
|
|
830
|
+
return out
|
|
708
831
|
|
|
709
832
|
|
|
710
833
|
def prd_version(text):
|
|
@@ -790,9 +913,11 @@ def bdd_report(feature, prd=None):
|
|
|
790
913
|
items = prd_items(ptext)
|
|
791
914
|
own = [c for c, alive in items.items() if alive and c.startswith(uc + "-")]
|
|
792
915
|
platforms = uc_platforms(ptext, uc)
|
|
793
|
-
if
|
|
916
|
+
if uc_platform_line(ptext, uc) is None:
|
|
794
917
|
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
918
|
"Chạy `/prd <EPIC-ID> change khai nền tảng …` trước." % uc)
|
|
919
|
+
elif not platforms:
|
|
920
|
+
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
921
|
scenarios, removed = parse_feature(text)
|
|
797
922
|
if not scenarios:
|
|
798
923
|
errors.append("file chưa có kịch bản nào")
|
|
@@ -860,7 +985,7 @@ def cmd_bddcheck(feature, prd=None):
|
|
|
860
985
|
if st:
|
|
861
986
|
print("%s: %d kịch bản (%d đã bỏ) · AC %d/%d · BR %d/%d · %s" % (
|
|
862
987
|
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
|
|
988
|
+
" · ".join("%s %d" % kv for kv in st["platforms"].items()) or "chưa có nền tảng hợp lệ"), flush=True)
|
|
864
989
|
ptext = read(prd or prd_of(feature))
|
|
865
990
|
todo = [u for u in re.findall(r"^###\s+(UC-\d{3})(?![\d-])", ptext, re.M)
|
|
866
991
|
if not os.path.isfile(os.path.join(os.path.dirname(os.path.abspath(feature)), u + ".feature"))]
|
|
@@ -881,6 +1006,10 @@ def guard_bdd(path, block):
|
|
|
881
1006
|
ps = re.search(r"^status:\s*(\S+)", ptext, re.M)
|
|
882
1007
|
if not ps or ps.group(1) != "approved":
|
|
883
1008
|
fail("không đặt được status=approved: PRD chưa approved. Duyệt PRD trước.")
|
|
1009
|
+
rv = re.search(r"^reviewed:\s*(\S+)", ptext, re.M)
|
|
1010
|
+
if not rv or rv.group(1) != prd_version(ptext):
|
|
1011
|
+
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."
|
|
1012
|
+
% prd_version(ptext))
|
|
884
1013
|
bv = re.search(r"^prd_version:\s*(\S+)", block, re.M)
|
|
885
1014
|
if not bv or bv.group(1) != prd_version(ptext):
|
|
886
1015
|
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 +1037,12 @@ def cmd_context(path, ucs):
|
|
|
908
1037
|
if not span:
|
|
909
1038
|
fail("không có %s trong %s" % (uc, path))
|
|
910
1039
|
mention = re.compile(r"%s(?![\d-])" % re.escape(uc))
|
|
911
|
-
|
|
1040
|
+
fm = lambda k: (re.search(r"^%s:\s*(\S+)" % k, text, re.M) or [None, "—"])[1]
|
|
1041
|
+
value = uc_platform_line(text, uc)
|
|
1042
|
+
plat = "chưa khai" if value is None else (" · ".join(uc_platforms(text, uc)) or "sai khuôn")
|
|
1043
|
+
# Dòng đầu đủ để /bdd chọn chế độ (Bước 2) mà không phải đọc frontmatter riêng.
|
|
1044
|
+
out = ["# PRD %s — v%s · status %s · reviewed %s · nền tảng của %s: %s"
|
|
1045
|
+
% (path, prd_version(text), fm("status"), fm("reviewed"), uc, plat)]
|
|
912
1046
|
out.append(section_text(text, "overview") or "(PRD chưa có mục Tổng quan)")
|
|
913
1047
|
flow = (section_text(text, "userflow") or "").split("\n")
|
|
914
1048
|
rows = [l for l in flow if l.startswith("| J") and mention.search(l)]
|
|
@@ -1073,6 +1207,14 @@ def cmd_confirm(path, codes):
|
|
|
1073
1207
|
# Mã khớp nguyên từ: AC2 không được khớp nhầm AC20.
|
|
1074
1208
|
targets = {}
|
|
1075
1209
|
for code in codes:
|
|
1210
|
+
# 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.
|
|
1211
|
+
ln = re.match(r"^L(\d+)$", code)
|
|
1212
|
+
if ln:
|
|
1213
|
+
i = int(ln.group(1)) - 1
|
|
1214
|
+
if not 0 <= i < len(lines) or not is_pending(lines[i]):
|
|
1215
|
+
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))
|
|
1216
|
+
targets[i] = code
|
|
1217
|
+
continue
|
|
1076
1218
|
pat = re.compile(r"(?<![\w-])%s(?![\w])" % re.escape(code))
|
|
1077
1219
|
hits = [i for i, line in enumerate(lines) if is_pending(line) and pat.search(line)]
|
|
1078
1220
|
if not hits:
|
|
@@ -1097,7 +1239,7 @@ def main(argv):
|
|
|
1097
1239
|
print("ok — Python %s" % sys.version.split()[0])
|
|
1098
1240
|
return
|
|
1099
1241
|
if len(argv) < 2 or argv[0] not in ("create", "edit", "set", "pending", "confirm", "next-uc", "coverage", "section", "upgrade", "flowcheck", "item", "uc", "finding", "decide",
|
|
1100
|
-
"where", "context", "next-sc", "scenario", "sc", "bddcheck"):
|
|
1242
|
+
"where", "context", "next-sc", "scenario", "sc", "bddcheck", "diff"):
|
|
1101
1243
|
fail(__doc__.split("Cách dùng")[1], 2)
|
|
1102
1244
|
cmd, path, rest = argv[0], argv[1], argv[2:]
|
|
1103
1245
|
if cmd == "create":
|
|
@@ -1108,7 +1250,12 @@ def main(argv):
|
|
|
1108
1250
|
else:
|
|
1109
1251
|
cmd_create(path)
|
|
1110
1252
|
elif cmd == "edit":
|
|
1111
|
-
|
|
1253
|
+
if path == "--form":
|
|
1254
|
+
if not rest:
|
|
1255
|
+
fail("cách dùng: edit --form <file>", 2)
|
|
1256
|
+
cmd_edit(rest[0], form=True)
|
|
1257
|
+
else:
|
|
1258
|
+
cmd_edit(path)
|
|
1112
1259
|
elif cmd == "pending":
|
|
1113
1260
|
cmd_pending(path)
|
|
1114
1261
|
elif cmd == "confirm":
|
|
@@ -1133,6 +1280,8 @@ def main(argv):
|
|
|
1133
1280
|
cmd_next_uc(path)
|
|
1134
1281
|
elif cmd == "where":
|
|
1135
1282
|
cmd_where(path, rest)
|
|
1283
|
+
elif cmd == "diff":
|
|
1284
|
+
cmd_diff(path)
|
|
1136
1285
|
elif cmd == "context":
|
|
1137
1286
|
cmd_context(path, rest)
|
|
1138
1287
|
elif cmd == "next-sc":
|