@educa-corp/fw 0.11.0 → 0.11.2

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 CHANGED
@@ -3,6 +3,21 @@
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.2 — 2026-10-02
7
+
8
+ - **Bản sửa PRD phải tự soát đồng bộ** trước khi tăng version, cho cả `/prd change` lẫn bước áp bản sửa của refine. Với từng BR / AC vừa viết: AC có kiểm được không, Luồng chính và Kết quả sau đã khớp chưa, User flow đã khớp chưa, UC khác trỏ tới có cần sửa theo không. Lý do: cả 3 lỗi lọt qua refine của LMS đều sinh ra từ chính bản sửa, và đều thuộc kiểu "thêm một dòng mà quên những chỗ liên quan".
9
+ - **`/bdd` đưa ra các vấn đề refine đã ghi "Có từ trước, để sau"** của UC đang viết, ngay ở lượt 1. Viết được theo BR đang có thì viết, kèm 🤖. Muốn viết đúng phải thêm nghĩa mới vào PRD thì dừng, và gom vào một lần `/prd change`.
10
+ - Cảnh báo kích thước PRD ghi đúng hơn: người đọc cả PRD là các agent refine / review (`/bdd` chỉ đọc một UC).
11
+
12
+ ## 0.11.1 — 2026-10-02
13
+
14
+ 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.
15
+
16
+ - **`/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.
17
+ - **`--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.
18
+ - **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.
19
+ - **`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.
20
+
6
21
  ## 0.11.0 — 2026-10-02
7
22
 
8
23
  - **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/commands/bdd.md CHANGED
@@ -18,7 +18,7 @@ Tham số: `$ARGUMENTS`
18
18
 
19
19
  1. Chạy **một** lệnh Bash: `cat .fw/core/ref/common.md .fw/config.yaml; python .fw/core/tools/spec_edit.py --check`. Làm đúng theo `common.md` (luật chung: xưng hô, Python, config, cách hỏi, cách sửa file bằng `SE`). Không có `common.md` thì báo *"Chưa cài framework vào dự án này. Chạy `npx @educa-corp/fw install` ở thư mục gốc dự án."*
20
20
  2. Tìm PRD: `SE where {specs} <UC-ID>`. Kết quả là `D`. File BDD: `F = {thư mục của D}/bdd/<UC-ID>.feature`.
21
- 3. Đọc đầu vào **duy nhất** từ PRD: `SE context D <UC-ID>`. Lệnh này in Tổng quan, các hành trình có UC này, các dòng Màn hình, khối UC, và đúng các mục của UC khác mà UC này trỏ tới. **Không đọc cả PRD.** Cần thêm một UC khác thì chỉ đọc `SE uc D UC-xxx`.
21
+ 3. Đọc đầu vào **duy nhất** từ PRD: `SE context D <UC-ID>`. Lệnh này in Tổng quan, các hành trình có UC này, các dòng Màn hình, khối UC, đúng các mục của UC khác mà UC này trỏ tới, và các vấn đề refine đã ghi *"Có từ trước, để sau"* cho UC này. **Không đọc cả PRD.** Cần thêm một UC khác thì chỉ đọc `SE uc D UC-xxx`.
22
22
  4. Đọc `{specs}/product/glossary.md` nếu có. Viết đúng thuật ngữ.
23
23
 
24
24
  ## Bước 2 — Chọn chế độ
@@ -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.0**. Chỉ mô tả những lệnh **đã có**. Lệnh mới có thì hướng dẫn mới được bổ sung.
4
+ > Hướng dẫn này viết cho **bản 0.11.2**. 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
  ---
@@ -29,6 +29,8 @@ AI chỉ đọc **đúng phần PRD cần cho UC này**: Tổng quan, các hành
29
29
  - **Bảng nhánh**: các BR có nhiều nhánh, mỗi nhánh ứng với kịch bản nào. Bạn soát xem có nhánh nào bị sót không.
30
30
  - **Điểm giao nhận** với UC khác, lấy từ User flow. Ví dụ *"→ UC-003: người dùng nhận email có link đặt mật khẩu"*. Lỗi ở chỗ nối giữa các UC hay bị phát hiện muộn nhất, nên phải viết rõ.
31
31
 
32
+ - **Vấn đề PRD còn để sau**: những lỗi mà refine đã thấy trong UC này nhưng chưa sửa. Viết được theo BR đang có thì AI viết, kèm 🤖. Không viết được thì AI dừng UC đó và đề nghị gom vào một lần `/prd change`.
33
+
32
34
  Bạn đồng ý, hoặc sửa: *"tách SC02 làm hai"*, *"thiếu nhánh email trống"*.
33
35
 
34
36
  **Lượt 2: ghi và duyệt.**
@@ -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 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.
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ú** | 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` |
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. Chỗ đó được chuyển thành ghi chú C.
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ỉ các mục vừa đổi, theo Lịch sử thay đổi |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@educa-corp/fw",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "Framework làm việc với Claude Code cho phòng PTPM",
5
5
  "bin": {
6
6
  "fw": "bin/fw.js"
package/ref/bdd/new.md CHANGED
@@ -24,6 +24,10 @@ Chỉ **2 lượt** hỏi-đáp. PRD đã được duyệt và refine, nên khô
24
24
 
25
25
  **c. Điểm giao nhận** với UC khác, lấy từ User flow. Mỗi điểm một dòng, ví dụ *"→ UC-003: người dùng nhận email có link đặt mật khẩu (Then của SC06)"*.
26
26
 
27
+ **d. Vấn đề PRD còn để sau.** `SE context` có mục *"Vấn đề PRD còn để sau"* thì đây là lỗi refine đã thấy nhưng chưa sửa. Viết kịch bản là lúc chúng lộ ra, ví dụ AC không kiểm được hoặc một nhánh bị bỏ qua. Với mỗi vấn đề, chọn một trong hai:
28
+ - Viết được kịch bản **chỉ theo các BR đang có**, không phải thêm nghĩa mới: viết theo BR, rồi gắn `🤖` kèm câu *"theo BR…, chờ sửa PRD"*.
29
+ - Muốn viết đúng thì phải thêm nghĩa mới vào PRD: **dừng** UC này, và báo *"Cần `/prd EP-xx change …` trước, cho các vấn đề: …"*. Gom mọi vấn đề như vậy của cả epic vào **một** lần change.
30
+
27
31
  Kèm tối đa 4 câu hỏi, chỉ khi cần. Ví dụ: BR không tìm được kịch bản nào, điều phải giả định vì PRD chưa ghi, chỗ PRD mơ hồ. Không có gì cần hỏi thì chỉ hỏi *"Dàn ý này được chưa?"*.
28
32
 
29
33
  ## Lượt 2 — Ghi file
package/ref/prd/change.md CHANGED
@@ -30,5 +30,12 @@ Khi PO đồng ý kế hoạch:
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
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`.
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=—`.
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`.
33
+ 2. **Soát đồng bộ trước khi tăng version.** Lỗi hay gặp nhất của bản sửa là thêm hoặc sửa một dòng mà quên những chỗ liên quan. Lần đo trên LMS: khoảng 20–25% bản sửa sinh ra lỗi mới, và cả 3 lỗi lọt qua refine đều thuộc kiểu này. Với **từng** dòng BR / AC vừa thêm hoặc sửa, đọc lại bằng `SE uc D <UC>` rồi trả lời:
34
+ - **AC kiểm được không?** Kết quả có nhìn thấy được, đánh đạt hay không đạt được không? Có khớp với các BR khác đang áp lên cùng tình huống không? Ví dụ một AC ghi *"vẫn đăng nhập được"* trong khi một BR khác bắt người đó đặt mật khẩu mới trước, thì AC đó không kiểm được.
35
+ - **Phần còn lại của UC đã khớp chưa?** Luồng chính, Điều kiện trước và Kết quả sau. Ví dụ thêm một nhánh BR mới thì Kết quả sau phải nói tới kết quả của nhánh đó.
36
+ - **User flow đã khớp chưa?** Hành trình đi qua UC này có nhánh nào bỏ qua luật mới không?
37
+ - **UC khác có trỏ tới mục này không?** Có thì xem các mục đó có cần sửa theo không.
38
+
39
+ Thiếu chỗ nào thì sửa luôn trong lần này, rồi ghi chỗ đã sửa thêm vào dòng Lịch sử thay đổi.
40
+ 3. 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=—`.
41
+ 4. 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
- Kết luận cho mỗi finding: `ĐÚNG` · `SAI` (bỏ) · `HẠ MỨC` (giữ, đổi mức).
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** | 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` |
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. Đế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.
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, và số bị hạ mức.
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ã."*
@@ -68,7 +68,8 @@ Phiên chính **không đọc** kết quả chi tiết của các agent. Agent g
68
68
  - đọc từng finding bằng `SE finding`, đọc UC liên quan bằng `SE uc`;
69
69
  - sửa hoặc thêm BR / AC **theo mã** bằng `SE item` (chỉ viết dòng mới); phần không phải dòng BR / AC (luồng, User flow, Tổng quan) thì dùng `SE edit`;
70
70
  - thêm dòng Lịch sử thay đổi nêu mã finding, ví dụ *"v1.3 — refine lượt 1: F01, F03, F04"*;
71
+ - **soát đồng bộ** từng dòng vừa viết theo bước 2 của `change.md` (AC kiểm được không · Luồng và Kết quả sau · User flow · UC khác trỏ tới). Thiếu thì sửa luôn;
71
72
  - chạy `SE flowcheck D`, rồi `SE set D version=… status=draft approved_by=— approved_at=—` và `SE set R status=applied`;
72
- - **chỉ trả về một dòng**: *"áp {k} finding → v{version}; {số BR/AC sửa · thêm · bỏ}"*.
73
+ - **chỉ trả về một dòng**: *"áp {k} finding → v{version}; {số BR/AC sửa · thêm · bỏ}; đồng bộ thêm {m} chỗ"*.
73
74
  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."*
74
75
  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."*
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: 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` |
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):** 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 …`.
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). `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.
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, 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 |
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:** 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ũ.
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}: {vấn đề} → cần `/prd … change`
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}"*.
@@ -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
- a, b = sorted(FORM_KEEP.findall(old)), sorted(FORM_KEEP.findall(new))
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,13 +621,69 @@ 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
 
