sdd-pipeline 1.0.0 → 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 +33 -26
- package/lib/bmad.js +60 -0
- package/lib/bundle/.claude/commands/sdd.md +261 -0
- package/lib/config.js +35 -0
- package/lib/init.js +126 -0
- package/lib/sanitize.js +10 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -5,57 +5,64 @@ Spec-Driven Development pipeline that transforms raw feature requests into valid
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
7
|
```powershell
|
|
8
|
-
#
|
|
9
|
-
|
|
8
|
+
# Install globally (once)
|
|
9
|
+
npm install -g sdd-pipeline
|
|
10
10
|
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
#
|
|
15
|
-
|
|
11
|
+
# In any new project directory:
|
|
12
|
+
sdd init # Initialize pipeline
|
|
13
|
+
sdd bmad "todo app" # Generate BMAD brief
|
|
14
|
+
sdd status # Check pipeline phases
|
|
15
|
+
```
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
claude "/sdd-tasks"
|
|
17
|
+
## Unified Command
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
Use `/sdd` in Claude Code for the full pipeline:
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
```powershell
|
|
22
|
+
claude "/sdd init"
|
|
23
|
+
claude "/sdd bmad landing page for my SaaS"
|
|
24
|
+
claude "/sdd spec"
|
|
25
|
+
claude "/sdd tasks"
|
|
26
|
+
claude "/sdd converge"
|
|
27
|
+
claude "/sdd status"
|
|
24
28
|
```
|
|
25
29
|
|
|
26
30
|
## Pipeline Phases
|
|
27
31
|
|
|
28
32
|
| Phase | Command | Output |
|
|
29
33
|
|-------|---------|--------|
|
|
30
|
-
| 0: BMAD | `/sdd
|
|
31
|
-
| 2: Spec | `/sdd
|
|
32
|
-
| 3: Tasks | `/sdd
|
|
33
|
-
| 5: Converge | `/sdd
|
|
34
|
+
| 0: BMAD | `/sdd bmad` | BMAD-brief.md |
|
|
35
|
+
| 2: Spec | `/sdd spec` | SPEC.md |
|
|
36
|
+
| 3: Tasks | `/sdd tasks` | TASKS.md |
|
|
37
|
+
| 5: Converge | `/sdd converge` | converge/validation.md |
|
|
38
|
+
|
|
39
|
+
Individual commands also available: `/sdd-bmad`, `/sdd-spec`, `/sdd-tasks`, `/sdd-converge`.
|
|
34
40
|
|
|
35
41
|
## Key Features
|
|
36
42
|
|
|
37
|
-
- **Phase gates:** `/sdd
|
|
38
|
-
- **Prompt injection protection:** All user input sanitized
|
|
39
|
-
- **SSRF protection:** URL validation blocks private IPs, localhost
|
|
40
|
-
- **Dev server health check:** 60s timeout
|
|
43
|
+
- **Phase gates:** `/sdd spec` fails if BMAD-brief.md missing — no bypass
|
|
44
|
+
- **Prompt injection protection:** All user input sanitized
|
|
45
|
+
- **SSRF protection:** URL validation blocks private IPs, localhost
|
|
46
|
+
- **Dev server health check:** 60s timeout before Lighthouse runs
|
|
41
47
|
- **GitHub Actions CI:** Runs converge on push/PR, creates Issue on failure
|
|
42
48
|
- **Domain extensions:** API, frontend, backend — set via `.sdd/config.json`
|
|
43
49
|
|
|
44
50
|
## Status
|
|
45
51
|
|
|
46
52
|
```powershell
|
|
47
|
-
|
|
48
|
-
pwsh commands/sdd-status.ps1 --Verify
|
|
53
|
+
sdd status # Quick status (npm package)
|
|
54
|
+
pwsh commands/sdd-status.ps1 --Verify # Full verification
|
|
49
55
|
```
|
|
50
56
|
|
|
51
57
|
## Architecture
|
|
52
58
|
|
|
53
59
|
```
|
|
54
|
-
Raw Idea -> /sdd
|
|
55
|
-
-> /sdd
|
|
56
|
-
-> /sdd
|
|
60
|
+
Raw Idea -> /sdd bmad -> BMAD-brief.md
|
|
61
|
+
-> /sdd spec -> SPEC.md
|
|
62
|
+
-> /sdd tasks -> TASKS.md
|
|
57
63
|
-> Implement
|
|
58
|
-
->
|
|
64
|
+
-> /sdd converge
|
|
65
|
+
-> pwsh run-converge.ps1 -> converge/validation.md
|
|
59
66
|
|
|
|
60
67
|
+-- PASS: merge
|
|
61
68
|
+-- FAIL: fix -> re-enter
|
package/lib/bmad.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const sanitize = require('./sanitize')
|
|
4
|
+
|
|
5
|
+
function run(description) {
|
|
6
|
+
if (!description) {
|
|
7
|
+
console.error('Usage: sdd bmad <description>')
|
|
8
|
+
console.error('Example: sdd bmad "landing page for my SaaS product"')
|
|
9
|
+
process.exit(1)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const sanitized = sanitize.run(description)
|
|
13
|
+
const date = new Date().toISOString().split('T')[0]
|
|
14
|
+
const expiry = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]
|
|
15
|
+
|
|
16
|
+
const content = `# BMAD Brief — [Project Name]
|
|
17
|
+
|
|
18
|
+
> Generated: ${date}. Expires: ${expiry}.
|
|
19
|
+
|
|
20
|
+
## Input
|
|
21
|
+
> ${sanitized}
|
|
22
|
+
|
|
23
|
+
## PM Perspective
|
|
24
|
+
**Target user:** [Who benefits most from this feature]
|
|
25
|
+
**User needs:** [Top 3 needs this feature addresses]
|
|
26
|
+
**Primary conversion action:** [What user does that matters to business]
|
|
27
|
+
|
|
28
|
+
## Architect Perspective
|
|
29
|
+
**Minimum viable approach:** [One paragraph]
|
|
30
|
+
**Technical risks:**
|
|
31
|
+
| Risk | Likelihood | Impact | Mitigation |
|
|
32
|
+
|------|-----------|--------|------------|
|
|
33
|
+
| [Risk 1] | [H/M/L] | [H/M/L] | [Mitigation] |
|
|
34
|
+
|
|
35
|
+
## UX Perspective
|
|
36
|
+
**First impression goal:** [Description]
|
|
37
|
+
**Microcopy needed:** [List]
|
|
38
|
+
|
|
39
|
+
## Synthesis
|
|
40
|
+
**What we're building:** [2-3 sentences describing the feature]
|
|
41
|
+
|
|
42
|
+
**What we're NOT building:** [Explicit exclusions]
|
|
43
|
+
1. [Exclusion]
|
|
44
|
+
|
|
45
|
+
## Open Questions
|
|
46
|
+
| # | Question | Impact |
|
|
47
|
+
|---|----------|--------|
|
|
48
|
+
| 1 | [Question] | [Blocking?] |
|
|
49
|
+
|
|
50
|
+
## Prompt Injection Check
|
|
51
|
+
- Input was sanitized before processing
|
|
52
|
+
- No system prompt overrides accepted
|
|
53
|
+
`
|
|
54
|
+
|
|
55
|
+
fs.writeFileSync('BMAD-brief.md', content, 'utf8')
|
|
56
|
+
console.log('[OK] BMAD-brief.md created.')
|
|
57
|
+
console.log('Next: claude "/sdd-spec" to generate SPEC.md')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { run }
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
---
|
|
2
|
+
tool: Bash
|
|
3
|
+
when: always
|
|
4
|
+
args: optional
|
|
5
|
+
---
|
|
6
|
+
$ErrorActionPreference = "Stop"
|
|
7
|
+
$args = $argsRaw.Trim()
|
|
8
|
+
|
|
9
|
+
function Write-Help {
|
|
10
|
+
Write-Host @"
|
|
11
|
+
|
|
12
|
+
sdd — Spec-Driven Development CLI
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
/sdd init Initialize SDD pipeline in current directory
|
|
16
|
+
/sdd bmad <desc> Generate BMAD brief
|
|
17
|
+
/sdd status Show pipeline phase status
|
|
18
|
+
/sdd help Show this help
|
|
19
|
+
|
|
20
|
+
Full pipeline commands:
|
|
21
|
+
/sdd spec Generate SPEC.md from BMAD-brief.md
|
|
22
|
+
/sdd tasks Generate TASKS.md from SPEC.md
|
|
23
|
+
/sdd converge Run converge validation
|
|
24
|
+
|
|
25
|
+
"@
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function Test-PromptInjection {
|
|
29
|
+
param([string]$Input)
|
|
30
|
+
$blocked = '(?i)(ignore previous|disregard|system:|</?script|ignore all previous)'
|
|
31
|
+
if ($Input -match $blocked) {
|
|
32
|
+
Write-Host "[WARN] Input sanitized — blocked pattern detected" -ForegroundColor Yellow
|
|
33
|
+
return $Input -replace $blocked, '[FILTERED]'
|
|
34
|
+
}
|
|
35
|
+
return $Input
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (-not $args) {
|
|
39
|
+
Write-Host "Usage: /sdd <command> [args]" -ForegroundColor Yellow
|
|
40
|
+
Write-Help
|
|
41
|
+
exit 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
$parts = $args -split '\s+', 2
|
|
45
|
+
$cmd = $parts[0].ToLower()
|
|
46
|
+
$rest = if ($parts.Length -gt 1) { Test-PromptInjection -Input $parts[1] } else { "" }
|
|
47
|
+
|
|
48
|
+
switch ($cmd) {
|
|
49
|
+
"init" {
|
|
50
|
+
New-Item -ItemType Directory -Force -Path ".sdd","templates","converge",".claude/commands","commands","extensions",".github/workflows" | Out-Null
|
|
51
|
+
|
|
52
|
+
if (Test-Path ".sdd/config.json") {
|
|
53
|
+
Write-Host "[SKIP] SDD pipeline already initialized" -ForegroundColor Yellow
|
|
54
|
+
} else {
|
|
55
|
+
$config = @{
|
|
56
|
+
version = "1.0"
|
|
57
|
+
phases = @{
|
|
58
|
+
current = 0
|
|
59
|
+
phase0 = @{ status = "pending"; brief = "BMAD-brief.md" }
|
|
60
|
+
phase1 = @{ status = "pending" }
|
|
61
|
+
phase2 = @{ status = "pending"; specFile = "SPEC.md" }
|
|
62
|
+
phase3 = @{ status = "pending"; tasksFile = "TASKS.md" }
|
|
63
|
+
phase4 = @{ status = "pending" }
|
|
64
|
+
phase5 = @{ status = "pending"; validationFile = "converge/validation.md" }
|
|
65
|
+
}
|
|
66
|
+
templateVersions = @{
|
|
67
|
+
constitution = "1.0.0"; bmad = "1.0.0"
|
|
68
|
+
spec = "1.0.0"; plan = "1.0.0"; tasks = "1.0.0"
|
|
69
|
+
}
|
|
70
|
+
project = @{ name = "TODO"; domain = "general" }
|
|
71
|
+
settings = @{ autoConverge = $false; strictMode = $true; maxTaskHours = 2 }
|
|
72
|
+
}
|
|
73
|
+
$config | ConvertTo-Json | Set-Content ".sdd/config.json" -Encoding UTF8
|
|
74
|
+
Write-Host "[OK] SDD pipeline initialized." -ForegroundColor Green
|
|
75
|
+
}
|
|
76
|
+
Write-Host ""
|
|
77
|
+
Write-Host "Next: /sdd bmad <feature description>"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
"bmad" {
|
|
81
|
+
if (-not $rest) {
|
|
82
|
+
Write-Host "Usage: /sdd bmad <description>" -ForegroundColor Red
|
|
83
|
+
Write-Host "Example: /sdd bmad landing page for my SaaS"
|
|
84
|
+
exit 1
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
$date = Get-Date -Format "yyyy-MM-dd"
|
|
88
|
+
$expiry = (Get-Date).AddDays(30).ToString("yyyy-MM-dd")
|
|
89
|
+
|
|
90
|
+
$brief = @"
|
|
91
|
+
# BMAD Brief — [Project Name]
|
|
92
|
+
|
|
93
|
+
> Generated: ${date}. Expires: ${expiry}.
|
|
94
|
+
|
|
95
|
+
## Input
|
|
96
|
+
> ${rest}
|
|
97
|
+
|
|
98
|
+
## PM Perspective
|
|
99
|
+
**Target user:** [Who benefits most from this feature]
|
|
100
|
+
**User needs:** [Top 3 needs this feature addresses]
|
|
101
|
+
**Primary conversion action:** [What user does that matters to business]
|
|
102
|
+
|
|
103
|
+
## Architect Perspective
|
|
104
|
+
**Minimum viable approach:** [One paragraph]
|
|
105
|
+
**Technical risks:**
|
|
106
|
+
| Risk | Likelihood | Impact | Mitigation |
|
|
107
|
+
|------|-----------|--------|------------|
|
|
108
|
+
| [Risk 1] | [H/M/L] | [H/M/L] | [Mitigation] |
|
|
109
|
+
|
|
110
|
+
## UX Perspective
|
|
111
|
+
**First impression goal:** [Description]
|
|
112
|
+
**Microcopy needed:** [List]
|
|
113
|
+
|
|
114
|
+
## Synthesis
|
|
115
|
+
**What we're building:** [2-3 sentences describing the feature]
|
|
116
|
+
**What we're NOT building:** [Explicit exclusions]
|
|
117
|
+
1. [Exclusion]
|
|
118
|
+
|
|
119
|
+
## Open Questions
|
|
120
|
+
| # | Question | Impact |
|
|
121
|
+
|---|----------|--------|
|
|
122
|
+
| 1 | [Question] | [Blocking?] |
|
|
123
|
+
|
|
124
|
+
## Prompt Injection Check
|
|
125
|
+
- Input was sanitized before processing
|
|
126
|
+
- No system prompt overrides accepted
|
|
127
|
+
"@
|
|
128
|
+
|
|
129
|
+
if (Test-Path "BMAD-brief.md") {
|
|
130
|
+
Write-Host "[WARN] BMAD-brief.md already exists — overwriting" -ForegroundColor Yellow
|
|
131
|
+
}
|
|
132
|
+
$brief | Set-Content "BMAD-brief.md" -Encoding UTF8
|
|
133
|
+
Write-Host "[OK] BMAD-brief.md created." -ForegroundColor Green
|
|
134
|
+
Write-Host ""
|
|
135
|
+
Write-Host "Next: /sdd spec"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
"spec" {
|
|
139
|
+
if (-not (Test-Path "BMAD-brief.md")) {
|
|
140
|
+
Write-Host "[ERROR] BMAD-brief.md not found" -ForegroundColor Red
|
|
141
|
+
Write-Host "Run: /sdd bmad <description> first"
|
|
142
|
+
exit 1
|
|
143
|
+
}
|
|
144
|
+
if (-not (Test-Path "templates/SPEC.template.md")) {
|
|
145
|
+
Write-Host "[ERROR] templates/SPEC.template.md not found" -ForegroundColor Red
|
|
146
|
+
Write-Host "Run: /sdd init first"
|
|
147
|
+
exit 1
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (Test-Path ".sdd/config.json") {
|
|
151
|
+
$config = Get-Content ".sdd/config.json" | ConvertFrom-Json
|
|
152
|
+
$config.phases.current = 2
|
|
153
|
+
$config.phases.phase0.status = "completed"
|
|
154
|
+
$config.phases.phase0.completedAt = (Get-Date -Format "yyyy-MM-dd")
|
|
155
|
+
$config.phases.phase2.status = "in-progress"
|
|
156
|
+
$config | ConvertTo-Json | Set-Content ".sdd/config.json" -Encoding UTF8
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Write-Host "[OK] Phase 0 gate passed." -ForegroundColor Green
|
|
160
|
+
Write-Host "Generate SPEC.md by expanding templates/SPEC.template.md"
|
|
161
|
+
Write-Host "using content from BMAD-brief.md."
|
|
162
|
+
Write-Host ""
|
|
163
|
+
Write-Host "The following sections are required:"
|
|
164
|
+
Write-Host " 1. Overview"
|
|
165
|
+
Write-Host " 2. User Stories (Must Have / Nice to Have)"
|
|
166
|
+
Write-Host " 3. Functional Requirements"
|
|
167
|
+
Write-Host " 4. Non-Functional Requirements"
|
|
168
|
+
Write-Host " 5. Technical Constraints"
|
|
169
|
+
Write-Host " 6. Open Questions"
|
|
170
|
+
Write-Host " 7. Success Criteria"
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
"tasks" {
|
|
174
|
+
if (-not (Test-Path "SPEC.md")) {
|
|
175
|
+
Write-Host "[ERROR] SPEC.md not found" -ForegroundColor Red
|
|
176
|
+
Write-Host "Run: /sdd spec first"
|
|
177
|
+
exit 1
|
|
178
|
+
}
|
|
179
|
+
if (-not (Test-Path "templates/TASKS.template.md")) {
|
|
180
|
+
Write-Host "[ERROR] templates/TASKS.template.md not found" -ForegroundColor Red
|
|
181
|
+
Write-Host "Run: /sdd init first"
|
|
182
|
+
exit 1
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (Test-Path ".sdd/config.json") {
|
|
186
|
+
$config = Get-Content ".sdd/config.json" | ConvertFrom-Json
|
|
187
|
+
$config.phases.current = 3
|
|
188
|
+
$config.phases.phase2.status = "completed"
|
|
189
|
+
$config.phases.phase2.completedAt = (Get-Date -Format "yyyy-MM-dd")
|
|
190
|
+
$config.phases.phase3.status = "in-progress"
|
|
191
|
+
$config | ConvertTo-Json | Set-Content ".sdd/config.json" -Encoding UTF8
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
Write-Host "[OK] Phase 2 gate passed." -ForegroundColor Green
|
|
195
|
+
Write-Host "Break SPEC.md into discrete tasks (T-001, T-002, ...)."
|
|
196
|
+
Write-Host "Each task must:"
|
|
197
|
+
Write-Host " - Take <= 2 hours"
|
|
198
|
+
Write-Host " - Be independently verifiable"
|
|
199
|
+
Write-Host " - Have output artifact specified"
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
"status" {
|
|
203
|
+
if (-not (Test-Path ".sdd/config.json")) {
|
|
204
|
+
Write-Host "[ERROR] .sdd/config.json not found" -ForegroundColor Red
|
|
205
|
+
Write-Host "Run: /sdd init first"
|
|
206
|
+
exit 1
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
$config = Get-Content ".sdd/config.json" | ConvertFrom-Json
|
|
210
|
+
Write-Host "=== SDD Pipeline Status ===" -ForegroundColor Cyan
|
|
211
|
+
Write-Host "Project: $($config.project.name)"
|
|
212
|
+
Write-Host "Phase: $($config.phases.current)"
|
|
213
|
+
Write-Host "Domain: $($config.project.domain)"
|
|
214
|
+
Write-Host ""
|
|
215
|
+
|
|
216
|
+
$phases = @("phase0","phase1","phase2","phase3","phase4","phase5")
|
|
217
|
+
foreach ($p in $phases) {
|
|
218
|
+
$phaseData = $config.phases.$p
|
|
219
|
+
$status = if ($phaseData.status) { $phaseData.status } else { "unknown" }
|
|
220
|
+
$icon = switch ($status) { "completed" { "[V]" } "in-progress" { "[>]" } default { "[ ]" } }
|
|
221
|
+
$color = switch ($status) { "completed" { "Green" } "in-progress" { "Yellow" } default { "Gray" } }
|
|
222
|
+
Write-Host "$icon $p : $status" -ForegroundColor $color
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
Write-Host ""
|
|
226
|
+
$next = $config.phases.current
|
|
227
|
+
$nextCmd = switch ($next) {
|
|
228
|
+
0 { "/sdd bmad <description>" }
|
|
229
|
+
2 { "/sdd spec" }
|
|
230
|
+
3 { "/sdd tasks" }
|
|
231
|
+
default { "All phases complete" }
|
|
232
|
+
}
|
|
233
|
+
Write-Host "Next: $nextCmd"
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
"converge" {
|
|
237
|
+
$required = @("SPEC.md", "PLAN.md", "TASKS.md")
|
|
238
|
+
$missing = $required | Where-Object { -not (Test-Path $_) }
|
|
239
|
+
if ($missing) {
|
|
240
|
+
Write-Host "[ERROR] Missing artifacts: $($missing -join ', ')" -ForegroundColor Red
|
|
241
|
+
Write-Host "Run: /sdd init && /sdd bmad <desc> && /sdd spec && /sdd tasks"
|
|
242
|
+
exit 1
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
Write-Host "[OK] All artifacts present." -ForegroundColor Green
|
|
246
|
+
Write-Host "Running converge validation..."
|
|
247
|
+
Write-Host ""
|
|
248
|
+
Write-Host "Run: pwsh run-converge.ps1 -Url 'http://localhost:3000' -Strict"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
"help" {
|
|
252
|
+
Write-Help
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
default {
|
|
256
|
+
Write-Host "Unknown command: $cmd" -ForegroundColor Red
|
|
257
|
+
Write-Host ""
|
|
258
|
+
Write-Help
|
|
259
|
+
exit 1
|
|
260
|
+
}
|
|
261
|
+
}
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
|
|
4
|
+
function read() {
|
|
5
|
+
const configPath = path.join(process.cwd(), '.sdd/config.json')
|
|
6
|
+
if (!fs.existsSync(configPath)) {
|
|
7
|
+
return null
|
|
8
|
+
}
|
|
9
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function printStatus() {
|
|
13
|
+
const config = read()
|
|
14
|
+
if (!config) {
|
|
15
|
+
console.error('ERROR: .sdd/config.json not found. Run: sdd init')
|
|
16
|
+
process.exit(1)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const phases = ['phase0', 'phase1', 'phase2', 'phase3', 'phase4', 'phase5']
|
|
20
|
+
console.log('=== SDD Pipeline Status ===')
|
|
21
|
+
console.log(`Project: ${config.project.name}`)
|
|
22
|
+
console.log(`Phase: ${config.phases.current}`)
|
|
23
|
+
console.log(`Domain: ${config.project.domain}`)
|
|
24
|
+
console.log('')
|
|
25
|
+
|
|
26
|
+
for (const p of phases) {
|
|
27
|
+
const phaseData = config.phases[p]
|
|
28
|
+
const status = phaseData?.status || 'unknown'
|
|
29
|
+
const icon = status === 'completed' ? '[V]' : status === 'in-progress' ? '[>]' : '[ ]'
|
|
30
|
+
const color = status === 'completed' ? '32' : status === 'in-progress' ? '33' : '90'
|
|
31
|
+
console.log(` \x1b[${color}m${icon} ${p}: ${status}\x1b[0m`)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { read, printStatus }
|
package/lib/init.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
|
|
4
|
+
const BUNDLE_DIR = path.join(__dirname, 'bundle')
|
|
5
|
+
|
|
6
|
+
const FILES = [
|
|
7
|
+
'.sdd/config.json',
|
|
8
|
+
'.sdd/smoke-test.ps1',
|
|
9
|
+
'.sdd/tool-versions.json',
|
|
10
|
+
'templates/CONSTITUTION.template.md',
|
|
11
|
+
'templates/SPEC.template.md',
|
|
12
|
+
'templates/PLAN.template.md',
|
|
13
|
+
'templates/TASKS.template.md',
|
|
14
|
+
'templates/BMAD-brief.template.md',
|
|
15
|
+
'templates/README.md',
|
|
16
|
+
'converge/validation.md',
|
|
17
|
+
'CLAUDE.md',
|
|
18
|
+
'run-converge.ps1',
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
const DIRS = ['.sdd', 'templates', 'converge', 'commands', 'extensions']
|
|
22
|
+
|
|
23
|
+
function ensureDir(dir) {
|
|
24
|
+
if (!fs.existsSync(dir)) {
|
|
25
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function copyFile(src, dest) {
|
|
30
|
+
ensureDir(path.dirname(dest))
|
|
31
|
+
if (fs.existsSync(dest)) {
|
|
32
|
+
console.log(` [SKIP] ${dest} already exists`)
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
const content = fs.readFileSync(src, 'utf8')
|
|
36
|
+
fs.writeFileSync(dest, content, 'utf8')
|
|
37
|
+
console.log(` [COPY] ${dest}`)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function run() {
|
|
41
|
+
console.log('Initializing SDD pipeline...')
|
|
42
|
+
console.log('')
|
|
43
|
+
|
|
44
|
+
if (!fs.existsSync(BUNDLE_DIR)) {
|
|
45
|
+
console.error('ERROR: Bundle directory not found. Is the package installed correctly?')
|
|
46
|
+
process.exit(1)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const dir of DIRS) {
|
|
50
|
+
ensureDir(dir)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const file of FILES) {
|
|
54
|
+
const src = path.join(BUNDLE_DIR, file)
|
|
55
|
+
const dest = path.join(process.cwd(), file)
|
|
56
|
+
if (fs.existsSync(src)) {
|
|
57
|
+
copyFile(src, dest)
|
|
58
|
+
} else {
|
|
59
|
+
console.log(` [WARN] ${src} not found in bundle`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Create .claude/commands/ with the command files
|
|
64
|
+
ensureDir('.claude/commands')
|
|
65
|
+
const cmdFiles = ['sdd-bmad.md', 'sdd-spec.md', 'sdd-tasks.md', 'sdd-converge.md', 'sdd-init.md']
|
|
66
|
+
for (const cmd of cmdFiles) {
|
|
67
|
+
const src = path.join(BUNDLE_DIR, '.claude/commands', cmd)
|
|
68
|
+
const dest = path.join(process.cwd(), '.claude/commands', cmd)
|
|
69
|
+
if (fs.existsSync(src)) {
|
|
70
|
+
copyFile(src, dest)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Create .github/workflows/
|
|
75
|
+
ensureDir('.github/workflows')
|
|
76
|
+
const workflowSrc = path.join(BUNDLE_DIR, '.github/workflows/converge.yml')
|
|
77
|
+
const workflowDest = path.join(process.cwd(), '.github/workflows/converge.yml')
|
|
78
|
+
if (fs.existsSync(workflowSrc)) {
|
|
79
|
+
copyFile(workflowSrc, workflowDest)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Create commands/sdd-status.ps1
|
|
83
|
+
ensureDir('commands')
|
|
84
|
+
const statusSrc = path.join(BUNDLE_DIR, 'commands/sdd-status.ps1')
|
|
85
|
+
const statusDest = path.join(process.cwd(), 'commands/sdd-status.ps1')
|
|
86
|
+
if (fs.existsSync(statusSrc)) {
|
|
87
|
+
copyFile(statusSrc, statusDest)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Create extensions/
|
|
91
|
+
for (const ext of ['sdd-api', 'sdd-frontend', 'sdd-backend']) {
|
|
92
|
+
const extDir = path.join(BUNDLE_DIR, 'extensions', ext)
|
|
93
|
+
if (fs.existsSync(extDir)) {
|
|
94
|
+
copyDirRecursive(extDir, path.join(process.cwd(), 'extensions', ext))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Copy extensions README
|
|
99
|
+
const extReadmeSrc = path.join(BUNDLE_DIR, 'extensions/README.md')
|
|
100
|
+
const extReadmeDest = path.join(process.cwd(), 'extensions/README.md')
|
|
101
|
+
if (fs.existsSync(extReadmeSrc)) {
|
|
102
|
+
copyFile(extReadmeSrc, extReadmeDest)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log('')
|
|
106
|
+
console.log('[OK] SDD pipeline initialized.')
|
|
107
|
+
console.log('')
|
|
108
|
+
console.log('Next: sdd bmad <your feature idea>')
|
|
109
|
+
console.log('Or: claude "/sdd-bmad <feature>"')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function copyDirRecursive(src, dest) {
|
|
113
|
+
ensureDir(dest)
|
|
114
|
+
const entries = fs.readdirSync(src, { withFileTypes: true })
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const srcPath = path.join(src, entry.name)
|
|
117
|
+
const destPath = path.join(dest, entry.name)
|
|
118
|
+
if (entry.isDirectory()) {
|
|
119
|
+
copyDirRecursive(srcPath, destPath)
|
|
120
|
+
} else {
|
|
121
|
+
copyFile(srcPath, destPath)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = { run }
|
package/lib/sanitize.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Prompt injection protection — sanitize user input before LLM embedding
|
|
2
|
+
function sanitize(input) {
|
|
3
|
+
if (!input) return ''
|
|
4
|
+
return input
|
|
5
|
+
.replace(/ignore previous|disregard|system:|<script|<\/script>/gi, '[FILTERED]')
|
|
6
|
+
.replace(/ignore all previous instructions/gi, '[FILTERED]')
|
|
7
|
+
.trim()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = { run: sanitize }
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sdd-pipeline",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Spec-Driven Development pipeline for Claude Code",
|
|
5
|
-
"main": "bin/sdd.js",
|
|
6
5
|
"bin": {
|
|
7
|
-
"sdd": "
|
|
6
|
+
"sdd": "bin/sdd.js"
|
|
8
7
|
},
|
|
9
8
|
"files": [
|
|
10
9
|
"bin",
|
|
10
|
+
"lib",
|
|
11
11
|
"lib/bundle"
|
|
12
12
|
],
|
|
13
13
|
"engines": {
|