@relipa/ai-flow-kit 0.2.0-beta.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,29 @@
3
3
  > **For:** `create-spec`
4
4
  > **Persona:** Business Analyst (BA)
5
5
  > **Goal:** Chuyển đổi yêu cầu thô (Jira / Backlog / file) thành tài liệu Đặc tả Use Case (UC Spec) hoàn chỉnh để bàn giao Dev/Test, tuân theo quy trình UCflow.
6
- > **Interaction Rules:** Hỏi ONE câu hỏi tại một thời điểm — đợi BA trả lời trước khi tiếp tục. KHÔNG hỏi nhiều câu cùng lúc.
6
+ > **Interaction Rules:** Hỏi ONE câu hỏi tại một thời điểm — đợi BA trả lời trước khi tiếp tục. KHÔNG hỏi nhiều câu cùng lúc. Số lượng câu hỏi KHÔNG cố định — phải phản ánh đúng độ phức tạp thực tế của yêu cầu (xem chi tiết ở Gate 1 Bước 2).
7
+
8
+ ---
9
+
10
+ ### Quy ước Blocking / Non-blocking & đánh dấu PENDING
11
+
12
+ > Áp dụng cho **cả 4 Gate** — dùng để quyết định câu hỏi nào bắt buộc phải Confirmed trước khi tiến gate tiếp theo, và câu nào được phép defer mà vẫn cho qua.
13
+
14
+ Mỗi Gap/Assumption/câu hỏi Q&A phải được gắn nhãn mức độ ảnh hưởng ngay khi phát hiện:
15
+
16
+ - **🔴 Blocking** — chưa trả lời thì KHÔNG thể tiến gate tiếp theo một cách đáng tin cậy. Gồm: ảnh hưởng cấu trúc dữ liệu (thêm/sửa field, quan hệ), luồng chính (happy path) của use case, phân quyền/bảo mật, hoặc quyết định kiến trúc khó đảo ngược sau khi đã code.
17
+ - **🟡 Non-blocking** — có thể tạm ghi Open (deferred) và vẫn đi tiếp. Gồm: chi tiết UI/text hiển thị, giá trị mặc định có thể chỉnh sau, alternative/exception flow hiếm gặp, câu hỏi cần chờ khách hàng xác nhận nhưng không chặn thiết kế các phần khác.
18
+
19
+ **Khi BA/khách hàng defer (chưa trả lời ngay) một câu hỏi:**
20
+
21
+ - 🟡 Non-blocking → đánh dấu **Open**, tiếp tục ngay sang câu hỏi kế tiếp hoặc gate kế tiếp — KHÔNG chặn.
22
+ - 🔴 Blocking → AI phải hiển thị cảnh báo rõ lý do đây là blocking + hệ quả nếu bỏ qua, rồi hỏi xác nhận BA có chắc muốn tiếp tục không. Nếu BA xác nhận đồng ý tiếp tục dù chưa trả lời → vẫn đánh dấu Open nhưng giữ nhãn 🔴 Blocking (không được hạ xuống Non-blocking) — mục "Điều kiện dừng chặn APPROVED" ở mỗi Gate dựa vào nhãn này.
23
+
24
+ **Đánh dấu PENDING trong tài liệu output:** bất kỳ nội dung nào phụ thuộc vào một câu hỏi còn Open (Blocking hoặc Non-blocking) phải ghi rõ marker tại đúng vị trí liên quan trong `Analysis_v(n).md` / `UI-Prototype` / `UC-Spec_v1.md`, thay vì tự suy diễn nội dung để lấp đầy:
25
+
26
+ ```
27
+ ⚠️ [PENDING - QA-0N] <mô tả ngắn nội dung còn thiếu>
28
+ ```
7
29
 
8
30
  ---
9
31
 
@@ -189,40 +211,48 @@ Nếu workspace mở dạng parent folder chứa cả `ak docs` lẫn source cod
189
211
  - Xem xét các khía cạnh: validate dữ liệu, định dạng nhập liệu, phân quyền, xử lý ngoại lệ, thông báo lỗi
190
212
  - Liệt kê **toàn bộ** Gap/Assumption tìm được — không chỉ những điểm "cốt yếu"
191
213
 