624
682
  def size_warning(path):
625
683
  size = os.path.getsize(path)
626
684
  if size > PRD_SIZE_WARN:
627
- print("⚠ PRD %d KB (ngưỡng %d KB): mọi bước sau đều nạp toàn bộ PRD. Cân nhắc tách epic thành nhiều PRD nhỏ hơn."
685
+ print("⚠ PRD %d KB (ngưỡng %d KB): mỗi agent refine / review đều đọc toàn bộ PRD, nên mỗi lần đổi PRD đều đắt. "
686
+ "Cân nhắc tách epic thành nhiều PRD nhỏ hơn."
628
687
  % (size // 1024, PRD_SIZE_WARN // 1024))
629
688
 
630
689
 
@@ -1013,9 +1072,26 @@ def cmd_context(path, ucs):
1013
1072
  for c in foreign:
1014
1073
  hits = find_item(lines, c)
1015
1074
  out.append(lines[hits[0]] if hits else "(không tìm thấy %s trong PRD)" % c)
1075
+ later = deferred_items(refine_path(path), re.compile(r"%s(?!\d)" % re.escape(uc)))
1076
+ if later:
1077
+ out += ["", "## Vấn đề PRD còn để sau (refine ghi \"Có từ trước, để sau\") — xem ref/bdd/new.md mục d"] + later
1016
1078
  print("\n".join(out).rstrip())
1017
1079
 
1018
1080
 
1081
+ def deferred_items(rpath, mention):
1082
+ """Các dòng dưới `### Có từ trước, để sau` của file refine có nhắc tới UC (mention là regex mã UC)."""
1083
+ if not os.path.isfile(rpath):
1084
+ return []
1085
+ out, inside = [], False
1086
+ for l in read(rpath).split("\n"):
1087
+ if re.match(r"^#{2,4}\s", l):
1088
+ inside = l.lstrip("#").strip().startswith("Có từ trước")
1089
+ continue
1090
+ if inside and l.startswith("- ") and mention.search(l):
1091
+ out.append(l)
1092
+ return out
1093
+
1094
+
1019
1095
  def used_sc(text, uc):
1020
1096
  return sorted({int(m.group(3)) for m in SC_ANY.finditer(text) if m.group(2) == uc[3:]})
1021
1097
 
@@ -1181,7 +1257,7 @@ def main(argv):
1181
1257
  print("ok — Python %s" % sys.version.split()[0])
1182
1258
  return
1183
1259
  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"):
1260
+ "where", "context", "next-sc", "scenario", "sc", "bddcheck", "diff"):
1185
1261
  fail(__doc__.split("Cách dùng")[1], 2)
1186
1262
  cmd, path, rest = argv[0], argv[1], argv[2:]
1187
1263
  if cmd == "create":
@@ -1222,6 +1298,8 @@ def main(argv):
1222
1298
  cmd_next_uc(path)
1223
1299
  elif cmd == "where":
1224
1300
  cmd_where(path, rest)
1301
+ elif cmd == "diff":
1302
+ cmd_diff(path)
1225
1303
  elif cmd == "context":
1226
1304
  cmd_context(path, rest)
1227
1305
  elif cmd == "next-sc":