@warnyin/agents 0.8.5 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -1
- package/package.json +1 -1
- package/src/.claude/commands/warnyin/build.md +2 -2
- package/src/.claude/commands/warnyin/design.md +2 -0
- package/src/.claude/commands/warnyin/next.md +1 -0
- package/src/.claude/commands/warnyin/ship.md +2 -2
- package/src/.claude/commands/warnyin/verify.md +1 -0
- package/src/.warnyin/template/docs/features/[feature-name]/spec.md +16 -0
- package/src/.warnyin/template/stages/[topic]/design.md +15 -0
- package/src/.warnyin/template/stages/[topic]/ship.md +1 -0
- package/src/.warnyin/workflow/README.md +1 -1
- package/src/.warnyin/workflow/next.md +1 -0
- package/src/.warnyin/workflow/scripts/build-wave.mjs +16 -0
- package/src/.warnyin/workflow/scripts/validate-topic.mjs +378 -0
- package/src/.warnyin/workflow/stages/build.md +4 -3
- package/src/.warnyin/workflow/stages/design.md +6 -4
- package/src/.warnyin/workflow/stages/ship.md +5 -4
- package/src/.warnyin/workflow/stages/verify.md +9 -7
package/CHANGELOG.md
CHANGED
|
@@ -23,6 +23,17 @@
|
|
|
23
23
|
|
|
24
24
|
## [Unreleased]
|
|
25
25
|
|
|
26
|
+
## [0.9.1] - 2026-06-08
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- **BUILD worktree เห็น dependency ครบทุก wave (build-wave sync build branch)** — harness fork worktree จาก **main** (คุมไม่ได้) ทำให้ build sub-agent ไม่เห็น `docs/stages/<slug>/` (topic docs) + output ของ wave ก่อนหน้า แล้ว improvise (KB#14). แก้ที่ payload แบบ **unify-in-place**: `src/.warnyin/workflow/scripts/build-wave.mjs` รับ arg `baseRef?` (ชื่อ build branch) + แทรก prompt **step `0.`** ให้ agent `git merge <baseRef> --no-edit` เป็นงานแรกก่อนอ่าน task **เฉพาะ `isolate && baseRef`** (`!baseRef` → ไม่แทรก = backward compat ไม่ renumber step 1-9) — มี **abort-on-conflict** (`|| git merge --abort` กันค้าง MERGE state) + retry transient lock + **hard-stop** (merge สำเร็จแต่ `task.md` ไม่ปรากฏ → STOP failed ห้าม improvise) + บันทึกผล merge ใน `notes`; command `src/.claude/commands/warnyin/build.md` step 6 ส่ง `baseRef` + integrate ด้วย `git checkout <branch> -- <scoped src files>` (เลี่ยง topic-docs copy + ปลอด KB#11 tracked-deletion); playbook `src/.warnyin/workflow/stages/build.md` §3 principle 3 + §4 step 5 อธิบายกลไก. **backward compatible** (caller ไม่ส่ง `baseRef`/`isolate:false` → พฤติกรรมเดิม); payload ติดมากับ `--update` รอบถัดไป
|
|
30
|
+
|
|
31
|
+
## [0.9.0] - 2026-06-08
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- **Structural validator + status script (`validate-topic.mjs`) wired เข้า workflow 3 จุด** — script เดียวใน payload (`src/.warnyin/workflow/scripts/validate-topic.mjs`, zero-dep ตาม pattern `lint-md.mjs`) 2 โหมด: **status** (ไม่ใส่ arg → ตารางทุก active topic, exit 0 เสมอ) · **validate** (`<slug>` → `✖`/`⚠` structural ละเอียด มี code C1–C5 กำกับ, exit 1 เมื่อมี ✖ · 0 เมื่อสะอาด/มีแค่ ⚠ · 2 slug ไม่ถูกต้อง/path traversal). wire เข้า playbook **3 จุดแบบ unify-in-place + node-guard ทุกจุด**: `next.md` (§2 step pre-scan โหมด status ก่อนอ่าน semantic — ตาราง heuristic เดิมคง fallback) · `stages/design.md` (§8 gate item "ทุก task มี 4 ไฟล์ครบ" → validate `<slug>` ควรไม่มี ✖, guidance ไม่ใช่ hard gate) · `stages/ship.md` (§4 step 1 → validate `<slug>` ก่อน promote, มี ✖ ควรแก้ก่อน) + command mirror `next/design/ship` (ชี้ playbook ไม่ duplicate รายการเช็ค). **structural เท่านั้น** — semantic ยังเป็นหน้าที่ model/ผู้ ship; **backward compatible** (เครื่องไม่มี node → playbook คง fallback เดิม); payload ติดมากับ `--update` รอบถัดไป
|
|
35
|
+
- **วงจร Feature spec delta ครบ 3 stage (DESIGN/VERIFY/SHIP)** — wiring discipline ของ behavior spec แบบ unify-in-place ลง playbook กลาง + template + command adapter: `src/.warnyin/workflow/stages/design.md` (§2 input อ่าน `docs/features/<name>/spec.md` · §4 step 5 + §5 + §8 gate ครอบ "Spec delta") · `verify.md` (feature spec = regression baseline — scenario เดิม = regression case, delta = test case ใหม่; §2/§3/§4/§6) · `ship.md` (§4 step 5.1 merge `spec.md` ตาม delta — ADDED ต่อท้าย/MODIFIED แทนที่/REMOVED ลบ + **read-modify-verify key ไม่เจอ → STOP** + rename `[เดิมชื่อ:]` + stale delta re-check; §3/§5/§6 gate) · template `stages/[topic]/design.md` (+section "9. Spec delta") + `ship.md` (+แถว `spec.md`) · `src/.warnyin/workflow/README.md` note `spec.md` (living behavior spec) · command mirror `design/verify/ship` (ชี้ playbook ไม่ duplicate logic). **backward compatible** (feature ไม่มี spec → วิธีเดิม; topic ไม่มี §9 delta → SHIP ทำแบบเดิม); payload `.md` ล้วน ติดมากับ `--update` รอบถัดไป
|
|
36
|
+
|
|
26
37
|
## [0.8.5] - 2026-06-07
|
|
27
38
|
|
|
28
39
|
### Added
|
|
@@ -82,5 +93,13 @@
|
|
|
82
93
|
### Removed
|
|
83
94
|
- รองรับ node 18 (drop ตาม EOL)
|
|
84
95
|
|
|
85
|
-
[Unreleased]: https://github.com/warnyin/warnyin-agents/compare/v0.
|
|
96
|
+
[Unreleased]: https://github.com/warnyin/warnyin-agents/compare/v0.9.1...HEAD
|
|
97
|
+
[0.9.1]: https://github.com/warnyin/warnyin-agents/compare/v0.9.0...v0.9.1
|
|
98
|
+
[0.9.0]: https://github.com/warnyin/warnyin-agents/compare/v0.8.5...v0.9.0
|
|
99
|
+
[0.8.5]: https://github.com/warnyin/warnyin-agents/compare/v0.8.4...v0.8.5
|
|
100
|
+
[0.8.4]: https://github.com/warnyin/warnyin-agents/compare/v0.8.3...v0.8.4
|
|
101
|
+
[0.8.3]: https://github.com/warnyin/warnyin-agents/compare/v0.8.2...v0.8.3
|
|
102
|
+
[0.8.2]: https://github.com/warnyin/warnyin-agents/compare/v0.8.1...v0.8.2
|
|
103
|
+
[0.8.1]: https://github.com/warnyin/warnyin-agents/compare/v0.8.0...v0.8.1
|
|
104
|
+
[0.8.0]: https://github.com/warnyin/warnyin-agents/compare/v0.7.0...v0.8.0
|
|
86
105
|
[0.7.0]: https://github.com/warnyin/warnyin-agents/compare/v0.6.0...v0.7.0
|
package/package.json
CHANGED
|
@@ -12,8 +12,8 @@ argument-hint: "[slug ของ topic]"
|
|
|
12
12
|
4. **Pre-check:** target เป็น git repo ไหม (จำเป็นสำหรับ worktree isolation) — ถ้าไม่ใช่ → fallback sequential shared-tree (`isolate:false`) และแจ้ง user. สร้าง build branch ใหม่ก่อนเริ่ม
|
|
13
13
|
5. **ขออนุมัติครั้งเดียว** — ใช้ AskUserQuestion แสดง execution plan: แต่ละ wave มี task อะไร, อันไหน parallel, isolation mode, build branch → รอ go/no-go (อย่าเริ่มก่อนได้ไฟเขียว)
|
|
14
14
|
6. **เดินทีละ wave** (หลังอนุมัติ):
|
|
15
|
-
- เรียก **Workflow** ด้วย `{ scriptPath: ".warnyin/workflow/scripts/build-wave.mjs", args: { slug, tasks: [<task ใน wave นี้>], isolate } }`
|
|
16
|
-
- เมื่อ workflow คืนผล: ถ้า `isolate` → **
|
|
15
|
+
- เรียก **Workflow** ด้วย `{ scriptPath: ".warnyin/workflow/scripts/build-wave.mjs", args: { slug, tasks: [<task ใน wave นี้>], isolate, baseRef: "<ชื่อ build branch ที่สร้าง step 4>" } }` — `baseRef` = build branch จริง (worktree fork จาก main → agent merge build branch เองก่อนทำงาน เพื่อเห็น `docs/stages/<slug>/` + output ของ wave ก่อนหน้า)
|
|
16
|
+
- เมื่อ workflow คืนผล: ถ้า `isolate` → **integrate ด้วย `git checkout <result.branch> -- <ไฟล์ source ที่ task แก้>`** (checkout เฉพาะไฟล์ source ที่ scoped — เลี่ยง topic-docs copy ที่ agent merge เข้า worktree + ปลอด KB#11 tracked-deletion), แก้ conflict ถ้ามี; ถ้า shared-tree → review + commit ให้ · `task.md` status/checklist → main loop อัปเดตที่ main working dir ตอน integrate (E1 — agent แก้จาก worktree ไม่ได้ถ้า gitignored)
|
|
17
17
|
- ถ้ามี task `failed` หรือ `skipped` → **หยุด** รายงาน user ก่อนไป wave ถัดไป
|
|
18
18
|
- **รวม troubleshooting:** ดึงฟิลด์ `troubleshooting` จากผลของทุก agent ในรอบนี้ → เขียนรวมลง `docs/stages/<slug>/troubleshooting.md` (main loop เขียนเอง กันไฟล์ชนกันใน worktree)
|
|
19
19
|
7. **★ Full build & test gate (หลัง merge ทุก wave):** บน build branch ที่ integrate แล้ว รัน build ทั้งหมด + test suite ทั้งหมด (รวม unit test) ของทุก component ที่กระทบ
|
|
@@ -14,6 +14,8 @@ argument-hint: "[slug ของ topic] [อธิบาย change สั้น
|
|
|
14
14
|
- ระบุ slug → ใช้/สร้าง `docs/stages/<slug>/` (ถ้ามาจาก Discovery ใช้โฟลเดอร์เดิม)
|
|
15
15
|
- ถ้าเป็นคำถาม/ยังไม่มั่นใจเรื่อง design → แนะนำ `/warnyin:discovery` ก่อน
|
|
16
16
|
4. ผลิต artifact โดยใช้ template ใน `.warnyin/template/stages/[topic]/` เป็นโครง: `business.md` (ข้ามได้ถ้า change เล็ก), `proposal.md`, `design.md` (lens `.warnyin/workflow/roles/sa.md`), แล้วแตก `tasks/<task-name>/` (lens `.warnyin/workflow/roles/tech-lead.md`) แต่ละใบมี `spec.md` `standard.md` `rule.md` `task.md`
|
|
17
|
+
- **Spec delta:** `design.md` ต้องครอบ section "9. Spec delta" — เทียบ `docs/features/<name>/spec.md` ปัจจุบัน (input ข้อ 6) แล้วเขียน ADDED/MODIFIED/REMOVED หรือ "ไม่มี delta" (กติกาเต็ม + gate ดู playbook §2/§4/§8 — ไม่ทำซ้ำที่นี่)
|
|
18
|
+
- **Gate ดู playbook §8** — ถ้ารัน node ได้ validate `<slug>` ควรไม่มี ✖ (รายการเช็คอยู่ใน script + playbook ไม่ทำซ้ำที่นี่)
|
|
17
19
|
- **Review panel (ถาม user ก่อน):** หลัง `design.md` เสร็จ ก่อนแตก task — ใช้ AskUserQuestion ถามว่าจะให้ panel รีวิวไหม ถ้า ok → fan-out subagent `warnyin-sa`, `warnyin-tech-lead`, `warnyin-qa`, `warnyin-security`, `warnyin-infra` (Agent tool, ขนาน, read-only) รีวิว proposal+design → รวมความเห็น สรุปให้ user → แก้ blocker ให้ครบ (ห้ามเดา) → บันทึก section "Design review" ท้าย `design.md`
|
|
18
20
|
5. ตอน generate ไฟล์ task หลายใบ สามารถใช้ sub-agent (Task/Agent tool) fan-out หนึ่ง agent ต่อหนึ่ง task ได้ — **แต่ต้องผ่าน Gate (ข้อ 8 ของ playbook) ก่อน**
|
|
19
21
|
6. **Dry-run (ถาม user ก่อนเสมอ):** หลังเขียนไฟล์ task ครบ ใช้ AskUserQuestion ถามว่าต้องการ dry-run ทั้งหมดเพื่อหาจุดบกพร่องก่อนเข้า BUILD ไหม — ถ้า ok ทำตามข้อ 4.9 ของ playbook:
|
|
@@ -12,5 +12,6 @@ argument-hint: "[slug (optional — ไม่ระบุ = สแกนทุ
|
|
|
12
12
|
- ไม่ระบุ → สแกนทุก topic ใน `docs/stages/` (ยกเว้น `achieved/`)
|
|
13
13
|
3. ต่อ topic: ระบุ stage ปัจจุบันจาก artifact ที่ถูกเติมจริง (ไฟล์ที่ยังเป็นโครง template = ยังไม่ทำ)
|
|
14
14
|
+ ไล่ gate ของ playbook stage นั้น + สถานะ task ใน `tasks/*/task.md`
|
|
15
|
+
- ทำตาม step pre-scan ใน playbook §2 — ถ้ารัน node ได้ รัน `validate-topic.mjs` โหมด status ก่อนอ่าน semantic (รายการเช็คอยู่ใน script + playbook)
|
|
15
16
|
4. รายงานในแชท: ตารางภาพรวม (topic · stage · งานค้าง · command ถัดไป) + รายละเอียดงานค้าง + ลำดับงานที่แนะนำ
|
|
16
17
|
5. ไม่รัน stage ต่อให้เอง — เสนอ command ให้ user ตัดสินใจ
|
|
@@ -7,12 +7,12 @@ argument-hint: "[slug ของ topic]"
|
|
|
7
7
|
|
|
8
8
|
1. อ่าน `.warnyin/workflow/stages/ship.md` ให้ครบก่อน แล้วทำตามทุกหลักการอย่างเคร่งครัด
|
|
9
9
|
2. slug: $ARGUMENTS — ถ้าไม่ระบุให้ถามก่อน ว่าจะ ship topic ไหน (ดูโฟลเดอร์ใน `docs/stages/`)
|
|
10
|
-
3. **อ่านทำความเข้าใจ topic + รวบรวม learned-rule candidate:** อ่าน `docs/stages/<slug>/` ทุกไฟล์ — topic นี้ทำอะไร ทำอย่างไร เกิดความรู้ใหม่อะไรบ้าง; เช็คว่า VERIFY ผ่าน Gate แล้ว (`verify.md` สรุปผลผ่าน) — ถ้ายังไม่ผ่าน → หยุด แจ้ง user. รวบรวม learned-rule candidate เป็นตาราง (`rule + evidence + scope + promote?`): **planned** จาก `tasks/*/rule.md` §2 "รอ SHIP" + `standard.md` · **emergent** สแกนบทเรียนใน `build.md`/`verify.md`/`troubleshooting.md`/diff — `rule` ต้อง generalize (ไม่ใช่ incident), `evidence` บังคับ (pointer + ลิงก์ artifact; ไม่มี = ไม่ promote), `scope` = `component:<c>` หรือ `project`
|
|
10
|
+
3. **อ่านทำความเข้าใจ topic + รวบรวม learned-rule candidate:** อ่าน `docs/stages/<slug>/` ทุกไฟล์ — topic นี้ทำอะไร ทำอย่างไร เกิดความรู้ใหม่อะไรบ้าง; เช็คว่า VERIFY ผ่าน Gate แล้ว (`verify.md` สรุปผลผ่าน) — ถ้ายังไม่ผ่าน → หยุด แจ้ง user. ทำตาม playbook §4 step 1 — ถ้ารัน node ได้ validate `<slug>` ก่อน promote (มี ✖ ควรแก้ก่อน — รายการเช็คอยู่ใน script + playbook). รวบรวม learned-rule candidate เป็นตาราง (`rule + evidence + scope + promote?`): **planned** จาก `tasks/*/rule.md` §2 "รอ SHIP" + `standard.md` · **emergent** สแกนบทเรียนใน `build.md`/`verify.md`/`troubleshooting.md`/diff — `rule` ต้อง generalize (ไม่ใช่ incident), `evidence` บังคับ (pointer + ลิงก์ artifact; ไม่มี = ไม่ promote), `scope` = `component:<c>` หรือ `project`
|
|
11
11
|
4. **จำแนก feature:** feature ใหม่ หรือปรับปรุง feature เดิม (เทียบกับ `docs/features/` ที่มีอยู่)
|
|
12
12
|
5. **ขออนุมัติครั้งเดียว** — ใช้ AskUserQuestion สรุป promotion plan: feature ใหม่/ปรับปรุง, ไฟล์กลางที่จะอัปเดต + สาระที่จะใส่, ชื่อโฟลเดอร์ archive — **fold ตาราง learned-rule (rule + evidence + scope) เข้า approval เดียวกัน ให้ user ยืนยัน per-rule** (✅ promote / ✂️ ตัด + เหตุผล) → รอ go/no-go (อย่าแก้ไฟล์ใดก่อนได้ไฟเขียว)
|
|
13
13
|
6. **★ Archive ก่อน:** ย้ายทั้งโฟลเดอร์ `docs/stages/<slug>/` → `docs/stages/achieved/<YYYY-MM-DD>-<slug>/` (ใช้ `git mv`; วันที่ = วันที่ ship) แล้วอ่านเนื้อหาจาก path ใหม่ระหว่าง promote
|
|
14
14
|
7. **อัปเดตเอกสารกลาง** (กลั่นเข้าโครงสร้างไฟล์เดิม ไม่ copy ดิบ, ระวัง duplicate):
|
|
15
|
-
- `docs/features/<feature-name>/` — ใหม่ → สร้าง (feature.md + business.md); เดิม → อัปเดต โดยใช้ business/proposal/design ของ topic
|
|
15
|
+
- `docs/features/<feature-name>/` — ใหม่ → สร้าง (feature.md + business.md); เดิม → อัปเดต โดยใช้ business/proposal/design ของ topic — **และ merge `spec.md` ตาม Spec delta** ใน `design.md` §9 (ADDED ต่อท้าย · MODIFIED แทนที่ · REMOVED ลบ; **key ไม่เจอ → STOP ถาม user ห้าม merge เงียบ** — กติกาเต็มดู playbook §4 step 5)
|
|
16
16
|
- `docs/techstack/<component>/{rule,standard}.md` — learned-rule ที่ยืนยันแล้ว scope `component:<c>` + note "รอ SHIP" (พิจารณาครบทุกข้อ: promote หรือตัดทิ้งพร้อมเหตุผล); learned-rule scope `project` → `docs/rule.md`
|
|
17
17
|
- `docs/techstack/<component>/structure.md` + `test.md` — โครงสร้างที่เปลี่ยน (ดูโค้ดจริง) + merge แผนเทสจาก `test.md` ของ topic
|
|
18
18
|
- `docs/rule.md` — global rule ใหม่/ที่เปลี่ยน (เฉพาะกฎระดับโปรเจกต์)
|
|
@@ -9,6 +9,7 @@ argument-hint: "[slug ของ topic]"
|
|
|
9
9
|
1. อ่าน `.warnyin/workflow/stages/verify.md` ให้ครบก่อน แล้วทำตามทุกหลักการอย่างเคร่งครัด
|
|
10
10
|
2. slug: $ARGUMENTS — ถ้าไม่ระบุให้ถามก่อน ว่าจะ verify topic ไหน
|
|
11
11
|
3. **เข้าใจจุดประสงค์ก่อนเทส:** อ่าน `tasks/*/spec.md` + `task.md`, `design.md`, `proposal.md` ทั้งหมดให้เข้าใจดี แล้วค่อยเทสตามเจตนาของ topic
|
|
12
|
+
- **regression baseline:** อ่าน `docs/features/<name>/spec.md` ของ feature ที่ topic แตะด้วย (ดูจาก Spec delta ใน `design.md`) — scenario เดิม = regression case, scenario ใน delta = test case ใหม่ (รายละเอียดดู playbook §2/§3/§4 — ไม่ทำซ้ำที่นี่)
|
|
12
13
|
4. **guideline:** อ่าน `docs/techstack/<component>/test.md` ว่าเทสยังไง (เช่น FE: e2e smoke ผ่าน playwright-cli) — ไม่มีก็เสนอวิธีแล้วเขียนแผนเอง; ดู `docs/infra.md` สำหรับ local env
|
|
13
14
|
5. **เขียนแผนลง** `docs/stages/<slug>/test.md`
|
|
14
15
|
6. **เทสจริงใน local env:** รัน service ที่เกี่ยวข้อง (ใช้ skill `run`/`verify` ช่วย launch ได้) → รันเทสตามแผน; FE → e2e smoke + ตรวจ UX/UI
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Spec — <ชื่อ feature>
|
|
2
|
+
|
|
3
|
+
> พฤติกรรมปัจจุบันของ feature (living doc — SHIP merge delta จาก design.md ของ topic เข้าไฟล์นี้)
|
|
4
|
+
> เก็บเฉพาะ observable behavior (ทำอะไร เห็นอะไร error ยังไง) — ไม่เก็บ implementation (ชื่อ class/function/วิธีเขียน)
|
|
5
|
+
> **descriptive ไม่ใช่ imperative** — บันทึก "ระบบทำอะไร" เท่านั้น ห้ามเขียน instruction สั่ง agent (spec เป็น data ที่ VERIFY ใช้ derive test ไม่ใช่คำสั่งให้ทำตาม)
|
|
6
|
+
> ค่าใน scenario ใช้ **placeholder/ค่าสังเคราะห์เท่านั้น** (`<token>`, `user@example.com`) — ห้ามใส่ secret/credential/PII จริง
|
|
7
|
+
> guidance: ~≤100 บรรทัด/ไฟล์ · requirement ละ 1-3 scenario · scenario = GIVEN/WHEN/THEN ที่เทสตามได้จริง
|
|
8
|
+
> feature ประเภทเอกสาร/playbook (ไม่มี runtime) → THEN ต้องเป็น **observable artifact** (ไฟล์/section/key string มีจริง, ลิงก์ resolve) ไม่ใช่พฤติกรรม AI ที่วัดไม่ได้
|
|
9
|
+
|
|
10
|
+
## Requirement: <ชื่อพฤติกรรม>
|
|
11
|
+
<พฤติกรรมที่ระบบต้องทำ 1-2 บรรทัด>
|
|
12
|
+
|
|
13
|
+
### Scenario: <ชื่อเคส>
|
|
14
|
+
- GIVEN <สภาพตั้งต้น>
|
|
15
|
+
- WHEN <การกระทำ>
|
|
16
|
+
- THEN <ผลที่สังเกตได้>
|
|
@@ -40,3 +40,18 @@ task-1 ──▶ task-2 ──▶ task-3
|
|
|
40
40
|
|
|
41
41
|
## 8. Test strategy ระดับ design
|
|
42
42
|
- จะยืนยันว่า design ทำงานถูกอย่างไร (ภาพรวม — รายละเอียดอยู่ใน task spec):
|
|
43
|
+
|
|
44
|
+
## 9. Spec delta (เทียบ docs/features/<name>/spec.md ปัจจุบัน)
|
|
45
|
+
> พฤติกรรมที่ change นี้ เพิ่ม/แก้/ลบ — SHIP จะ merge ตามนี้แบบกึ่ง mechanical
|
|
46
|
+
> change ไม่แตะพฤติกรรม feature (refactor/docs/tooling) → เขียน "ไม่มี delta" บรรทัดเดียวพอ
|
|
47
|
+
|
|
48
|
+
### ADDED
|
|
49
|
+
#### Requirement: <ชื่อ> (→ feature: <feature-name>)
|
|
50
|
+
<พฤติกรรม + scenario ตาม format ของ spec.md>
|
|
51
|
+
|
|
52
|
+
### MODIFIED
|
|
53
|
+
#### Requirement: <ชื่อใหม่> (→ feature: <feature-name>) [เดิมชื่อ: <ชื่อเก่า> — ใส่เฉพาะกรณี rename]
|
|
54
|
+
<เวอร์ชันใหม่เต็ม> _(เดิม: <สรุปสั้น>)_
|
|
55
|
+
|
|
56
|
+
### REMOVED
|
|
57
|
+
#### Requirement: <ชื่อเดิมใน spec> (→ feature: <feature-name>) — เหตุผลที่เลิก
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
| ไฟล์ | สาระที่ promote |
|
|
12
12
|
|---|---|
|
|
13
13
|
| `docs/features/<feature-name>/` | |
|
|
14
|
+
| `docs/features/<feature-name>/spec.md` | <!-- merge Spec delta จาก design.md §9 --> |
|
|
14
15
|
| `docs/techstack/<component>/rule.md` | |
|
|
15
16
|
| `docs/techstack/<component>/standard.md` | |
|
|
16
17
|
| `docs/techstack/<component>/structure.md` | |
|
|
@@ -56,7 +56,7 @@ Discovery (optional) ──▶ DESIGN ──▶ BUILD ──▶ VERIFY ──▶
|
|
|
56
56
|
docs/ # โครง docs — installer seed เข้า docs/ ตอนติดตั้ง
|
|
57
57
|
project.md rule.md infra.md troubleshooting.md codemap/index.md
|
|
58
58
|
techstack/[component]/ # copy เป็น docs/techstack/<component> (โดย /warnyin:init)
|
|
59
|
-
features/[feature-name]/ # copy เป็น docs/features/<feature-name> (โดย SHIP)
|
|
59
|
+
features/[feature-name]/ # copy เป็น docs/features/<feature-name> (โดย SHIP) — มี spec.md (living behavior spec)
|
|
60
60
|
installer/templates/ # template CLAUDE.md ของ target (installer ใช้เอง — ไม่ถูก copy ไป target)
|
|
61
61
|
|
|
62
62
|
.claude/ # adapter Claude Code (ชี้กลับ playbook กลาง)
|
|
@@ -14,6 +14,7 @@ NEXT คือโหมด **อ่านอย่างเดียว (read-on
|
|
|
14
14
|
|
|
15
15
|
## 2. วิธีหาสถานะ (สแกนจากไฟล์จริง — ไม่ถาม user ก่อน)
|
|
16
16
|
|
|
17
|
+
0. **structural pre-scan (ถ้ารัน node ได้):** ถ้ารัน node ได้ → รัน `node .warnyin/workflow/scripts/validate-topic.mjs` (โหมด status) เป็น structural pre-scan ก่อน แล้วค่อยอ่านเชิง semantic เฉพาะจุดที่ต้องตัดสิน — เครื่องที่รันไม่ได้ ใช้ตาราง heuristic ด้านล่างเหมือนเดิม
|
|
17
18
|
1. **หา topic ที่ active:** โฟลเดอร์ใน `docs/stages/<slug>/` ทั้งหมด ยกเว้น `achieved/` และ `context.md`
|
|
18
19
|
- ไม่มี topic เลย → รายงานว่า "ไม่มีงานค้าง" + แนะนำเริ่มงานใหม่ด้วย `/warnyin:discovery` หรือ `/warnyin:design`
|
|
19
20
|
2. **อ่าน `docs/stages/context.md`** — บริบทงานที่จดไว้ (ถ้ามี)
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// slug: string, // ชื่อ topic เช่น "billing-redesign"
|
|
6
6
|
// tasks: string[], // ชื่อ task ใน wave นี้ (โฟลเดอร์ docs/stages/<slug>/tasks/<task>)
|
|
7
7
|
// isolate?: boolean, // true = worktree ต่อ task (ดีฟอลต์), false = shared tree (sequential)
|
|
8
|
+
// baseRef?: string, // ชื่อ build branch เช่น "build/my-topic"; ไม่ส่ง = ไม่ sync (backward compat)
|
|
8
9
|
// }
|
|
9
10
|
|
|
10
11
|
export const meta = {
|
|
@@ -18,6 +19,7 @@ const A = typeof args === 'string' ? JSON.parse(args) : (args || {})
|
|
|
18
19
|
const slug = A.slug
|
|
19
20
|
const tasks = A.tasks || []
|
|
20
21
|
const isolate = !(A.isolate === false)
|
|
22
|
+
const baseRef = A.baseRef || null // ชื่อ build branch เช่น "build/my-topic"; ไม่ส่ง = ไม่ sync (backward compat)
|
|
21
23
|
|
|
22
24
|
if (!slug || tasks.length === 0) {
|
|
23
25
|
log('ไม่มี slug หรือ tasks — ไม่มีอะไรให้ build')
|
|
@@ -80,6 +82,20 @@ function prompt(task) {
|
|
|
80
82
|
`7. อัปเดตสถานะ + acceptance ที่ผ่านใน ${dir}/task.md`,
|
|
81
83
|
`8. ปัญหาที่ "ยาก/เจอซ้ำ" และแก้สำเร็จ → ใส่ในฟิลด์ troubleshooting (main loop จะรวมลง topic troubleshooting.md)`,
|
|
82
84
|
]
|
|
85
|
+
// worktree fork จาก main (คุมไม่ได้) → ให้ agent sync build branch เข้า worktree เองก่อนทำงาน
|
|
86
|
+
// แทรกเป็น step "0." ก่อน "1. อ่านให้ครบ" — เฉพาะ isolate && baseRef (ไม่ renumber step 1-9; !baseRef = พฤติกรรมเดิม)
|
|
87
|
+
if (isolate && baseRef) {
|
|
88
|
+
lines.splice(2, 0,
|
|
89
|
+
`0. **★ Sync build branch เข้า worktree ก่อน (ทำก่อน Read ไฟล์ใดๆ):** รัน`,
|
|
90
|
+
` \`git merge ${baseRef} --no-edit || (git merge --abort; <รายงาน failed>)\``,
|
|
91
|
+
` (worktree fork จาก main — ต้อง merge build branch เพื่อให้เห็น docs/stages/${slug}/ + output ของ wave ก่อนหน้า)`,
|
|
92
|
+
` - ปกติเป็น fast-forward (main มักเป็น ancestor ของ build branch); ถ้าเป็น 3-way แล้ว conflict → **abort + รายงาน failed** (ห้ามทิ้ง worktree ค้าง MERGE state — step commit ท้ายจะพัง)`,
|
|
93
|
+
` - ถ้าล้มด้วย lock error ชั่วคราว (transient \`index.lock\`/\`packed-refs\`) → **retry 1 ครั้ง** ก่อนรายงาน failed`,
|
|
94
|
+
` - **★ hard-stop กัน improvise (panel B2):** หลัง merge ถ้าไฟล์ \`${dir}/task.md\` **ยังไม่ปรากฏ** → **STOP รายงาน failed ทันที ห้าม improvise/git reset เอง** (กันวนรอย KB#14)`,
|
|
95
|
+
` - บันทึกผล merge ลงฟิลด์ \`notes\` (เช่น "merged ${baseRef}: fast-forward to <sha>") เพื่อ main loop verify ว่า sync เกิดจริง (Infra-S5)`,
|
|
96
|
+
``,
|
|
97
|
+
)
|
|
98
|
+
}
|
|
83
99
|
if (isolate) {
|
|
84
100
|
lines.push(
|
|
85
101
|
`9. คุณอยู่ใน git worktree แยก: เมื่อเสร็จและเขียวแล้ว ให้ commit งาน (git add -A && git commit -m "build(${task}): ...")`,
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
// Structural validator ของ topic ใน docs/stages/ — zero-dep (mirror lint-md.mjs: pure fn + injectable IO + main-guard)
|
|
6
|
+
// 2 โหมด: status (ไม่มี arg, ตารางทุก topic, exit 0) · validate (<slug>, รายการ ✖/⚠, exit 1 เมื่อมี ✖ / 2 เมื่อ slug ผิด)
|
|
7
|
+
// เช็ค C1–C5 = structural เท่านั้น (semantic เป็นของ model ตาม gate เดิม) — canonical contract: design §4
|
|
8
|
+
//
|
|
9
|
+
// หลักการแยกระดับ (design §4.2): ✖ checks (C2/C3/C5) ไม่พึ่ง filled-detection (existence/structure ล้วน)
|
|
10
|
+
// · C1/C4 = ⚠ best-effort (heuristic เดา "เริ่มเติม" — ยอมรับ false ได้ ไม่ block)
|
|
11
|
+
// security (design §4.4): เฉพาะ node:fs/node:path/node:url — ไม่มี child_process/network/write
|
|
12
|
+
// · report structural เท่านั้น (ชื่อไฟล์/section/code — ไม่ echo เนื้อ artifact) · ENOENT/EACCES guard ไม่พ่น absolute path
|
|
13
|
+
|
|
14
|
+
// ── canonical: stage → artifact (design §4.3) ──────────────────────────────
|
|
15
|
+
// required = ต้องมีถึงจะนับว่าผ่าน stage · optional = ข้ามได้ปกติ (ไม่ count เป็น "ข้าม stage")
|
|
16
|
+
const STAGES = [
|
|
17
|
+
{ order: 1, stage: 'Discovery', required: [], optional: ['discovery.md', 'research.md'] },
|
|
18
|
+
{ order: 2, stage: 'DESIGN', required: ['proposal.md', 'design.md'], optional: ['business.md'] },
|
|
19
|
+
{ order: 4, stage: 'BUILD', required: ['build.md'], optional: [] },
|
|
20
|
+
{ order: 5, stage: 'VERIFY', required: ['verify.md', 'test.md'], optional: [] },
|
|
21
|
+
{ order: 6, stage: 'SHIP', required: ['ship.md'], optional: [] },
|
|
22
|
+
]
|
|
23
|
+
// ไฟล์ artifact ทั้งหมดที่ใช้ infer stage (รวม required + optional ของทุก stage)
|
|
24
|
+
const STAGE_FILES = STAGES.flatMap((s) => [...s.required, ...s.optional])
|
|
25
|
+
const TASK_REQUIRED = ['spec.md', 'standard.md', 'rule.md', 'task.md']
|
|
26
|
+
|
|
27
|
+
// ── filled heuristic (B1): "เริ่มเติม" = H1 (บรรทัดแรกที่ไม่ว่าง) ไม่มี placeholder <...> ─────
|
|
28
|
+
// ทุก template artifact มี `— <ชื่อ...>` ที่ H1; ห้ามใช้ const FILLED_MARKERS list (เปราะ)
|
|
29
|
+
function isFilled(content) {
|
|
30
|
+
if (content == null) return false
|
|
31
|
+
const lines = content.split('\n')
|
|
32
|
+
for (const line of lines) {
|
|
33
|
+
const t = line.trim()
|
|
34
|
+
if (t === '') continue
|
|
35
|
+
// H1 = บรรทัดแรกที่ไม่ว่าง — เริ่มเติมเมื่อไม่มี placeholder <...>
|
|
36
|
+
return !/<[^>]+>/.test(t)
|
|
37
|
+
}
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// helper: เอา content ของไฟล์ระดับ topic (relPath = ชื่อไฟล์ตรง ๆ เช่น 'design.md')
|
|
42
|
+
function topLevel(files, name) {
|
|
43
|
+
return files.has(name) ? files.get(name) : null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── C2: ทุกโฟลเดอร์ใน tasks/ (ข้าม [...]) มีครบ 4 ไฟล์ ──────────────────────
|
|
47
|
+
// files key รูปแบบ: 'tasks/<taskName>/<file>' — รวบ taskName + เซตไฟล์ที่มี
|
|
48
|
+
function checkTasks(files) {
|
|
49
|
+
const issues = []
|
|
50
|
+
const tasks = new Map() // taskName -> Set<file>
|
|
51
|
+
for (const key of files.keys()) {
|
|
52
|
+
const parts = key.split('/')
|
|
53
|
+
if (parts[0] !== 'tasks' || parts.length < 3) continue
|
|
54
|
+
const taskName = parts[1]
|
|
55
|
+
if (taskName.startsWith('[')) continue // skip template placeholder [task-name]
|
|
56
|
+
if (!tasks.has(taskName)) tasks.set(taskName, new Set())
|
|
57
|
+
tasks.get(taskName).add(parts[2])
|
|
58
|
+
}
|
|
59
|
+
for (const [taskName, present] of tasks) {
|
|
60
|
+
const missing = TASK_REQUIRED.filter((f) => !present.has(f))
|
|
61
|
+
if (missing.length) {
|
|
62
|
+
issues.push({
|
|
63
|
+
code: 'C2',
|
|
64
|
+
level: 'error',
|
|
65
|
+
msg: `tasks/${taskName} ขาด ${missing.join(', ')}`,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return issues
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── C3: ship.md เริ่มเติมแล้ว → ต้องมี '## 3. Learned rules' + ≥1 data row จริง ──────
|
|
73
|
+
// (B4) ยัง template H1 → ข้าม (chicken-egg) · (B3) ≥1 row จริง (ไม่นับ header/separator/row ว่าง)
|
|
74
|
+
function checkShipData(files) {
|
|
75
|
+
const issues = []
|
|
76
|
+
const ship = topLevel(files, 'ship.md')
|
|
77
|
+
if (ship == null) return issues // ไม่มีไฟล์ → ไม่เช็ค (ยังไม่ถึง SHIP)
|
|
78
|
+
if (!isFilled(ship)) return issues // ยัง template → ข้าม (B4)
|
|
79
|
+
|
|
80
|
+
const lines = ship.split('\n')
|
|
81
|
+
// หา section '## 3. Learned rules' (anchor H2 ที่ขึ้นต้นด้วยข้อความนี้)
|
|
82
|
+
let secStart = -1
|
|
83
|
+
for (let i = 0; i < lines.length; i++) {
|
|
84
|
+
if (/^##\s+3\.\s+Learned rules/.test(lines[i])) { secStart = i; break }
|
|
85
|
+
}
|
|
86
|
+
if (secStart === -1) {
|
|
87
|
+
issues.push({ code: 'C3', level: 'error', msg: 'ship.md ขาด section "## 3. Learned rules"' })
|
|
88
|
+
return issues
|
|
89
|
+
}
|
|
90
|
+
// ขอบเขต section = จนเจอ '## ' ถัดไป
|
|
91
|
+
let secEnd = lines.length
|
|
92
|
+
for (let i = secStart + 1; i < lines.length; i++) {
|
|
93
|
+
if (/^##\s/.test(lines[i])) { secEnd = i; break }
|
|
94
|
+
}
|
|
95
|
+
// หา data row จริงในตาราง: บรรทัดที่มี '|' ≥2, ไม่ใช่ separator (|---|), ไม่ใช่ header (มี cell ไม่ว่าง อย่างน้อย 1)
|
|
96
|
+
// header แยกจาก data ด้วย separator — นับ row หลัง separator ที่มี cell ไม่ว่าง
|
|
97
|
+
let sawSeparator = false
|
|
98
|
+
let hasDataRow = false
|
|
99
|
+
for (let i = secStart + 1; i < secEnd; i++) {
|
|
100
|
+
const t = lines[i].trim()
|
|
101
|
+
if (!t.startsWith('|')) continue
|
|
102
|
+
if (/^\|[\s|:-]*\|?$/.test(t) && t.includes('-')) { sawSeparator = true; continue } // separator |---|
|
|
103
|
+
if (!sawSeparator) continue // ยังไม่ถึง separator = ยังเป็น header
|
|
104
|
+
// data row: split cells, มี cell ที่ไม่ว่าง
|
|
105
|
+
const cells = t.split('|').slice(1, -1).map((c) => c.trim())
|
|
106
|
+
if (cells.some((c) => c !== '')) { hasDataRow = true; break }
|
|
107
|
+
}
|
|
108
|
+
if (!hasDataRow) {
|
|
109
|
+
issues.push({ code: 'C3', level: 'error', msg: 'ship.md section "Learned rules" ไม่มี data row (มีแค่ header/ตารางว่าง)' })
|
|
110
|
+
}
|
|
111
|
+
return issues
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── C1: artifact ของ stage N เริ่มเติม แต่ required ของ stage < N ยัง template (ข้ามลำดับ) → ⚠ ──
|
|
115
|
+
// + stage inference: stage ปัจจุบัน = stage สูงสุดที่มี artifact "เริ่มเติม"
|
|
116
|
+
function inferStageAndC1(files) {
|
|
117
|
+
const issues = []
|
|
118
|
+
const filledOf = (name) => {
|
|
119
|
+
const c = topLevel(files, name)
|
|
120
|
+
return c != null && isFilled(c)
|
|
121
|
+
}
|
|
122
|
+
// stage ที่ "เริ่มเติม" = มี artifact required หรือ optional ตัวใดตัวหนึ่ง filled
|
|
123
|
+
let maxOrder = 0
|
|
124
|
+
let stageName = '(ยังไม่เริ่ม)'
|
|
125
|
+
for (const s of STAGES) {
|
|
126
|
+
const anyFilled = [...s.required, ...s.optional].some(filledOf)
|
|
127
|
+
if (anyFilled && s.order > maxOrder) { maxOrder = s.order; stageName = s.stage }
|
|
128
|
+
}
|
|
129
|
+
// C1: stage ที่เริ่มเติม (order N) แต่ required ของ stage order < N ยังไม่ครบ filled
|
|
130
|
+
for (const s of STAGES) {
|
|
131
|
+
if (s.required.length === 0) continue
|
|
132
|
+
const anyFilledHere = [...s.required, ...s.optional].some(filledOf)
|
|
133
|
+
if (!anyFilledHere) continue
|
|
134
|
+
// เช็ค required ของ stage ก่อนหน้า (order < s.order) ที่ยังไม่ filled
|
|
135
|
+
for (const prev of STAGES) {
|
|
136
|
+
if (prev.order >= s.order || prev.required.length === 0) continue
|
|
137
|
+
const prevDone = prev.required.every(filledOf)
|
|
138
|
+
if (!prevDone) {
|
|
139
|
+
issues.push({
|
|
140
|
+
code: 'C1',
|
|
141
|
+
level: 'warn',
|
|
142
|
+
msg: `${s.stage} เริ่มเติมแต่ ${prev.stage} (${prev.required.join('/')}) ยังเป็น template (ข้ามลำดับ)`,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { issues, stage: stageName }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── C4: design.md เริ่มเติมแล้ว → ต้องมี section 'Spec delta' (หรือ 'ไม่มี delta') → ⚠ ─────
|
|
151
|
+
function checkSpecDelta(files) {
|
|
152
|
+
const issues = []
|
|
153
|
+
const design = topLevel(files, 'design.md')
|
|
154
|
+
if (design == null || !isFilled(design)) return issues // ไม่มี/ยัง template → ข้าม
|
|
155
|
+
const hasDelta = /Spec delta/i.test(design) || /ไม่มี delta/.test(design)
|
|
156
|
+
if (!hasDelta) {
|
|
157
|
+
issues.push({ code: 'C4', level: 'warn', msg: 'design.md เริ่มเติมแล้วแต่ไม่มี section "Spec delta"' })
|
|
158
|
+
}
|
|
159
|
+
return issues
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── pure fn หลัก: checkTopic(files) → {issues, stage} ────────────────────────
|
|
163
|
+
export function checkTopic(files) {
|
|
164
|
+
const issues = []
|
|
165
|
+
issues.push(...checkTasks(files)) // C2 ✖
|
|
166
|
+
issues.push(...checkShipData(files)) // C3 ✖
|
|
167
|
+
const { issues: c1Issues, stage } = inferStageAndC1(files) // C1 ⚠ + stage
|
|
168
|
+
issues.push(...c1Issues)
|
|
169
|
+
issues.push(...checkSpecDelta(files)) // C4 ⚠
|
|
170
|
+
return { issues, stage }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── C5: feature spec format (checkFeatureSpec) ──────────────────────────────
|
|
174
|
+
// มี '## Requirement:' ≥1 (anchor H2 เป๊ะ) · ทุก Requirement มี '### Scenario:' ≥1
|
|
175
|
+
// · ทุก Scenario มี GIVEN+WHEN+THEN (case-insensitive, ไม่ enforce order)
|
|
176
|
+
// group ด้วย section boundary (เจอ '## Requirement:' ถัดไป = ปิด block ของ Requirement ก่อน) — defer #1
|
|
177
|
+
export function checkFeatureSpec(name, content) {
|
|
178
|
+
const issues = []
|
|
179
|
+
const lines = content.split('\n')
|
|
180
|
+
|
|
181
|
+
// index ของ '## Requirement:' (H2 เป๊ะ — กัน false-match #### ใน design.md §9; defer #2)
|
|
182
|
+
const reqIdx = []
|
|
183
|
+
for (let i = 0; i < lines.length; i++) {
|
|
184
|
+
if (/^##\s+Requirement:/.test(lines[i])) reqIdx.push(i)
|
|
185
|
+
}
|
|
186
|
+
if (reqIdx.length === 0) {
|
|
187
|
+
issues.push({ code: 'C5', level: 'error', msg: `${name}: ไม่มี "## Requirement:" (≥1)` })
|
|
188
|
+
return issues
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// แต่ละ Requirement block = [reqIdx[k] .. reqIdx[k+1]) (หรือจบไฟล์)
|
|
192
|
+
for (let k = 0; k < reqIdx.length; k++) {
|
|
193
|
+
const start = reqIdx[k]
|
|
194
|
+
const end = k + 1 < reqIdx.length ? reqIdx[k + 1] : lines.length
|
|
195
|
+
const reqTitle = lines[start].replace(/^##\s+Requirement:\s*/, '').trim() || '(ไม่มีชื่อ)'
|
|
196
|
+
|
|
197
|
+
// หา '### Scenario:' ใน block นี้
|
|
198
|
+
const scenIdx = []
|
|
199
|
+
for (let i = start + 1; i < end; i++) {
|
|
200
|
+
if (/^###\s+Scenario:/.test(lines[i])) scenIdx.push(i)
|
|
201
|
+
}
|
|
202
|
+
if (scenIdx.length === 0) {
|
|
203
|
+
issues.push({ code: 'C5', level: 'error', msg: `${name}: Requirement "${reqTitle}" ไม่มี "### Scenario:"` })
|
|
204
|
+
continue
|
|
205
|
+
}
|
|
206
|
+
// แต่ละ Scenario block = [scenIdx[j] .. scenIdx[j+1] หรือ end)
|
|
207
|
+
for (let j = 0; j < scenIdx.length; j++) {
|
|
208
|
+
const sStart = scenIdx[j]
|
|
209
|
+
const sEnd = j + 1 < scenIdx.length ? scenIdx[j + 1] : end
|
|
210
|
+
const scenTitle = lines[sStart].replace(/^###\s+Scenario:\s*/, '').trim() || '(ไม่มีชื่อ)'
|
|
211
|
+
const body = lines.slice(sStart + 1, sEnd).join('\n')
|
|
212
|
+
const missing = []
|
|
213
|
+
if (!/\bGIVEN\b/i.test(body)) missing.push('GIVEN')
|
|
214
|
+
if (!/\bWHEN\b/i.test(body)) missing.push('WHEN')
|
|
215
|
+
if (!/\bTHEN\b/i.test(body)) missing.push('THEN')
|
|
216
|
+
if (missing.length) {
|
|
217
|
+
issues.push({
|
|
218
|
+
code: 'C5',
|
|
219
|
+
level: 'error',
|
|
220
|
+
msg: `${name}: Scenario "${scenTitle}" ขาด ${missing.join('/')}`,
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return issues
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── render helper ───────────────────────────────────────────────────────────
|
|
229
|
+
const SYM = { error: '✖', warn: '⚠' }
|
|
230
|
+
function countLevels(issues) {
|
|
231
|
+
let err = 0
|
|
232
|
+
let warn = 0
|
|
233
|
+
for (const i of issues) {
|
|
234
|
+
if (i.level === 'error') err++
|
|
235
|
+
else if (i.level === 'warn') warn++
|
|
236
|
+
}
|
|
237
|
+
return { err, warn }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── fs walk (main เท่านั้น — pure fn ไม่รู้จัก fs) ──────────────────────────
|
|
241
|
+
const SKIP_TOPIC = new Set(['achieved'])
|
|
242
|
+
const SKIP_FILE = new Set(['context.md'])
|
|
243
|
+
|
|
244
|
+
// อ่านไฟล์ทั้งหมดใต้ dir ของ topic เป็น Map<relPath,content> (relPath relative จาก topic dir, POSIX)
|
|
245
|
+
function readTopicFiles(topicDir) {
|
|
246
|
+
const files = new Map()
|
|
247
|
+
const walk = (dir, prefix) => {
|
|
248
|
+
let entries
|
|
249
|
+
try {
|
|
250
|
+
entries = readdirSync(dir, { withFileTypes: true })
|
|
251
|
+
} catch {
|
|
252
|
+
return // ENOENT/EACCES guard — ข้ามเงียบ ไม่พ่น absolute path
|
|
253
|
+
}
|
|
254
|
+
for (const e of entries) {
|
|
255
|
+
const rel = prefix ? `${prefix}/${e.name}` : e.name
|
|
256
|
+
if (e.isDirectory()) {
|
|
257
|
+
walk(join(dir, e.name), rel)
|
|
258
|
+
} else if (e.isFile()) {
|
|
259
|
+
if (!e.name.endsWith('.md')) continue
|
|
260
|
+
try {
|
|
261
|
+
files.set(rel, readFileSync(join(dir, e.name), 'utf8'))
|
|
262
|
+
} catch {
|
|
263
|
+
// ENOENT/EACCES — ข้ามไฟล์ที่อ่านไม่ได้ ไม่ leak path
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
walk(topicDir, '')
|
|
269
|
+
return files
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// list active topic slugs (dir ใต้ docs/stages/ ข้าม achieved) — ใช้ทั้ง status + slug whitelist (B7)
|
|
273
|
+
function listTopics(stagesDir) {
|
|
274
|
+
let entries
|
|
275
|
+
try {
|
|
276
|
+
entries = readdirSync(stagesDir, { withFileTypes: true })
|
|
277
|
+
} catch {
|
|
278
|
+
return []
|
|
279
|
+
}
|
|
280
|
+
return entries
|
|
281
|
+
.filter((e) => e.isDirectory() && !SKIP_TOPIC.has(e.name))
|
|
282
|
+
.map((e) => e.name)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// walk docs/features/*/spec.md → [{name, content}]
|
|
286
|
+
function readFeatureSpecs(featuresDir) {
|
|
287
|
+
const specs = []
|
|
288
|
+
let entries
|
|
289
|
+
try {
|
|
290
|
+
entries = readdirSync(featuresDir, { withFileTypes: true })
|
|
291
|
+
} catch {
|
|
292
|
+
return specs
|
|
293
|
+
}
|
|
294
|
+
for (const e of entries) {
|
|
295
|
+
if (!e.isDirectory()) continue
|
|
296
|
+
const specPath = join(featuresDir, e.name, 'spec.md')
|
|
297
|
+
try {
|
|
298
|
+
const content = readFileSync(specPath, 'utf8')
|
|
299
|
+
specs.push({ name: `docs/features/${e.name}/spec.md`, content })
|
|
300
|
+
} catch {
|
|
301
|
+
// ไม่มี spec.md → ข้าม (เช็คเฉพาะที่มีไฟล์)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return specs
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ── main: 2 โหมด ────────────────────────────────────────────────────────────
|
|
308
|
+
function main() {
|
|
309
|
+
const cwd = process.cwd()
|
|
310
|
+
const stagesDir = join(cwd, 'docs', 'stages')
|
|
311
|
+
const featuresDir = join(cwd, 'docs', 'features')
|
|
312
|
+
const args = process.argv.slice(2)
|
|
313
|
+
|
|
314
|
+
if (args.length > 1) {
|
|
315
|
+
console.error('✖ ใช้: validate-topic.mjs [<slug>] — รับได้สูงสุด 1 arg')
|
|
316
|
+
process.exit(2)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// C5 spec ใช้ทั้งสองโหมด (รวมเข้า total count)
|
|
320
|
+
const featureSpecs = readFeatureSpecs(featuresDir)
|
|
321
|
+
const featureIssues = featureSpecs.flatMap((s) => checkFeatureSpec(s.name, s.content))
|
|
322
|
+
|
|
323
|
+
if (args.length === 0) {
|
|
324
|
+
// ── โหมด status ──
|
|
325
|
+
const topics = listTopics(stagesDir)
|
|
326
|
+
if (topics.length === 0 && featureIssues.length === 0) {
|
|
327
|
+
console.log('ไม่มีงานค้าง')
|
|
328
|
+
process.exit(0)
|
|
329
|
+
}
|
|
330
|
+
if (topics.length === 0) {
|
|
331
|
+
console.log('ไม่มี topic ใน docs/stages/')
|
|
332
|
+
} else {
|
|
333
|
+
console.log('topic'.padEnd(28), 'stage'.padEnd(14), '✖/⚠')
|
|
334
|
+
console.log('-'.repeat(28), '-'.repeat(14), '-----')
|
|
335
|
+
for (const slug of topics.sort()) {
|
|
336
|
+
const files = readTopicFiles(join(stagesDir, slug))
|
|
337
|
+
const { issues, stage } = checkTopic(files)
|
|
338
|
+
const { err, warn } = countLevels(issues)
|
|
339
|
+
console.log(slug.padEnd(28), stage.padEnd(14), `✖${err}/⚠${warn}`)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (featureIssues.length) {
|
|
343
|
+
console.log('')
|
|
344
|
+
console.log(`feature spec (C5): ✖${featureIssues.length}`)
|
|
345
|
+
}
|
|
346
|
+
process.exit(0) // status เป็นรายงาน ไม่ใช่ gate
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ── โหมด validate <slug> ──
|
|
350
|
+
const slug = args[0]
|
|
351
|
+
// slug whitelist (B7): ต้องตรง basename ของ dir ที่มีอยู่จริง — กัน path traversal
|
|
352
|
+
const topics = listTopics(stagesDir)
|
|
353
|
+
if (!topics.includes(slug)) {
|
|
354
|
+
console.error(`✖ ไม่พบ topic "${slug}" ใน docs/stages/`)
|
|
355
|
+
process.exit(2)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const files = readTopicFiles(join(stagesDir, slug))
|
|
359
|
+
const { issues, stage } = checkTopic(files)
|
|
360
|
+
// รวม C5 ของ feature spec (เป็น cross-cutting — report ในโหมด validate ด้วย)
|
|
361
|
+
const allIssues = [...issues, ...featureIssues]
|
|
362
|
+
|
|
363
|
+
console.log(`topic: ${slug} · stage (ประมาณการ): ${stage}`)
|
|
364
|
+
if (allIssues.length === 0) {
|
|
365
|
+
console.log('✓ โครงครบ (structural)')
|
|
366
|
+
process.exit(0)
|
|
367
|
+
}
|
|
368
|
+
for (const i of allIssues) {
|
|
369
|
+
console.log(`${SYM[i.level] || '?'} [${i.code}] ${i.msg}`)
|
|
370
|
+
}
|
|
371
|
+
const { err } = countLevels(allIssues)
|
|
372
|
+
process.exit(err > 0 ? 1 : 0)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// main-guard: argv[1] comparison (ไม่ใช่ import.meta.main ที่ undefined บน node 20) — import จาก unit ไม่ trigger main
|
|
376
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
377
|
+
main()
|
|
378
|
+
}
|
|
@@ -29,6 +29,7 @@ BUILD จะ **orchestrate การ implement** โดยกระจายง
|
|
|
29
29
|
- task ใน wave เดียวกัน = independent → รัน **parallel**
|
|
30
30
|
- ข้าม wave = มี dependency → wave ถัดไปเริ่มหลัง wave ก่อนหน้ารวมผลเสร็จ
|
|
31
31
|
3. **Worktree isolation ต่อ task** — แต่ละ parallel task ทำใน git worktree ของตัวเอง ไม่แก้ไฟล์ชนกัน แล้ว **integrate (merge) เข้า build branch หลังจบแต่ละ wave**
|
|
32
|
+
- **★ worktree fork จาก main (คุมไม่ได้) → agent ต้อง sync build branch ก่อน** — harness fork worktree จาก main จึงยังไม่เห็น `docs/stages/<slug>/` (topic docs) + output ของ wave ก่อนหน้า; build-wave สั่ง agent `git merge <baseRef>` (= build branch) เป็น step แรกก่อนอ่าน task เพื่อให้เห็น dependency ครบ (กลไกอยู่ใน `build-wave.mjs` — orchestrator ส่ง `baseRef` เข้ามา)
|
|
32
33
|
- ถ้า target ไม่ใช่ git repo → fallback เป็น **sequential shared-tree** (ทีละ task ตาม dependency)
|
|
33
34
|
4. **แต่ละ build agent ต้อง self-verify** — implement → รัน test-flow ใน `spec.md` + build/lint → **ต้องผ่านก่อนถึง mark passed**; ถ้าแก้ไม่ได้ให้รายงาน `failed` พร้อมเหตุผล **ห้ามรายงานผ่านทั้งที่ยังแดง**
|
|
34
35
|
5. **เคารพ standard/rule ของ task** — ทำตาม `standard.md` (pattern โค้ด, reuse shared component) และ `rule.md` (กฎ) อย่างเคร่งครัด
|
|
@@ -52,9 +53,9 @@ BUILD จะ **orchestrate การ implement** โดยกระจายง
|
|
|
52
53
|
3. **Pre-check:** target เป็น git repo ไหม (สำหรับ worktree) — ถ้าไม่ใช่ → fallback sequential shared-tree และแจ้ง user
|
|
53
54
|
4. **เสนอ execution plan + ขออนุมัติ (ครั้งเดียว):** แสดง wave / task ในแต่ละ wave / อันไหน parallel / isolation mode → ถาม user go/no-go
|
|
54
55
|
5. **เดินทีละ wave:**
|
|
55
|
-
- fan-out sub-agent ของ task ใน wave นั้น (parallel, worktree isolation) ผ่าน **Workflow** (`.warnyin/workflow/scripts/build-wave.mjs`)
|
|
56
|
-
- แต่ละ agent: implement → test/lint → commit (ถ้า worktree) → รายงานผลแบบ structured (status, files, branch, test result)
|
|
57
|
-
- **integrate:** main loop
|
|
56
|
+
- fan-out sub-agent ของ task ใน wave นั้น (parallel, worktree isolation) ผ่าน **Workflow** (`.warnyin/workflow/scripts/build-wave.mjs`) — orchestrator **ส่ง `baseRef` (= build branch)** เข้า build-wave เพื่อให้ agent sync build branch เข้า worktree ก่อน (เห็น topic docs + output wave ก่อนหน้า)
|
|
57
|
+
- แต่ละ agent: sync build branch (`git merge <baseRef>`) → implement → test/lint → commit (ถ้า worktree) → รายงานผลแบบ structured (status, files, branch, test result)
|
|
58
|
+
- **integrate:** main loop checkout **เฉพาะไฟล์ source ที่ scoped** ของ wave นั้นจาก worktree branch (`git checkout <branch> -- <files>` — เลี่ยง topic-docs copy ที่ agent merge เข้า worktree + ปลอด KB#11); ถ้า conflict → แก้/รายงาน
|
|
58
59
|
- ถ้ามี task `failed` → หยุด รายงาน user
|
|
59
60
|
6. **★ Full build & test gate (หลังทุก wave merge เสร็จ):** บน build branch ที่ integrate แล้ว รัน **build ทั้งหมด + test suite ทั้งหมด (รวม unit test)** ของทุก component ที่กระทบ
|
|
60
61
|
- มี error / test แดง → **แก้จนเขียวหมด (loop)** อาจ delegate fix ให้ sub-agent ทีละจุด แล้ว rerun ใหม่
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
3. `docs/techstack/<component>/standard.md` — ★ pattern/มาตรฐานการเขียนโค้ด
|
|
24
24
|
4. `docs/techstack/<component>/{about,structure,test}.md`, `docs/codemap/index.md` — โครงสร้าง/วิธีเทสต์
|
|
25
25
|
5. ถ้ามี Discovery → `docs/stages/<slug>/discovery.md`, `research.md`
|
|
26
|
-
6.
|
|
26
|
+
6. `docs/features/<name>/spec.md` — ★ behavior spec ปัจจุบันของ feature ที่ change แตะ (baseline สำหรับเขียน Spec delta; feature ยังไม่มี spec → ข้ามได้)
|
|
27
|
+
7. โค้ดจริงที่เกี่ยวข้อง (inspect เพื่อตอบคำถามแทนการเดา)
|
|
27
28
|
|
|
28
29
|
---
|
|
29
30
|
|
|
@@ -46,7 +47,7 @@
|
|
|
46
47
|
2. **Ground + เคลียร์ความไม่ชัด:** อ่าน Input; ทุกจุดกำกวมเรื่อง design → ถามทีละข้อ + recommended answer จนชัด (ถ้าใหญ่/ไม่ชัดมาก → แนะนำ Discovery ก่อน)
|
|
47
48
|
3. **business.md** *(optional — ข้ามได้ถ้า change เล็ก เช่น fix bug นิดหน่อย)*: what & why เชิงธุรกิจ — goal, คุณค่า, persona, success metric
|
|
48
49
|
4. **proposal.md** (what & why): สรุป change ที่จะทำ, เหตุผล, ทางเลือกที่พิจารณา/ตัดทิ้ง, scope in/out
|
|
49
|
-
5. **design.md** (how): ออกแบบเชิงเทคนิคแบบ vertical slice — slice มีอะไรบ้าง, แต่ละ slice ตัดผ่าน layer ไหน, data model, interface/contract, flow, ผลกระทบต่อระบบเดิม (ใช้ lens `.warnyin/workflow/roles/sa.md`)
|
|
50
|
+
5. **design.md** (how): ออกแบบเชิงเทคนิคแบบ vertical slice — slice มีอะไรบ้าง, แต่ละ slice ตัดผ่าน layer ไหน, data model, interface/contract, flow, ผลกระทบต่อระบบเดิม (ใช้ lens `.warnyin/workflow/roles/sa.md`) — **ครอบ "Spec delta" ด้วย**: เทียบพฤติกรรมที่ change นี้แตะกับ `docs/features/<name>/spec.md` ปัจจุบัน แล้วเขียน ADDED/MODIFIED/REMOVED (SHIP merge ตามนี้); change ไม่แตะพฤติกรรม feature → ระบุ "ไม่มี delta"
|
|
50
51
|
6. **Review panel (optional — ถาม user ก่อน):** เสนอ user ว่าจะให้ panel หลาย role รีวิว design ก่อนแตก task ไหม — ถ้า ok:
|
|
51
52
|
1. fan-out sub-agent reviewer **ขนาน (read-only)** ตาม role card: **SA** (architecture/data model/contract), **Tech Lead** (feasibility/ขนาด task/dependency), **QA** (testability/acceptance), **Security** (ช่องโหว่/ข้อมูลอ่อนไหว), **Infra** (env/config/migration) — แต่ละตัวอ่าน `proposal.md` + `design.md` + โค้ดจริงที่เกี่ยว แล้วให้ความเห็นตาม checklist ใน `.warnyin/workflow/roles/<role>.md` แบ่งเป็น **blocker / suggestion**
|
|
52
53
|
2. รวมความเห็นทุก role → สรุปให้ user เห็นภาพ
|
|
@@ -77,7 +78,7 @@
|
|
|
77
78
|
|---|---|---|---|
|
|
78
79
|
| `business.md` | what & why เชิงธุรกิจ (goal, persona, success metric) | ✅ ข้ามได้ถ้า change เล็ก | `.warnyin/template/stages/[topic]/business.md` |
|
|
79
80
|
| `proposal.md` | what & why ของ change + ทางเลือก + scope | จำเป็น | `.warnyin/template/stages/[topic]/proposal.md` |
|
|
80
|
-
| `design.md` | how — vertical slice, data model, contract, flow | จำเป็น | `.warnyin/template/stages/[topic]/design.md` |
|
|
81
|
+
| `design.md` | how — vertical slice, data model, contract, flow, **Spec delta** | จำเป็น | `.warnyin/template/stages/[topic]/design.md` |
|
|
81
82
|
| `tasks/<task-name>/spec.md` | spec เฉพาะ task (ดูข้อ 6) | จำเป็นต่อ task | `.warnyin/template/stages/[topic]/tasks/[task-name]/spec.md` |
|
|
82
83
|
| `tasks/<task-name>/standard.md` | pattern โค้ด/shared component อิง techstack standard | จำเป็นต่อ task | `.warnyin/template/stages/[topic]/tasks/[task-name]/standard.md` |
|
|
83
84
|
| `tasks/<task-name>/rule.md` | rule ที่ต้อง follow + rule ที่เสนอเพิ่ม (รอ SHIP) | จำเป็นต่อ task | `.warnyin/template/stages/[topic]/tasks/[task-name]/rule.md` |
|
|
@@ -108,7 +109,8 @@
|
|
|
108
109
|
- [ ] proposal.md (+ business.md ถ้าจำเป็น) + design.md ครบ และ user เห็นชอบ
|
|
109
110
|
- [ ] **ไม่มีการเดา** — ทุกจุดที่ไม่ชัดถูกถาม (ทีละข้อ + recommended answer) และปิดแล้ว
|
|
110
111
|
- [ ] design เป็น vertical slice ชัด — แต่ละ task/slice ส่งมอบคุณค่า end-to-end ได้
|
|
111
|
-
- [ ]
|
|
112
|
+
- [ ] **Spec delta ครบ** — เทียบ `docs/features/<name>/spec.md` ของ feature ที่แตะ (ADDED/MODIFIED/REMOVED) หรือระบุ "ไม่มี delta"
|
|
113
|
+
- [ ] ทุก task มี `spec.md` + `standard.md` + `rule.md` + `task.md` ครบ (ถ้ารัน node ได้: `node .warnyin/workflow/scripts/validate-topic.mjs <slug>` ควรไม่มี ✖ — เช็คโครง ไม่แทนการอ่าน semantic)
|
|
112
114
|
- [ ] dependency / ลำดับระหว่าง task และ sub-task ชัดเจน เชื่อมต่อกัน
|
|
113
115
|
- [ ] rule ที่ต้อง follow ถูกระบุครบ; rule/standard ใหม่ที่อยากเพิ่มถูก note ไว้ (รอ SHIP)
|
|
114
116
|
- [ ] ถ้าทำ review panel: **blocker จากทุก role ถูกแก้/ปิดครบ** — สรุปผลบันทึกใน `design.md` section "Design review"
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
2. **ขอ user ยืนยัน "ครั้งเดียวก่อนลงมือ"** — สรุป promotion plan (feature ใหม่หรือปรับปรุง, ไฟล์กลางที่จะอัปเดต + สาระที่จะใส่, ชื่อโฟลเดอร์ archive) ให้ user อนุมัติ แล้วจึง execute ไม่ถามซ้ำระหว่างทาง
|
|
34
34
|
3. **★ Archive ก่อนอัปเดตเอกสาร** — ย้ายทั้งโฟลเดอร์ `docs/stages/<slug>/` → `docs/stages/achieved/<YYYY-MM-DD>-<slug>/` (วันที่ของวันที่ SHIP) **ก่อน** เริ่มแก้ `docs/` แล้วอ่านเนื้อหาจาก path ใหม่ระหว่าง promote
|
|
35
35
|
4. **กลั่น ไม่ใช่ copy ดิบ** — merge สาระเข้าโครงสร้างของไฟล์กลางเดิม ระวัง duplicate/ขัดแย้งกับเนื้อหาที่มีอยู่; เขียนแบบ "ความรู้ถาวร" ไม่ใช่บันทึกรายงาน
|
|
36
|
-
5. **เอกสารต้องตรงโค้ดจริง** — structure/codemap อัปเดตจากการดูโค้ดจริง ไม่ใช่จากความจำหรือ design
|
|
36
|
+
5. **เอกสารต้องตรงโค้ดจริง** — structure/codemap อัปเดตจากการดูโค้ดจริง ไม่ใช่จากความจำหรือ design เดิม; **ครอบ Spec delta ด้วย** — ถ้าพฤติกรรมจริง (หลัง BUILD/VERIFY) ต่างจาก delta ที่ approve ไว้ → **อัปเดต delta ใน design.md ก่อน แล้วค่อย merge**; และ re-check delta เทียบ `spec.md` ปัจจุบัน ณ เวลา ship (ไม่ใช่ ณ เวลา design)
|
|
37
37
|
6. **อย่าเดา** — เนื้อหาที่ไม่แน่ใจว่าควร promote หรือควรวางไว้ไฟล์ไหน → ถามทีละข้อ + เสนอคำตอบที่แนะนำ
|
|
38
38
|
7. **เก็บ learned-rule ให้หมด — planned + emergent** — รวบรวม rule ที่จะ promote ทุกตัว: ทั้ง **planned** (note "รอ SHIP" ใน `tasks/*/rule.md` §2) และ **emergent** (บทเรียนที่โผล่ตอนลงมือ — สแกน `build.md`/`verify.md`/`troubleshooting.md`/diff); ทุกตัวต้องมี **evidence (บังคับ)** + **scope** + **user ยืนยัน** ก่อน promote — ไม่มี evidence ไม่ promote (สอด "ห้ามเดา"); **learned-rule = กฎ generalize ไม่ใช่ incident** (troubleshooting = incident ที่ *อ้าง* เป็น evidence ได้). พิจารณาครบทุกข้อ (promote หรือตัดทิ้งพร้อมเหตุผล) ห้ามปล่อยค้าง
|
|
39
39
|
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
## 4. ลำดับขั้นการทำงาน (process)
|
|
43
43
|
|
|
44
|
-
1. **อ่านทำความเข้าใจ topic + รวบรวม learned-rule candidate:** อ่าน `docs/stages/<slug>/` ทุกไฟล์ — topic นี้ทำอะไร ทำอย่างไร เกิดความรู้ใหม่อะไรบ้าง (เช็คก่อนว่า VERIFY ผ่าน Gate แล้ว — มี `verify.md`
|
|
44
|
+
1. **อ่านทำความเข้าใจ topic + รวบรวม learned-rule candidate:** อ่าน `docs/stages/<slug>/` ทุกไฟล์ — topic นี้ทำอะไร ทำอย่างไร เกิดความรู้ใหม่อะไรบ้าง (เช็คก่อนว่า VERIFY ผ่าน Gate แล้ว — มี `verify.md` สรุปผลผ่าน; ถ้ารัน node ได้ → รัน `node .warnyin/workflow/scripts/validate-topic.mjs <slug>` — มี ✖ ควรแก้ก่อน promote (script เช็คโครง; ความถูกของเนื้อหายังเป็นหน้าที่ผู้ ship)) — พร้อมกันนั้น **รวบรวม learned-rule candidate** เป็นตาราง (ทุกตัว = `rule + evidence + scope + promote?`):
|
|
45
45
|
- **planned:** จาก `tasks/*/rule.md` §2 "เสนอเพิ่ม rule ใหม่ (รอ SHIP)"
|
|
46
46
|
- **emergent:** สแกนบทเรียนที่โผล่ตอนลงมือ — `build.md` (pattern แก้ซ้ำ/integration), `verify.md` (รายการแก้+จำนวนรอบ), `troubleshooting.md` (ปัญหายาก→"กันซ้ำ" = candidate ชัดสุด), diff/commit
|
|
47
47
|
- **entry แต่ละตัว:** `rule` = ข้อความ **generalize** (ถ้าเป็น incident "X พังเพราะ Y" ยกเป็นกฎ "ก่อนแก้ Z เช็ค Y เสมอ") · `evidence` = **บังคับ** concrete pointer 1 บรรทัด + ลิงก์ artifact (`build.md`/`verify.md`/`troubleshooting.md`/diff/commit) — **ไม่มี evidence = ไม่ promote** · `scope` = `component:<c>`→`docs/techstack/<c>/rule.md` หรือ `project`→`docs/rule.md`
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
3. **สรุป promotion plan + ขออนุมัติ (ครั้งเดียว):** feature ใหม่/ปรับปรุง, รายการไฟล์กลางที่จะอัปเดต + สาระ, ชื่อโฟลเดอร์ archive — **fold ตาราง learned-rule (rule + evidence + scope) เข้า approval เดียวกันนี้ ให้ user ยืนยัน per-rule** (✅ promote / ✂️ ตัด + เหตุผล) → รอ user ไฟเขียว
|
|
50
50
|
4. **★ Archive:** ย้ายทั้งโฟลเดอร์ → `docs/stages/achieved/<YYYY-MM-DD>-<slug>/` (ใช้ `git mv` ถ้าเป็น git repo)
|
|
51
51
|
5. **อัปเดตเอกสารกลาง** (อ่านเนื้อหาจาก path ใน achieved):
|
|
52
|
-
1. **`docs/features/<feature-name>/`** — feature ใหม่ → สร้างโฟลเดอร์ใหม่ (`feature.md` + `business.md`); ปรับปรุง feature เดิม → อัปเดตโฟลเดอร์เดิม โดยใช้เนื้อหาจาก `business.md` / `proposal.md` / `design.md` ของ topic
|
|
52
|
+
1. **`docs/features/<feature-name>/`** — feature ใหม่ → สร้างโฟลเดอร์ใหม่ (`feature.md` + `business.md`); ปรับปรุง feature เดิม → อัปเดตโฟลเดอร์เดิม โดยใช้เนื้อหาจาก `business.md` / `proposal.md` / `design.md` ของ topic — **และ merge `spec.md`** ตาม Spec delta ใน `design.md` §9: **ADDED** → ต่อท้าย `spec.md` · **MODIFIED** → แทนที่ requirement ชื่อตรงกัน (rename → หาด้วย `[เดิมชื่อ:]`) · **REMOVED** → ลบ requirement; **read-modify-verify — key ไม่เจอ → STOP:** MODIFIED/REMOVED ที่หา key ไม่เจอใน `spec.md` ของ feature ที่ `(→ feature:)` ระบุ → **หยุด ถาม user ห้าม merge เงียบ** (ห้ามตีความเป็น ADDED เอง); **feature ใหม่** → สร้าง `spec.md` จาก ADDED ทั้งก้อน (template `[feature-name]/spec.md`); **feature เดิมยังไม่มี `spec.md`** → สร้างใหม่จาก delta + พฤติกรรมจริง
|
|
53
53
|
2. **`docs/techstack/<component>/`** — `rule.md` / `standard.md` (learned-rule ที่ยืนยันแล้ว scope `component:<c>` + note "รอ SHIP" ใน tasks), `structure.md` (โครงสร้างที่เปลี่ยน), `test.md` (merge แผนเทสจาก `test.md` ของ topic)
|
|
54
54
|
3. **`docs/rule.md`** — global rule ใหม่/ที่เปลี่ยน (learned-rule ที่ยืนยันแล้ว scope `project` — กฎระดับโปรเจกต์ ไม่ผูกกับ component เดียว)
|
|
55
55
|
> promote learned-rule **เฉพาะตัวที่ user ยืนยัน (step 3)** ตาม scope: `component:<c>` → `docs/techstack/<c>/rule.md` · `project` → `docs/rule.md`
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
|
|
66
66
|
| ที่ | เนื้อหา |
|
|
67
67
|
|---|---|
|
|
68
|
-
| `docs/features/<feature-name>/` | feature.md + business.md (สร้างใหม่หรืออัปเดต) |
|
|
68
|
+
| `docs/features/<feature-name>/` | feature.md + business.md + spec.md (สร้างใหม่หรืออัปเดต — spec.md merge ตาม Spec delta) |
|
|
69
69
|
| `docs/techstack/<component>/{rule,standard,structure,test}.md` | rule/standard ใหม่, โครงสร้าง, วิธีเทสที่ promote ขึ้น |
|
|
70
70
|
| `docs/rule.md` | global rule ที่เพิ่ม/เปลี่ยน |
|
|
71
71
|
| `docs/troubleshooting.md` | KB ปัญหา-วิธีแก้ที่ merge เข้า |
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
|
|
80
80
|
- [ ] topic ถูกย้ายไป `docs/stages/achieved/<YYYY-MM-DD>-<slug>/` แล้ว (ไม่เหลือใน `docs/stages/`)
|
|
81
81
|
- [ ] `docs/features/` สะท้อน feature ใหม่/ที่ปรับปรุงแล้ว
|
|
82
|
+
- [ ] **Spec delta merge แล้ว** — `spec.md` ของ feature ที่แตะถูก merge ตาม delta; ทุก MODIFIED/REMOVED match requirement จริง (read-modify-verify — key ไม่เจอ → STOP ถาม user แล้ว ไม่ merge เงียบ)
|
|
82
83
|
- [ ] learned-rules (planned + emergent) พิจารณาครบทุกตัว — note "รอ SHIP" ใน `tasks/*/rule.md` + `standard.md` + บทเรียน emergent จาก build/verify/troubleshooting; ทุก promote มี **evidence + user ยืนยัน**, ตัดทิ้งมีเหตุผล
|
|
83
84
|
- [ ] `docs/troubleshooting.md` รวม entry จาก topic แล้ว
|
|
84
85
|
- [ ] `docs/techstack/`, `docs/rule.md`, `docs/infra.md`, `docs/project.md` อัปเดตตามที่เกี่ยวข้อง
|
|
@@ -17,17 +17,18 @@
|
|
|
17
17
|
|
|
18
18
|
1. **spec + tasks ทั้งหมด** — `docs/stages/<slug>/tasks/*/spec.md` + `task.md`, `design.md`, `proposal.md`
|
|
19
19
|
→ เข้าใจ **จุดประสงค์ของ topic** และสิ่งที่ต้องยืนยัน (อย่าเทสผ่านๆ โดยไม่เข้าใจ)
|
|
20
|
-
2. **`docs/
|
|
20
|
+
2. **`docs/features/<name>/spec.md` = regression baseline** — อ่าน behavior spec ของ feature ที่ topic แตะ (ดูจาก Spec delta ใน `design.md`); topic แตะหลาย feature → baseline = union ของ spec ทุก feature ที่ delta อ้างถึง; feature ยังไม่มี spec → ข้ามได้ (วิธีเดิม)
|
|
21
|
+
3. **`docs/techstack/<component>/test.md`** — guideline ว่าเทสยังไง (เช่น frontend: e2e smoke ผ่าน **playwright-cli**)
|
|
21
22
|
→ ถ้า **ไม่มี** guideline → แนะนำว่าควรเทสแบบไหน/วิธีใดได้บ้าง แล้วเขียนแผนลง `test.md`
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
4. **`docs/infra.md`** — local env / service ที่ต้องรันเพื่อเทส
|
|
24
|
+
5. **`docs/troubleshooting.md`** — เผื่อปัญหาที่จะเจอเคยถูกแก้แล้ว
|
|
25
|
+
6. **runtime security** (`.warnyin/workflow/roles/security.md` → "Runtime / operational security") — ตอนรันเทส local env ที่มี secret จริง ทบทวน secret isolation / no-egress / identity separation ก่อนปล่อย agent แตะของจริง
|
|
25
26
|
|
|
26
27
|
---
|
|
27
28
|
|
|
28
29
|
## 3. หลักการทำงาน (operating principles)
|
|
29
30
|
|
|
30
|
-
1. **เข้าใจจุดประสงค์ก่อนเทส** — เทสตามเจตนาของ topic ไม่ใช่แค่ให้เขียว
|
|
31
|
+
1. **เข้าใจจุดประสงค์ก่อนเทส** — เทสตามเจตนาของ topic ไม่ใช่แค่ให้เขียว **และพฤติกรรมเดิมจาก feature spec** — scenario เดิมใน `docs/features/<name>/spec.md` = regression case (ต้องไม่พัง ยกเว้นที่ MODIFIED/REMOVED ระบุ), scenario ใน Spec delta = test case ใหม่
|
|
31
32
|
2. **เทสในสภาพแวดล้อมจริง (local env)** — รัน service ที่เกี่ยวข้องใน local แล้วเทสตามแผน
|
|
32
33
|
3. **ใช้ guideline จาก `test.md`** ของ techstack; ถ้าไม่มีให้เสนอวิธีเทสที่เหมาะแล้วเขียนแผนเอง
|
|
33
34
|
4. **Frontend → verify UX/UI ด้วย** (ไม่ใช่แค่ functional — ดู layout, state, flow, ความถูกต้องของหน้าจอ)
|
|
@@ -44,8 +45,8 @@
|
|
|
44
45
|
## 4. ลำดับขั้นการทำงาน (process)
|
|
45
46
|
|
|
46
47
|
0. **★ เช็ค context window ก่อนเริ่ม:** ประเมินว่า context ของ session ปัจจุบันถูกใช้ไปมากน้อยแค่ไหน — ถ้าใช้ไปเยอะหรือ **เกินครึ่ง** → **เสนอ user ให้ `/compact` หรือ `/clear` ก่อนเสมอ** แล้วค่อยเริ่ม VERIFY ใน context ที่โล่ง (สถานะงานอยู่ในไฟล์ `docs/stages/<slug>/` ครบ ไม่หายไปกับ context) — VERIFY เป็นลูปเทส-แก้ที่อาจยาว ต้องมี context เหลือมากพอ; เครื่องอื่นที่ไม่มีคำสั่งนี้ → แนะนำเริ่ม session ใหม่
|
|
47
|
-
1.
|
|
48
|
-
2. **วางแผนเทส → เขียน `test.md`:** ตาม guideline `docs/techstack/<component>/test.md`; ไม่มีก็เสนอวิธี (e2e smoke / integration / manual ฯลฯ) แล้วเขียนแผน
|
|
48
|
+
1. **เข้าใจจุดประสงค์ + อ่าน baseline:** อ่าน spec/tasks/design + `docs/features/<name>/spec.md` ของ feature ที่ topic แตะ (union ถ้าหลาย feature) → สรุปสิ่งที่ต้อง verify (functional + UX/UI)
|
|
49
|
+
2. **วางแผนเทส → เขียน `test.md`:** ตาม guideline `docs/techstack/<component>/test.md`; ไม่มีก็เสนอวิธี (e2e smoke / integration / manual ฯลฯ) แล้วเขียนแผน — **ครอบทั้ง regression (scenario เดิมใน baseline) + test case ใหม่ (scenario ใน Spec delta)**
|
|
49
50
|
3. **เตรียม local env:** รัน service ที่เกี่ยวข้องตาม `infra.md`
|
|
50
51
|
4. **รันเทสตามแผน:** functional ตาม test-flow ใน spec; FE → e2e smoke (playwright-cli) + ตรวจ UX/UI
|
|
51
52
|
5. **ไม่ผ่าน → แก้ → rerun:** วนจนผ่าน **นับจำนวนรอบ/จำนวนแก้**; ปัญหายาก→`troubleshooting.md`; ถ้านานเกิน→ถาม user (ทีละข้อ + recommended)
|
|
@@ -67,6 +68,7 @@
|
|
|
67
68
|
## 6. Gate → เข้า SHIP ได้เมื่อ
|
|
68
69
|
|
|
69
70
|
- [ ] เทสตาม **จุดประสงค์ของ topic** ครบ (functional ตาม test-flow ใน spec)
|
|
71
|
+
- [ ] **regression ตาม baseline** — scenario เดิมใน `docs/features/<name>/spec.md` ของ feature ที่แตะ ยังผ่าน (ยกเว้นที่ MODIFIED/REMOVED) + scenario ใน Spec delta ผ่าน
|
|
70
72
|
- [ ] Frontend: **UX/UI verify ผ่าน**
|
|
71
73
|
- [ ] ทุกข้อที่ไม่ผ่านถูกแก้จน **verify ผ่านหมด**
|
|
72
74
|
- [ ] `test.md` (แผน) + `verify.md` (สรุป + จำนวนการแก้ไข) เขียนครบ
|