214
+ > ❌ **KHÔNG** tự giới hạn số câu hỏi theo một số cố định (ví dụ luôn dừng ở 5-6 câu) bất kể độ phức tạp của input. Số câu hỏi phải phản ánh đúng số Gap/Assumption thực sự tồn tại: yêu cầu đơn giản (1 field, 1 luồng) có thể chỉ cần 2-3 câu; yêu cầu phức tạp (nhiều actor, nhiều luồng, tích hợp hệ thống khác) có thể cần 15-20+ câu. Nếu trong lúc hỏi/nghe trả lời phát hiện thêm Gap mới, bổ sung ngay câu hỏi mới vào cuối vòng lặp — không đóng vòng lặp sớm chỉ vì đã hỏi "đủ nhiều".
215
+
216
+ **Phân loại mức ảnh hưởng:** trước khi hỏi, gắn nhãn mỗi Gap là 🔴 Blocking hoặc 🟡 Non-blocking theo [Quy ước Blocking / Non-blocking](#quy-ước-blocking--non-blocking--đánh-dấu-pending) ở đầu file.
217
+
192
218
  **Vòng lặp hỏi-đáp:** Với từng Gap/Assumption trong danh sách, hỏi **ONE câu một lúc**, đợi BA trả lời trước khi hỏi câu tiếp theo:
193
219
 
194
220
  - BA trả lời đủ rõ → đánh dấu **Confirmed**, tích hợp ngay vào bản phân tích đang xây dựng, chuyển sang câu hỏi tiếp theo
195
- - BA trả lời "chưa biết" / "để hỏi lại [stakeholder]" / "chưa chốt được ngay" → đánh dấu **Open** (deferred), ghi chú lý do, vẫn chuyển sang câu hỏi tiếp theo — KHÔNG chặn cả vòng lặp lại vì một câu chưa trả lời được
196
- - Lặp lại cho tới khi **mọi** Gap/Assumption trong danh sách đã được hỏi (mỗi câu trạng thái Confirmed hoặc Open tường minh)
221
+ - BA trả lời "chưa biết" / "để hỏi lại [stakeholder]" / "chưa chốt được ngay":
222
+ - 🟡 Non-blocking đánh dấu **Open** (deferred), ghi chú do, chuyển ngay sang câu hỏi tiếp theo KHÔNG chặn
223
+ - 🔴 Blocking → hiển thị cảnh báo lý do đây là blocking + hệ quả nếu bỏ qua, hỏi BA xác nhận có chắc muốn tiếp tục không; BA xác nhận → đánh dấu **Open** nhưng giữ nhãn 🔴 Blocking, vẫn chuyển sang câu tiếp theo
224
+ - Lặp lại cho tới khi **mọi** Gap/Assumption trong danh sách đã được hỏi (mỗi câu ở trạng thái Confirmed hoặc Open tường minh, kèm nhãn Blocking/Non-blocking)
197
225
 
198
- Chỉ sau khi vòng lặp này hoàn tất (không còn Gap nào **chưa được hỏi**) mới chuyển sang Bước 3. Số lượng câu hỏi Open còn lại sau vòng lặp này chính là input cho Gate 2.
226
+ Chỉ sau khi vòng lặp này hoàn tất (không còn Gap nào **chưa được hỏi**) mới chuyển sang Bước 3. Số lượng câu hỏi Open còn lại (và nhãn Blocking/Non-blocking của chúng) chính là input cho Gate 2.
199
227
 
200
228
  #### Bước 3: Soạn thảo Kết quả phân tích sơ bộ
201
229
  - **READ template:** `.claude/skills/ba-skills/skill-ba-initial-analysis-template-v1.md`
202
230
  - Điền đầy đủ các mục theo template: Actors, Facts, Assumptions, Gap Analysis table — dùng câu trả lời **Confirmed** từ vòng lặp Bước 2 làm nội dung chính, không phải giả định của AI
203
- - Gap Analysis table chỉ còn liệt kê các Gap **Open** (deferred) từ vòng lặp Bước 2
231
+ - Gap Analysis table chỉ còn liệt kê các Gap **Open** (deferred) từ vòng lặp Bước 2, kèm nhãn 🔴 Blocking / 🟡 Non-blocking
232
+ - Với mỗi Gap Open, chèn marker `⚠️ [PENDING - QA-0N]` tại đúng vị trí nội dung liên quan (mục 3/4 của template) thay vì bỏ trống hoặc tự suy diễn
233
+ - Nếu tài liệu còn ít nhất 1 Gap Open → thêm banner đầu tài liệu: `⚠️ TÀI LIỆU CHƯA ĐẦY ĐỦ — còn [N] câu hỏi Open ([X] Blocking, [Y] Non-blocking), xem QnA-Log_v1.md`
204
234
  - Lưu: `02.BA-Specs/01.Analysis/[functionId]/Analysis_v1.md`
205
235
 
206
236
  #### Bước 4: Soạn thảo danh sách Q&A
207
237
 
208
238
  - **READ skill:** `.claude/skills/ba-skills/skill-ba-qna-v1.md`
209
239
  - **READ template:** `.claude/skills/ba-skills/skill-ba-qna-template-v1.md`
210
- - Ghi lại **toàn bộ** câu hỏi đã hỏi ở vòng lặp Bước 2 dưới dạng log: mỗi câu kèm câu trả lời và trạng thái cuối cùng
240
+ - Ghi lại **toàn bộ** câu hỏi đã hỏi ở vòng lặp Bước 2 dưới dạng log: mỗi câu kèm câu trả lời, nhãn Blocking/Non-blocking, và trạng thái cuối cùng
211
241
  - **Confirmed** — BA đã trả lời trong Bước 2, ghi kèm câu trả lời
212
- - **Open** — BA đã xác nhận chưa trả lời được ngay (cần hỏi lại stakeholder khác), ghi kèm lý do deferred
242
+ - **Open** — BA đã xác nhận chưa trả lời được ngay (cần hỏi lại stakeholder khác), ghi kèm lý do deferred + nhãn 🔴 Blocking hoặc 🟡 Non-blocking
213
243
  - Không tạo câu hỏi mới ở bước này — đây là bản ghi (log) của vòng lặp đã chạy ở Bước 2, không phải một vòng hỏi mới
214
244
  - Lưu: `02.BA-Specs/02.QnA/[functionId]/QnA-Log_v1.md`
215
245
 
216
246
  #### Bước 5: Gate Review & Pause
217
247
  - **INVOKE** `gate-review` skill (generate mode) — ghi `.aiflow/review/gate-1-[functionId].md`
218
- - **Kiểm tra Q&A bắt buộc:** Đọc `QnA-Log_v1.md` — đếm số câu trạng thái **Open**
248
+ - **Kiểm tra Q&A bắt buộc:** Đọc `QnA-Log_v1.md` — đếm số câu **Open**, tách riêng theo nhãn 🔴 Blocking và 🟡 Non-blocking
219
249
  - Hiển thị gate pause message — chờ phản hồi từ BA
220
250
 
221
- **Nếu nhận được APPROVED mà QnA-Log_v1.md còn câu hỏi Open → từ chối ngay, hiển thị:**
251
+ **Nếu còn câu Open 🔴 Blocking → từ chối APPROVED ngay, hiển thị:**
222
252
 
223
- ⛔ GATE 1 BLOCKED — Còn [N] câu hỏi chưa được trả lời trong QnA-Log_v1.md.
253
+ ⛔ GATE 1 BLOCKED — Còn [N] câu hỏi Blocking chưa được trả lời trong QnA-Log_v1.md.
224
254
 
225
- | ID | Câu hỏi còn Open |
255
+ | ID | Câu hỏi còn Open (Blocking) |
226
256
  |----|-----------------|
227
257
  | [Q_ID] | [nội dung câu hỏi] |
228
258
 
@@ -231,17 +261,26 @@ Vui lòng cung cấp câu trả lời theo một trong 3 cách:
231
261
  → Option B: paste nội dung bảng Q&A đã điền vào chat
232
262
  → Option C: đặt file `QnA-Log_v1_response.md` vào thư mục `02.QnA/[functionId]/` rồi gõ NEXT
233
263
 
234
- ❌ APPROVED không được chấp nhận cho đến khi 100% câu hỏi đã Confirmed.
264
+ ❌ APPROVED không được chấp nhận cho đến khi mọi câu hỏi **Blocking** đã Confirmed.
235
265
 
236
- **Khi APPROVED (chỉ sau khi tất cả câu hỏi trong QA(1).md đã Confirmed):**
266
+ **Nếu chỉ còn câu Open 🟡 Non-blocking (không còn Blocking) cho phép APPROVED, nhưng hiển thị cảnh báo ngay trong gate pause message trước khi BA gõ APPROVED:**
267
+
268
+ ⚠️ Còn [N] câu hỏi Non-blocking chưa Confirmed — có thể APPROVED để tiếp tục, tài liệu sẽ giữ marker `[PENDING - QA-0N]` tại các phần liên quan cho tới khi được bổ sung ở Gate 2.
269
+
270
+ | ID | Câu hỏi còn Open (Non-blocking) |
271
+ |----|-----------------|
272
+ | [Q_ID] | [nội dung câu hỏi] |
273
+
274
+ **Khi APPROVED (không còn câu Blocking Open — câu Non-blocking Open được phép tồn tại):**
237
275
  → **INVOKE** `gate-review` skill (verify mode) — chạy `ak review check --gate 1 --ticket [functionId]`
238
276
  → Nếu passed: `ak gate 1 approved --ticket [functionId]` → chuyển Gate 2
239
277
  → Nếu blocked: làm theo gate-review skill response protocol
240
278
 
241
279
  **Definition of Done:**
242
280
  - [ ] `functionId` đã được xác định và thư mục đầu ra đã sẵn sàng
243
- - [ ] File `Analysis_v1.md` tạo đúng vị trí, cấu trúc đúng template, có bảng Gap Analysis
244
- - [ ] File `QnA-Log_v1.md` có ít nhất 1 câu hỏi cho mỗi Gap, tất cả trạng thái **Open**
281
+ - [ ] File `Analysis_v1.md` tạo đúng vị trí, cấu trúc đúng template, có bảng Gap Analysis, số Gap phản ánh đúng độ phức tạp thực tế (không bị gò về một số cố định)
282
+ - [ ] File `QnA-Log_v1.md` có ít nhất 1 câu hỏi cho mỗi Gap, mỗi câu nhãn Blocking/Non-blocking rõ ràng
283
+ - [ ] Không còn câu hỏi 🔴 Blocking ở trạng thái Open
245
284
  - [ ] Không có giả định nào được tự ý chốt thành Spec mà không đưa vào Q&A
246
285
 
247
286
  > **Telemetry:** Run `ak gate 1 start --ticket [functionId]` khi bắt đầu gate này.
@@ -270,8 +309,8 @@ Vui lòng cung cấp câu trả lời theo một trong 3 cách:
270
309
  #### Bước 1: Tiếp nhận và đánh giá câu trả lời
271
310
  - Đối chiếu từng câu trả lời với câu hỏi trong `QnA-Log_v(m).md`
272
311
  - Phân loại:
273
- - Đủ thông tin → cập nhật trạng thái: **Confirmed**
274
- - Chưa rõ hoặc phát sinh nghiệp vụ mới → giữ trạng thái **Open**, bổ sung ghi chú
312
+ - Đủ thông tin → cập nhật trạng thái: **Confirmed**, xóa marker `[PENDING - QA-ID]` liên quan
313
+ - Chưa rõ hoặc phát sinh nghiệp vụ mới → giữ trạng thái **Open**, bổ sung ghi chú, giữ nguyên nhãn 🔴 Blocking / 🟡 Non-blocking đã gắn từ Gate 1 (hoặc gắn mới nếu là Gap phát sinh)
275
314
 
276
315
  #### Bước 2: Tích hợp thông tin vào tài liệu phân tích
277
316
  - Lấy thông tin từ các câu Confirmed → cập nhật vào `Analysis_v(n).md`
@@ -280,7 +319,7 @@ Vui lòng cung cấp câu trả lời theo một trong 3 cách:
280
319
 
281
320
  #### Bước 3: Rẽ nhánh theo trạng thái Q&A
282
321
 
283
- **Kịch bản A — Vẫn còn câu hỏi Open:**
322
+ **Kịch bản A — Còn câu hỏi Open 🔴 Blocking:**
284
323
  1. Kiểm tra `Analysis_v(n+1).md` có phát sinh Gap mới không
285
324
  2. Tạo: `02.BA-Specs/02.QnA/[functionId]/QnA-Log_v(m+1).md` — chỉ giữ câu Open và câu mới phát sinh
286
325
  3. **INVOKE** `gate-review` skill (generate mode) — ghi `.aiflow/review/gate-2-[functionId]-round-[m+1].md`
@@ -289,7 +328,8 @@ Vui lòng cung cấp câu trả lời theo một trong 3 cách:
289
328
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
290
329
  ⏸️ GATE 2 — VÒNG Q&A [m+1]
291
330
 
292
- Câu hỏi còn Open: [N]
331
+ Câu hỏi còn Open (🔴 Blocking): [N]
332
+ Câu hỏi còn Open (🟡 Non-blocking): [M]
293
333
  File Q&A mới: [02.BA-Specs/02.QnA/[functionId]/QnA-Log_v(m+1).md](02.BA-Specs/02.QnA/[functionId]/QnA-Log_v(m+1).md)
294
334
  File phân tích: [02.BA-Specs/01.Analysis/[functionId]/Analysis_v(n+1).md](02.BA-Specs/01.Analysis/[functionId]/Analysis_v(n+1).md)
295
335
 
@@ -299,23 +339,40 @@ File phân tích: [02.BA-Specs/01.Analysis/[functionId]/Analysis_v(n+1).md](02
299
339
 
300
340
  5. Lặp lại Bước 1 khi nhận phản hồi mới
301
341
 
302
- **Nếu BA gõ APPROVED khi đang ở Kịch bản A (còn câu hỏi Open) → từ chối ngay:**
342
+ **Nếu BA gõ APPROVED khi đang ở Kịch bản A (còn câu hỏi Open 🔴 Blocking) → từ chối ngay:**
303
343
 
304
- ⛔ GATE 2 BLOCKED — Còn [N] câu hỏi chưa Confirmed.
344
+ ⛔ GATE 2 BLOCKED — Còn [N] câu hỏi Blocking chưa Confirmed.
305
345
 
306
- | ID | Câu hỏi còn Open |
346
+ | ID | Câu hỏi còn Open (Blocking) |
307
347
  |----|-----------------|
308
348
  | [Q_ID] | [nội dung câu hỏi] |
309
349
 
310
350
  → Cung cấp câu trả lời (Option A/B/C) rồi gõ NEXT để AI xử lý tiếp.
311
- ❌ APPROVED chỉ được chấp nhận khi 100% câu hỏi đã Confirmed.
351
+ ❌ APPROVED chỉ được chấp nhận khi mọi câu hỏi **Blocking** đã Confirmed.
352
+
353
+ **Kịch bản B — Chỉ còn câu hỏi Open 🟡 Non-blocking (không còn Blocking):**
354
+
355
+ 1. Tạo `QnA-Log_v(m+1).md` như Kịch bản A (giữ câu Non-blocking Open + câu mới phát sinh)
356
+ 2. Hiển thị cho BA 2 lựa chọn, không tự chọn thay:
357
+
358
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
359
+ ⏸️ GATE 2 — CÒN [M] CÂU HỎI NON-BLOCKING CHƯA CONFIRMED
360
+
361
+ Các câu hỏi này không chặn thiết kế Gate 3/4, nhưng output sẽ giữ marker `[PENDING - QA-ID]` tại các phần liên quan cho tới khi được bổ sung.
362
+
363
+ → Cách 1: cung cấp câu trả lời (Option A/B/C) rồi gõ NEXT để xử lý tiếp
364
+ → Cách 2: gõ APPROVED để chấp nhận tiếp tục dù output chưa đầy đủ ở các mục này
365
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
366
+
367
+ 1. BA gõ NEXT với phản hồi mới → lặp lại Bước 1
368
+ 2. BA gõ APPROVED → coi là đã xác nhận chấp nhận output chưa đầy đủ, chuyển sang xử lý APPROVED bên dưới
312
369
 
313
- **Kịch bản B — Tất cả đã Confirmed:**
370
+ **Kịch bản C — Tất cả đã Confirmed:**
314
371
  1. Chốt tài liệu phân tích (phiên bản cuối cùng là đầu vào Gate 3)
315
372
  2. **INVOKE** `gate-review` skill (generate mode) — ghi `.aiflow/review/gate-2-[functionId].md`
316
373
  3. Hiển thị gate pause message — đợi **APPROVED**
317
374
 
318
- **Khi APPROVED (chỉ áp dụng Kịch bản B):**
375
+ **Khi APPROVED (Kịch bản B hoặc C — không còn câu Blocking Open):**
319
376
  → **INVOKE** `gate-review` skill (verify mode) — chạy `ak review check --gate 2 --ticket [functionId]`
320
377
  → Nếu passed: `ak gate 2 approved --ticket [functionId]` → chuyển Gate 3
321
378
  → Nếu blocked: làm theo gate-review skill response protocol
@@ -323,7 +380,8 @@ File phân tích: [02.BA-Specs/01.Analysis/[functionId]/Analysis_v(n+1).md](02
323
380
  **Definition of Done:**
324
381
  - [ ] Mọi câu trả lời đều được phân tích và phân loại trạng thái
325
382
  - [ ] Tài liệu phân tích phiên bản mới tích hợp đầy đủ thông tin nghiệp vụ mới nhất
326
- - [ ] Không còn câu hỏi nào ở trạng thái **Open** trước khi tiến Gate 3
383
+ - [ ] Không còn câu hỏi 🔴 Blocking ở trạng thái Open trước khi tiến Gate 3
384
+ - [ ] Nếu còn câu 🟡 Non-blocking Open, BA đã xác nhận rõ ràng (gõ APPROVED sau khi thấy cảnh báo) và các mục liên quan trong Analysis đã có marker `[PENDING - QA-ID]`
327
385
 
328
386
  > **Telemetry:** Run `ak gate 2 start --ticket [functionId]` khi bắt đầu gate này.
329
387
  > Run `ak gate 2 approved --ticket [functionId]` sau khi gate-review verify passed. Run as-is — không thêm shell redirects.
@@ -334,7 +392,7 @@ File phân tích: [02.BA-Specs/01.Analysis/[functionId]/Analysis_v(n+1).md](02
334
392
 
335
393
  **Mục tiêu:** Dựa trên tài liệu phân tích đã chốt, thiết kế bản mẫu giao diện (Prototype) độc lập bằng HTML/CSS để trực quan hóa các thành phần và luồng tương tác.
336
394
 
337
- **Điều kiện vào Gate 3:** Tất cả Q&A đã **Confirmed** và Gate 2 đã **APPROVED**.
395
+ **Điều kiện vào Gate 3:** Không còn câu hỏi 🔴 Blocking Open và Gate 2 đã **APPROVED** (câu 🟡 Non-blocking Open, nếu còn, mang marker `[PENDING - QA-ID]` sang cùng Analysis).
338
396
 
339
397
  **Đầu vào (Inputs):**
340
398
  - `02.BA-Specs/01.Analysis/[functionId]/Analysis_v(n).md` — phiên bản cuối
@@ -349,6 +407,7 @@ File phân tích: [02.BA-Specs/01.Analysis/[functionId]/Analysis_v(n+1).md](02
349
407
  #### Bước 1: Liệt kê thành phần giao diện
350
408
  - Đọc tài liệu phân tích đã chốt
351
409
  - Xác định: ô nhập liệu, nút bấm, liên kết, vùng hiển thị lỗi, vùng hiển thị dữ liệu
410
+ - Nếu Analysis còn marker `[PENDING - QA-ID]` liên quan đến thành phần giao diện nào → giữ nguyên placeholder trực quan cho thành phần đó (không tự bịa nội dung/behavior để lấp đầy) và ghi chú `<!-- PENDING - QA-ID -->` cạnh phần tử HTML tương ứng
352
411
 
353
412
  #### Bước 2: Thiết kế và viết code HTML/CSS
354
413
  - **READ skill:** `.claude/skills/ba-skills/skill-ba-prototype-v1.md` và làm theo
@@ -406,6 +465,7 @@ File phân tích: [02.BA-Specs/01.Analysis/[functionId]/Analysis_v(n+1).md](02
406
465
  - **READ skill:** `.claude/skills/ba-skills/skill-ba-write-uc-spec-v1.md`
407
466
  - Tạo: `02.BA-Specs/04.UC-Specs/[functionId]/UC-Spec_v1.md`
408
467
  - Điền General Information: Use Case ID, Title, Version, Actors, Preconditions, Postconditions
468
+ - Nếu Analysis/Prototype đầu vào còn marker `[PENDING - QA-ID]` (câu Non-blocking chưa Confirmed) → giữ nguyên marker tại đúng vị trí liên quan (Business Rule, UI Component, Flow step) trong UC Spec, không tự bịa nội dung để lấp đầy; thêm banner đầu tài liệu: `⚠️ UC SPEC CHƯA ĐẦY ĐỦ — còn [N] mục PENDING, xem QnA-Log_v(m).md`
409
469
 
410
470
  #### Bước 2: Mô tả luồng sự kiện (Flow of Events)
411
471
  - **Main Flow** — happy path
@@ -450,7 +510,7 @@ File phân tích: [02.BA-Specs/01.Analysis/[functionId]/Analysis_v(n+1).md](02
450
510
  - [ ] 100% element HTML được mô tả trong bảng UI Components (id, loại, ràng buộc)
451
511
  - [ ] Mã Mermaid hợp lệ, thể hiện đủ Main/Alternative/Exception Flow
452
512
  - [ ] Business Rules đầy đủ với mã `[BR-NNN]` và thông báo lỗi chính xác
453
- - [ ] Tài liệu không chứa ghi chú mơ hồ hoặc câu hỏi chưa giải quyết
513
+ - [ ] Không còn giả định ngầm chưa Confirm bị viết như thể đã chốt; mọi nội dung Non-blocking Open còn lại đã có marker `[PENDING - QA-ID]` rõ ràng tại đúng vị trí (không phải ghi chú mơ hồ chung)
454
514
 
455
515
  > **Telemetry:** Run `ak gate 4 start --ticket [functionId]` khi bắt đầu gate này.
456
516
  > Run `ak gate 4 approved --ticket [functionId]` sau khi gate-review verify passed. Run as-is — không thêm shell redirects.
@@ -497,9 +557,11 @@ Sau khi Gate 4 đã APPROVED (UC Spec hoàn thành):
497
557
  - ❌ **KHÔNG** bỏ qua thứ tự Gate — luôn đi từ Gate 1 → 2 → 3 → 4
498
558
  - ❌ **KHÔNG** tự chốt giả định nghiệp vụ mà không đưa vào Q&A
499
559
  - ❌ **KHÔNG** dùng `superpowers:brainstorming` ở Gate 1 — terminal state của skill đó invoke `writing-plans`, bypass cấu trúc BA workflow
500
- - ✅ **BẮT BUỘC** ở Gate 1 Bước 2: hỏi hết **toàn bộ** Gap/Assumption xác định được, từng câu một, ngay trong hội thoại — không giới hạn ở "điểm mơ hồ cốt yếu". Chỉ được ghi Open (deferred sang QnA-Log) khi BA xác nhận rõ ràng là chưa trả lời được ngay
501
- - **KHÔNG** tiến Gate 3 khi vẫn còn câu hỏi Open
502
- - ❌ **KHÔNG chấp nhận APPROVED** Gate 1 hoặc Gate 2 khi còn bất kỳ câu hỏi nào trạng thái **Open** — từ chối và hiển thị danh sách câu hỏi cần trả lời
560
+ - ✅ **BẮT BUỘC** ở Gate 1 Bước 2: hỏi hết **toàn bộ** Gap/Assumption xác định được, từng câu một, ngay trong hội thoại — không giới hạn ở "điểm mơ hồ cốt yếu" và **không giới hạn theo một số câu hỏi cố định** bất kể độ phức tạp input. Chỉ được ghi Open (deferred sang QnA-Log) khi BA xác nhận rõ ràng là chưa trả lời được ngay
561
+ - **BẮT BUỘC** gắn nhãn 🔴 Blocking / 🟡 Non-blocking cho mỗi Gap/câu hỏi ngay khi phát hiện (xem [Quy ước Blocking / Non-blocking](#quy-ước-blocking--non-blocking--đánh-dấu-pending))
562
+ - ❌ **KHÔNG** tiến Gate 3 khi vẫn còn câu hỏi 🔴 Blocking ở trạng thái Open
563
+ - ❌ **KHÔNG chấp nhận APPROVED** ở Gate 1 hoặc Gate 2 khi còn bất kỳ câu hỏi 🔴 **Blocking** nào ở trạng thái **Open** — từ chối và hiển thị danh sách câu hỏi cần trả lời
564
+ - ✅ **ĐƯỢC PHÉP chấp nhận APPROVED** ở Gate 1/2/4 khi chỉ còn câu hỏi 🟡 **Non-blocking** Open, với điều kiện: đã hiển thị cảnh báo rõ ràng cho BA trước khi nhận APPROVED, và mọi nội dung phụ thuộc câu hỏi đó được đánh dấu `[PENDING - QA-ID]` tại đúng vị trí trong output — không được tự suy diễn nội dung để lấp đầy
503
565
  - ✅ **BẮT BUỘC** chạy Bước 0 ở Gate 1 — xác nhận `functionId` và thư mục đầu ra trước khi làm bất cứ điều gì
504
566
  - ✅ **BẮT BUỘC** chạy Bước 0.5 ở Gate 1 — đảm bảo AK-Docs đang ở branch `feature/[functionId]/[taskId]` trước khi ghi file đầu tiên
505
567
  - ✅ **BẮT BUỘC** đọc skill từ `.claude/skills/ba-skills/` — không suy luận từ bộ nhớ
@@ -18,6 +18,8 @@
18
18
 
19
19
  Nếu `AK-Docs/` hoặc `Shared-Docs/` chưa tồn tại tại workspace root, hoặc không phải git repo → bỏ qua bước tương ứng, không cảnh báo.
20
20
 
21
+ > ❌ **KHÔNG** tự ý liệt kê (`git branch -a`), checkout, hoặc đọc/diff nhiều branch trong repo source để tự dò tìm "nhánh đang phát triển" của feature — rất tốn token và thời gian, và dễ đọc nhầm code chưa hoàn chỉnh/chưa merge. Bước 1 chỉ `git pull` đúng branch hiện tại đang checkout (thường là `main`/`develop`). Nếu cần biết chính xác code nào đã thay đổi cho ticket này → xem bước **Dev Artifacts Check** (Bước 1.5, Gate 1, bên dưới): hỏi TESTER cung cấp PR link hoặc commit SHA/branch name cụ thể, rồi fetch/diff **đúng** phạm vi đó qua skill `pr-impact-analysis`.
22
+
21
23
  **Nếu bất kỳ lệnh `git pull` nào ở Bước 1–3 thất bại** → **KHÔNG dừng workflow** — hiển thị cảnh báo và tiếp tục gate với dữ liệu local hiện có:
22
24
 
23
25
  ```
@@ -33,6 +35,7 @@ Các skill sau đây được cài tự động vào `.claude/skills/test-skills
33
35
 
34
36
  | Skill | File |
35
37
  |---|---|
38
+ | `PR Impact Analysis` | `.claude/skills/pr-impact-analysis/SKILL.md` |
36
39
  | `QA Writing Standards` | `.claude/skills/test-skills/rules/qa-writing-standards.md` |
37
40
  | `Directory & Naming Convention` | `.claude/skills/test-skills/rules/directory-and-naming-convention.md` |
38
41
  | `Template TestCase` | `.claude/skills/test-skills/template/testcase-template.md` |
@@ -204,6 +207,26 @@ Nếu tất cả điều kiện thỏa mãn → thông báo ngắn rồi tiếp
204
207
  - Nếu có `supplementaryContext[]` → đọc từng item (SRS file, Figma link, API spec, spec MD file)
205
208
  - Nếu có file yêu cầu thô được chỉ định → đọc file đó
206
209
 
210
+ #### Bước 1.5: Dev Artifacts Check (PR/Commit-based)
211
+
212
+ **Mục tiêu:** xác định **chính xác** phạm vi code đã thay đổi cho ticket này, để mở rộng coverage (regression scope) mà **không** phải tự dò/đọc toàn bộ branch đang phát triển của feature (tốn token, tốn thời gian, dễ đọc nhầm code chưa xong).
213
+
214
+ 1. Kiểm tra ticket/context có PR link đính kèm không (`.aiflow/context/current.json`, mô tả ticket).
215
+ 2. **Có PR link, hoặc TESTER đã khai báo `PR: <url>` trong chat** → **INVOKE** skill `pr-impact-analysis` ngay, dùng PR đó.
216
+ 3. **Chưa có PR/commit nào được biết** → hỏi TESTER **một câu duy nhất**:
217
+
218
+ ```text
219
+ Để xác định đúng phạm vi ảnh hưởng code (tránh phải rà toàn bộ branch), bạn cung cấp giúp PR link hoặc commit SHA/branch name của thay đổi cho ticket này (có thể nhiều PR/commit nếu multi-repo). Nếu chưa có, gõ "chưa có" để bỏ qua bước này.
220
+ ```
221
+
222
+ 4. Xử lý câu trả lời của TESTER:
223
+ - Cung cấp PR/commit/branch → **INVOKE** skill `pr-impact-analysis` với thông tin đó (dùng fallback `git fetch` + `git diff main...<branch>` nếu không phải PR).
224
+ - Trả lời "chưa có" → ghi nhận vào `test-plan/impact-analysis.md` (hoặc tương đương) "chưa có dev artifacts — sẽ re-check ở Gate 3", **không block Gate 1**.
225
+ 5. Kết quả `pr-impact-analysis` (màn hình ảnh hưởng trực tiếp/gián tiếp, đề xuất regression TCs) được dùng làm input bổ sung cho Bước 2 và Gate 2 (Scenario Building) — **không** dùng để xác định expected result (vẫn lấy từ ticket/UC Spec/System Requirement).
226
+
227
+ > ❌ **KHÔNG** tự ý `git branch -a`, checkout, hoặc đọc lần lượt nhiều branch để tìm "code đang phát triển" — luôn đi qua PR/commit cụ thể mà TESTER xác nhận, qua skill `pr-impact-analysis`.
228
+ > Ở Gate 3 (trước khi thiết kế TC chi tiết), re-run bước này nếu đã có `impact-analysis.md` từ Gate 1 — dùng Delta Detection của `pr-impact-analysis` (so SHA cũ/mới) để bắt commit mới, không đọc lại toàn bộ diff.
229
+
207
230
  #### Bước 2: Phân tích yêu cầu và đánh giá rủi ro
208
231
  - **READ skill:** `.claude/skills/test-skills/rules/qa-writing-standards.md` — đọc trước để nắm quy ước chung
209
232
  - **READ skill:** `.claude/skills/test-skills/categories/00-core/00.01.requirement-analysis.md`
@@ -357,6 +380,10 @@ Nếu tất cả điều kiện thỏa mãn → thông báo ngắn rồi tiếp
357
380
 
358
381
  **Pre-flight (bắt buộc, chạy trước Bước 1):** chạy [Pre-flight — Đồng bộ Source & Docs](#pre-flight-bắt-buộc--đồng-bộ-source--docs-đầu-mỗi-gate) ở đầu file. Lỗi → hiển thị ⚠️ cảnh báo, không dừng gate.
359
382
 
383
+ #### Bước 0.7: Dev Artifacts Check — Delta Detection
384
+
385
+ Nếu `test-plan/impact-analysis.md` đã có PR/commit từ Gate 1 (Bước 1.5) → re-run `pr-impact-analysis` (Delta Detection): so SHA đã ghi với SHA hiện tại, nếu có commit mới thì chỉ đọc diff phần thêm mới, bổ sung TC nếu cần. Không có gì mới hoặc chưa từng có PR → bỏ qua, không cần hỏi lại TESTER.
386
+
360
387
  #### Bước 1: Đọc template và quy ước bắt buộc
361
388
  - **READ template:** `.claude/skills/test-skills/template/testcase-template.md` — **BẮT BUỘC** tuân thủ 100%
362
389
  - **READ skill:** `.claude/skills/test-skills/rules/qa-writing-standards.md`
@@ -545,15 +572,16 @@ Sau khi Gate 4 đã APPROVED (bộ Test Case hoàn thành):
545
572
 
546
573
  | Gate | Skills đọc | Output |
547
574
  |---|---|---|
548
- | Gate 1 | `QA_Writing_Standards`, `00.01 Requirement Analysis`, `00.02 Risk Analysis` | `01_Requirement_Analysis_Result.md`, `01_QA.md` |
575
+ | Gate 1 | `pr-impact-analysis` (Bước 1.5), `QA_Writing_Standards`, `00.01 Requirement Analysis`, `00.02 Risk Analysis` | `01_Requirement_Analysis_Result.md`, `01_QA.md` |
549
576
  | Gate 2 | `00.03 Scenario Builder`, `01.06 Error Guessing`, `03.02 Workflow Testing`, Domain skills | `02_Test_Scenarios_Result.md`, `02_QA.md` |
550
- | Gate 3 | `testcase-template`, `01.01 BVA`, `01.02 EP`, `01.03 Decision Table`, `01.06 Error Guessing`, `02.01`–`02.04`, `03.01`, `03.03`, `04.01`, `04.04` | `03_Test_Cases_Draft_Result.md`, `03_QA.md` |
577
+ | Gate 3 | `pr-impact-analysis` (Delta Detection nếu có PR từ Gate 1), `testcase-template`, `01.01 BVA`, `01.02 EP`, `01.03 Decision Table`, `01.06 Error Guessing`, `02.01`–`02.04`, `03.01`, `03.03`, `04.01`, `04.04` | `03_Test_Cases_Draft_Result.md`, `03_QA.md` |
551
578
  | Gate 4 | `00.04 TC Review`, `99.01 Coverage Review` | `04_Final_Result.md`, `04_QA.md`, `[functionId]_TestCase.md` |
552
579
 
553
580
  ---
554
581
 
555
582
  ### Quy tắc bắt buộc
556
583
 
584
+ - ❌ **KHÔNG** tự ý liệt kê/checkout/đọc nhiều branch trong repo source để dò tìm thay đổi code — xác định phạm vi ảnh hưởng code phải qua PR link hoặc commit SHA/branch cụ thể do TESTER cung cấp (Bước 1.5, Gate 1) + skill `pr-impact-analysis`
557
585
  - ❌ **KHÔNG** bỏ qua thứ tự Gate — luôn đi từ Gate 1 → 2 → 3 → 4
558
586
  - ❌ **KHÔNG** tự suy diễn nghiệp vụ — ghi rõ Assumption và TBD khi thiếu thông tin
559
587
  - ❌ **KHÔNG** sinh `04_Final_Result.md` mà thiếu `[functionId]_TestCase.md` (hoặc ngược lại)
@@ -725,12 +725,13 @@ Bugs logged: [N] | Skipped: [N]
725
725
  > Implements "Vấn đề 5" trong `docs/internal/PM Workflow_v1.0.md`. **Không chờ APPROVED để bắt đầu** — chạy tự động ngay sau Gate 2 Bước 7, không cần lệnh riêng và không cần chờ PM merge MR (merge là việc độc lập của PM). `INVOKE`: `ingest-data` skill § 5 cho toàn bộ logic phân tích/đề xuất — mục này chỉ nêu gate mechanics.
726
726
 
727
727
  1. Đọc lại entry vừa ghi + điều tra source/docs liên quan (skill § 5.1).
728
- 2. Quyết định content này có cần task không (skill § 5.2). Nếu KHÔNG cần task → hiển thị thông báo tương ứng **kết thúc luôn, không chạy `ak gate 3 start`, không có Gate 3 Display**.
728
+ 2. Quyết định content này có cần task không (skill § 5.2 — với entry từ `Confirmations-Log`, chạy qua checklist tối thiểu thay vì chỉ dựa Action items viết sẵn). Nếu KHÔNG cần task → hiển thị thông báo tương ứng, chạy `ak gate 3 skip --ticket [ticket-id] --reason "<1 câu lý do>"` (không chạy `ak gate 3 start`), và **kết thúc luôn, không có Gate 3 Display**. `skip` vẫn đóng gate đúng cách (advance `currentGate` giống `approved`) — task hiện đúng "done" trên dashboard, không treo ở trạng thái "sẵn sàng chạy Gate 3" mãi.
729
729
  3. Nếu CẦN task → chạy `ak gate 3 start --ticket [ticket-id]`, rồi đề xuất danh sách task (skill § 5.3), cho PM sửa/thêm/xoá (skill § 5.4), lặp tới khi PM hài lòng.
730
730
  4. Điểm dừng xác nhận trước khi tạo ticket (skill § 5.5) — ❌ không tự thêm `--yes`, phải có xác nhận rõ ràng trong hội thoại.
731
731
  5. Xác định project đích trên Backlog/Jira (skill § 5.6, dùng `ak backlog-projects`/`ak jira-projects` + `ak backlog-set-default-project`/`ak jira-set-default-project`).
732
- 6. Tạo ticket bằng `ak tasks create-tickets <file>.json --json` (skill § 5.7). Nếu thiếu WRITE credentials, hỏi PM nhập ngay trong chat rồi `ak credentials set <field> "<value>"` thử lại đúng 1 lần.
733
- 7. Ghi ngược link ticket vào entry gốc + hiển thị tổng kết (skill § 5.8).
732
+ 6. Nếu danh sách 2 task, hỏi PM muốn gộp dưới 1 ticket cha không tạo mới / dùng sẵn / không cần (skill § 5.6b).
733
+ 7. Tạo ticket bằng `ak tasks create-tickets <file>.json --json` (skill § 5.7), kèm `parentTicket` nếu Bước 6 xác định có. Nếu thiếu WRITE credentials, hỏi PM nhập ngay trong chat rồi `ak credentials set <field> "<value>"` và thử lại đúng 1 lần. Nếu lỗi `parent-not-found`/`parent-create-failed`, hiển thị nguyên lỗi và hỏi PM muốn thử ticket cha khác, bỏ qua ticket cha, hay dừng lại.
734
+ 8. Ghi ngược link ticket vào entry gốc + hiển thị tổng kết (skill § 5.8).
734
735
  8. Run: `ak gate 3 approved --ticket [ticket-id]` để đóng task.
735
736
 
736
- > **Telemetry:** Run `ak gate 3 start --ticket [ticket-id]` chỉ khi Bước 2 kết luận CẦN task. Run `ak gate 3 approved --ticket [ticket-id]` khi Gate 3 hoàn tất.
737
+ > **Telemetry:** Run `ak gate 3 start --ticket [ticket-id]` chỉ khi Bước 2 kết luận CẦN task. Run `ak gate 3 approved --ticket [ticket-id]` khi Gate 3 hoàn tất, hoặc `ak gate 3 skip --ticket [ticket-id] --reason "..."` khi Bước 2 kết luận KHÔNG cần task.
@@ -7,6 +7,16 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.2.2] - 2026-08-06
11
+
12
+ ### Added
13
+
14
+ - **`ak gate <n> skip --ticket <id> [--reason <text>]`** — a gate can now close as "skipped" (legitimately produced no deliverable) instead of only `start`/`approved`. First consumer: `ingest-data` Gate 3, when the AI decides the ingested content is reference-only and no task list is needed — it now runs `ak gate 3 skip` instead of leaving the gate untouched. `scripts/task.js`'s `updateTaskGateState()` gained a `skip` branch that records `skippedGates[gate] = {at, reason}` in `task-state.json` and advances `currentGate` exactly like `approved` does, so every existing "is this task done" check (which only ever inspects `currentGate`) keeps working with zero changes. Prompted by an impact assessment for the `ai-flow-ex` VS Code extension's dashboard, which was showing skipped-Gate-3 ingest-data tasks stuck as "ready to run Gate 3" forever — see `docs/superpowers/specs/2026-08-06-ingest-data-gate3-skipped-state-design.md` in `ai-flow-ex` for the paired UI-side change (reads `skippedGates` to render a distinct "⏭ Skipped" badge instead of the generic "✓ Approved").
15
+
16
+ ### Fixed
17
+
18
+ - **`ak task next` treated `ingest-data` as a 2-gate flow** — a stale assumption left over from before Gate 3 (task creation & ticket generation) was added to `ingest-data` earlier in the same release cycle. `scripts/task.js` grouped it with `gen-doc`/`create-system-requirement` (`maxGate = 2`), so pausing via `ak task next` on Gate 2 would prematurely mark the task `status: 'done'` and `gateLabel(3, 'ingest-data')` returned `'Done'` instead of `'Sinh Task & Tạo Ticket'`. Split into its own 3-gate branch (`maxGate = 3`); `gen-doc`/`create-system-requirement` are unaffected.
19
+
10
20
  ## [0.2.1] - 2026-08-05
11
21
 
12
22
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relipa/ai-flow-kit",
3
- "version": "0.2.0-beta.1",
3
+ "version": "0.2.0",
4
4
  "description": "All-in-one AI Flow Kit for team development with Claude AI - skills, templates, and MCP adapters",
5
5
  "author": "Example Team",
6
6
  "publishConfig": {
@@ -3,9 +3,13 @@ const https = require('https');
3
3
  const chalk = require('chalk');
4
4
 
5
5
  const BACKLOG_RE = /https?:\/\/([\w.-]+\.backlog(?:tool)?\.com)\/view\/([A-Z][A-Z0-9_]+-\d+)(?:#comment-(\d+))?/i;
6
- // Backlog Document permalink — "Copy link" on a Document page gives /alias/document/{id}.
7
- // NOTE: Nulab's Document REST API is new (2026) — path unverified against a live space, adjust if it 404s.
8
- const BACKLOG_DOCUMENT_RE = /https?:\/\/([\w.-]+\.backlog(?:tool)?\.com)\/(?:alias\/)?document\/(\d+)(?:#comment-(\d+))?/i;
6
+ // Backlog Document permalink — "Copy link" on a Document page gives /alias/document/{id},
7
+ // where {id} is Nulab's hex document ID (e.g. 0193b335c62173de9547bab5dd0b5324), not numeric.
8
+ // Newer "Share link" format adds a project-key + /e/ prefix: /document/{PROJECT_KEY}/e/{id}.
9
+ // Confirmed live against a real space (2026-08-06): GET /api/v2/documents/{id} returns the
10
+ // body as `plain` (not `plainText`/`content`) — see fetchBacklogDocument below.
11
+ // Comment fetch (fetchBacklogDocumentComment) is still unverified — adjust its field names if it 404s or returns empty content.
12
+ const BACKLOG_DOCUMENT_RE = /https?:\/\/([\w.-]+\.backlog(?:tool)?\.com)\/(?:alias\/)?document\/(?:[A-Z][A-Z0-9_]*\/e\/)?([0-9a-f]+)(?:#comment-(\d+))?/i;
9
13
  // Backlog Wiki permalink — "Copy link" on a Wiki page gives /alias/wiki/{id}.
10
14
  const BACKLOG_WIKI_RE = /https?:\/\/([\w.-]+\.backlog(?:tool)?\.com)\/alias\/wiki\/(\d+)/i;
11
15
  const JIRA_RE = /https?:\/\/([\w.-]+\.atlassian\.net)\/browse\/([A-Z][A-Z0-9_]+-\d+)/i;
@@ -150,7 +154,7 @@ async function fetchBacklogDocument(domain, apiKey, documentId) {
150
154
  documentId: String(documentId),
151
155
  title: doc.title || doc.name || '',
152
156
  tag: (doc.tags || []).map(t => t.name).join(', '),
153
- content: (doc.plainText || doc.content || '').substring(0, 4000),
157
+ content: (doc.plain || doc.plainText || doc.content || '').substring(0, 4000),
154
158
  };
155
159
  }
156
160
 
@@ -193,24 +197,41 @@ async function fetchBacklogPriorities(domain, apiKey) {
193
197
  return httpsGet(`https://${domain}/api/v2/priorities?apiKey=${apiKey}`);
194
198
  }
195
199
 
200
+ /**
201
+ * Fetches a single Backlog issue — used to validate a PM-supplied "ticket cha có sẵn" before
202
+ * using it as `parentIssueId` for a batch of child issues (PM Workflow_v1.0.md Vấn đề 5, ticket cha).
203
+ * Returns the internal numeric `id` (needed for `parentIssueId`, which is NOT the issueKey).
204
+ */
205
+ async function getBacklogIssue(domain, apiKey, issueIdOrKey) {
206
+ const issue = await httpsGet(`https://${domain}/api/v2/issues/${issueIdOrKey}?apiKey=${apiKey}`);
207
+ return { id: issue.id, issueKey: issue.issueKey, projectId: issue.projectId, summary: issue.summary || '' };
208
+ }
209
+
196
210
  /**
197
211
  * Creates a Backlog issue. Requires an apiKey with WRITE permission — distinct from the
198
212
  * read-only key used elsewhere in this file for `ak fetch-links`/`ak use`.
199
213
  * `projectId`, `summary`, `issueTypeId`, `priorityId` are mandatory per Backlog's API.
214
+ *
215
+ * `parentIssueId` (optional) — Backlog's internal numeric issue `id` (not the `issueKey`) to
216
+ * link this as a child issue. ⚠️ NOT verified against a live space: requires the target project
217
+ * to have issue hierarchy ("Subtasking") enabled — if it isn't, Backlog rejects the request and
218
+ * the caller should surface the raw error rather than assume the link succeeded.
200
219
  */
201
- async function createBacklogIssue(domain, apiKeyWrite, { projectId, summary, description, issueTypeId, priorityId }) {
220
+ async function createBacklogIssue(domain, apiKeyWrite, { projectId, summary, description, issueTypeId, priorityId, parentIssueId }) {
221
+ const payload = {
222
+ projectId: String(projectId),
223
+ summary,
224
+ issueTypeId: String(issueTypeId),
225
+ priorityId: String(priorityId),
226
+ description: description || '',
227
+ };
228
+ if (parentIssueId) payload.parentIssueId = String(parentIssueId);
202
229
  const issue = await httpsPost(
203
230
  `https://${domain}/api/v2/issues?apiKey=${apiKeyWrite}`,
204
- {
205
- projectId: String(projectId),
206
- summary,
207
- issueTypeId: String(issueTypeId),
208
- priorityId: String(priorityId),
209
- description: description || '',
210
- },
231
+ payload,
211
232
  { bodyType: 'form' }
212
233
  );
213
- return { ticketId: issue.issueKey, url: `https://${domain}/view/${issue.issueKey}`, raw: issue };
234
+ return { ticketId: issue.issueKey, internalId: issue.id, url: `https://${domain}/view/${issue.issueKey}`, raw: issue };
214
235
  }
215
236
 
216
237
  function jiraAuth(email, token) {
@@ -258,25 +279,44 @@ async function fetchJiraComment(domain, email, token, ticketId, commentId) {
258
279
 
259
280
  // ── Jira WRITE — issue creation (PM Workflow_v1.0.md "Vấn đề 5") ────────────
260
281
 
282
+ /**
283
+ * Fetches a single Jira issue — used to validate a PM-supplied "ticket cha có sẵn" before
284
+ * using its key as `parent` for a batch of child issues.
285
+ */
286
+ async function getJiraIssue(domain, email, token, ticketId) {
287
+ const issue = await httpsGet(
288
+ `https://${domain}/rest/api/3/issue/${ticketId}`,
289
+ { Authorization: jiraAuth(email, token), Accept: 'application/json' }
290
+ );
291
+ const fields = issue.fields || {};
292
+ return { key: issue.key, projectKey: fields.project ? fields.project.key : null, summary: fields.summary || '' };
293
+ }
294
+
261
295
  /**
262
296
  * Creates a Jira issue. `apiTokenWrite`/`emailWrite` are distinct from the read-only
263
297
  * credentials used elsewhere in this file — same "separate WRITE key" policy as Backlog.
298
+ *
299
+ * `parentKey` (optional) — Jira issue key to link this as a child via `fields.parent`.
300
+ * ⚠️ NOT verified against a live instance: this field works directly for team-managed
301
+ * ("next-gen") projects; company-managed ("classic") projects may require the child's
302
+ * `issuetype` to be a dedicated `Subtask` type instead — if Jira rejects the request,
303
+ * surface the raw error rather than assume the link succeeded.
264
304
  */
265
- async function createJiraIssue(domain, emailWrite, apiTokenWrite, { projectKey, summary, description, issueTypeName }) {
305
+ async function createJiraIssue(domain, emailWrite, apiTokenWrite, { projectKey, summary, description, issueTypeName, parentKey }) {
306
+ const fields = {
307
+ project: { key: projectKey },
308
+ summary,
309
+ issuetype: { name: issueTypeName || 'Task' },
310
+ description: {
311
+ type: 'doc',
312
+ version: 1,
313
+ content: [{ type: 'paragraph', content: [{ type: 'text', text: description || '' }] }],
314
+ },
315
+ };
316
+ if (parentKey) fields.parent = { key: parentKey };
266
317
  const issue = await httpsPost(
267
318
  `https://${domain}/rest/api/3/issue`,
268
- {
269
- fields: {
270
- project: { key: projectKey },
271
- summary,
272
- issuetype: { name: issueTypeName || 'Task' },
273
- description: {
274
- type: 'doc',
275
- version: 1,
276
- content: [{ type: 'paragraph', content: [{ type: 'text', text: description || '' }] }],
277
- },
278
- },
279
- },
319
+ { fields },
280
320
  { headers: { Authorization: jiraAuth(emailWrite, apiTokenWrite), Accept: 'application/json' }, bodyType: 'json' }
281
321
  );
282
322
  return { ticketId: issue.key, url: `https://${domain}/browse/${issue.key}`, raw: issue };
@@ -361,6 +401,6 @@ module.exports = {
361
401
  fetchBacklogTicket, fetchBacklogComment,
362
402
  fetchBacklogDocument, fetchBacklogDocumentComment, fetchBacklogWiki,
363
403
  fetchJiraTicket, fetchJiraComment,
364
- fetchBacklogIssueTypes, fetchBacklogPriorities, createBacklogIssue,
365
- createJiraIssue,
404
+ fetchBacklogIssueTypes, fetchBacklogPriorities, createBacklogIssue, getBacklogIssue,
405
+ createJiraIssue, getJiraIssue,
366
406
  };
package/scripts/task.js CHANGED
@@ -345,8 +345,9 @@ async function nextGate(taskId) {
345
345
  const isGenDoc = taskType === 'gen-doc';
346
346
  const isSystemRequirement = taskType === 'create-system-requirement';
347
347
  const isIngestData = taskType === 'ingest-data';
348
- const isTwoGateDoc = isGenDoc || isSystemRequirement || isIngestData;
349
- const maxGate = isTwoGateDoc ? 2 : 5;
348
+ const isTwoGateDoc = isGenDoc || isSystemRequirement;
349
+ const isDocFlow = isTwoGateDoc || isIngestData;
350
+ const maxGate = isTwoGateDoc ? 2 : isIngestData ? 3 : 5;
350
351
 
351
352
  const taskState = {
352
353
  ...existing,
@@ -371,7 +372,7 @@ async function nextGate(taskId) {
371
372
  console.log(chalk.green(`✓ Gate ${currentGate} approved for ${resolvedId}.`));
372
373
  console.log(chalk.gray(` Summary saved to: .aiflow/tasks/${resolvedId}/task-summary.md`));
373
374
 
374
- if (isTwoGateDoc && currentGate >= maxGate) {
375
+ if (isDocFlow && currentGate >= maxGate) {
375
376
  console.log(chalk.green('\n ✅ Document workflow complete. Task is done.'));
376
377
  } else {
377
378
  const nextLabel = gateLabel(nextGateNum, taskType);
@@ -380,7 +381,7 @@ async function nextGate(taskId) {
380
381
  console.log(chalk.gray(` 1. Open a NEW chatbox or terminal session.`));
381
382
  console.log(chalk.gray(` 2. Run: aiflow task resume ${resolvedId} (to load context).`));
382
383
  console.log(chalk.gray(` 3. Type "start" or "continue from the current plan".`));
383
- if (!isTwoGateDoc) {
384
+ if (!isDocFlow) {
384
385
  console.log(chalk.yellow(` (Note: Gate 3 progress is saved via [x] checkboxes in the Gate 2 plan doc (AK-Docs/04.Coding/02.Plans/).`));
385
386
  console.log(chalk.yellow(` The AI will automatically resume the exact task you left off.)`));
386
387
  }
@@ -485,7 +486,10 @@ async function detectCurrentGate(taskId) {
485
486
 
486
487
  async function generateMarkdownSummary(taskState, taskType = null) {
487
488
  taskType = taskType || taskState.taskType || 'feature';
488
- const maxGate = taskType === 'testing' ? 4 : ((taskType === 'gen-doc' || taskType === 'create-system-requirement' || taskType === 'ingest-data') ? 2 : 5);
489
+ const maxGate = taskType === 'testing' ? 4
490
+ : taskType === 'ingest-data' ? 3
491
+ : (taskType === 'gen-doc' || taskType === 'create-system-requirement') ? 2
492
+ : 5;
489
493
  const lines = [];
490
494
  lines.push(`# Task Summary: ${taskState.taskId}`);
491
495
  lines.push(`**Title:** ${taskState.title}`);
@@ -531,7 +535,8 @@ function gateLabel(n, taskType = null) {
531
535
  if (taskType === 'ingest-data') {
532
536
  if (n === 1) return 'Fetch, Classify, Draft';
533
537
  if (n === 2) return 'Branch + Merge Request';
534
- if (n >= 3) return 'Done';
538
+ if (n === 3) return 'Sinh Task & Tạo Ticket';
539
+ if (n >= 4) return 'Done';
535
540
  }
536
541
  const labels = {
537
542
  1: 'AI Analyze Requirement',
@@ -599,15 +604,19 @@ module.exports.createOrActivateTaskState = async function createOrActivateTaskSt
599
604
  };
600
605
 
601
606
  /**
602
- * Called by `aiflow gate <N> <start|approved>` to keep task-state.json in sync
607
+ * Called by `aiflow gate <N> <start|approved|skip>` to keep task-state.json in sync
603
608
  * with the actual gate progress — without requiring the user to run `task next`.
604
609
  *
605
610
  * - 'start' : advances currentGate to N (if N is higher than stored value)
606
611
  * - 'approved': records approval timestamp + advances currentGate to N+1
612
+ * - 'skip' : records a skip (gate legitimately produced no deliverable, e.g.
613
+ * ingest-data Gate 3 deciding no task is needed) + advances currentGate
614
+ * to N+1, same as 'approved' — every "is this task done" check only ever
615
+ * inspects `currentGate`, so a skipped gate still lets the task complete.
607
616
  *
608
617
  * Silently no-ops when task-state.json does not exist yet (task not saved).
609
618
  */
610
- module.exports.updateTaskGateState = async function updateTaskGateState(ticketId, gateNum, action) {
619
+ module.exports.updateTaskGateState = async function updateTaskGateState(ticketId, gateNum, action, options = {}) {
611
620
  if (!ticketId) return;
612
621
  const taskDir = path.join(TASKS_DIR, ticketId);
613
622
  const statePath = path.join(taskDir, 'task-state.json');
@@ -633,6 +642,14 @@ module.exports.updateTaskGateState = async function updateTaskGateState(ticketId
633
642
  if (gateNum >= (existing.currentGate || 1)) {
634
643
  updated.currentGate = gateNum + 1;
635
644
  }
645
+ } else if (action === 'skip') {
646
+ const skippedGates = { ...(existing.skippedGates || {}) };
647
+ skippedGates[String(gateNum)] = { at: now, reason: options.reason || '' };
648
+ updated.skippedGates = skippedGates;
649
+ // Advance currentGate to next gate (only if not already ahead) — same as 'approved'
650
+ if (gateNum >= (existing.currentGate || 1)) {
651
+ updated.currentGate = gateNum + 1;
652
+ }
636
653
  }
637
654
 
638
655
  await fs.writeJson(statePath, updated, { spaces: 2 });