@warnyin/agents 0.8.5 → 0.9.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.
package/CHANGELOG.md CHANGED
@@ -23,6 +23,12 @@
23
23
 
24
24
  ## [Unreleased]
25
25
 
26
+ ## [0.9.0] - 2026-06-08
27
+
28
+ ### Added
29
+ - **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` รอบถัดไป
30
+ - **วงจร 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` รอบถัดไป
31
+
26
32
  ## [0.8.5] - 2026-06-07
27
33
 
28
34
  ### Added
@@ -82,5 +88,12 @@
82
88
  ### Removed
83
89
  - รองรับ node 18 (drop ตาม EOL)
84
90
 
85
- [Unreleased]: https://github.com/warnyin/warnyin-agents/compare/v0.7.0...HEAD
91
+ [Unreleased]: https://github.com/warnyin/warnyin-agents/compare/v0.9.0...HEAD
92
+ [0.9.0]: https://github.com/warnyin/warnyin-agents/compare/v0.8.5...v0.9.0
93
+ [0.8.5]: https://github.com/warnyin/warnyin-agents/compare/v0.8.4...v0.8.5
94
+ [0.8.4]: https://github.com/warnyin/warnyin-agents/compare/v0.8.3...v0.8.4
95
+ [0.8.3]: https://github.com/warnyin/warnyin-agents/compare/v0.8.2...v0.8.3
96
+ [0.8.2]: https://github.com/warnyin/warnyin-agents/compare/v0.8.1...v0.8.2
97
+ [0.8.1]: https://github.com/warnyin/warnyin-agents/compare/v0.8.0...v0.8.1
98
+ [0.8.0]: https://github.com/warnyin/warnyin-agents/compare/v0.7.0...v0.8.0
86
99
  [0.7.0]: https://github.com/warnyin/warnyin-agents/compare/v0.6.0...v0.7.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warnyin/agents",
3
- "version": "0.8.5",
3
+ "version": "0.9.0",
4
4
  "description": "Warnyin Standard Workflow installer — 5-stage ways of work (Discovery/DESIGN/BUILD/VERIFY/SHIP) สำหรับทุกโปรเจกต์",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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`** — บริบทงานที่จดไว้ (ถ้ามี)
@@ -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
+ }
@@ -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. โค้ดจริงที่เกี่ยวข้อง (inspect เพื่อตอบคำถามแทนการเดา)
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
- - [ ] ทุก task มี `spec.md` + `standard.md` + `rule.md` + `task.md` ครบ
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` สรุปผลผ่าน) — พร้อมกันนั้น **รวบรวม learned-rule candidate** เป็นตาราง (ทุกตัว = `rule + evidence + scope + promote?`):
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/techstack/<component>/test.md`**guideline ว่าเทสยังไง (เช่น frontend: e2e smoke ผ่าน **playwright-cli**)
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
- 3. **`docs/infra.md`** — local env / service ที่ต้องรันเพื่อเทส
23
- 4. **`docs/troubleshooting.md`** — เผื่อปัญหาที่จะเจอเคยถูกแก้แล้ว
24
- 5. **runtime security** (`.warnyin/workflow/roles/security.md` → "Runtime / operational security") — ตอนรันเทส local env ที่มี secret จริง ทบทวน secret isolation / no-egress / identity separation ก่อนปล่อย agent แตะของจริง
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. **เข้าใจจุดประสงค์:** อ่าน spec/tasks/design → สรุปสิ่งที่ต้อง verify (functional + UX/UI)
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` (สรุป + จำนวนการแก้ไข) เขียนครบ