@educa-corp/fw 0.11.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 +9 -0
- package/docs/guide/README.md +1 -1
- package/docs/guide/lenh/prd-refine.md +1 -1
- package/docs/guide/lenh/prd-review.md +5 -3
- package/package.json +1 -1
- package/ref/prd/lenses.md +7 -3
- package/ref/prd/refine.md +3 -3
- package/ref/prd/review.md +15 -7
- package/tools/spec_edit.py +62 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
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
|
+
|
|
6
15
|
## 0.11.0 — 2026-10-02
|
|
7
16
|
|
|
8
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ú.
|
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.11.
|
|
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
|
---
|
|
@@ -71,7 +71,7 @@ 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ỉ soi
|
|
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
|
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|---|---|---|
|
|
23
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
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ú** |
|
|
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
26
|
|
|
27
27
|
Bạn quyết loại B theo mã, hoặc theo nhóm:
|
|
28
28
|
|
|
@@ -36,7 +36,9 @@ bác R05
|
|
|
36
36
|
## Review không bao giờ làm đổi nghĩa
|
|
37
37
|
|
|
38
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.
|
|
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.
|
|
40
42
|
- Nhờ vậy, review không cần refine lại sau đó.
|
|
41
43
|
|
|
42
44
|
## Phạm vi
|
|
@@ -44,7 +46,7 @@ bác R05
|
|
|
44
46
|
| Khi nào | Rà gì |
|
|
45
47
|
|---|---|
|
|
46
48
|
| PRD chưa từng review | Toàn bộ PRD |
|
|
47
|
-
| Sau `/prd EP-01 change` → refine | Chỉ
|
|
49
|
+
| Sau `/prd EP-01 change` → refine | Chỉ phần vừa đổi so với bản đã duyệt gần nhất |
|
|
48
50
|
|
|
49
51
|
Thứ tự sau mỗi lần đổi PRD: `change` → `refine` → `review` → duyệt.
|
|
50
52
|
|
package/package.json
CHANGED
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,7 +27,7 @@ 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. **Chọn số agent rà** theo lượt và phạm vi ở Bước A.
|
|
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
31
|
|
|
32
32
|
| Lượt và phạm vi | Agent rà |
|
|
33
33
|
|---|---|
|
|
@@ -49,7 +49,7 @@ Phiên chính **không đọc** kết quả chi tiết của các agent. Agent g
|
|
|
49
49
|
## Bước C — Trình
|
|
50
50
|
|
|
51
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).
|
|
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,
|
|
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).
|
|
53
53
|
3. Nhắc PO cách quyết:
|
|
54
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'."*
|
|
55
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ã."*
|
package/ref/prd/review.md
CHANGED
|
@@ -18,7 +18,7 @@ Xét **từ trên xuống**, gặp dòng nào khớp trước thì theo dòng đ
|
|
|
18
18
|
| `reviewed` = `version`, không có `--full` | **Dừng.** Báo *"Bản v{version} đã rà hình thức xong."* |
|
|
19
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
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:
|
|
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
22
|
|
|
23
23
|
## Bước B — Rà (1 sub-agent)
|
|
24
24
|
|
|
@@ -30,12 +30,15 @@ Dùng công cụ **Agent**, `model: "sonnet"`, gọi **1** agent. Giao cho agent
|
|
|
30
30
|
2. Trình theo thứ tự:
|
|
31
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
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):**
|
|
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
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
35
|
|
|
36
36
|
## Bước D — Áp
|
|
37
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).
|
|
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.
|
|
39
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 |`.
|
|
40
43
|
3. `SE set D reviewed={version} updated={ngày}`.
|
|
41
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.
|
|
@@ -53,10 +56,15 @@ Bạn rà **hình thức** của PRD trong đúng phạm vi được giao. Phạ
|
|
|
53
56
|
| Loại | Gồm | Làm gì |
|
|
54
57
|
|---|---|---|
|
|
55
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 |
|
|
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
|
|
57
|
-
| **C. Ghi chú** |
|
|
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 |
|
|
58
61
|
|
|
59
|
-
**Không làm:**
|
|
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ũ.
|
|
60
68
|
|
|
61
69
|
**Ghi file** `W/review-{version}.md` bằng `SE create --replace`, đúng khuôn sau:
|
|
62
70
|
|
|
@@ -71,7 +79,7 @@ Bạn rà **hình thức** của PRD trong đúng phạm vi được giao. Phạ
|
|
|
71
79
|
- Lý do: {…}
|
|
72
80
|
|
|
73
81
|
## Ghi chú (C)
|
|
74
|
-
- {mã mục}: {
|
|
82
|
+
- {mã mục}: {lỗi hình thức không sửa được mà không đổi nghĩa}
|
|
75
83
|
```
|
|
76
84
|
|
|
77
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/tools/spec_edit.py
CHANGED
|
@@ -31,6 +31,7 @@ Cách dùng (nội dung truyền qua stdin):
|
|
|
31
31
|
spec_edit.py scenario <feature> <SC…>… in đúng các kịch bản đó
|
|
32
32
|
spec_edit.py sc <feature> stdin = JSON [{"id": mã, "new": khối} | {"after": mã | "end", "new": khối} | {"remove": mã, "version": "1.1"}]
|
|
33
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
|
|
34
35
|
|
|
35
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.
|
|
36
37
|
"""
|
|
@@ -118,7 +119,9 @@ FORM_KEEP = re.compile("UC-\\d{3}(?:-(?:BR|AC|SC)\\d+)?|(?:CON|ACT)-\\d+|\\b(?:B
|
|
|
118
119
|
|
|
119
120
|
def form_changes(old, new):
|
|
120
121
|
"""Những thứ mang nghĩa bị thêm / mất khi đổi old → new. Rỗng = chỉ đổi hình thức."""
|
|
121
|
-
|
|
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)))
|
|
122
125
|
lost = [x for x in a if a.count(x) > b.count(x)]
|
|
123
126
|
added = [x for x in b if b.count(x) > a.count(x)]
|
|
124
127
|
return sorted(set(lost)), sorted(set(added))
|
|
@@ -618,6 +621,61 @@ def cmd_decide(path):
|
|
|
618
621
|
print("đã ghi %d quyết định vào %s" % (len(ops), path))
|
|
619
622
|
|
|
620
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
|
+
|
|
621
679
|
PRD_SIZE_WARN = 60 * 1024
|
|
622
680
|
|
|
623
681
|
|
|
@@ -1181,7 +1239,7 @@ def main(argv):
|
|
|
1181
1239
|
print("ok — Python %s" % sys.version.split()[0])
|
|
1182
1240
|
return
|
|
1183
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",
|
|
1184
|
-
"where", "context", "next-sc", "scenario", "sc", "bddcheck"):
|
|
1242
|
+
"where", "context", "next-sc", "scenario", "sc", "bddcheck", "diff"):
|
|
1185
1243
|
fail(__doc__.split("Cách dùng")[1], 2)
|
|
1186
1244
|
cmd, path, rest = argv[0], argv[1], argv[2:]
|
|
1187
1245
|
if cmd == "create":
|
|
@@ -1222,6 +1280,8 @@ def main(argv):
|
|
|
1222
1280
|
cmd_next_uc(path)
|
|
1223
1281
|
elif cmd == "where":
|
|
1224
1282
|
cmd_where(path, rest)
|
|
1283
|
+
elif cmd == "diff":
|
|
1284
|
+
cmd_diff(path)
|
|
1225
1285
|
elif cmd == "context":
|
|
1226
1286
|
cmd_context(path, rest)
|
|
1227
1287
|
elif cmd == "next-sc":
|