sdd-pipeline 1.2.4 → 1.2.7
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/bin/sdd.cjs +201 -0
- package/lib/bmad/index.js +20 -3
- package/lib/bundle/.claude/commands/sdd-check.md +153 -0
- package/lib/bundle/.claude/commands/sdd-cook.md +149 -167
- package/lib/bundle/.claude/commands/sdd-task-status.md +97 -27
- package/lib/bundle/bin/sdd.cjs +201 -0
- package/lib/bundle/bin/sdd.js +159 -0
- package/lib/bundle/commands/sdd-task-status.ps1 +82 -22
- package/lib/bundle/lib/bmad/confidence.js +129 -0
- package/lib/bundle/lib/bmad/index.js +157 -0
- package/lib/bundle/lib/bmad/problem-first.js +91 -0
- package/lib/bundle/lib/bmad/questions.js +110 -0
- package/lib/bundle/lib/bmad/synthesis.js +109 -0
- package/lib/bundle/lib/config.js +35 -0
- package/lib/bundle/lib/init.js +204 -0
- package/lib/bundle/lib/sanitize.js +10 -0
- package/lib/bundle/run-converge.ps1 +288 -42
- package/lib/bundle/skills/sdd-tasks/SKILL.md +259 -0
- package/lib/init.js +108 -32
- package/package.json +8 -8
- package/bin/sdd.js +0 -70
- package/lib/bmad.js +0 -54
|
@@ -4,54 +4,114 @@ tool: Bash
|
|
|
4
4
|
when: always
|
|
5
5
|
description: Update task status in MASTER-TASKS.md + .sdd/config.json. Args: <T-XXX> <pending|in-progress|completed>. Recalculates progress counters, suggests next task.
|
|
6
6
|
---
|
|
7
|
+
$ErrorActionPreference = "Stop"
|
|
8
|
+
|
|
9
|
+
# --- SDD Path Resolution ---
|
|
10
|
+
$SDD_TASKS_DIR = "sdd/tasks"
|
|
11
|
+
|
|
12
|
+
if (Test-Path ".sdd/config.json") {
|
|
13
|
+
try {
|
|
14
|
+
$config = Get-Content ".sdd/config.json" -Raw | ConvertFrom-Json
|
|
15
|
+
if ($config.output -and $config.output.tasksDir) {
|
|
16
|
+
$SDD_TASKS_DIR = $config.output.tasksDir
|
|
17
|
+
}
|
|
18
|
+
} catch {}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# --- Parse arguments ---
|
|
7
22
|
$taskId = $args[0]
|
|
8
23
|
$newStatus = $args[1]
|
|
9
24
|
|
|
10
25
|
if (-not $taskId -or -not $newStatus) {
|
|
11
|
-
Write-Host "
|
|
12
|
-
Write-Host "Usage: /sdd-task-status T-001
|
|
13
|
-
Write-Host "
|
|
26
|
+
Write-Host ""
|
|
27
|
+
Write-Host "Usage: /sdd-task-status T-001 done" -ForegroundColor White
|
|
28
|
+
Write-Host " /sdd-task-status T-001 in-progress" -ForegroundColor White
|
|
29
|
+
Write-Host " /sdd-task-status T-001 pending" -ForegroundColor White
|
|
14
30
|
exit 1
|
|
15
31
|
}
|
|
16
32
|
|
|
33
|
+
# Map status to display format
|
|
34
|
+
$statusMap = @{
|
|
35
|
+
"pending" = "[ ]"
|
|
36
|
+
"in-progress" = "[>]"
|
|
37
|
+
"completed" = "[x]"
|
|
38
|
+
"done" = "[x]"
|
|
39
|
+
"x" = "[x]"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
$displayStatus = $statusMap[$newStatus]
|
|
43
|
+
if (-not $displayStatus) {
|
|
44
|
+
$displayStatus = "[ ]"
|
|
45
|
+
}
|
|
46
|
+
|
|
17
47
|
# Validate status
|
|
18
|
-
$validStatuses = @("pending", "in-progress", "completed")
|
|
48
|
+
$validStatuses = @("pending", "in-progress", "completed", "done", "x")
|
|
19
49
|
if ($validStatuses -notcontains $newStatus) {
|
|
50
|
+
Write-Host ""
|
|
20
51
|
Write-Host "ERROR: Invalid status '$newStatus'. Use: pending, in-progress, completed" -ForegroundColor Red
|
|
21
52
|
exit 1
|
|
22
53
|
}
|
|
23
54
|
|
|
24
|
-
# Find task directory
|
|
25
|
-
$
|
|
26
|
-
|
|
27
|
-
|
|
55
|
+
# --- Find task directory (check new path first, then legacy) ---
|
|
56
|
+
$taskDir = $null
|
|
57
|
+
|
|
58
|
+
# Check new location
|
|
59
|
+
if (Test-Path $SDD_TASKS_DIR) {
|
|
60
|
+
$taskDirs = Get-ChildItem -Path $SDD_TASKS_DIR -Filter "task-*-*" -Directory -ErrorAction SilentlyContinue
|
|
61
|
+
if ($taskDirs) {
|
|
62
|
+
$taskDir = Join-Path $SDD_TASKS_DIR ($taskDirs | Select-Object -First 1).Name
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Fallback to legacy location
|
|
67
|
+
if (-not $taskDir) {
|
|
68
|
+
$legacyTaskDirs = Get-ChildItem -Filter "task-*-*" -Directory -ErrorAction SilentlyContinue
|
|
69
|
+
if ($legacyTaskDirs) {
|
|
70
|
+
$taskDir = ($legacyTaskDirs | Select-Object -First 1).FullName
|
|
71
|
+
Write-Host "[LEGACY] Task directory found at legacy location: $taskDir" -ForegroundColor Yellow
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (-not $taskDir) {
|
|
76
|
+
Write-Host ""
|
|
77
|
+
Write-Host "ERROR: No task directory found." -ForegroundColor Red
|
|
78
|
+
Write-Host "Run /sdd-tasks first." -ForegroundColor Yellow
|
|
28
79
|
exit 1
|
|
29
80
|
}
|
|
30
|
-
$taskDir = ($taskDirs | Select-Object -First 1).Name
|
|
31
|
-
$taskFile = Join-Path $taskDir "${taskId}-*.md"
|
|
32
81
|
|
|
33
|
-
# Find task file
|
|
82
|
+
# --- Find task file ---
|
|
34
83
|
$taskFiles = Get-ChildItem -Path $taskDir -Filter "${taskId}-*.md" -ErrorAction SilentlyContinue
|
|
84
|
+
$taskFiles = $taskFiles | Where-Object { $_.Name -ne "MASTER-TASKS.md" }
|
|
35
85
|
$taskFilePath = ($taskFiles | Select-Object -First 1).FullName
|
|
36
86
|
|
|
37
87
|
if (-not $taskFilePath) {
|
|
88
|
+
Write-Host ""
|
|
38
89
|
Write-Host "ERROR: $taskId not found in $taskDir" -ForegroundColor Red
|
|
39
90
|
exit 1
|
|
40
91
|
}
|
|
41
92
|
|
|
42
|
-
# Update task file status in frontmatter
|
|
93
|
+
# --- Update task file status in frontmatter ---
|
|
94
|
+
$frontmatterStatus = $newStatus
|
|
95
|
+
if ($newStatus -in @("done", "x")) {
|
|
96
|
+
$frontmatterStatus = "completed"
|
|
97
|
+
}
|
|
98
|
+
|
|
43
99
|
$content = Get-Content $taskFilePath -Raw
|
|
44
|
-
$content = $content -replace '(?m)^(status:\s*)\S+', "`$1$
|
|
100
|
+
$content = $content -replace '(?m)^(status:\s*)\S+', "`$1$frontmatterStatus"
|
|
45
101
|
Set-Content $taskFilePath $content -Encoding UTF8
|
|
46
102
|
|
|
47
|
-
# Update MASTER-TASKS.md
|
|
103
|
+
# --- Update MASTER-TASKS.md ---
|
|
48
104
|
$masterPath = Join-Path $taskDir "MASTER-TASKS.md"
|
|
49
105
|
$masterContent = Get-Content $masterPath -Raw
|
|
50
106
|
|
|
51
|
-
# Update task table row status
|
|
52
|
-
|
|
53
|
-
$
|
|
54
|
-
$masterContent = $masterContent -replace $pattern, $
|
|
107
|
+
# Update task table row status - handle both [ ] and [x] patterns
|
|
108
|
+
# Pattern matches the status column and replaces it
|
|
109
|
+
$pattern = "(\|\s*$taskId\s*\|\s*[^\|]+\|\s*[^\|]+\|\s*)\[[^\]]\](\s*\|)"
|
|
110
|
+
$masterContent = $masterContent -replace $pattern, "`$1$displayStatus`$2"
|
|
111
|
+
|
|
112
|
+
# Also handle T-XXX format with status in brackets
|
|
113
|
+
$pattern2 = "(\|\s*T-\d+\s*\|\s*[^\|]+\|\s*[^\|]+\|\s*)\[[^\]]\](\s*\|)"
|
|
114
|
+
$masterContent = $masterContent -replace $pattern2, "`$1$displayStatus`$2"
|
|
55
115
|
|
|
56
116
|
# Update progress counters
|
|
57
117
|
$allTaskFiles = Get-ChildItem -Path $taskDir -Filter "T-*.md" | Where-Object { $_.Name -ne "MASTER-TASKS.md" }
|
|
@@ -65,31 +125,41 @@ foreach ($f in $allTaskFiles) {
|
|
|
65
125
|
elseif ($c -match '(?m)^status:\s*in-progress') { $inProgress++ }
|
|
66
126
|
}
|
|
67
127
|
|
|
128
|
+
# Update "Completed: X / N" pattern
|
|
68
129
|
$masterContent = $masterContent -replace 'Completed: \d+ / \d+', "Completed: $completed / $total"
|
|
130
|
+
# Update "In Progress: X" pattern
|
|
69
131
|
$masterContent = $masterContent -replace 'In Progress: \d+', "In Progress: $inProgress"
|
|
132
|
+
# Update "Pending: X" pattern
|
|
70
133
|
$masterContent = $masterContent -replace 'Pending: \d+', "Pending: $($total - $completed - $inProgress)"
|
|
71
134
|
|
|
72
135
|
Set-Content $masterPath $masterContent -Encoding UTF8
|
|
73
136
|
|
|
74
|
-
# Update .sdd/config.json
|
|
137
|
+
# --- Update .sdd/config.json ---
|
|
75
138
|
if (Test-Path ".sdd/config.json") {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
139
|
+
try {
|
|
140
|
+
$config = Get-Content ".sdd/config.json" -Raw | ConvertFrom-Json
|
|
141
|
+
if (-not $config.tasks) { $config | Add-Member -NotePropertyName "tasks" -NotePropertyValue ([ordered]@{}) }
|
|
142
|
+
$config.tasks.$taskId = $frontmatterStatus
|
|
143
|
+
$config | ConvertTo-Json -Depth 10 | Set-Content ".sdd/config.json"
|
|
144
|
+
} catch {}
|
|
80
145
|
}
|
|
81
146
|
|
|
82
|
-
|
|
83
|
-
|
|
147
|
+
# --- Summary ---
|
|
148
|
+
$statusColor = if ($frontmatterStatus -eq "completed") { "Green" } elseif ($frontmatterStatus -eq "in-progress") { "Yellow" } else { "Gray" }
|
|
149
|
+
Write-Host ""
|
|
150
|
+
Write-Host "[OK] $taskId -> $displayStatus" -ForegroundColor Green
|
|
151
|
+
Write-Host " Progress: $completed/$total completed, $inProgress in-progress" -ForegroundColor Gray
|
|
152
|
+
Write-Host " Task file: $taskFilePath" -ForegroundColor Gray
|
|
84
153
|
|
|
85
154
|
# Suggest next task
|
|
86
|
-
if ($
|
|
155
|
+
if ($frontmatterStatus -eq "completed") {
|
|
87
156
|
$pending = Get-ChildItem -Path $taskDir -Filter "T-*.md" | Where-Object {
|
|
88
157
|
$_.Name -ne "MASTER-TASKS.md" -and
|
|
89
158
|
((Get-Content $_.FullName -Raw) -notmatch '(?m)^status:\s*completed')
|
|
90
159
|
}
|
|
91
160
|
if ($pending) {
|
|
92
161
|
$next = ($pending | Select-Object -First 1).Name -replace '\.md$', ''
|
|
93
|
-
Write-Host "
|
|
162
|
+
Write-Host ""
|
|
163
|
+
Write-Host "Next: /sdd-task-status $next in-progress" -ForegroundColor Cyan
|
|
94
164
|
}
|
|
95
165
|
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// sdd — Spec-Driven Development CLI
|
|
3
|
+
// Unified entry point for both npm package and local development
|
|
4
|
+
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const fs = require('fs')
|
|
7
|
+
|
|
8
|
+
// Determine if we're in npm package or local development
|
|
9
|
+
const isNpmPackage = fs.existsSync(path.join(__dirname, 'lib/bmad.js'))
|
|
10
|
+
const libDir = isNpmPackage ? path.join(__dirname, 'lib') : path.join(__dirname, '..', 'lib')
|
|
11
|
+
|
|
12
|
+
// Dynamic requires
|
|
13
|
+
const init = require(path.join(libDir, 'init'))
|
|
14
|
+
const bmad = require(path.join(libDir, 'bmad/index'))
|
|
15
|
+
const config = require(path.join(libDir, 'config'))
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2)
|
|
18
|
+
const cmd = args[0]
|
|
19
|
+
|
|
20
|
+
// PowerShell detection - use correct command for platform
|
|
21
|
+
function getPowerShellCommand() {
|
|
22
|
+
if (process.platform === 'win32') {
|
|
23
|
+
// Windows: try powershell.exe first (always available), then pwsh
|
|
24
|
+
const system32 = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'
|
|
25
|
+
if (fs.existsSync(system32)) {
|
|
26
|
+
return 'powershell'
|
|
27
|
+
}
|
|
28
|
+
return 'powershell' // Fallback to whatever is in PATH
|
|
29
|
+
}
|
|
30
|
+
return 'pwsh' // macOS/Linux: PowerShell Core
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function runPowerShellScript(scriptPath, scriptArgs, callback) {
|
|
34
|
+
const psCmd = getPowerShellCommand()
|
|
35
|
+
const psArgs = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', scriptPath, ...scriptArgs]
|
|
36
|
+
|
|
37
|
+
const { spawn } = require('child_process')
|
|
38
|
+
const ps = spawn(psCmd, psArgs, {
|
|
39
|
+
stdio: 'inherit',
|
|
40
|
+
shell: false,
|
|
41
|
+
windowsHide: true
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
ps.on('error', (err) => {
|
|
45
|
+
callback(err, null)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
ps.on('close', (code) => {
|
|
49
|
+
callback(null, code)
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
switch (cmd) {
|
|
54
|
+
case 'init':
|
|
55
|
+
init.run()
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
case 'bmad': {
|
|
59
|
+
const input = args.slice(1).join(' ')
|
|
60
|
+
if (!input) {
|
|
61
|
+
console.error('Usage: sdd bmad <feature description>')
|
|
62
|
+
console.error('Example: sdd bmad "landing page for my SaaS product"')
|
|
63
|
+
console.error('')
|
|
64
|
+
console.error('For interactive mode (9-question interview), use Claude Code:')
|
|
65
|
+
console.error(' claude "/sdd-bmad landing page for my SaaS"')
|
|
66
|
+
process.exit(1)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Try to run PowerShell script if available
|
|
70
|
+
const commandsDir = isNpmPackage
|
|
71
|
+
? path.join(libDir, 'bundle', '.claude', 'commands')
|
|
72
|
+
: path.join(__dirname, '..', '.claude', 'commands')
|
|
73
|
+
const bmadScript = path.join(commandsDir, 'sdd-bmad.md')
|
|
74
|
+
|
|
75
|
+
// Note: Claude Code commands are .md files designed for Claude Code CLI
|
|
76
|
+
// PowerShell cannot run .md files directly
|
|
77
|
+
// So we always use the Node.js module for fast mode
|
|
78
|
+
|
|
79
|
+
// Generate brief using Node.js module
|
|
80
|
+
const session = bmad.init(input)
|
|
81
|
+
const result = bmad.generateBrief()
|
|
82
|
+
|
|
83
|
+
console.log('[OK] sdd/brief.md created.')
|
|
84
|
+
console.log(`Confidence: ${result.confidence.score}/100 — ${result.confidence.level}`)
|
|
85
|
+
console.log('')
|
|
86
|
+
console.log('Output: sdd/brief.md')
|
|
87
|
+
console.log('')
|
|
88
|
+
if (result.confidence.score < 70) {
|
|
89
|
+
console.log('Tip: For higher confidence, use Claude Code with interactive mode:')
|
|
90
|
+
console.log(' claude "/sdd-bmad ' + input.substring(0, 30) + '..."')
|
|
91
|
+
}
|
|
92
|
+
console.log('')
|
|
93
|
+
console.log('Next: claude "/sdd-spec" to generate SPEC.md')
|
|
94
|
+
console.log(' (or use Claude Code for full interactive experience)')
|
|
95
|
+
break
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
case 'spec':
|
|
99
|
+
console.log('=== SDD SPEC Generation ===')
|
|
100
|
+
console.log('')
|
|
101
|
+
console.log('This command requires Claude Code for best results.')
|
|
102
|
+
console.log('')
|
|
103
|
+
console.log('Run: claude "/sdd-spec"')
|
|
104
|
+
console.log('')
|
|
105
|
+
console.log('Claude Code will:')
|
|
106
|
+
console.log(' 1. Read sdd/brief.md')
|
|
107
|
+
console.log(' 2. Validate confidence score (requires >= 20)')
|
|
108
|
+
console.log(' 3. Generate sdd/SPEC.md from template')
|
|
109
|
+
break
|
|
110
|
+
|
|
111
|
+
case 'tasks':
|
|
112
|
+
console.log('=== SDD Task Generation ===')
|
|
113
|
+
console.log('')
|
|
114
|
+
console.log('This command requires Claude Code.')
|
|
115
|
+
console.log('')
|
|
116
|
+
console.log('Run: claude "/sdd-tasks"')
|
|
117
|
+
console.log('')
|
|
118
|
+
console.log('Claude Code will:')
|
|
119
|
+
console.log(' 1. Read sdd/SPEC.md')
|
|
120
|
+
console.log(' 2. Extract features from Section 4.1')
|
|
121
|
+
console.log(' 3. Generate sdd/tasks/task-*/ directory')
|
|
122
|
+
console.log(' 4. Create MASTER-TASKS.md and T-XXX-*.md files')
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
case 'cook':
|
|
126
|
+
console.log('=== SDD Implementation ===')
|
|
127
|
+
console.log('')
|
|
128
|
+
console.log('This command requires Claude Code.')
|
|
129
|
+
console.log('')
|
|
130
|
+
console.log('Run: claude "/sdd-cook"')
|
|
131
|
+
console.log('')
|
|
132
|
+
console.log('Claude Code will:')
|
|
133
|
+
console.log(' 1. Read sdd/tasks/task-*/MASTER-TASKS.md')
|
|
134
|
+
console.log(' 2. Execute tasks sequentially')
|
|
135
|
+
console.log(' 3. Scout, implement, test, review each task')
|
|
136
|
+
console.log('')
|
|
137
|
+
console.log('Flags:')
|
|
138
|
+
console.log(' --all Execute all pending tasks')
|
|
139
|
+
console.log(' --task T-001 Execute specific task')
|
|
140
|
+
break
|
|
141
|
+
|
|
142
|
+
case 'converge':
|
|
143
|
+
console.log('=== SDD Converge Validation ===')
|
|
144
|
+
console.log('')
|
|
145
|
+
console.log('Full validation (requires dev server):')
|
|
146
|
+
console.log(' pwsh run-converge.ps1 -Url http://localhost:3000 -Strict')
|
|
147
|
+
console.log('')
|
|
148
|
+
console.log('Per-task validation:')
|
|
149
|
+
console.log(' pwsh run-converge.ps1 -TaskId T-001')
|
|
150
|
+
console.log('')
|
|
151
|
+
console.log('Claude Code:')
|
|
152
|
+
console.log(' claude "/sdd-converge"')
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
case 'status': {
|
|
156
|
+
config.printStatus()
|
|
157
|
+
break
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
case 'help':
|
|
161
|
+
default:
|
|
162
|
+
if (cmd && cmd !== 'help') {
|
|
163
|
+
console.error(`Unknown command: ${cmd}\n`)
|
|
164
|
+
}
|
|
165
|
+
console.log(`
|
|
166
|
+
sdd — Spec-Driven Development CLI
|
|
167
|
+
|
|
168
|
+
Output Structure (v1.1.0+):
|
|
169
|
+
sdd/ All generated artifacts
|
|
170
|
+
brief.md Phase 0: BMAD brief
|
|
171
|
+
SPEC.md Phase 2: Specification
|
|
172
|
+
tasks/ Phase 3: Task directories
|
|
173
|
+
converge/ Phase 5: Validation reports
|
|
174
|
+
|
|
175
|
+
Usage:
|
|
176
|
+
sdd init Initialize SDD pipeline
|
|
177
|
+
sdd bmad <desc> Phase 0: Generate brief (fast mode)
|
|
178
|
+
sdd status Show pipeline status
|
|
179
|
+
sdd help Show this help
|
|
180
|
+
|
|
181
|
+
Full Pipeline (requires Claude Code):
|
|
182
|
+
claude "/sdd-bmad" Interactive 9-question interview (high confidence)
|
|
183
|
+
claude "/sdd-spec" Generate SPEC.md
|
|
184
|
+
claude "/sdd-tasks" Generate task directory
|
|
185
|
+
claude "/sdd-cook" Execute implementation
|
|
186
|
+
claude "/sdd-converge" Validate implementation
|
|
187
|
+
|
|
188
|
+
Quick Start:
|
|
189
|
+
1. sdd init
|
|
190
|
+
2. sdd bmad "my feature idea"
|
|
191
|
+
3. claude "/sdd-spec"
|
|
192
|
+
4. claude "/sdd-tasks"
|
|
193
|
+
5. claude "/sdd-cook"
|
|
194
|
+
6. claude "/sdd-converge"
|
|
195
|
+
|
|
196
|
+
Notes:
|
|
197
|
+
- Claude Code CLI provides the best experience with interactive commands
|
|
198
|
+
- Install Claude Code: npm install -g @anthropic-ai/claude-code
|
|
199
|
+
- Without Claude Code, use "sdd bmad" for fast mode (lower confidence)
|
|
200
|
+
`)
|
|
201
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// sdd — Spec-Driven Development CLI
|
|
3
|
+
// Unified entry point for both npm package and local development
|
|
4
|
+
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const fs = require('fs')
|
|
7
|
+
const { spawn } = require('child_process')
|
|
8
|
+
|
|
9
|
+
// Determine if we're in npm package or local development
|
|
10
|
+
const isNpmPackage = fs.existsSync(path.join(__dirname, 'lib/bmad.js'))
|
|
11
|
+
const libDir = isNpmPackage ? path.join(__dirname, 'lib') : path.join(__dirname, '..', 'lib')
|
|
12
|
+
|
|
13
|
+
// Dynamic requires
|
|
14
|
+
const init = require(path.join(libDir, 'init'))
|
|
15
|
+
const bmad = require(path.join(libDir, 'bmad/index'))
|
|
16
|
+
const config = require(path.join(libDir, 'config'))
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2)
|
|
19
|
+
const cmd = args[0]
|
|
20
|
+
|
|
21
|
+
// PowerShell detection - use correct command for platform
|
|
22
|
+
function getPowerShellCommand() {
|
|
23
|
+
if (process.platform === 'win32') {
|
|
24
|
+
// Windows: try powershell.exe first (always available), then pwsh
|
|
25
|
+
const system32 = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'
|
|
26
|
+
if (fs.existsSync(system32)) {
|
|
27
|
+
return 'powershell'
|
|
28
|
+
}
|
|
29
|
+
return 'powershell' // Fallback to whatever is in PATH
|
|
30
|
+
}
|
|
31
|
+
return 'pwsh' // macOS/Linux: PowerShell Core
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function runPowerShellCommand(scriptPath, args = []) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const psCmd = getPowerShellCommand()
|
|
37
|
+
const psArgs = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', scriptPath, ...args]
|
|
38
|
+
|
|
39
|
+
const ps = spawn(psCmd, psArgs, {
|
|
40
|
+
stdio: 'inherit',
|
|
41
|
+
shell: false,
|
|
42
|
+
windowsHide: true
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
ps.on('error', (err) => {
|
|
46
|
+
reject(err)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
ps.on('close', (code) => {
|
|
50
|
+
resolve(code)
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
switch (cmd) {
|
|
56
|
+
case 'init':
|
|
57
|
+
init.run()
|
|
58
|
+
break
|
|
59
|
+
|
|
60
|
+
case 'bmad': {
|
|
61
|
+
const input = args.slice(1).join(' ')
|
|
62
|
+
if (!input) {
|
|
63
|
+
console.error('Usage: sdd bmad <feature description>')
|
|
64
|
+
console.error('Example: sdd bmad "landing page for my SaaS product"')
|
|
65
|
+
console.error('')
|
|
66
|
+
console.error('For interactive mode (9-question interview), use Claude Code:')
|
|
67
|
+
console.error(' claude "/sdd-bmad landing page for my SaaS"')
|
|
68
|
+
process.exit(1)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Try interactive mode via PowerShell
|
|
72
|
+
const commandsDir = isNpmPackage
|
|
73
|
+
? path.join(libDir, 'bundle', '.claude', 'commands')
|
|
74
|
+
: path.join(__dirname, '..', '.claude', 'commands')
|
|
75
|
+
const bmadScript = path.join(commandsDir, 'sdd-bmad.md')
|
|
76
|
+
|
|
77
|
+
if (fs.existsSync(bmadScript)) {
|
|
78
|
+
try {
|
|
79
|
+
await runPowerShellCommand(bmadScript, [input])
|
|
80
|
+
process.exit(0)
|
|
81
|
+
} catch (err) {
|
|
82
|
+
// PowerShell not available - fall back to non-interactive
|
|
83
|
+
console.warn('[sdd] PowerShell not available, using fast mode.')
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Non-interactive mode: generate brief directly
|
|
88
|
+
const session = bmad.init(input)
|
|
89
|
+
const result = bmad.generateBrief()
|
|
90
|
+
console.log('[OK] sdd/brief.md created.')
|
|
91
|
+
console.log(`Confidence: ${result.confidence.score}/100 — ${result.confidence.level}`)
|
|
92
|
+
console.log('')
|
|
93
|
+
if (result.confidence.score < 70) {
|
|
94
|
+
console.log('Tip: Use Claude Code "/sdd-bmad" for interactive mode with 9 questions.')
|
|
95
|
+
}
|
|
96
|
+
console.log('Next: claude "/sdd-spec" to generate SPEC.md')
|
|
97
|
+
break
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
case 'spec':
|
|
101
|
+
console.log('Run: claude "/sdd-spec" to generate SPEC.md')
|
|
102
|
+
console.log('(SPEC generation requires Claude Code for best results)')
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
case 'tasks':
|
|
106
|
+
console.log('Run: claude "/sdd-tasks" to generate TASKS.md')
|
|
107
|
+
console.log('(Task generation requires Claude Code)')
|
|
108
|
+
break
|
|
109
|
+
|
|
110
|
+
case 'cook':
|
|
111
|
+
console.log('Run: claude "/sdd-cook" to execute tasks')
|
|
112
|
+
console.log('(Implementation requires Claude Code)')
|
|
113
|
+
break
|
|
114
|
+
|
|
115
|
+
case 'converge':
|
|
116
|
+
console.log('Run: pwsh run-converge.ps1 -Strict to run converge')
|
|
117
|
+
console.log('')
|
|
118
|
+
console.log('For full validation:')
|
|
119
|
+
console.log(' pwsh run-converge.ps1 -Url http://localhost:3000 -Strict')
|
|
120
|
+
break
|
|
121
|
+
|
|
122
|
+
case 'status':
|
|
123
|
+
config.printStatus()
|
|
124
|
+
break
|
|
125
|
+
|
|
126
|
+
case 'help':
|
|
127
|
+
default:
|
|
128
|
+
if (cmd && cmd !== 'help') {
|
|
129
|
+
console.error(`Unknown command: ${cmd}\n`)
|
|
130
|
+
}
|
|
131
|
+
console.log(`
|
|
132
|
+
sdd — Spec-Driven Development CLI
|
|
133
|
+
|
|
134
|
+
Output Structure (v1.1.0+):
|
|
135
|
+
sdd/ All generated artifacts
|
|
136
|
+
brief.md Phase 0: BMAD brief
|
|
137
|
+
SPEC.md Phase 2: Specification
|
|
138
|
+
tasks/ Phase 3: Task directories
|
|
139
|
+
converge/ Phase 5: Validation reports
|
|
140
|
+
|
|
141
|
+
Usage:
|
|
142
|
+
sdd init Initialize SDD pipeline
|
|
143
|
+
sdd bmad <desc> Phase 0: Generate brief (fast mode)
|
|
144
|
+
sdd status Show pipeline status
|
|
145
|
+
sdd help Show this help
|
|
146
|
+
|
|
147
|
+
Full Pipeline (requires Claude Code):
|
|
148
|
+
claude "/sdd-bmad" Interactive 9-question interview (high confidence)
|
|
149
|
+
claude "/sdd-spec" Generate SPEC.md
|
|
150
|
+
claude "/sdd-tasks" Generate task directory
|
|
151
|
+
claude "/sdd-cook" Execute implementation
|
|
152
|
+
pwsh run-converge.ps1 Phase 5: Validate
|
|
153
|
+
|
|
154
|
+
Notes:
|
|
155
|
+
- Interactive commands require Claude Code CLI
|
|
156
|
+
- Run "npm install -g @anthropic-ai/claude-code" to enable Claude Code
|
|
157
|
+
- Without Claude Code, use fast mode for quick briefs (lower confidence)
|
|
158
|
+
`)
|
|
159
|
+
}
|