sdd-pipeline 1.3.2 → 1.3.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/README.md
CHANGED
|
@@ -80,6 +80,7 @@ Raw Idea
|
|
|
80
80
|
|-------|---------|--------|------|
|
|
81
81
|
| 0 | `sdd bmad "idea"` | `sdd/brief.md` | None |
|
|
82
82
|
| 0 | `claude "/sdd-bmad"` | `sdd/brief.md` (interactive) | None |
|
|
83
|
+
| 0 | `claude "/sdd-bmad --batch answers.json"` | `sdd/brief.md` | None (CI/CD mode) |
|
|
83
84
|
| 2 | `claude "/sdd-spec"` | `sdd/SPEC.md` | Requires `sdd/brief.md` + confidence ≥20 |
|
|
84
85
|
| 3 | `claude "/sdd-tasks"` | `sdd/tasks/task-*/` | Requires `sdd/SPEC.md` |
|
|
85
86
|
| 4 | `claude "/sdd-cook"` | Code | Requires task files |
|
|
@@ -96,6 +97,7 @@ Raw Idea
|
|
|
96
97
|
| `sdd init` | — | `.sdd/`, `templates/`, `sdd/`, `skills/` | Initialize SDD pipeline. Run once per project. |
|
|
97
98
|
| `sdd bmad <desc>` | 0 | `sdd/brief.md` | Fast mode brief (low confidence). |
|
|
98
99
|
| `claude "/sdd-bmad <desc>"` | 0 | `sdd/brief.md` | Interactive 9-question brainstorm. Generates confidence-scored brief. |
|
|
100
|
+
| `claude "/sdd-bmad --batch answers.json <desc>"` | 0 | `sdd/brief.md` | Batch mode for CI/CD. Pre-defined answers file. |
|
|
99
101
|
| `claude "/sdd-spec"` | 2 | `sdd/SPEC.md` | Generate SPEC.md from brief. Validates confidence ≥20/100. |
|
|
100
102
|
| `claude "/sdd-tasks"` | 3 | `sdd/tasks/task-*/` | Break SPEC.md into tasks (≤2 hours each). |
|
|
101
103
|
| `claude "/sdd-cook [--all\|--task T-XXX]"` | 4 | Code | Unified: execution plan + implementation guidance + status tracking. |
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
tool: Bash
|
|
6
6
|
when: always
|
|
7
7
|
args: optional
|
|
8
|
-
description: Phase 4 — Implement tasks from MASTER-TASKS.md. Args: [--all | --task T-XXX] [--dry-run]. Reads task file, shows requirements, guides implementation
|
|
8
|
+
description: Phase 4 — Implement tasks from MASTER-TASKS.md. Args: [--all | --task T-XXX] [--dry-run]. Reads task file, shows requirements, guides implementation.
|
|
9
|
+
|
|
10
|
+
Note: After implementation, manually update status with:
|
|
11
|
+
pwsh commands/sdd-task-status.ps1 -TaskId T-001 -Status completed
|
|
9
12
|
---
|
|
10
13
|
$ErrorActionPreference = 'Continue'
|
|
11
14
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)]
|
|
3
|
+
[string]$FilePath,
|
|
4
|
+
|
|
5
|
+
[string]$Section = "",
|
|
6
|
+
|
|
7
|
+
[string]$SpecFile = "sdd/SPEC.md"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
$ErrorActionPreference = "Continue"
|
|
11
|
+
|
|
12
|
+
function Get-ClauseContext {
|
|
13
|
+
param([string]$SpecFile, [string]$ClauseId)
|
|
14
|
+
|
|
15
|
+
$content = Get-Content $SpecFile -Raw -ErrorAction SilentlyContinue
|
|
16
|
+
if (-not $content) {
|
|
17
|
+
# Try legacy path
|
|
18
|
+
$content = Get-Content "SPEC.md" -Raw -ErrorAction SilentlyContinue
|
|
19
|
+
}
|
|
20
|
+
if (-not $content) {
|
|
21
|
+
return ""
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Find the clause and get surrounding text
|
|
25
|
+
$pattern = "\[${ClauseId}\](?:[:\s]+([^\n]+))?"
|
|
26
|
+
if ($content -match $pattern) {
|
|
27
|
+
return $Matches[1].Trim()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Try to find clause in multi-line format
|
|
31
|
+
$pattern2 = "\[${ClauseId}\][\s\n]+([^-]+?)(?=\n\s*\[|\n\s*---|\z)"
|
|
32
|
+
if ($content -match $pattern2) {
|
|
33
|
+
return $Matches[1].Trim() -replace '\s+', ' '
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return ""
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function Test-ClauseMatch {
|
|
40
|
+
param([string]$Text, [string]$ImplContent)
|
|
41
|
+
|
|
42
|
+
if ([string]::IsNullOrWhiteSpace($Text)) { return $false }
|
|
43
|
+
|
|
44
|
+
# Extract key terms (remove brackets, parens, common words)
|
|
45
|
+
$cleanText = $Text -replace '\[.*?\]', '' -replace '\(.*?\)', '' -replace '[?!.,]', ''
|
|
46
|
+
|
|
47
|
+
# Try exact match first
|
|
48
|
+
if ($ImplContent -match [regex]::Escape($Text)) {
|
|
49
|
+
return @{ Match = $true; Method = "exact" }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Try case-insensitive
|
|
53
|
+
if ($ImplContent -match [regex]::Escape($Text) -ignorecase) {
|
|
54
|
+
return @{ Match = $true; Method = "case-insensitive" }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Try key terms matching
|
|
58
|
+
$words = $cleanText -split '\s+' | Where-Object { $_.Length -gt 3 }
|
|
59
|
+
$foundWords = 0
|
|
60
|
+
foreach ($word in $words) {
|
|
61
|
+
if ($ImplContent -match [regex]::Escape($word)) {
|
|
62
|
+
$foundWords++
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if ($foundWords -ge ($words.Count * 0.5) -and $words.Count -gt 0) {
|
|
67
|
+
return @{ Match = $true; Method = "partial"; Coverage = "$foundWords/$($words.Count)" }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return @{ Match = $false; Method = "none" }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Main execution
|
|
74
|
+
Write-Host ""
|
|
75
|
+
Write-Host "╔══════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
|
|
76
|
+
Write-Host "║ SDD Check: $FilePath" -ForegroundColor Cyan
|
|
77
|
+
Write-Host "╚══════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
|
|
78
|
+
Write-Host ""
|
|
79
|
+
|
|
80
|
+
# Check if spec exists
|
|
81
|
+
$specExists = (Test-Path $SpecFile) -or (Test-Path "SPEC.md")
|
|
82
|
+
if (-not $specExists) {
|
|
83
|
+
Write-Host "No SPEC.md found. Run /sdd-spec first." -ForegroundColor Yellow
|
|
84
|
+
exit 1
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Use correct spec path
|
|
88
|
+
$actualSpecFile = if (Test-Path $SpecFile) { $SpecFile } else { "SPEC.md" }
|
|
89
|
+
|
|
90
|
+
# Read spec content
|
|
91
|
+
$specContent = Get-Content $actualSpecFile -Raw
|
|
92
|
+
if (-not $specContent) {
|
|
93
|
+
Write-Host "Failed to read SPEC.md" -ForegroundColor Red
|
|
94
|
+
exit 1
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Extract all clause IDs
|
|
98
|
+
$clausePattern = '\[(SC-[A-Z0-9]+|SC-\d+)\]'
|
|
99
|
+
$clauseIds = [regex]::Matches($specContent, $clausePattern) | ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique
|
|
100
|
+
|
|
101
|
+
# Filter by section if specified
|
|
102
|
+
if ($Section) {
|
|
103
|
+
# Parse section to find relevant clauses
|
|
104
|
+
$sectionPattern = "##\s+.*?$Section[\s\S]+?(?=##|\z)"
|
|
105
|
+
$sectionContent = if ($specContent -match $sectionPattern) { $Matches[0] } else { $specContent }
|
|
106
|
+
$clauseIds = [regex]::Matches($sectionContent, $clausePattern) | ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Read implementation file
|
|
110
|
+
if (-not (Test-Path $FilePath)) {
|
|
111
|
+
Write-Host "File not found: $FilePath" -ForegroundColor Red
|
|
112
|
+
exit 1
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
$implContent = Get-Content $FilePath -Raw -ErrorAction Stop
|
|
116
|
+
|
|
117
|
+
# Check each clause
|
|
118
|
+
$passCount = 0
|
|
119
|
+
$failCount = 0
|
|
120
|
+
$results = @()
|
|
121
|
+
|
|
122
|
+
foreach ($clauseId in $clauseIds) {
|
|
123
|
+
$clauseText = Get-ClauseContext -SpecFile $actualSpecFile -ClauseId $clauseId
|
|
124
|
+
$result = Test-ClauseMatch -Text $clauseText -ImplContent $implContent
|
|
125
|
+
|
|
126
|
+
$results += @{
|
|
127
|
+
Id = $clauseId
|
|
128
|
+
Text = $clauseText
|
|
129
|
+
Result = $result
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if ($result.Match) {
|
|
133
|
+
$methodNote = if ($result.Method -eq "partial") { " [$($result.Coverage)]" } else { "" }
|
|
134
|
+
Write-Host " [OK] $clauseId$methodNote" -ForegroundColor Green
|
|
135
|
+
$passCount++
|
|
136
|
+
} else {
|
|
137
|
+
$preview = if ($clauseText.Length -gt 60) { $clauseText.Substring(0, 60) + "..." } else { $clauseText }
|
|
138
|
+
Write-Host " [--] $clauseId" -ForegroundColor Yellow
|
|
139
|
+
if ($preview) {
|
|
140
|
+
Write-Host " Expected: $preview" -ForegroundColor Gray
|
|
141
|
+
}
|
|
142
|
+
$failCount++
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# Summary
|
|
147
|
+
Write-Host ""
|
|
148
|
+
Write-Host "═══════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
|
149
|
+
$total = $passCount + $failCount
|
|
150
|
+
if ($failCount -eq 0) {
|
|
151
|
+
Write-Host " Result: $passCount/$total clauses verified" -ForegroundColor Green
|
|
152
|
+
} else {
|
|
153
|
+
Write-Host " Result: $passCount/$total verified | $failCount unclear" -ForegroundColor Yellow
|
|
154
|
+
}
|
|
155
|
+
Write-Host "═══════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
|
156
|
+
Write-Host ""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SDD Tasks Generator - Phase 3
|
|
1
|
+
# SDD Tasks Generator - Phase 3
|
|
2
2
|
# Standalone script for non-Claude Code usage
|
|
3
3
|
# Usage: powershell -File commands/sdd-tasks.ps1
|
|
4
4
|
|
|
@@ -27,26 +27,22 @@ $specPath = "$SDD_OUTPUT_BASE/SPEC.md"
|
|
|
27
27
|
$legacySpecPath = "SPEC.md"
|
|
28
28
|
|
|
29
29
|
if (-not (Test-Path $specPath) -and -not (Test-Path $legacySpecPath)) {
|
|
30
|
-
Write-Host "[ERROR] SPEC.md not found
|
|
31
|
-
Write-Host "Run: sdd bmad [feature]" -ForegroundColor Yellow
|
|
32
|
-
Write-Host "Then: claude `/sdd-spec`" -ForegroundColor Yellow
|
|
30
|
+
Write-Host "[ERROR] SPEC.md not found" -ForegroundColor Red
|
|
33
31
|
exit 1
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
# Use new path if exists, else use legacy
|
|
37
34
|
if (Test-Path $specPath) {
|
|
38
35
|
Write-Host "[OK] Found: $specPath" -ForegroundColor Green
|
|
39
36
|
$activeSpec = $specPath
|
|
40
37
|
} else {
|
|
41
|
-
Write-Host "[OK] Found: $legacySpecPath
|
|
38
|
+
Write-Host "[OK] Found: $legacySpecPath" -ForegroundColor Green
|
|
42
39
|
$activeSpec = $legacySpecPath
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
# Read template
|
|
46
43
|
$templatePath = "templates/task-detail.template.md"
|
|
47
44
|
if (-not (Test-Path $templatePath)) {
|
|
48
|
-
Write-Host "[ERROR] Template not found
|
|
49
|
-
Write-Host "Run: /sdd-init first" -ForegroundColor Yellow
|
|
45
|
+
Write-Host "[ERROR] Template not found" -ForegroundColor Red
|
|
50
46
|
exit 1
|
|
51
47
|
}
|
|
52
48
|
$templateContent = Get-Content $templatePath -Raw -Encoding UTF8
|
|
@@ -57,7 +53,7 @@ $specContent = Get-Content $activeSpec -Raw -Encoding UTF8
|
|
|
57
53
|
|
|
58
54
|
# Extract project name
|
|
59
55
|
$projectName = "Project"
|
|
60
|
-
if ($specContent -match "^#\s+SPEC\.md\s*[
|
|
56
|
+
if ($specContent -match "^#\s+SPEC\.md\s*[-]\s*(.+)$") {
|
|
61
57
|
$projectName = $Matches[1].Trim()
|
|
62
58
|
}
|
|
63
59
|
|
|
@@ -90,13 +86,12 @@ $timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
|
|
90
86
|
$safeName = $projectName -replace '[^a-zA-Z0-9]', '-' -replace '-+', '-'
|
|
91
87
|
$taskDir = "$SDD_TASKS_DIR/task-$timestamp-$safeName"
|
|
92
88
|
|
|
93
|
-
# Check for existing task directories
|
|
89
|
+
# Check for existing task directories
|
|
94
90
|
$existingDirs = Get-ChildItem -Path $SDD_TASKS_DIR -Filter "task-*-${safeName}" -Directory -ErrorAction SilentlyContinue
|
|
95
91
|
if ($existingDirs) {
|
|
96
92
|
$existingDir = $existingDirs[0].FullName
|
|
97
93
|
Write-Host ""
|
|
98
|
-
Write-Host "[WARN] Task directory already exists
|
|
99
|
-
Write-Host "[INFO] Using existing directory" -ForegroundColor Cyan
|
|
94
|
+
Write-Host "[WARN] Task directory already exists" -ForegroundColor Yellow
|
|
100
95
|
$taskDir = $existingDir
|
|
101
96
|
} else {
|
|
102
97
|
New-Item -ItemType Directory -Path $taskDir -Force | Out-Null
|
|
@@ -130,7 +125,7 @@ foreach ($category in $testCategories) {
|
|
|
130
125
|
$tasks += @{ Id = "T-00$phase"; Name = "Final Polish"; Phase = $phase; DependsOn = $prevId }
|
|
131
126
|
|
|
132
127
|
# Generate MASTER-TASKS.md
|
|
133
|
-
$masterContent = "# MASTER-TASKS
|
|
128
|
+
$masterContent = "# MASTER-TASKS - $projectName`n`n"
|
|
134
129
|
$masterContent += "| ID | Name | Estimate | Status |`n|----|------|----------|--------|`n"
|
|
135
130
|
foreach ($t in $tasks) {
|
|
136
131
|
$masterContent += "| $($t.Id) | $($t.Name) | 1h | [ ] |`n"
|
|
@@ -146,7 +141,7 @@ Write-Host " [CREATED] MASTER-TASKS.md" -ForegroundColor Gray
|
|
|
146
141
|
foreach ($t in $tasks) {
|
|
147
142
|
$safeName = $t.Name -replace '[^a-zA-Z0-9]', '-' -replace '-+', '-'
|
|
148
143
|
$filename = "$($t.Id)-$safeName.md"
|
|
149
|
-
$dependsOn = if ($t.DependsOn) { "`"
|
|
144
|
+
$dependsOn = if ($t.DependsOn) { "`"" + ($t.DependsOn) + "`"" } else { "" }
|
|
150
145
|
|
|
151
146
|
# Apply template substitutions
|
|
152
147
|
$taskContent = $templateContent
|
|
@@ -179,4 +174,4 @@ Write-Host "[OK] Tasks generated: $($tasks.Count) tasks" -ForegroundColor Green
|
|
|
179
174
|
Write-Host ""
|
|
180
175
|
Write-Host "Directory: $taskDir" -ForegroundColor White
|
|
181
176
|
Write-Host ""
|
|
182
|
-
Write-Host "Next: /sdd-cook --all" -ForegroundColor Cyan
|
|
177
|
+
Write-Host "Next: /sdd-cook --all" -ForegroundColor Cyan
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[Parameter(Mandatory=$true)]
|
|
3
|
+
[string]$FilePath,
|
|
4
|
+
|
|
5
|
+
[string]$Section = "",
|
|
6
|
+
|
|
7
|
+
[string]$SpecFile = "sdd/SPEC.md"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
$ErrorActionPreference = "Continue"
|
|
11
|
+
|
|
12
|
+
# ============================================================
|
|
13
|
+
# SDD Path Resolution
|
|
14
|
+
# ============================================================
|
|
15
|
+
$SDD_SPEC_FILE = "sdd/SPEC.md"
|
|
16
|
+
|
|
17
|
+
# Legacy fallback
|
|
18
|
+
if (-not $SpecFile) {
|
|
19
|
+
if (Test-Path "SPEC.md") { $SpecFile = "SPEC.md" }
|
|
20
|
+
elseif (Test-Path "$SDD_SPEC_FILE") { $SpecFile = $SDD_SPEC_FILE }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# ============================================================
|
|
24
|
+
# Helper Functions
|
|
25
|
+
# ============================================================
|
|
26
|
+
|
|
27
|
+
# Get-ClauseContext - Extract clause text from SPEC.md
|
|
28
|
+
function Get-ClauseContext {
|
|
29
|
+
param(
|
|
30
|
+
[string]$SpecFile,
|
|
31
|
+
[string]$ClauseId
|
|
32
|
+
)
|
|
33
|
+
if (-not (Test-Path $SpecFile)) { return "" }
|
|
34
|
+
|
|
35
|
+
$content = Get-Content $SpecFile -Raw
|
|
36
|
+
# Match clause pattern like [SC-001] or [AC-001]
|
|
37
|
+
$pattern = "\[$ClauseId\]\s*(.+?)(?=\n\n|\n\[|$)"
|
|
38
|
+
if ($content -match $pattern) {
|
|
39
|
+
$text = $matches[1] -replace '\s+', ' ' -replace '^\s+|\s+$', ''
|
|
40
|
+
return $text
|
|
41
|
+
}
|
|
42
|
+
return ""
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Get-SpecClauses - Extract all clause IDs from SPEC.md
|
|
46
|
+
function Get-SpecClauses {
|
|
47
|
+
param([string]$File)
|
|
48
|
+
$clauses = @()
|
|
49
|
+
if (Test-Path $File) {
|
|
50
|
+
$content = Get-Content $File -Raw
|
|
51
|
+
# Match [SC-xxx], [AC-xxx], [FE-xxx] patterns
|
|
52
|
+
$pattern = '\[([A-Z]{2,3}-[A-Z0-9]+-[0-9]+|[A-Z]{2,3}-[0-9]+)\]'
|
|
53
|
+
$matches = [regex]::Matches($content, $pattern)
|
|
54
|
+
foreach ($m in $matches) {
|
|
55
|
+
$id = $m.Groups[1].Value
|
|
56
|
+
if ($clauses.Id -notcontains $id) {
|
|
57
|
+
$text = Get-ClauseContext -SpecFile $File -ClauseId $id
|
|
58
|
+
$clauses += @{
|
|
59
|
+
Id = $id
|
|
60
|
+
Text = $text
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return $clauses
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Search-Implementation - Find clause text in file
|
|
69
|
+
function Search-Implementation {
|
|
70
|
+
param(
|
|
71
|
+
[string]$File,
|
|
72
|
+
[string]$SearchText
|
|
73
|
+
)
|
|
74
|
+
if (-not (Test-Path $File)) { return @{ Found = $false; Line = 0 } }
|
|
75
|
+
|
|
76
|
+
$lines = Get-Content $File
|
|
77
|
+
for ($i = 0; $i -lt $lines.Count; $i++) {
|
|
78
|
+
if ($lines[$i] -match [regex]::Escape($SearchText)) {
|
|
79
|
+
return @{
|
|
80
|
+
Found = $true
|
|
81
|
+
Line = $i + 1
|
|
82
|
+
Match = $matches[0]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return @{ Found = $false; Line = 0 }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Search-Color - Find hex color in file
|
|
90
|
+
function Search-Color {
|
|
91
|
+
param(
|
|
92
|
+
[string]$File,
|
|
93
|
+
[string]$Color
|
|
94
|
+
)
|
|
95
|
+
if (-not (Test-Path $File)) { return @{ Found = $false; Line = 0 } }
|
|
96
|
+
|
|
97
|
+
$content = Get-Content $File -Raw
|
|
98
|
+
if ($content -match $Color) {
|
|
99
|
+
$lines = Get-Content $File
|
|
100
|
+
for ($i = 0; $i -lt $lines.Count; $i++) {
|
|
101
|
+
if ($lines[$i] -match [regex]::Escape($Color)) {
|
|
102
|
+
return @{
|
|
103
|
+
Found = $true
|
|
104
|
+
Line = $i + 1
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return @{ Found = $false; Line = 0 }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# ============================================================
|
|
113
|
+
# Main Execution
|
|
114
|
+
# ============================================================
|
|
115
|
+
|
|
116
|
+
# Validate inputs
|
|
117
|
+
if (-not (Test-Path $FilePath)) {
|
|
118
|
+
Write-Host "[ERROR] File not found: $FilePath" -ForegroundColor Red
|
|
119
|
+
exit 1
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (-not (Test-Path $SpecFile)) {
|
|
123
|
+
Write-Host "[WARN] SPEC file not found: $SpecFile" -ForegroundColor Yellow
|
|
124
|
+
Write-Host " Searching for SPEC.md in common locations..." -ForegroundColor Gray
|
|
125
|
+
|
|
126
|
+
$foundSpec = $null
|
|
127
|
+
$searchPaths = @("SPEC.md", "sdd/SPEC.md", "../sdd/SPEC.md", "../../sdd/SPEC.md")
|
|
128
|
+
foreach ($p in $searchPaths) {
|
|
129
|
+
if (Test-Path $p) {
|
|
130
|
+
$foundSpec = $p
|
|
131
|
+
break
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if ($foundSpec) {
|
|
136
|
+
$SpecFile = $foundSpec
|
|
137
|
+
Write-Host " Found: $SpecFile" -ForegroundColor Green
|
|
138
|
+
} else {
|
|
139
|
+
Write-Host "[ERROR] No SPEC file found" -ForegroundColor Red
|
|
140
|
+
exit 1
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# Resolve relative path
|
|
145
|
+
$absFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePath)
|
|
146
|
+
|
|
147
|
+
Write-Host ""
|
|
148
|
+
Write-Host "========================================" -ForegroundColor Cyan
|
|
149
|
+
Write-Host " SDD Check: $FilePath" -ForegroundColor Cyan
|
|
150
|
+
Write-Host "========================================" -ForegroundColor Cyan
|
|
151
|
+
Write-Host " Spec: $SpecFile" -ForegroundColor Gray
|
|
152
|
+
Write-Host ""
|
|
153
|
+
|
|
154
|
+
# Extract clauses from SPEC
|
|
155
|
+
$clauses = Get-SpecClauses -File $SpecFile
|
|
156
|
+
|
|
157
|
+
if ($clauses.Count -eq 0) {
|
|
158
|
+
Write-Host "[INFO] No clause IDs found in SPEC.md" -ForegroundColor Yellow
|
|
159
|
+
Write-Host " Expected patterns: [SC-xxx], [AC-xxx], [FE-xxx]" -ForegroundColor Gray
|
|
160
|
+
exit 0
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
Write-Host "[INFO] Found $($clauses.Count) clause IDs in SPEC" -ForegroundColor Cyan
|
|
164
|
+
Write-Host ""
|
|
165
|
+
|
|
166
|
+
$passCount = 0
|
|
167
|
+
$failCount = 0
|
|
168
|
+
$results = @()
|
|
169
|
+
|
|
170
|
+
foreach ($clause in $clauses) {
|
|
171
|
+
$searchText = $clause.Text -replace '\[.*?\]', '' -replace '\(.*?\)', '' -replace '^\s+|\s+$', ''
|
|
172
|
+
|
|
173
|
+
if ([string]::IsNullOrWhiteSpace($searchText)) {
|
|
174
|
+
Write-Host "⚠ $($clause.Id): No text content found" -ForegroundColor Yellow
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# Check if it's a color value (hex pattern)
|
|
179
|
+
$isColor = $searchText -match '^#[0-9A-Fa-f]{3,8}$'
|
|
180
|
+
|
|
181
|
+
if ($isColor) {
|
|
182
|
+
$result = Search-Color -File $absFilePath -Color $searchText
|
|
183
|
+
} else {
|
|
184
|
+
$result = Search-Implementation -File $absFilePath -SearchText $searchText
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if ($result.Found) {
|
|
188
|
+
$passCount++
|
|
189
|
+
$icon = if ($isColor) { "🎨" } else { "✅" }
|
|
190
|
+
Write-Host "$icon $($clause.Id): FOUND at line $($result.Line)" -ForegroundColor Green
|
|
191
|
+
$results += @{
|
|
192
|
+
Id = $clause.Id
|
|
193
|
+
Status = "PASS"
|
|
194
|
+
Line = $result.Line
|
|
195
|
+
Text = $searchText.Substring(0, [Math]::Min(60, $searchText.Length))
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
$failCount++
|
|
199
|
+
Write-Host "❌ $($clause.Id): NOT FOUND" -ForegroundColor Red
|
|
200
|
+
Write-Host " Expected: $($searchText.Substring(0, [Math]::Min(80, $searchText.Length)))" -ForegroundColor Yellow
|
|
201
|
+
if ($searchText.Length -gt 80) {
|
|
202
|
+
Write-Host " ..." -ForegroundColor Yellow
|
|
203
|
+
}
|
|
204
|
+
$results += @{
|
|
205
|
+
Id = $clause.Id
|
|
206
|
+
Status = "FAIL"
|
|
207
|
+
Line = 0
|
|
208
|
+
Text = $searchText.Substring(0, [Math]::Min(60, $searchText.Length))
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# Summary
|
|
214
|
+
Write-Host ""
|
|
215
|
+
Write-Host "========================================" -ForegroundColor Cyan
|
|
216
|
+
$total = $passCount + $failCount
|
|
217
|
+
$status = if ($failCount -eq 0) { "PASS" } else { "ISSUES FOUND" }
|
|
218
|
+
$color = if ($failCount -eq 0) { "Green" } else { "Yellow" }
|
|
219
|
+
|
|
220
|
+
Write-Host "Result: $passCount/$total clauses verified | $status" -ForegroundColor $color
|
|
221
|
+
|
|
222
|
+
if ($failCount -gt 0) {
|
|
223
|
+
Write-Host ""
|
|
224
|
+
Write-Host "Next steps:" -ForegroundColor Cyan
|
|
225
|
+
Write-Host " 1. Add missing clauses to $FilePath" -ForegroundColor Gray
|
|
226
|
+
Write-Host " 2. Run: sdd check $FilePath" -ForegroundColor Gray
|
|
227
|
+
Write-Host " 3. Run: sdd converge --task <T-xxx>" -ForegroundColor Gray
|
|
228
|
+
exit 1
|
|
229
|
+
} else {
|
|
230
|
+
Write-Host " All clauses verified!" -ForegroundColor Green
|
|
231
|
+
exit 0
|
|
232
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sdd-pipeline",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "Spec-Driven Development pipeline for Claude Code CLI — transforms raw feature requests into validated, shipped code through a self-correcting converge loop",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|