@panuwatbas/spec-driven-dev 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +378 -0
- package/bin/cli.js +289 -0
- package/package.json +39 -0
- package/skill/README.md +346 -0
- package/skill/SKILL.md +195 -0
- package/skill/examples/example-design.md +287 -0
- package/skill/examples/example-requirements.md +151 -0
- package/skill/examples/example-tasks.md +189 -0
- package/skill/scripts/init-spec.bat +70 -0
- package/skill/scripts/init-spec.sh +159 -0
- package/skill/templates/design.template.md +248 -0
- package/skill/templates/requirements.template.md +116 -0
- package/skill/templates/tasks.template.md +127 -0
package/README.md
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# Spec-Driven Development Skill
|
|
2
|
+
|
|
3
|
+
> **Plan first, then build.** สร้าง Requirements → Design → Tasks ก่อน แล้วค่อย Code
|
|
4
|
+
|
|
5
|
+
Skill เป็น workflow ที่เน้นการวางแผนอย่างเป็นระบบ
|
|
6
|
+
ก่อนเขียน code ทุกครั้ง ลดความผิดพลาด ลดการทำซ้ำ และสร้าง documentation
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Structured Planning** — 3 เฟสชัดเจน: Requirements → Design → Tasks
|
|
13
|
+
- **GIVEN/WHEN/THEN** — Acceptance criteria แบบ Gherkin ที่ทดสอบได้
|
|
14
|
+
- **Mermaid Diagrams** — Architecture และ Sequence diagrams อัตโนมัติ
|
|
15
|
+
- **Traceability** — ทุก task อ้างอิงกลับไปหา requirement
|
|
16
|
+
- **Progress Tracking** — `[ ]` `[/]` `[x]` สำหรับ track progress
|
|
17
|
+
- **Human-in-the-Loop** — ต้อง approve ก่อนไปเฟสถัดไป
|
|
18
|
+
- **3 Workflow Variants** — Requirements-First, Design-First, Bugfix Mode
|
|
19
|
+
- **Agent-Compatible** — ใช้ได้กับ AI agent ทุกตัว
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## โครงสร้าง Skill
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
spec-driven-dev/
|
|
27
|
+
├── SKILL.md # คำสั่งหลัก (AI Agent อ่านไฟล์นี้)
|
|
28
|
+
├── README.md # คู่มือนี้
|
|
29
|
+
├── templates/
|
|
30
|
+
│ ├── requirements.template.md # เทมเพลต: กำหนดว่า "ทำอะไร"
|
|
31
|
+
│ ├── design.template.md # เทมเพลต: กำหนดว่า "ทำยังไง"
|
|
32
|
+
│ └── tasks.template.md # เทมเพลต: แผนการทำงาน
|
|
33
|
+
├── examples/
|
|
34
|
+
│ ├── example-requirements.md # ตัวอย่าง: Shopping Cart requirements
|
|
35
|
+
│ ├── example-design.md # ตัวอย่าง: Shopping Cart design
|
|
36
|
+
│ └── example-tasks.md # ตัวอย่าง: Shopping Cart tasks
|
|
37
|
+
└── scripts/
|
|
38
|
+
├── init-spec.sh # Script สร้าง spec ใหม่ (Linux/Mac)
|
|
39
|
+
└── init-spec.bat # Script สร้าง spec ใหม่ (Windows)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## การติดตั้ง
|
|
45
|
+
|
|
46
|
+
### วิธีที่ 1: ติดตั้งผ่าน npx (แนะนำ)
|
|
47
|
+
|
|
48
|
+
รันคำสั่งเดียวที่ root ของ project:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx spec-driven-dev
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
ระบบจะถามว่าต้องการติดตั้งลง IDE ตัวไหน แล้วคัดลอกไฟล์ให้อัตโนมัติ
|
|
55
|
+
|
|
56
|
+
ถ้าต้องการระบุ IDE เลย ใช้ `--target`:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# ติดตั้งลง Gemini CLI / Antigravity
|
|
60
|
+
npx spec-driven-dev --target gemini
|
|
61
|
+
|
|
62
|
+
# ติดตั้งลง Cursor
|
|
63
|
+
npx spec-driven-dev --target cursor
|
|
64
|
+
|
|
65
|
+
# ติดตั้งลง Windsurf
|
|
66
|
+
npx spec-driven-dev --target windsurf
|
|
67
|
+
|
|
68
|
+
# ติดตั้งลง GitHub Copilot
|
|
69
|
+
npx spec-driven-dev --target copilot
|
|
70
|
+
|
|
71
|
+
# ติดตั้งลง path ที่กำหนดเอง
|
|
72
|
+
npx spec-driven-dev --target ./my-custom-path
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
สร้าง spec ใหม่สำหรับ feature:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx spec-driven-dev --init user-authentication
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
### วิธีที่ 2: คัดลอกด้วยตัวเอง
|
|
84
|
+
|
|
85
|
+
คัดลอกโฟลเดอร์ `spec-driven-dev` ไปวางในโฟลเดอร์ skills ของเครื่องมือที่ใช้:
|
|
86
|
+
|
|
87
|
+
```powershell
|
|
88
|
+
# Gemini CLI / Antigravity
|
|
89
|
+
Copy-Item -Recurse "spec-driven-dev" ".gemini\skills\spec-driven-dev"
|
|
90
|
+
|
|
91
|
+
# Cursor
|
|
92
|
+
Copy-Item -Recurse "spec-driven-dev" ".cursor\skills\spec-driven-dev"
|
|
93
|
+
|
|
94
|
+
# Windsurf
|
|
95
|
+
Copy-Item -Recurse "spec-driven-dev" ".windsurf\skills\spec-driven-dev"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### เครื่องมืออื่นๆ
|
|
99
|
+
|
|
100
|
+
ชี้ให้ AI agent อ่านไฟล์ `SKILL.md` โดยตรง หรือ copy เนื้อหาเข้า system prompt
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## วิธีใช้งาน
|
|
105
|
+
|
|
106
|
+
### วิธีที่ 1: บอก AI Agent โดยตรง
|
|
107
|
+
|
|
108
|
+
พิมพ์ประโยคแบบนี้ใน chat:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
สร้าง spec สำหรับ feature ระบบ login
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
ช่วย spec feature ตะกร้าสินค้า ตาม spec-driven-dev skill
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
Start a spec for user-authentication using the spec-driven-dev skill
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Agent จะเริ่มทำตาม workflow 3 เฟสอัตโนมัติ
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### วิธีที่ 2: ใช้ Script สร้างโครงสร้างก่อน
|
|
127
|
+
|
|
128
|
+
```powershell
|
|
129
|
+
# Windows
|
|
130
|
+
.\scripts\init-spec.bat user-authentication
|
|
131
|
+
|
|
132
|
+
# Linux / Mac
|
|
133
|
+
bash scripts/init-spec.sh user-authentication
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
จะได้โครงสร้าง:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
.specs/user-authentication/
|
|
140
|
+
├── requirements.md <- เทมเพลตพร้อมแก้ไข
|
|
141
|
+
├── design.md <- เทมเพลตพร้อมแก้ไข
|
|
142
|
+
└── tasks.md <- เทมเพลตพร้อมแก้ไข
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
แล้วบอก agent: `"ช่วยเติม spec ใน .specs/user-authentication/"`
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Workflow 3 เฟส
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
153
|
+
│ Phase 1 │ │ Phase 2 │ │ Phase 3 │
|
|
154
|
+
│ REQUIREMENTS │ --> │ DESIGN │ --> │ TASKS │
|
|
155
|
+
│ │ │ │ │ │
|
|
156
|
+
│ กำหนดว่า │ │ กำหนดว่า │ │ แผนการทำงาน │
|
|
157
|
+
│ "ทำอะไร" │ │ "ทำยังไง" │ │ ทีละขั้นตอน │
|
|
158
|
+
│ │ │ │ │ │
|
|
159
|
+
│ - User Stories │ │ - Architecture │ │ - Task List │
|
|
160
|
+
│ - GIVEN/WHEN/ │ │ - Components │ │ - Phases │
|
|
161
|
+
│ THEN │ │ - Data Models │ │ - Verification │
|
|
162
|
+
│ - Edge Cases │ │ - API Contracts│ │ - Status [x] │
|
|
163
|
+
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
|
|
164
|
+
│ │ │
|
|
165
|
+
คุณ approve คุณ approve คุณ approve
|
|
166
|
+
│
|
|
167
|
+
เริ่ม code!
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### เฟส 1 — Requirements (`requirements.md`)
|
|
171
|
+
|
|
172
|
+
กำหนด **"ทำอะไร"** จากมุมมองผู้ใช้
|
|
173
|
+
|
|
174
|
+
```markdown
|
|
175
|
+
# Feature: ระบบ Login
|
|
176
|
+
|
|
177
|
+
## User Story
|
|
178
|
+
**As a** ผู้ใช้ทั่วไป,
|
|
179
|
+
**I want to** เข้าสู่ระบบด้วย email และ password,
|
|
180
|
+
**so that** ฉันสามารถเข้าถึงข้อมูลส่วนตัวของฉันได้.
|
|
181
|
+
|
|
182
|
+
## Acceptance Criteria
|
|
183
|
+
|
|
184
|
+
### AC-1.1: เข้าสู่ระบบสำเร็จ
|
|
185
|
+
**Given** ผู้ใช้อยู่ที่หน้า login
|
|
186
|
+
**And** มีบัญชีอยู่แล้วในระบบ
|
|
187
|
+
**When** กรอก email และ password ที่ถูกต้อง แล้วกดปุ่ม "Login"
|
|
188
|
+
**Then** ระบบพาไปหน้า dashboard
|
|
189
|
+
**And** แสดงชื่อผู้ใช้ที่มุมบนขวา
|
|
190
|
+
|
|
191
|
+
### AC-1.2: กรอก password ผิด
|
|
192
|
+
**Given** ผู้ใช้อยู่ที่หน้า login
|
|
193
|
+
**When** กรอก email ถูกต้อง แต่ password ผิด
|
|
194
|
+
**Then** แสดงข้อความ "อีเมลหรือรหัสผ่านไม่ถูกต้อง"
|
|
195
|
+
**And** ไม่บอกว่าผิดอันไหน (เพื่อความปลอดภัย)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**คุณรีวิว -> แก้ไข -> approve** -> ไปเฟส 2
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### เฟส 2 — Design (`design.md`)
|
|
203
|
+
|
|
204
|
+
กำหนด **"ทำยังไง"** ในเชิงเทคนิค
|
|
205
|
+
|
|
206
|
+
```markdown
|
|
207
|
+
## Architecture
|
|
208
|
+
┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
209
|
+
│ React │ --> │ Express │ --> │ Postgres │
|
|
210
|
+
│ Frontend│ │ API │ │ Database │
|
|
211
|
+
└──────────┘ └──────────┘ └──────────┘
|
|
212
|
+
|
|
213
|
+
## Component: AuthService
|
|
214
|
+
- Responsibility: จัดการ login, logout, token refresh
|
|
215
|
+
- Location: src/server/services/AuthService.ts
|
|
216
|
+
|
|
217
|
+
## API Contract
|
|
218
|
+
POST /api/auth/login
|
|
219
|
+
Request: { "email": "string", "password": "string" }
|
|
220
|
+
Response: { "token": "jwt", "user": { "id", "name" } }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**คุณรีวิว -> แก้ไข -> approve** -> ไปเฟส 3
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
### เฟส 3 — Tasks (`tasks.md`)
|
|
228
|
+
|
|
229
|
+
แผนการทำงานเป็น **checklist**
|
|
230
|
+
|
|
231
|
+
```markdown
|
|
232
|
+
## Phase 1: Database
|
|
233
|
+
- [x] Task 1.1: สร้าง users table migration
|
|
234
|
+
- [x] Task 1.2: สร้าง User model
|
|
235
|
+
|
|
236
|
+
## Phase 2: Backend API
|
|
237
|
+
- [/] Task 2.1: สร้าง POST /api/auth/login <- กำลังทำ
|
|
238
|
+
- [ ] Task 2.2: สร้าง JWT middleware
|
|
239
|
+
- [ ] Task 2.3: สร้าง rate limiting
|
|
240
|
+
|
|
241
|
+
## Phase 3: Frontend
|
|
242
|
+
- [ ] Task 3.1: สร้าง LoginForm component
|
|
243
|
+
- [ ] Task 3.2: สร้าง auth context/store
|
|
244
|
+
- [ ] Task 3.3: สร้าง protected route
|
|
245
|
+
|
|
246
|
+
## Phase 4: Testing
|
|
247
|
+
- [ ] Task 4.1: Unit tests สำหรับ AuthService
|
|
248
|
+
- [ ] Task 4.2: E2E test login flow
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**คุณ approve -> Agent เริ่ม code ทีละ task**
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Workflow Variants
|
|
256
|
+
|
|
257
|
+
### 1. Requirements-First (ค่าเริ่มต้น)
|
|
258
|
+
|
|
259
|
+
เหมาะกับ **feature ใหม่** ที่เน้น business logic
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
requirements.md -> design.md -> tasks.md
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**ใช้เมื่อ:** สร้างฟีเจอร์ใหม่ตั้งแต่ต้น
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
### 2. Design-First
|
|
270
|
+
|
|
271
|
+
เหมาะกับ **ระบบซับซ้อน** ที่มีข้อจำกัดทางเทคนิค
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
design.md -> requirements.md (derived) -> tasks.md
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**ใช้เมื่อ:** มี architecture constraints, performance requirements, หรือต้อง integrate กับระบบเดิม
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
### 3. Bugfix Mode
|
|
282
|
+
|
|
283
|
+
เหมาะกับ **bug ที่ซับซ้อน** ต้องวิเคราะห์ root cause
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
bugfix.md -> design.md (root cause + fix) -> tasks.md
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**ใช้เมื่อ:** Bug ที่ reproduce ยาก หรือเกี่ยวข้องหลาย component
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## ตัวอย่างคำสั่งสำหรับ AI Agent
|
|
294
|
+
|
|
295
|
+
| สถานการณ์ | พูดกับ Agent ว่า |
|
|
296
|
+
|----------|-----------------|
|
|
297
|
+
| เริ่ม feature ใหม่ | `"สร้าง spec สำหรับ feature [ชื่อ]"` |
|
|
298
|
+
| Fix bug ซับซ้อน | `"สร้าง bugfix spec สำหรับ [ปัญหา]"` |
|
|
299
|
+
| มี design อยู่แล้ว | `"เริ่มจาก design-first สำหรับ [ชื่อ]"` |
|
|
300
|
+
| Approve เฟส | `"approve แล้ว ไปเฟสถัดไป"` |
|
|
301
|
+
| เริ่ม code | `"approve tasks แล้ว เริ่ม execute ได้เลย"` |
|
|
302
|
+
| แก้ requirement | `"แก้ AC-1.2 เป็น... แล้ว sync ลง tasks"` |
|
|
303
|
+
| ดูตัวอย่าง | `"ดูตัวอย่าง requirements จาก examples"` |
|
|
304
|
+
| สร้างจากเทมเพลต | `"สร้าง requirements จาก template"` |
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Output ที่ได้
|
|
309
|
+
|
|
310
|
+
เมื่อใช้ skill นี้กับ project จะได้โครงสร้าง:
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
your-project/
|
|
314
|
+
├── .specs/
|
|
315
|
+
│ ├── user-authentication/
|
|
316
|
+
│ │ ├── requirements.md <- Approved
|
|
317
|
+
│ │ ├── design.md <- Approved
|
|
318
|
+
│ │ └── tasks.md <- In Progress (12/15 tasks done)
|
|
319
|
+
│ │
|
|
320
|
+
│ ├── shopping-cart/
|
|
321
|
+
│ │ ├── requirements.md <- Approved
|
|
322
|
+
│ │ ├── design.md <- Draft
|
|
323
|
+
│ │ └── tasks.md <- Not Started
|
|
324
|
+
│ │
|
|
325
|
+
│ └── payment-integration/
|
|
326
|
+
│ └── requirements.md <- Draft
|
|
327
|
+
│
|
|
328
|
+
├── src/
|
|
329
|
+
│ └── ... <- code ที่สร้างจาก tasks
|
|
330
|
+
└── ...
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## หลักการสำคัญ
|
|
336
|
+
|
|
337
|
+
| หลักการ | คำอธิบาย |
|
|
338
|
+
|---------|---------|
|
|
339
|
+
| **Plan First** | วางแผนก่อน code เสมอ |
|
|
340
|
+
| **Traceability** | ทุก task อ้างอิงกลับไป requirement |
|
|
341
|
+
| **Human-in-the-Loop** | คนต้อง approve ก่อนทุกเฟส |
|
|
342
|
+
| **Small Tasks** | แต่ละ task เล็กพอทำจบได้ในรอบเดียว |
|
|
343
|
+
| **Living Documentation** | Spec อัปเดตตาม code ที่เปลี่ยน |
|
|
344
|
+
| **Verify Often** | ทุกเฟสมี verification step |
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## ไฟล์ Reference
|
|
349
|
+
|
|
350
|
+
| ไฟล์ | คำอธิบาย |
|
|
351
|
+
|------|---------|
|
|
352
|
+
| [`SKILL.md`](./SKILL.md) | คำสั่งหลักที่ AI Agent อ่าน |
|
|
353
|
+
| [`templates/requirements.template.md`](./templates/requirements.template.md) | เทมเพลต requirements |
|
|
354
|
+
| [`templates/design.template.md`](./templates/design.template.md) | เทมเพลต design |
|
|
355
|
+
| [`templates/tasks.template.md`](./templates/tasks.template.md) | เทมเพลต tasks |
|
|
356
|
+
| [`examples/example-requirements.md`](./examples/example-requirements.md) | ตัวอย่าง requirements (Shopping Cart) |
|
|
357
|
+
| [`examples/example-design.md`](./examples/example-design.md) | ตัวอย่าง design (Shopping Cart) |
|
|
358
|
+
| [`examples/example-tasks.md`](./examples/example-tasks.md) | ตัวอย่าง tasks (Shopping Cart) |
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Compatibility
|
|
363
|
+
|
|
364
|
+
| เครื่องมือ | รองรับ | วิธีติดตั้ง |
|
|
365
|
+
|-----------|-------|-----------|
|
|
366
|
+
| Google Antigravity / Gemini CLI | Yes | `.gemini/skills/` |
|
|
367
|
+
| Cursor | Yes | `.cursor/skills/` |
|
|
368
|
+
| Windsurf | Yes | `.windsurf/skills/` |
|
|
369
|
+
| Claude (Anthropic) | Yes | ใส่ SKILL.md ใน system prompt |
|
|
370
|
+
| ChatGPT / GPT-4 | Yes | ใส่ SKILL.md ใน system prompt |
|
|
371
|
+
| GitHub Copilot | Yes | `.github/copilot/skills/` |
|
|
372
|
+
| อื่นๆ | Yes | อ้างอิง SKILL.md โดยตรง |
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
<p align="center">
|
|
377
|
+
<b>Spec-Driven Development</b> — Stop vibe coding. Start spec coding.
|
|
378
|
+
</p>
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const readline = require("readline");
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Config
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
const SKILL_SOURCE = path.join(__dirname, "..", "skill");
|
|
12
|
+
|
|
13
|
+
const TARGETS = {
|
|
14
|
+
gemini: ".gemini/skills/spec-driven-dev",
|
|
15
|
+
cursor: ".cursor/skills/spec-driven-dev",
|
|
16
|
+
windsurf: ".windsurf/skills/spec-driven-dev",
|
|
17
|
+
copilot: ".github/copilot/skills/spec-driven-dev",
|
|
18
|
+
custom: null, // user picks path
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const SPEC_INIT_TARGET = ".specs";
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
function log(msg) { console.log(msg); }
|
|
28
|
+
function warn(msg) { console.log("[WARN] " + msg); }
|
|
29
|
+
function err(msg) { console.error("[ERROR] " + msg); process.exit(1); }
|
|
30
|
+
|
|
31
|
+
function copyDirSync(src, dest) {
|
|
32
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
33
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
const srcPath = path.join(src, entry.name);
|
|
36
|
+
const destPath = path.join(dest, entry.name);
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
copyDirSync(srcPath, destPath);
|
|
39
|
+
} else {
|
|
40
|
+
fs.copyFileSync(srcPath, destPath);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function detectIDEs(projectDir) {
|
|
46
|
+
const found = [];
|
|
47
|
+
if (fs.existsSync(path.join(projectDir, ".gemini"))) found.push("gemini");
|
|
48
|
+
if (fs.existsSync(path.join(projectDir, ".cursor"))) found.push("cursor");
|
|
49
|
+
if (fs.existsSync(path.join(projectDir, ".windsurf"))) found.push("windsurf");
|
|
50
|
+
if (fs.existsSync(path.join(projectDir, ".github", "copilot"))) found.push("copilot");
|
|
51
|
+
return found;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function prompt(question) {
|
|
55
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
rl.question(question, (answer) => {
|
|
58
|
+
rl.close();
|
|
59
|
+
resolve(answer.trim());
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function printHelp() {
|
|
65
|
+
log("");
|
|
66
|
+
log(" spec-driven-dev - Install Spec-Driven Development skill");
|
|
67
|
+
log("");
|
|
68
|
+
log(" Usage:");
|
|
69
|
+
log(" npx spec-driven-dev Interactive install");
|
|
70
|
+
log(" npx spec-driven-dev --target gemini Install to .gemini/skills/");
|
|
71
|
+
log(" npx spec-driven-dev --target cursor Install to .cursor/skills/");
|
|
72
|
+
log(" npx spec-driven-dev --target windsurf Install to .windsurf/skills/");
|
|
73
|
+
log(" npx spec-driven-dev --target copilot Install to .github/copilot/skills/");
|
|
74
|
+
log(" npx spec-driven-dev --target <path> Install to custom path");
|
|
75
|
+
log(" npx spec-driven-dev --init <name> Scaffold a new spec");
|
|
76
|
+
log(" npx spec-driven-dev --help Show this help");
|
|
77
|
+
log("");
|
|
78
|
+
log(" Examples:");
|
|
79
|
+
log(" npx spec-driven-dev");
|
|
80
|
+
log(" npx spec-driven-dev --target gemini");
|
|
81
|
+
log(" npx spec-driven-dev --init user-authentication");
|
|
82
|
+
log("");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Commands
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
function installSkill(targetPath, projectDir) {
|
|
90
|
+
const fullPath = path.resolve(projectDir, targetPath);
|
|
91
|
+
|
|
92
|
+
if (fs.existsSync(fullPath)) {
|
|
93
|
+
warn("Directory already exists: " + fullPath);
|
|
94
|
+
warn("Overwriting...");
|
|
95
|
+
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
copyDirSync(SKILL_SOURCE, fullPath);
|
|
99
|
+
log("");
|
|
100
|
+
log(" Installed to: " + fullPath);
|
|
101
|
+
log("");
|
|
102
|
+
log(" Files:");
|
|
103
|
+
|
|
104
|
+
const listFiles = (dir, prefix) => {
|
|
105
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
const rel = prefix + entry.name;
|
|
108
|
+
if (entry.isDirectory()) {
|
|
109
|
+
log(" " + rel + "/");
|
|
110
|
+
listFiles(path.join(dir, entry.name), rel + "/");
|
|
111
|
+
} else {
|
|
112
|
+
log(" " + rel);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
listFiles(fullPath, "");
|
|
117
|
+
|
|
118
|
+
log("");
|
|
119
|
+
log(" Done! Your AI agent can now use the spec-driven-dev skill.");
|
|
120
|
+
log("");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function initSpec(featureName, projectDir) {
|
|
124
|
+
const specDir = path.join(projectDir, SPEC_INIT_TARGET, featureName);
|
|
125
|
+
|
|
126
|
+
if (fs.existsSync(specDir)) {
|
|
127
|
+
err("Spec directory already exists: " + specDir);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fs.mkdirSync(specDir, { recursive: true });
|
|
131
|
+
|
|
132
|
+
// Try to find templates from installed skill
|
|
133
|
+
let templateDir = null;
|
|
134
|
+
const searchPaths = [
|
|
135
|
+
path.join(projectDir, ".gemini/skills/spec-driven-dev/templates"),
|
|
136
|
+
path.join(projectDir, ".cursor/skills/spec-driven-dev/templates"),
|
|
137
|
+
path.join(projectDir, ".windsurf/skills/spec-driven-dev/templates"),
|
|
138
|
+
path.join(projectDir, ".github/copilot/skills/spec-driven-dev/templates"),
|
|
139
|
+
path.join(SKILL_SOURCE, "templates"),
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
for (const p of searchPaths) {
|
|
143
|
+
if (fs.existsSync(p)) {
|
|
144
|
+
templateDir = p;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const date = new Date().toISOString().split("T")[0];
|
|
150
|
+
const title = featureName
|
|
151
|
+
.replace(/[-_]/g, " ")
|
|
152
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
153
|
+
|
|
154
|
+
if (templateDir) {
|
|
155
|
+
const files = {
|
|
156
|
+
"requirements.template.md": "requirements.md",
|
|
157
|
+
"design.template.md": "design.md",
|
|
158
|
+
"tasks.template.md": "tasks.md",
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
for (const [src, dest] of Object.entries(files)) {
|
|
162
|
+
const srcPath = path.join(templateDir, src);
|
|
163
|
+
if (fs.existsSync(srcPath)) {
|
|
164
|
+
let content = fs.readFileSync(srcPath, "utf-8");
|
|
165
|
+
content = content.replace(/\[Feature Name\]/g, title);
|
|
166
|
+
content = content.replace(/\[Date\]/g, date);
|
|
167
|
+
fs.writeFileSync(path.join(specDir, dest), content, "utf-8");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
log(" Copied templates from: " + templateDir);
|
|
171
|
+
} else {
|
|
172
|
+
// Create minimal files
|
|
173
|
+
fs.writeFileSync(
|
|
174
|
+
path.join(specDir, "requirements.md"),
|
|
175
|
+
"# Feature: " + title + "\n\n> **Status:** Draft\n\n## Overview\n\n[Describe the feature here.]\n\n## User Stories\n\n### US-1: [Story Title]\n\n**As a** [persona],\n**I want to** [action],\n**so that** [benefit].\n\n#### Acceptance Criteria\n\n##### AC-1.1: [Scenario]\n\n**Given** [precondition]\n**When** [action]\n**Then** [outcome]\n",
|
|
176
|
+
"utf-8"
|
|
177
|
+
);
|
|
178
|
+
fs.writeFileSync(
|
|
179
|
+
path.join(specDir, "design.md"),
|
|
180
|
+
"# Technical Design: " + title + "\n\n> **Status:** Draft\n\n## Overview\n\n[Describe the technical approach.]\n\n## Architecture\n\n[Define the architecture.]\n\n## Component Design\n\n[Define the components.]\n",
|
|
181
|
+
"utf-8"
|
|
182
|
+
);
|
|
183
|
+
fs.writeFileSync(
|
|
184
|
+
path.join(specDir, "tasks.md"),
|
|
185
|
+
"# Implementation Tasks: " + title + "\n\n> **Status:** Not Started\n\n## Phase 1: [Foundation]\n\n- [ ] **Task 1.1:** [Description]\n- [ ] **Task 1.2 (Verify):** Verification\n\n## Phase 2: [Core Logic]\n\n- [ ] **Task 2.1:** [Description]\n- [ ] **Task 2.2 (Verify):** Verification\n",
|
|
186
|
+
"utf-8"
|
|
187
|
+
);
|
|
188
|
+
log(" Created minimal spec files (templates not found in project).");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
log("");
|
|
192
|
+
log(" Spec initialized: " + specDir);
|
|
193
|
+
log("");
|
|
194
|
+
log(" " + featureName + "/requirements.md <- Define WHAT to build");
|
|
195
|
+
log(" " + featureName + "/design.md <- Define HOW to build it");
|
|
196
|
+
log(" " + featureName + "/tasks.md <- Execution plan");
|
|
197
|
+
log("");
|
|
198
|
+
log(" Next: Ask your AI agent to fill in the spec, or edit manually.");
|
|
199
|
+
log("");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Interactive mode
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
async function interactive(projectDir) {
|
|
207
|
+
log("");
|
|
208
|
+
log(" Spec-Driven Development Skill Installer");
|
|
209
|
+
log(" ----------------------------------------");
|
|
210
|
+
log("");
|
|
211
|
+
|
|
212
|
+
// Detect existing IDEs
|
|
213
|
+
const detected = detectIDEs(projectDir);
|
|
214
|
+
|
|
215
|
+
log(" Where do you want to install the skill?");
|
|
216
|
+
log("");
|
|
217
|
+
log(" 1) Gemini CLI / Antigravity (.gemini/skills/)" + (detected.includes("gemini") ? " [detected]" : ""));
|
|
218
|
+
log(" 2) Cursor (.cursor/skills/)" + (detected.includes("cursor") ? " [detected]" : ""));
|
|
219
|
+
log(" 3) Windsurf (.windsurf/skills/)" + (detected.includes("windsurf") ? " [detected]" : ""));
|
|
220
|
+
log(" 4) GitHub Copilot (.github/copilot/skills/)" + (detected.includes("copilot") ? " [detected]" : ""));
|
|
221
|
+
log(" 5) Custom path");
|
|
222
|
+
log("");
|
|
223
|
+
|
|
224
|
+
const defaultChoice = detected.length > 0
|
|
225
|
+
? String(["gemini", "cursor", "windsurf", "copilot"].indexOf(detected[0]) + 1)
|
|
226
|
+
: "1";
|
|
227
|
+
|
|
228
|
+
const choice = await prompt(" Choose [" + defaultChoice + "]: ") || defaultChoice;
|
|
229
|
+
|
|
230
|
+
let targetPath;
|
|
231
|
+
|
|
232
|
+
switch (choice) {
|
|
233
|
+
case "1": targetPath = TARGETS.gemini; break;
|
|
234
|
+
case "2": targetPath = TARGETS.cursor; break;
|
|
235
|
+
case "3": targetPath = TARGETS.windsurf; break;
|
|
236
|
+
case "4": targetPath = TARGETS.copilot; break;
|
|
237
|
+
case "5":
|
|
238
|
+
targetPath = await prompt(" Enter custom path: ");
|
|
239
|
+
if (!targetPath) err("Path is required.");
|
|
240
|
+
break;
|
|
241
|
+
default:
|
|
242
|
+
err("Invalid choice: " + choice);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
installSkill(targetPath, projectDir);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// Main
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
async function main() {
|
|
253
|
+
const args = process.argv.slice(2);
|
|
254
|
+
const projectDir = process.cwd();
|
|
255
|
+
|
|
256
|
+
// --help
|
|
257
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
258
|
+
printHelp();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// --init <name>
|
|
263
|
+
const initIdx = args.indexOf("--init");
|
|
264
|
+
if (initIdx !== -1) {
|
|
265
|
+
const name = args[initIdx + 1];
|
|
266
|
+
if (!name) err("Feature name is required. Usage: npx spec-driven-dev --init <feature-name>");
|
|
267
|
+
initSpec(name, projectDir);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// --target <target>
|
|
272
|
+
const targetIdx = args.indexOf("--target");
|
|
273
|
+
if (targetIdx !== -1) {
|
|
274
|
+
const target = args[targetIdx + 1];
|
|
275
|
+
if (!target) err("Target is required. Usage: npx spec-driven-dev --target <gemini|cursor|windsurf|copilot|path>");
|
|
276
|
+
|
|
277
|
+
const targetPath = TARGETS[target] || target;
|
|
278
|
+
installSkill(targetPath, projectDir);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// No args -> interactive
|
|
283
|
+
await interactive(projectDir);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
main().catch((e) => {
|
|
287
|
+
console.error(e);
|
|
288
|
+
process.exit(1);
|
|
289
|
+
});
|