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.
@@ -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 }