sdd-pipeline 1.2.2 → 1.2.3
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/bundle/.claude/commands/sdd-tasks.md +183 -62
- package/lib/bundle/commands/sdd-tasks.ps1 +86 -331
- package/lib/bundle/generate-tasks.ps1 +450 -0
- package/lib/generate-tasks.ps1 +450 -0
- package/package.json +1 -1
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
# SDD Task Generation - Shared Logic
|
|
2
|
+
# Used by: .claude/commands/sdd-tasks.md, commands/sdd-tasks.ps1, skills/sdd-tasks/
|
|
3
|
+
# Version: 1.0.0
|
|
4
|
+
|
|
5
|
+
param(
|
|
6
|
+
[string]$SpecPath = "sdd/SPEC.md",
|
|
7
|
+
[string]$OutputDir = "sdd/tasks"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
$ErrorActionPreference = 'Stop'
|
|
11
|
+
|
|
12
|
+
# ============================================================
|
|
13
|
+
# Utility Functions
|
|
14
|
+
# ============================================================
|
|
15
|
+
|
|
16
|
+
function Write-SddHeader {
|
|
17
|
+
param([string]$Text)
|
|
18
|
+
Write-Host "" -ForegroundColor Cyan
|
|
19
|
+
Write-Host "=== $Text ===" -ForegroundColor Cyan
|
|
20
|
+
Write-Host ""
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function Write-SddSuccess {
|
|
24
|
+
param([string]$Text)
|
|
25
|
+
Write-Host "[OK] $Text" -ForegroundColor Green
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function Write-SddError {
|
|
29
|
+
param([string]$Text)
|
|
30
|
+
Write-Host "[ERROR] $Text" -ForegroundColor Red
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function Write-SddWarn {
|
|
34
|
+
param([string]$Text)
|
|
35
|
+
Write-Host "[WARN] $Text" -ForegroundColor Yellow
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function Write-SddInfo {
|
|
39
|
+
param([string]$Text)
|
|
40
|
+
Write-Host " $Text" -ForegroundColor Gray
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# ============================================================
|
|
44
|
+
# Parse SPEC.md
|
|
45
|
+
# ============================================================
|
|
46
|
+
|
|
47
|
+
function Get-SpecInfo {
|
|
48
|
+
param([string]$Path)
|
|
49
|
+
|
|
50
|
+
$content = Get-Content $Path -Raw -Encoding UTF8
|
|
51
|
+
$spec = @{
|
|
52
|
+
Name = "Project"
|
|
53
|
+
Features = @()
|
|
54
|
+
TestCategories = @()
|
|
55
|
+
UserStories = @()
|
|
56
|
+
Sections = @{}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Extract project name
|
|
60
|
+
if ($content -match "^#\s+SPEC\.md\s*[-—]\s*(.+)$", $multiline) {
|
|
61
|
+
$spec.Name = $Matches[1].Trim()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Extract features from Section 4.1
|
|
65
|
+
if ($content -match "(?s)### 4\.1\s+Core Features(.+?)(?=###|\Z)") {
|
|
66
|
+
$featuresSection = $Matches[1]
|
|
67
|
+
|
|
68
|
+
# Match feature blocks
|
|
69
|
+
$featureBlocks = [regex]::Matches($featuresSection, '(?s)####\s+\[([^\]]+)\](.+?)(?=####|\Z)')
|
|
70
|
+
foreach ($block in $featureBlocks) {
|
|
71
|
+
$featureName = $block.Groups[1].Value.Trim()
|
|
72
|
+
$featureContent = $block.Groups[2].Value
|
|
73
|
+
|
|
74
|
+
# Extract acceptance criteria
|
|
75
|
+
$criteria = @()
|
|
76
|
+
$criteriaMatches = [regex]::Matches($featureContent, '\[\[AC-(\w+)\]\]\s*(.+)')
|
|
77
|
+
foreach ($c in $criteriaMatches) {
|
|
78
|
+
$criteria += @{
|
|
79
|
+
Id = $c.Groups[1].Value
|
|
80
|
+
Text = $c.Groups[2].Value.Trim()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
$spec.Features += @{
|
|
85
|
+
Name = $featureName
|
|
86
|
+
Content = $featureContent
|
|
87
|
+
Criteria = $criteria
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Extract test categories from Section 9.1
|
|
93
|
+
if ($content -match "(?s)### 9\.1\s+Test Categories(.+?)(?=###|\Z)") {
|
|
94
|
+
$testSection = $Matches[1]
|
|
95
|
+
|
|
96
|
+
if ($testSection -match 'Unit\s+Tests') { $spec.TestCategories += 'Unit' }
|
|
97
|
+
if ($testSection -match 'Integration\s+Tests') { $spec.TestCategories += 'Integration' }
|
|
98
|
+
if ($testSection -match 'E2E\s+Tests') { $spec.TestCategories += 'E2E' }
|
|
99
|
+
if ($testSection -match 'Accessibility\s+Tests') { $spec.TestCategories += 'Accessibility' }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Extract user stories from Section 2
|
|
103
|
+
if ($content -match "(?s)### 2\.1\s+Must Have(.+?)(?=###|\Z)") {
|
|
104
|
+
$storiesSection = $Matches[1]
|
|
105
|
+
$storyMatches = [regex]::Matches($storiesSection, '\*\*As a\*\*.+?(?=\n-|\n\n|\Z)', [System.Text.RegularExpressions.RegexOptions]::Singleline)
|
|
106
|
+
foreach ($story in $storyMatches) {
|
|
107
|
+
$storyText = $story.Value.Trim()
|
|
108
|
+
if ($storyText) {
|
|
109
|
+
$spec.UserStories += $storyText
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return $spec
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# ============================================================
|
|
118
|
+
# Generate Tasks
|
|
119
|
+
# ============================================================
|
|
120
|
+
|
|
121
|
+
function New-TaskFile {
|
|
122
|
+
param(
|
|
123
|
+
[string]$Id,
|
|
124
|
+
[string]$Name,
|
|
125
|
+
[string]$Phase,
|
|
126
|
+
[string]$Estimate,
|
|
127
|
+
[string[]]$DependsOn,
|
|
128
|
+
[string[]]$SpecSections,
|
|
129
|
+
[string]$Description,
|
|
130
|
+
[string]$Content
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
$safeName = $Name -replace '[^a-zA-Z0-9]', '-' -replace '-+', '-'
|
|
134
|
+
$filename = "$Id-$safeName.md"
|
|
135
|
+
|
|
136
|
+
$frontmatter = @"
|
|
137
|
+
---
|
|
138
|
+
name: $Id-$safeName
|
|
139
|
+
description: "$Description"
|
|
140
|
+
phase: $Phase
|
|
141
|
+
estimate: $Estimate
|
|
142
|
+
spec_sections: [$($SpecSections -join ', ')]
|
|
143
|
+
depends_on: [$($DependsOn -join ', ')]
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
"@
|
|
147
|
+
|
|
148
|
+
$fullContent = $frontmatter + "# $Id`: $Name`n`n" + $Content
|
|
149
|
+
return @{
|
|
150
|
+
Filename = $filename
|
|
151
|
+
Content = $fullContent
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function Get-TaskFiles {
|
|
156
|
+
param(
|
|
157
|
+
[object]$Spec,
|
|
158
|
+
[int]$StartPhase = 2
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
$tasks = @()
|
|
162
|
+
$phase = $StartPhase
|
|
163
|
+
$prevTask = $null
|
|
164
|
+
|
|
165
|
+
# Feature tasks
|
|
166
|
+
foreach ($feature in $Spec.Features) {
|
|
167
|
+
$dependsOn = if ($prevTask) { @($prevTask.Id) } else { @() }
|
|
168
|
+
|
|
169
|
+
$task = New-TaskFile `
|
|
170
|
+
-Id "T-00$phase" `
|
|
171
|
+
-Name $feature.Name `
|
|
172
|
+
-Phase $phase `
|
|
173
|
+
-Estimate "1h" `
|
|
174
|
+
-DependsOn $dependsOn `
|
|
175
|
+
-SpecSections @('"4.1"') `
|
|
176
|
+
-Description "Implement: $($feature.Name)" `
|
|
177
|
+
-Content @"
|
|
178
|
+
|
|
179
|
+
## Files to Modify/Create
|
|
180
|
+
|
|
181
|
+
- [List files based on feature requirements]
|
|
182
|
+
|
|
183
|
+
## Implementation Steps
|
|
184
|
+
|
|
185
|
+
1. [Step 1]
|
|
186
|
+
2. [Step 2]
|
|
187
|
+
3. [Step 3]
|
|
188
|
+
|
|
189
|
+
## Verification
|
|
190
|
+
|
|
191
|
+
$($feature.Criteria | ForEach-Object { "- [ ] [[AC-$($_.Id)]] $($_.Text)" } -join "`n")
|
|
192
|
+
|
|
193
|
+
"@
|
|
194
|
+
|
|
195
|
+
$tasks += $task
|
|
196
|
+
$prevTask = $tasks[-1]
|
|
197
|
+
$phase++
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
# Test tasks
|
|
201
|
+
foreach ($category in $Spec.TestCategories) {
|
|
202
|
+
$dependsOn = if ($prevTask) { @($prevTask.Id) } else { @() }
|
|
203
|
+
|
|
204
|
+
switch ($category) {
|
|
205
|
+
'Unit' {
|
|
206
|
+
$testContent = @"
|
|
207
|
+
|
|
208
|
+
## Test Files to Create
|
|
209
|
+
|
|
210
|
+
- `tests/unit/*.test.js`
|
|
211
|
+
|
|
212
|
+
## Unit Tests
|
|
213
|
+
|
|
214
|
+
- Test happy path
|
|
215
|
+
- Test error path
|
|
216
|
+
- Test edge cases
|
|
217
|
+
|
|
218
|
+
## Verification
|
|
219
|
+
|
|
220
|
+
- [ ] All unit tests pass
|
|
221
|
+
- [ ] Code coverage > 80%
|
|
222
|
+
"@
|
|
223
|
+
}
|
|
224
|
+
'Integration' {
|
|
225
|
+
$testContent = @"
|
|
226
|
+
|
|
227
|
+
## Test Files to Create
|
|
228
|
+
|
|
229
|
+
- `tests/integration/*.test.js`
|
|
230
|
+
|
|
231
|
+
## Integration Tests
|
|
232
|
+
|
|
233
|
+
$($Spec.UserStories | ForEach-Object { "- $_" } -join "`n")
|
|
234
|
+
|
|
235
|
+
## Verification
|
|
236
|
+
|
|
237
|
+
- [ ] All integration tests pass
|
|
238
|
+
- [ ] Components interact correctly
|
|
239
|
+
"@
|
|
240
|
+
}
|
|
241
|
+
'E2E' {
|
|
242
|
+
$testContent = @"
|
|
243
|
+
|
|
244
|
+
## Test Files to Create
|
|
245
|
+
|
|
246
|
+
- `tests/e2e/*.test.js`
|
|
247
|
+
|
|
248
|
+
## E2E Scenarios
|
|
249
|
+
|
|
250
|
+
- Complete user journey test
|
|
251
|
+
- Error recovery test
|
|
252
|
+
|
|
253
|
+
## Verification
|
|
254
|
+
|
|
255
|
+
- [ ] All E2E tests pass
|
|
256
|
+
- [ ] Full flows work end-to-end
|
|
257
|
+
"@
|
|
258
|
+
}
|
|
259
|
+
'Accessibility' {
|
|
260
|
+
$testContent = @"
|
|
261
|
+
|
|
262
|
+
## Accessibility Verification
|
|
263
|
+
|
|
264
|
+
- Keyboard navigation
|
|
265
|
+
- Screen reader support
|
|
266
|
+
- Color contrast
|
|
267
|
+
|
|
268
|
+
## Tools
|
|
269
|
+
|
|
270
|
+
- axe-core
|
|
271
|
+
- Lighthouse
|
|
272
|
+
|
|
273
|
+
## Verification
|
|
274
|
+
|
|
275
|
+
- [ ] axe-core: 0 violations
|
|
276
|
+
- [ ] Keyboard navigation works
|
|
277
|
+
- [ ] Color contrast meets WCAG AA
|
|
278
|
+
"@
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
$task = New-TaskFile `
|
|
283
|
+
-Id "T-00$phase" `
|
|
284
|
+
-Name "$category Tests" `
|
|
285
|
+
-Phase $phase `
|
|
286
|
+
-Estimate "1h" `
|
|
287
|
+
-DependsOn $dependsOn `
|
|
288
|
+
-SpecSections @('"9.1"') `
|
|
289
|
+
-Description "Write and run $category tests" `
|
|
290
|
+
-Content $testContent
|
|
291
|
+
|
|
292
|
+
$tasks += $task
|
|
293
|
+
$prevTask = $tasks[-1]
|
|
294
|
+
$phase++
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
# Polish task
|
|
298
|
+
$dependsOn = if ($prevTask) { @($prevTask.Id) } else { @() }
|
|
299
|
+
$tasks += New-TaskFile `
|
|
300
|
+
-Id "T-00$phase" `
|
|
301
|
+
-Name "Final Polish" `
|
|
302
|
+
-Phase $phase `
|
|
303
|
+
-Estimate "1h" `
|
|
304
|
+
-DependsOn $dependsOn `
|
|
305
|
+
-SpecSections @('"4.1"') `
|
|
306
|
+
-Description "Final polish, styling, edge cases" `
|
|
307
|
+
-Content @"
|
|
308
|
+
|
|
309
|
+
## Final Checks
|
|
310
|
+
|
|
311
|
+
- [ ] All features work correctly
|
|
312
|
+
- [ ] No console errors
|
|
313
|
+
- [ ] Responsive design
|
|
314
|
+
- [ ] Edge cases handled
|
|
315
|
+
|
|
316
|
+
## Verification
|
|
317
|
+
|
|
318
|
+
- [ ] Manual testing complete
|
|
319
|
+
- [ ] Code review passed
|
|
320
|
+
"@
|
|
321
|
+
|
|
322
|
+
$phase++
|
|
323
|
+
|
|
324
|
+
# Converge task
|
|
325
|
+
$dependsOn = @($tasks[-1].Id)
|
|
326
|
+
$tasks += New-TaskFile `
|
|
327
|
+
-Id "T-00$phase" `
|
|
328
|
+
-Name "Converge Validation" `
|
|
329
|
+
-Phase $phase `
|
|
330
|
+
-Estimate "1h" `
|
|
331
|
+
-DependsOn $dependsOn `
|
|
332
|
+
-SpecSections @('"9"') `
|
|
333
|
+
-Description "Run converge validation" `
|
|
334
|
+
-Content @"
|
|
335
|
+
|
|
336
|
+
## Converge Steps
|
|
337
|
+
|
|
338
|
+
1. Run: `pwsh run-converge.ps1 -Strict`
|
|
339
|
+
2. Fix any issues
|
|
340
|
+
3. Verify all checks pass
|
|
341
|
+
|
|
342
|
+
## Verification
|
|
343
|
+
|
|
344
|
+
- [ ] Converge passes
|
|
345
|
+
- [ ] Ready for deployment
|
|
346
|
+
"@
|
|
347
|
+
|
|
348
|
+
return $tasks
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function New-MasterTasks {
|
|
352
|
+
param(
|
|
353
|
+
[string]$ProjectName,
|
|
354
|
+
[object[]]$Tasks,
|
|
355
|
+
[string[]]$TestCategories
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
$content = @"
|
|
359
|
+
# MASTER-TASKS — $ProjectName
|
|
360
|
+
|
|
361
|
+
## Task Index
|
|
362
|
+
|
|
363
|
+
| ID | Name | Estimate | Status |
|
|
364
|
+
|----|------|----------|--------|
|
|
365
|
+
"@
|
|
366
|
+
|
|
367
|
+
foreach ($task in $Tasks) {
|
|
368
|
+
$content += "| $($task.Id) | $($task.Name) | 1h | [ ] |`n"
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
$total = $Tasks.Count
|
|
372
|
+
$content += @"
|
|
373
|
+
|
|
374
|
+
## Progress
|
|
375
|
+
|
|
376
|
+
- Completed: 0 / $total
|
|
377
|
+
- In Progress: 0
|
|
378
|
+
- Pending: $total
|
|
379
|
+
|
|
380
|
+
## Test Categories
|
|
381
|
+
|
|
382
|
+
$($TestCategories -join ', ')
|
|
383
|
+
|
|
384
|
+
## Notes
|
|
385
|
+
|
|
386
|
+
Generated from: SPEC.md
|
|
387
|
+
"@
|
|
388
|
+
|
|
389
|
+
return $content
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
# ============================================================
|
|
393
|
+
# Main Execution
|
|
394
|
+
# ============================================================
|
|
395
|
+
|
|
396
|
+
Write-SddHeader "SDD Tasks Generator - Phase 3"
|
|
397
|
+
|
|
398
|
+
# Check SPEC exists
|
|
399
|
+
if (-not (Test-Path $SpecPath)) {
|
|
400
|
+
Write-SddError "SPEC.md not found at $SpecPath"
|
|
401
|
+
Write-SddWarn "Run /sdd-spec first to generate SPEC.md"
|
|
402
|
+
exit 1
|
|
403
|
+
}
|
|
404
|
+
Write-SddSuccess "Found: $SpecPath"
|
|
405
|
+
|
|
406
|
+
# Parse SPEC
|
|
407
|
+
Write-Host "Parsing SPEC.md..." -ForegroundColor Gray
|
|
408
|
+
$spec = Get-SpecInfo -Path $SpecPath
|
|
409
|
+
Write-SddSuccess "Project: $($spec.Name)"
|
|
410
|
+
Write-SddSuccess "Features: $($spec.Features.Count)"
|
|
411
|
+
Write-SddSuccess "Test categories: $($spec.TestCategories -join ', ')"
|
|
412
|
+
|
|
413
|
+
# Create output directory
|
|
414
|
+
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
|
415
|
+
$safeName = $spec.Name -replace '[^a-zA-Z0-9]', '-' -replace '-+', '-'
|
|
416
|
+
$taskDir = "$OutputDir/task-$timestamp-$safeName"
|
|
417
|
+
|
|
418
|
+
if (-not (Test-Path $taskDir)) {
|
|
419
|
+
New-Item -ItemType Directory -Path $taskDir -Force | Out-Null
|
|
420
|
+
}
|
|
421
|
+
Write-SddSuccess "Created: $taskDir"
|
|
422
|
+
|
|
423
|
+
# Generate tasks
|
|
424
|
+
Write-Host ""
|
|
425
|
+
Write-Host "Generating tasks..." -ForegroundColor Gray
|
|
426
|
+
$tasks = Get-TaskFiles -Spec $spec
|
|
427
|
+
|
|
428
|
+
# Write task files
|
|
429
|
+
foreach ($task in $tasks) {
|
|
430
|
+
$filepath = Join-Path $taskDir $task.Filename
|
|
431
|
+
Set-Content -Path $filepath -Value $task.Content -Encoding UTF8
|
|
432
|
+
Write-SddInfo "[CREATED] $($task.Filename)"
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
# Write MASTER-TASKS.md
|
|
436
|
+
$masterContent = New-MasterTasks -ProjectName $spec.Name -Tasks $tasks -TestCategories $spec.TestCategories
|
|
437
|
+
$masterPath = Join-Path $taskDir "MASTER-TASKS.md"
|
|
438
|
+
Set-Content -Path $masterPath -Value $masterContent -Encoding UTF8
|
|
439
|
+
Write-SddInfo "[CREATED] MASTER-TASKS.md"
|
|
440
|
+
|
|
441
|
+
# Summary
|
|
442
|
+
Write-Host ""
|
|
443
|
+
Write-SddSuccess "Tasks generated successfully!"
|
|
444
|
+
Write-Host ""
|
|
445
|
+
Write-Host "Summary:" -ForegroundColor Cyan
|
|
446
|
+
Write-Host " Project: $($spec.Name)"
|
|
447
|
+
Write-Host " Tasks: $($tasks.Count)"
|
|
448
|
+
Write-Host " Directory: $taskDir"
|
|
449
|
+
Write-Host ""
|
|
450
|
+
Write-Host "Next: claude `/sdd-cook --all` or implement tasks manually" -ForegroundColor Cyan
|
package/package.json
CHANGED