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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BMAD Synthesis — merges three agent perspectives into a unified brief.
|
|
3
|
+
* Uses single-pass LLM synthesis mode (Claude Code analyzes all 3 perspectives).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const pmAgent = require('./agents/pm')
|
|
7
|
+
const architectAgent = require('./agents/architect')
|
|
8
|
+
const uxAgent = require('./agents/ux')
|
|
9
|
+
const confidenceCalc = require('./confidence')
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Synthesize a full BMAD brief from interview state.
|
|
13
|
+
* @param {object} state - { input, answers, inversion }
|
|
14
|
+
* @returns {{ brief: string, confidence: object }}
|
|
15
|
+
*/
|
|
16
|
+
function synthesize(state) {
|
|
17
|
+
// Run all three agents in parallel (synchronous in Node.js — single pass)
|
|
18
|
+
const pmResult = pmAgent.analyze(state)
|
|
19
|
+
const architectResult = architectAgent.analyze(state)
|
|
20
|
+
const uxResult = uxAgent.analyze(state)
|
|
21
|
+
|
|
22
|
+
// Calculate confidence
|
|
23
|
+
const confidence = confidenceCalc.calculate(state)
|
|
24
|
+
|
|
25
|
+
// Build problem-first note if inversion was applied
|
|
26
|
+
const inversionBlock = state.inversion?.inversionNote
|
|
27
|
+
? `\n## ⚠️ Problem-First Inversion Applied\n${state.inversion.inversionNote}\n`
|
|
28
|
+
: ''
|
|
29
|
+
|
|
30
|
+
const synthesis = `${inversionBlock}
|
|
31
|
+
## Synthesis
|
|
32
|
+
|
|
33
|
+
**What we're building:** ${synthesizeWhat(state)}
|
|
34
|
+
|
|
35
|
+
**What we're NOT building:** ${synthesizeNotBuilding(state)}
|
|
36
|
+
|
|
37
|
+
## Open Questions
|
|
38
|
+
| # | Question | Impact |
|
|
39
|
+
|---|----------|--------|
|
|
40
|
+
| 1 | [Open question from interview] | [Blocking?] |
|
|
41
|
+
| 2 | [Another unresolved item] | [Clarify before implementation] |
|
|
42
|
+
`
|
|
43
|
+
|
|
44
|
+
// Concatenate all sections
|
|
45
|
+
const brief = `${pmResult.section}
|
|
46
|
+
|
|
47
|
+
${architectResult.section}
|
|
48
|
+
|
|
49
|
+
${uxResult.section}
|
|
50
|
+
|
|
51
|
+
${synthesis}
|
|
52
|
+
|
|
53
|
+
## Confidence Score
|
|
54
|
+
**Score:** ${confidence.score}/100 — ${confidence.level}
|
|
55
|
+
${confidenceCalc.getWarning(confidence.score)}
|
|
56
|
+
|
|
57
|
+
_Scoring breakdown:_
|
|
58
|
+
${formatFactors(confidence.factors)}
|
|
59
|
+
|
|
60
|
+
## Prompt Injection Check
|
|
61
|
+
- Input was sanitized before processing
|
|
62
|
+
- No system prompt overrides accepted
|
|
63
|
+
`
|
|
64
|
+
|
|
65
|
+
return { brief, confidence }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function synthesizeWhat(state) {
|
|
69
|
+
const input = state.input || ''
|
|
70
|
+
const problem = state.inversion?.problem || input
|
|
71
|
+
const q1 = state.answers?.q1 || ''
|
|
72
|
+
const q4 = state.answers?.q4 || ''
|
|
73
|
+
|
|
74
|
+
const problemStatement = q1 || problem
|
|
75
|
+
const approach = q4
|
|
76
|
+
? q4.split('.')[0] + '.'
|
|
77
|
+
: `Build the feature described as: ${input.slice(0, 80)}`
|
|
78
|
+
|
|
79
|
+
return `${problemStatement}. ${approach}`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function synthesizeNotBuilding(state) {
|
|
83
|
+
const exclusions = [
|
|
84
|
+
'Second-order features — ship core first, add polish in v2',
|
|
85
|
+
'Nice-to-have integrations — MVP ships with zero mandatory external dependencies',
|
|
86
|
+
'Re-architecture of existing systems — integrate within current constraints',
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
// Extract explicit exclusions from Q9 or other answers
|
|
90
|
+
const q9 = state.answers?.q9 || ''
|
|
91
|
+
if (q9.length > 0) {
|
|
92
|
+
const dynamic = q9
|
|
93
|
+
.split(/[,;\n]/)
|
|
94
|
+
.filter((l) => l.toLowerCase().includes('not ') || l.toLowerCase().includes("won't "))
|
|
95
|
+
.slice(0, 2)
|
|
96
|
+
.map((l) => l.trim())
|
|
97
|
+
exclusions.unshift(...dynamic)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return exclusions.map((e, i) => `${i + 1}. ${e}`).join('\n')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function formatFactors(factors) {
|
|
104
|
+
return Object.entries(factors)
|
|
105
|
+
.map(([key, val]) => ` - **${key}:** ${val.points} pts — ${val.note}`)
|
|
106
|
+
.join('\n')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = { synthesize }
|
|
@@ -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 }
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
|
|
4
|
+
const BUNDLE_DIR = path.join(__dirname, 'bundle')
|
|
5
|
+
|
|
6
|
+
// All command files to copy
|
|
7
|
+
const CMD_FILES = [
|
|
8
|
+
'sdd.md', // Unified entry point
|
|
9
|
+
'sdd-bmad.md', // Phase 0: BMAD
|
|
10
|
+
'sdd-spec.md', // Phase 2: SPEC
|
|
11
|
+
'sdd-tasks.md', // Phase 3: TASKS
|
|
12
|
+
'sdd-cook.md', // Phase 4: COOK + Implement (unified)
|
|
13
|
+
'sdd-converge.md', // Phase 5: CONVERGE
|
|
14
|
+
'sdd-check.md', // Real-time validation
|
|
15
|
+
'sdd-task-status.md', // Task status update
|
|
16
|
+
'sdd-init.md' // Initialization
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
const FILES = [
|
|
20
|
+
'.sdd/config.json',
|
|
21
|
+
'.sdd/smoke-test.ps1',
|
|
22
|
+
'.sdd/tool-versions.json',
|
|
23
|
+
'templates/CONSTITUTION.template.md',
|
|
24
|
+
'templates/SPEC.template.md',
|
|
25
|
+
'templates/PLAN.template.md',
|
|
26
|
+
'templates/TASKS.template.md',
|
|
27
|
+
'templates/BMAD-brief.template.md',
|
|
28
|
+
'templates/task-master.template.md',
|
|
29
|
+
'templates/task-detail.template.md',
|
|
30
|
+
'templates/README.md',
|
|
31
|
+
'converge/validation.md',
|
|
32
|
+
'CLAUDE.md',
|
|
33
|
+
'run-converge.ps1',
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
const DIRS = ['.sdd', 'templates', 'converge', 'commands', 'extensions', 'sdd', 'skills']
|
|
37
|
+
|
|
38
|
+
function ensureDir(dir) {
|
|
39
|
+
if (!fs.existsSync(dir)) {
|
|
40
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function copyFile(src, dest) {
|
|
45
|
+
ensureDir(path.dirname(dest))
|
|
46
|
+
if (fs.existsSync(dest)) {
|
|
47
|
+
console.log(` [SKIP] ${dest} already exists`)
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const content = fs.readFileSync(src, 'utf8')
|
|
52
|
+
fs.writeFileSync(dest, content, 'utf8')
|
|
53
|
+
console.log(` [COPY] ${dest}`)
|
|
54
|
+
return true
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.log(` [WARN] Failed to copy ${src}: ${err.message}`)
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function copyDirRecursive(src, dest) {
|
|
62
|
+
ensureDir(dest)
|
|
63
|
+
if (!fs.existsSync(src)) {
|
|
64
|
+
console.log(` [WARN] Source directory not found: ${src}`)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
const entries = fs.readdirSync(src, { withFileTypes: true })
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
const srcPath = path.join(src, entry.name)
|
|
70
|
+
const destPath = path.join(dest, entry.name)
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
copyDirRecursive(srcPath, destPath)
|
|
73
|
+
} else {
|
|
74
|
+
copyFile(srcPath, destPath)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function run() {
|
|
80
|
+
console.log('Initializing SDD pipeline...')
|
|
81
|
+
console.log('')
|
|
82
|
+
|
|
83
|
+
if (!fs.existsSync(BUNDLE_DIR)) {
|
|
84
|
+
console.error('ERROR: Bundle directory not found. Is the package installed correctly?')
|
|
85
|
+
console.error('Try: npm install -g sdd-pipeline')
|
|
86
|
+
process.exit(1)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Ensure output directories exist
|
|
90
|
+
for (const dir of DIRS) {
|
|
91
|
+
ensureDir(dir)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Copy config files
|
|
95
|
+
console.log('Copying config files...')
|
|
96
|
+
for (const file of FILES) {
|
|
97
|
+
const src = path.join(BUNDLE_DIR, file)
|
|
98
|
+
const dest = path.join(process.cwd(), file)
|
|
99
|
+
if (fs.existsSync(src)) {
|
|
100
|
+
copyFile(src, dest)
|
|
101
|
+
} else {
|
|
102
|
+
console.log(` [WARN] ${src} not found in bundle`)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Copy Claude Code commands
|
|
107
|
+
console.log('')
|
|
108
|
+
console.log('Copying Claude Code commands...')
|
|
109
|
+
ensureDir('.claude/commands')
|
|
110
|
+
let cmdCopied = 0
|
|
111
|
+
for (const cmd of CMD_FILES) {
|
|
112
|
+
const src = path.join(BUNDLE_DIR, '.claude/commands', cmd)
|
|
113
|
+
const dest = path.join(process.cwd(), '.claude/commands', cmd)
|
|
114
|
+
if (fs.existsSync(src)) {
|
|
115
|
+
if (copyFile(src, dest)) {
|
|
116
|
+
cmdCopied++
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
// Try alternative path for npm package
|
|
120
|
+
const altSrc = path.join(BUNDLE_DIR, '.claude', 'commands', cmd)
|
|
121
|
+
if (fs.existsSync(altSrc)) {
|
|
122
|
+
if (copyFile(altSrc, dest)) {
|
|
123
|
+
cmdCopied++
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
console.log(` [WARN] Command not found: ${cmd}`)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (cmdCopied === 0) {
|
|
131
|
+
console.log(' [INFO] No new commands copied (all exist)')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Copy GitHub workflows
|
|
135
|
+
console.log('')
|
|
136
|
+
console.log('Copying GitHub workflows...')
|
|
137
|
+
ensureDir('.github/workflows')
|
|
138
|
+
const workflowSrc = path.join(BUNDLE_DIR, '.github/workflows/converge.yml')
|
|
139
|
+
const workflowDest = path.join(process.cwd(), '.github/workflows/converge.yml')
|
|
140
|
+
if (fs.existsSync(workflowSrc)) {
|
|
141
|
+
copyFile(workflowSrc, workflowDest)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Copy commands directory
|
|
145
|
+
console.log('')
|
|
146
|
+
console.log('Copying standalone commands...')
|
|
147
|
+
const commandsDir = path.join(BUNDLE_DIR, 'commands')
|
|
148
|
+
if (fs.existsSync(commandsDir)) {
|
|
149
|
+
const entries = fs.readdirSync(commandsDir, { withFileTypes: true })
|
|
150
|
+
for (const entry of entries) {
|
|
151
|
+
if (entry.isFile()) {
|
|
152
|
+
const src = path.join(commandsDir, entry.name)
|
|
153
|
+
const dest = path.join(process.cwd(), 'commands', entry.name)
|
|
154
|
+
copyFile(src, dest)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Copy extensions
|
|
160
|
+
console.log('')
|
|
161
|
+
console.log('Copying extensions...')
|
|
162
|
+
for (const ext of ['sdd-api', 'sdd-frontend', 'sdd-backend']) {
|
|
163
|
+
const extDir = path.join(BUNDLE_DIR, 'extensions', ext)
|
|
164
|
+
if (fs.existsSync(extDir)) {
|
|
165
|
+
copyDirRecursive(extDir, path.join(process.cwd(), 'extensions', ext))
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Copy extensions README
|
|
170
|
+
const extReadmeSrc = path.join(BUNDLE_DIR, 'extensions/README.md')
|
|
171
|
+
const extReadmeDest = path.join(process.cwd(), 'extensions/README.md')
|
|
172
|
+
if (fs.existsSync(extReadmeSrc)) {
|
|
173
|
+
copyFile(extReadmeSrc, extReadmeDest)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Copy skills directory (for Phase 4 implementation)
|
|
177
|
+
console.log('')
|
|
178
|
+
console.log('Copying skills...')
|
|
179
|
+
const skillsDir = path.join(BUNDLE_DIR, 'skills')
|
|
180
|
+
if (fs.existsSync(skillsDir)) {
|
|
181
|
+
copyDirRecursive(skillsDir, path.join(process.cwd(), 'skills'))
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Create sdd/ directory structure if not exists
|
|
185
|
+
ensureDir('sdd/tasks')
|
|
186
|
+
ensureDir('sdd/converge')
|
|
187
|
+
|
|
188
|
+
console.log('')
|
|
189
|
+
console.log('[OK] SDD pipeline initialized.')
|
|
190
|
+
console.log('')
|
|
191
|
+
console.log('Directory structure:')
|
|
192
|
+
console.log(' skills/ # Implementation skills (Phase 4)')
|
|
193
|
+
console.log(' .claude/commands/ # Claude Code commands')
|
|
194
|
+
console.log(' sdd/ # Generated artifacts')
|
|
195
|
+
console.log('')
|
|
196
|
+
console.log('Next steps:')
|
|
197
|
+
console.log(' 1. claude "/sdd-bmad <your feature idea>" # Interactive (recommended)')
|
|
198
|
+
console.log(' 2. sdd bmad "<idea>" # Fast mode (lower confidence)')
|
|
199
|
+
console.log('')
|
|
200
|
+
console.log('For best results, install Claude Code:')
|
|
201
|
+
console.log(' npm install -g @anthropic-ai/claude-code')
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = { run }
|
|
@@ -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 }
|