sdd-pipeline 1.0.1 → 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/lib/bmad.js +60 -0
- package/lib/config.js +35 -0
- package/lib/init.js +126 -0
- package/lib/sanitize.js +10 -0
- package/package.json +2 -1
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 }
|
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