Skill: plan
Purpose
Biến SPEC đã duyệt thành văn bản mà một kỹ sư giỏi nhưng không biết gì về codebase này, và gu thì đáng ngờ vẫn thi hành đúng. Trong overstack, "kỹ sư" đó thường là một CLI rẻ chạy headless (opencode / agy / kiro): nó không thừa hưởng context nào của phiên chính, không hỏi lại được, và khi gặp chỗ mơ hồ nó sẽ đoán rồi im lặng. Thực đo bài học 250626: brief mỏng → giao hàng ~1/5.
Cái gì không nằm trong PLAN thì agent không có. Đó là toàn bộ nguyên lý của skill này.
When to use
- SPEC (
/propose) đã được user duyệt ở cổng, và sắp dispatch task cho agent.
- Việc nhiều bước, nhiều file, hoặc chia cho ≥2 agent chạy song song.
- KHÔNG dùng khi chưa có SPEC duyệt (dùng
/propose trước), và không dùng cho sửa một dòng.
Steps
Đọc SPEC đã duyệt — llmwiki/wiki/sources/draft/DDMMYY-<tên>.md. Lấy nguyên: ## Context, ## Global constraints, ## Plan (các dòng - [ ]), ## Agent Task Assignment.
Scope check — SPEC ôm nhiều hệ con độc lập → tách thành nhiều PLAN, mỗi PLAN tự nó ra được phần mềm chạy được và test được. Đừng nhồi.
Vẽ ## File structure trước khi chia task — liệt kê mọi file sẽ tạo/sửa và trách nhiệm của từng file (một file một trách nhiệm; file nào đổi cùng nhau thì ở cùng chỗ). Quyết định phân rã bị chốt ở đây, không phải trong lúc code.
Chia task thành TRACER BULLET — mỗi task là một lát cắt DỌC: một lát mỏng xuyên hết các tầng (data → logic → giao diện/CLI), tự nó chạy được và tự chứng minh được, để lại một deliverable test được độc lập. Không phải "lát ngang" kiểu "làm hết tầng data" rồi task sau "làm hết tầng logic" — lát ngang không cái nào chạy được một mình. Setup, config, scaffolding, docs → gộp vào task cần chúng; chỉ tách khi một reviewer có thể bác task này mà vẫn duyệt task kia.
Ngoại lệ có tên — WIDE REFACTOR (expand → migrate → contract). Một thay đổi cơ học mà blast radius trải khắp codebase — đổi tên một cột, đổi kiểu một symbol dùng chung — thì không lát cắt dọc nào xanh nổi: một chỗ sửa làm hàng nghìn call-site đỏ cùng lúc. Đừng ép nó vào khuôn tracer bullet. Sequence:
- expand — thêm dạng MỚI cạnh dạng cũ, chưa xoá gì. Một task. Không làm hỏng gì vì dạng cũ vẫn còn.
- migrate theo lô — dời call-site sang dạng mới, chia lô theo blast radius (per-package, per-directory). Mỗi lô một task, bị chặn bởi task expand. CI xanh từng lô vì dạng cũ vẫn sống.
- contract — xoá dạng cũ khi không còn caller nào. Một task, bị chặn bởi mọi lô migrate.
- Khi ngay cả từng lô cũng không tự xanh nổi: cho chúng chung một nhánh tích hợp, tất cả cùng chặn một task "integrate-and-verify" cuối — xanh chỉ được hứa ở đó.
Viết từng task theo khuôn dưới (bắt buộc đủ Files + Interfaces + Steps).
Self-review (3 mắt lưới, mục 'Self-review' bên dưới), sửa tại chỗ.
Ghi file llmwiki/wiki/sources/draft/DDMMYY-<tên>-PLAN.md, thêm dòng vào llmwiki/wiki/index.md, append llmwiki/wiki/log.md.
Bàn giao: mỗi ### Task N là một orca orchestration task-create; dispatch --inject bơm nguyên văn brief của task đó kèm ## Global constraints. Không tóm tắt lại — tóm tắt là chỗ context rụng.
Khuôn PLAN
Header bắt buộc (frontmatter và ## Origin KHÔNG được bỏ — file nằm trong wiki/sources/draft/ nên R9 chặn nếu thiếu frontmatter, R2 chặn nếu thiếu ## Origin):
---
type: draft
title: <tên>-PLAN
status: proposed
timestamp: YYYY-MM-DD
task: T-YYMMDD-NN # cùng task-id với SPEC
---
# <Tên> — PLAN thi hành
**Goal:** <một câu: cái này xây ra cái gì>
**Architecture:** <2-3 câu: cách tiếp cận>
**Tech stack:** <ngôn ngữ, lib, test runner, version>
**SPEC nguồn:** `wiki/sources/draft/DDMMYY-<tên>.md` (đã duyệt <ngày>)
## Origin
- **SPEC:** `wiki/sources/draft/DDMMYY-<tên>.md`
- **Commit:** _(verify-before-commit điền)_
## Global constraints
<chép NGUYÊN VĂN từ SPEC — sàn version, giới hạn dependency, luật đặt tên, gate trước push.
Mỗi task ngầm mang theo section này.>
## File structure
- Tạo `path/chính/xác.py` — <trách nhiệm duy nhất của file>
- Sửa `path/có/sẵn.py` — <đổi gì>
Mỗi task:
### Task N: <tên>
**Thoả:** FR-001, FR-003
**Files:**
- Tạo: `đường/dẫn/chính/xác.py`
- Sửa: `đường/dẫn/có/sẵn.py:123-145`
- Test: `tests/đường/dẫn/test_x.py`
**Interfaces:**
- Consumes: <dùng gì từ task trước — chữ ký chính xác>
- Produces: <task sau dựa vào cái gì — tên hàm, kiểu tham số, kiểu trả về>
- [ ] **Step 1: viết test fail**
```python
def test_hanh_vi_cu_the():
assert ham(dau_vao) == ket_qua_mong_doi
```
- [ ] **Step 2: chạy cho THẤY nó fail**
Chạy: `pytest tests/đường/dẫn/test_x.py::test_hanh_vi_cu_the -v`
Mong đợi: FAIL — `NameError: name 'ham' is not defined`
- [ ] **Step 3: code tối thiểu cho pass**
```python
def ham(dau_vao):
return ket_qua_mong_doi
```
- [ ] **Step 4: chạy lại — PASS**
Chạy: `pytest tests/đường/dẫn/test_x.py::test_hanh_vi_cu_the -v`
Mong đợi: PASS
- [ ] **Step 5: commit**
```bash
git add tests/đường/dẫn/test_x.py src/đường/dẫn/x.py
git commit -m "feat: <việc cụ thể>"
```
Vì sao **Thoả:** là bắt buộc: SPEC đánh id ổn định FR-001, FR-002… cho từng yêu cầu. Mỗi task khai mình gánh id nào → câu "phủ hết SPEC" ở Self-review thôi làm bằng mắt, nó thành dữ liệu grep được, và R18 chặn nếu có FR-xxx nào không task nào nhận (in ra đúng id bị bỏ rơi). Một yêu cầu trôi mất giữa hai task là lỗi thầm lặng nhất của mọi kế hoạch — nó chỉ lộ ra lúc agent giao hàng thiếu, hoặc tệ hơn, lúc user dùng.
Vì sao Interfaces là bắt buộc khi có ≥2 agent: agent thi hành chỉ nhìn thấy task của chính nó. Không khai chữ ký thì hai agent song song đặt tên hàm lệch nhau và phần ghép vỡ — đây là lỗi chắc chắn xảy ra, không phải rủi ro.
Cấm placeholder — đây là plan HỎNG, không phải plan chưa xong
R7 chặn ở write-time và commit. Không bao giờ viết:
TBD, TODO, "điền sau", "chi tiết sau"
- "xử lý lỗi phù hợp" / "thêm validation" / "handle edge cases" — nói làm gì, không nói "làm cho phù hợp"
- "viết test cho phần trên" mà không có code test thật
- "tương tự Task N" — chép lại code ra. Agent có thể đọc task không theo thứ tự, và mỗi agent chỉ được bơm task của nó.
- Bước mô tả phải làm gì mà không chỉ làm thế nào (bước đổi code thì bắt buộc có code)
- Nhắc tới hàm/kiểu/method không được định nghĩa ở bất kỳ task nào
Self-review
Viết xong, soi lại bằng mắt mới — sửa tại chỗ, không cần vòng hai:
- Phủ SPEC — lướt từng yêu cầu trong SPEC, chỉ ra được task nào thực hiện nó. Thiếu → thêm task.
- Quét placeholder — dò đúng các mẫu ở mục trên.
- Nhất quán kiểu/tên — tên hàm, chữ ký, tên field dùng ở task sau có khớp cái định nghĩa ở task trước không?
clearLayers() ở Task 3 mà clearFullLayers() ở Task 7 là một con bug.
Cổng ngược — /plan được quyền BÁC SPEC
Viết PLAN chính là cách phát hiện SPEC sai: tới lúc phải khai đường dẫn file thật và chữ ký hàm thật thì thiết kế bất khả thi mới lòi ra. Nếu cổng duyệt đã cho qua rồi mà giờ mới lòi, nghĩa là user đã duyệt một thứ không xây được — cổng đã hỏng.
DỪNG, không viết tiếp PLAN nửa vời, khi gặp bất kỳ điều nào:
- Một yêu cầu trong SPEC không quy được về task nào làm được.
- Hai mục trong SPEC mâu thuẫn nhau, hoặc mâu thuẫn với
## Global constraints.
- Phương án đã chọn ở
## Approaches hoá ra bất khả thi (file không tồn tại, API không có, ràng buộc chặn).
- Phải bịa một hàm/kiểu/file mà SPEC không hề nhắc, để cho plan "chạy được trên giấy".
Xử lý: gom TẤT CẢ chỗ vỡ thành MỘT lần báo (không ngắt user từng phát một), mỗi chỗ kèm đúng câu trong SPEC gây ra nó, rồi quay về /propose sửa SPEC và duyệt lại. Một PLAN viết trên nền SPEC hỏng còn tệ hơn không có PLAN: nó bơm cái sai vào agent với vẻ mặt tự tin, và agent rẻ sẽ thi hành nguyên xi.
Rules
- PLAN không cần file
.html — nó là thứ máy đọc. HTML gắn với SPEC (thứ người xem lúc duyệt), do /propose sinh. R7 miễn check diagram cho nhánh -PLAN.md.
- Đường dẫn file luôn chính xác, kèm dải dòng khi sửa file có sẵn.
- Bước đổi code thì phải có code đầy đủ — không mô tả suông.
- Lệnh chạy chính xác, kèm output mong đợi (agent cần biết thế nào là fail đúng, thế nào là pass).
- DRY, YAGNI, TDD, commit thường xuyên.
- Trong codebase có sẵn: theo pattern đang có. Đừng nhân tiện refactor thứ ngoài task.
1---2name: plan3description: Mở rộng một draft SPEC ĐÃ ĐƯỢC DUYỆT thành kế hoạch thi hành được — file `DDMMYY-<tên>-PLAN.md` mà một agent KHÔNG có context nào (CLI rẻ chạy headless, không hỏi lại được) vẫn làm đúng: đường dẫn file chính xác, khối Interfaces (Consumes/Produces) khai chữ ký cho task hàng xóm, ràng buộc bao trùm chép nguyên văn, và từng bước 2-5 phút kiểu TDD có code thật + lệnh chạy + output mong đợi. Gọi SAU `/propose` và SAU khi user duyệt ở cổng, TRƯỚC khi dispatch task cho agent. Trigger - "viết plan", "mở rộng proposal thành plan", "plan thi hành", "chuẩn bị brief cho agent", "/plan". KHÔNG dùng để thiết kế hay để hỏi yêu cầu (đó là `/propose`), KHÔNG dùng khi chưa có SPEC duyệt.4---56# Skill: plan78## Purpose9Biến SPEC đã duyệt thành văn bản mà **một kỹ sư giỏi nhưng không biết gì về codebase này, và gu thì đáng ngờ** vẫn thi hành đúng. Trong overstack, "kỹ sư" đó thường là một CLI rẻ chạy headless (`opencode` / `agy` / `kiro`): nó **không thừa hưởng context nào** của phiên chính, **không hỏi lại được**, và khi gặp chỗ mơ hồ nó sẽ **đoán rồi im lặng**. Thực đo bài học 250626: brief mỏng → giao hàng ~1/5.1011Cái gì không nằm trong PLAN thì agent không có. Đó là toàn bộ nguyên lý của skill này.1213## When to use14- SPEC (`/propose`) đã được user duyệt ở cổng, và sắp dispatch task cho agent.15- Việc nhiều bước, nhiều file, hoặc chia cho ≥2 agent chạy song song.16- KHÔNG dùng khi chưa có SPEC duyệt (dùng `/propose` trước), và không dùng cho sửa một dòng.1718## Steps191. **Đọc SPEC đã duyệt** — `llmwiki/wiki/sources/draft/DDMMYY-<tên>.md`. Lấy nguyên: `## Context`, `## Global constraints`, `## Plan` (các dòng `- [ ]`), `## Agent Task Assignment`.202. **Scope check** — SPEC ôm nhiều hệ con độc lập → tách thành nhiều PLAN, mỗi PLAN tự nó ra được phần mềm chạy được và test được. Đừng nhồi.213. **Vẽ `## File structure` trước khi chia task** — liệt kê mọi file sẽ tạo/sửa và trách nhiệm của từng file (một file một trách nhiệm; file nào đổi cùng nhau thì ở cùng chỗ). Quyết định phân rã bị chốt ở đây, không phải trong lúc code.224. **Chia task thành TRACER BULLET** — mỗi task là một **lát cắt DỌC**: một lát mỏng xuyên hết các tầng (data → logic → giao diện/CLI), tự nó chạy được và tự chứng minh được, để lại một deliverable test được độc lập. Không phải "lát ngang" kiểu "làm hết tầng data" rồi task sau "làm hết tầng logic" — lát ngang không cái nào chạy được một mình. Setup, config, scaffolding, docs → gộp vào task cần chúng; chỉ tách khi một reviewer có thể *bác task này mà vẫn duyệt task kia*.2324 **Ngoại lệ có tên — WIDE REFACTOR (expand → migrate → contract).** Một thay đổi cơ học mà **blast radius** trải khắp codebase — đổi tên một cột, đổi kiểu một symbol dùng chung — thì không lát cắt dọc nào xanh nổi: một chỗ sửa làm hàng nghìn call-site đỏ cùng lúc. Đừng ép nó vào khuôn tracer bullet. Sequence:25 - **expand** — thêm dạng MỚI cạnh dạng cũ, chưa xoá gì. Một task. Không làm hỏng gì vì dạng cũ vẫn còn.26 - **migrate theo lô** — dời call-site sang dạng mới, chia lô theo blast radius (per-package, per-directory). Mỗi lô một task, **bị chặn bởi** task expand. CI xanh từng lô vì dạng cũ vẫn sống.27 - **contract** — xoá dạng cũ khi không còn caller nào. Một task, **bị chặn bởi mọi lô migrate**.28 - Khi ngay cả từng lô cũng không tự xanh nổi: cho chúng chung một nhánh tích hợp, tất cả cùng chặn một task "integrate-and-verify" cuối — xanh chỉ được hứa ở đó.295. **Viết từng task theo khuôn dưới** (bắt buộc đủ Files + Interfaces + Steps).306. **Self-review** (3 mắt lưới, mục 'Self-review' bên dưới), sửa tại chỗ.317. **Ghi file** `llmwiki/wiki/sources/draft/DDMMYY-<tên>-PLAN.md`, thêm dòng vào `llmwiki/wiki/index.md`, append `llmwiki/wiki/log.md`.328. Bàn giao: mỗi `### Task N` là một `orca orchestration task-create`; `dispatch --inject` bơm **nguyên văn** brief của task đó **kèm `## Global constraints`**. Không tóm tắt lại — tóm tắt là chỗ context rụng.3334## Khuôn PLAN3536Header bắt buộc (frontmatter và `## Origin` KHÔNG được bỏ — file nằm trong `wiki/sources/draft/` nên R9 chặn nếu thiếu frontmatter, R2 chặn nếu thiếu `## Origin`):3738```markdown39---40type: draft41title: <tên>-PLAN42status: proposed43timestamp: YYYY-MM-DD44task: T-YYMMDD-NN # cùng task-id với SPEC45---4647# <Tên> — PLAN thi hành4849**Goal:** <một câu: cái này xây ra cái gì>50**Architecture:** <2-3 câu: cách tiếp cận>51**Tech stack:** <ngôn ngữ, lib, test runner, version>52**SPEC nguồn:** `wiki/sources/draft/DDMMYY-<tên>.md` (đã duyệt <ngày>)5354## Origin55- **SPEC:** `wiki/sources/draft/DDMMYY-<tên>.md`56- **Commit:** _(verify-before-commit điền)_5758## Global constraints59<chép NGUYÊN VĂN từ SPEC — sàn version, giới hạn dependency, luật đặt tên, gate trước push.60 Mỗi task ngầm mang theo section này.>6162## File structure63- Tạo `path/chính/xác.py` — <trách nhiệm duy nhất của file>64- Sửa `path/có/sẵn.py` — <đổi gì>65```6667Mỗi task:6869````markdown70### Task N: <tên>7172**Thoả:** FR-001, FR-0037374**Files:**75- Tạo: `đường/dẫn/chính/xác.py`76- Sửa: `đường/dẫn/có/sẵn.py:123-145`77- Test: `tests/đường/dẫn/test_x.py`7879**Interfaces:**80- Consumes: <dùng gì từ task trước — chữ ký chính xác>81- Produces: <task sau dựa vào cái gì — tên hàm, kiểu tham số, kiểu trả về>8283- [ ] **Step 1: viết test fail**8485```python86def test_hanh_vi_cu_the():87 assert ham(dau_vao) == ket_qua_mong_doi88```8990- [ ] **Step 2: chạy cho THẤY nó fail**9192Chạy: `pytest tests/đường/dẫn/test_x.py::test_hanh_vi_cu_the -v`93Mong đợi: FAIL — `NameError: name 'ham' is not defined`9495- [ ] **Step 3: code tối thiểu cho pass**9697```python98def ham(dau_vao):99 return ket_qua_mong_doi100```101102- [ ] **Step 4: chạy lại — PASS**103104Chạy: `pytest tests/đường/dẫn/test_x.py::test_hanh_vi_cu_the -v`105Mong đợi: PASS106107- [ ] **Step 5: commit**108109```bash110git add tests/đường/dẫn/test_x.py src/đường/dẫn/x.py111git commit -m "feat: <việc cụ thể>"112```113````114115**Vì sao `**Thoả:**` là bắt buộc:** SPEC đánh id ổn định `FR-001`, `FR-002`… cho từng yêu cầu. Mỗi task khai mình gánh id nào → câu "phủ hết SPEC" ở Self-review thôi làm bằng mắt, nó thành **dữ liệu grep được**, và **R18 chặn** nếu có `FR-xxx` nào không task nào nhận (in ra đúng id bị bỏ rơi). Một yêu cầu trôi mất giữa hai task là lỗi thầm lặng nhất của mọi kế hoạch — nó chỉ lộ ra lúc agent giao hàng thiếu, hoặc tệ hơn, lúc user dùng.116117**Vì sao `Interfaces` là bắt buộc khi có ≥2 agent:** agent thi hành **chỉ nhìn thấy task của chính nó**. Không khai chữ ký thì hai agent song song đặt tên hàm lệch nhau và phần ghép vỡ — đây là lỗi *chắc chắn* xảy ra, không phải rủi ro.118119## Cấm placeholder — đây là plan HỎNG, không phải plan chưa xong120R7 chặn ở write-time và commit. Không bao giờ viết:121- `TBD`, `TODO`, "điền sau", "chi tiết sau"122- "xử lý lỗi phù hợp" / "thêm validation" / "handle edge cases" — nói *làm gì*, không nói "làm cho phù hợp"123- "viết test cho phần trên" mà không có code test thật124- **"tương tự Task N"** — chép lại code ra. Agent có thể đọc task không theo thứ tự, và mỗi agent chỉ được bơm task của nó.125- Bước mô tả *phải làm gì* mà không chỉ *làm thế nào* (bước đổi code thì bắt buộc có code)126- Nhắc tới hàm/kiểu/method không được định nghĩa ở bất kỳ task nào127128## Self-review129Viết xong, soi lại bằng mắt mới — sửa tại chỗ, không cần vòng hai:1301. **Phủ SPEC** — lướt từng yêu cầu trong SPEC, chỉ ra được task nào thực hiện nó. Thiếu → thêm task.1312. **Quét placeholder** — dò đúng các mẫu ở mục trên.1323. **Nhất quán kiểu/tên** — tên hàm, chữ ký, tên field dùng ở task sau có khớp cái định nghĩa ở task trước không? `clearLayers()` ở Task 3 mà `clearFullLayers()` ở Task 7 là một con bug.133134## Cổng ngược — `/plan` được quyền BÁC SPEC135Viết PLAN chính là cách phát hiện SPEC sai: tới lúc phải khai đường dẫn file thật và chữ ký hàm thật thì thiết kế bất khả thi mới lòi ra. Nếu cổng duyệt đã cho qua rồi mà giờ mới lòi, nghĩa là user đã duyệt một thứ không xây được — cổng đã hỏng.136137**DỪNG, không viết tiếp PLAN nửa vời**, khi gặp bất kỳ điều nào:138- Một yêu cầu trong SPEC không quy được về task nào làm được.139- Hai mục trong SPEC mâu thuẫn nhau, hoặc mâu thuẫn với `## Global constraints`.140- Phương án đã chọn ở `## Approaches` hoá ra bất khả thi (file không tồn tại, API không có, ràng buộc chặn).141- Phải bịa một hàm/kiểu/file mà SPEC không hề nhắc, để cho plan "chạy được trên giấy".142143Xử lý: **gom TẤT CẢ chỗ vỡ thành MỘT lần báo** (không ngắt user từng phát một), mỗi chỗ kèm đúng câu trong SPEC gây ra nó, rồi quay về `/propose` sửa SPEC và **duyệt lại**. Một PLAN viết trên nền SPEC hỏng còn tệ hơn không có PLAN: nó bơm cái sai vào agent với vẻ mặt tự tin, và agent rẻ sẽ thi hành nguyên xi.144145## Rules146- PLAN **không cần** file `.html` — nó là thứ máy đọc. HTML gắn với SPEC (thứ người xem lúc duyệt), do `/propose` sinh. R7 miễn check diagram cho nhánh `-PLAN.md`.147- Đường dẫn file **luôn chính xác**, kèm dải dòng khi sửa file có sẵn.148- Bước đổi code thì **phải có code đầy đủ** — không mô tả suông.149- Lệnh chạy **chính xác**, kèm **output mong đợi** (agent cần biết thế nào là fail đúng, thế nào là pass).150- DRY, YAGNI, TDD, commit thường xuyên.151- Trong codebase có sẵn: theo pattern đang có. Đừng nhân tiện refactor thứ ngoài task.