sdd-pipeline 1.2.2 → 1.2.4
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/package.json +12 -9
- package/lib/converge-task.ps1 +0 -260
- package/lib/output-dirs.ps1 +0 -461
- package/lib/sdd-check.ps1 +0 -130
- package/lib/shared-converge.ps1 +0 -270
- package/lib/verify-patterns.ps1 +0 -208
package/lib/sdd-check.ps1
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
param(
|
|
2
|
-
[Parameter(Mandatory=$true, Position=0)]
|
|
3
|
-
[string]$FilePath,
|
|
4
|
-
|
|
5
|
-
[string]$Section = "",
|
|
6
|
-
|
|
7
|
-
[string]$SpecFile = "SPEC.md",
|
|
8
|
-
|
|
9
|
-
[ValidateSet("table", "json", "minimal")]
|
|
10
|
-
[string]$OutputFormat = "table"
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
# Import shared functions
|
|
14
|
-
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
15
|
-
. "$scriptDir/shared-converge.ps1"
|
|
16
|
-
|
|
17
|
-
$ErrorActionPreference = "Continue"
|
|
18
|
-
|
|
19
|
-
# Resolve Files
|
|
20
|
-
$files = @()
|
|
21
|
-
if ($FilePath -match '\*|\?') {
|
|
22
|
-
$files = Get-ChildItem -Path $FilePath -File -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName }
|
|
23
|
-
} else {
|
|
24
|
-
if (Test-Path $FilePath) {
|
|
25
|
-
$files = @((Resolve-Path $FilePath).Path)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if ($files.Count -eq 0) {
|
|
30
|
-
Write-Error "No files found matching: $FilePath"
|
|
31
|
-
exit 1
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
# Load SPEC
|
|
35
|
-
if (-not (Test-Path $SpecFile)) {
|
|
36
|
-
Write-Error "SPEC file not found: $SpecFile"
|
|
37
|
-
exit 1
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
Write-Host ""
|
|
41
|
-
Write-Host "========================================" -ForegroundColor Cyan
|
|
42
|
-
Write-Host " SDD Check" -ForegroundColor Cyan
|
|
43
|
-
Write-Host "========================================" -ForegroundColor Cyan
|
|
44
|
-
Write-Host ""
|
|
45
|
-
Write-Host " Files: $($files.Count)" -ForegroundColor Gray
|
|
46
|
-
Write-Host " Spec: $SpecFile" -ForegroundColor Gray
|
|
47
|
-
Write-Host ""
|
|
48
|
-
|
|
49
|
-
# Get clauses from SPEC
|
|
50
|
-
$clauses = Get-SpecClauses -File $SpecFile
|
|
51
|
-
Write-Host " Clauses found: $($clauses.Count)" -ForegroundColor Gray
|
|
52
|
-
Write-Host ""
|
|
53
|
-
|
|
54
|
-
if ($clauses.Count -eq 0) {
|
|
55
|
-
Write-Host "[WARN] No clause IDs found in SPEC.md" -ForegroundColor Yellow
|
|
56
|
-
Write-Host " Add clause IDs like [SC-001], [SC-COLOR-001] to SPEC.md" -ForegroundColor Yellow
|
|
57
|
-
exit 0
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
# Filter by section if specified
|
|
61
|
-
if ($Section) {
|
|
62
|
-
$specContent = Get-Content $SpecFile -Raw
|
|
63
|
-
if ($specContent -match "(?s)##\s+$Section.*?(?=\n##\s+|\Z)") {
|
|
64
|
-
$sectionText = $Matches[0]
|
|
65
|
-
$filtered = @()
|
|
66
|
-
foreach ($clause in $clauses) {
|
|
67
|
-
if ($sectionText -match "\[$($clause.Id)\]") {
|
|
68
|
-
$filtered += $clause
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
$clauses = $filtered
|
|
72
|
-
Write-Host " Section: $Section -> $($clauses.Count) clauses" -ForegroundColor Gray
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
# Check each clause
|
|
77
|
-
$results = @()
|
|
78
|
-
$passCount = 0
|
|
79
|
-
$failCount = 0
|
|
80
|
-
|
|
81
|
-
foreach ($clause in $clauses) {
|
|
82
|
-
$result = Test-ClauseMatch -ClauseId $clause.Id -ClauseText $clause.Text -ClauseType $clause.Type -Files $files
|
|
83
|
-
$results += $result
|
|
84
|
-
|
|
85
|
-
if ($result.Found) {
|
|
86
|
-
$passCount++
|
|
87
|
-
} else {
|
|
88
|
-
$failCount++
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
# Output
|
|
93
|
-
if ($OutputFormat -eq "json") {
|
|
94
|
-
$results | ConvertTo-Json -Depth 10
|
|
95
|
-
} elseif ($OutputFormat -eq "minimal") {
|
|
96
|
-
foreach ($r in $results) {
|
|
97
|
-
$status = if ($r.Found) { "[PASS]" } else { "[FAIL]" }
|
|
98
|
-
Write-Host "$status $($r.ClauseId)"
|
|
99
|
-
}
|
|
100
|
-
} else {
|
|
101
|
-
# Table format
|
|
102
|
-
$maxIdLen = [Math]::Max(10, ($results | ForEach-Object { $_.ClauseId.Length } | Measure-Object -Maximum).Maximum)
|
|
103
|
-
$maxTypeLen = [Math]::Max(10, ($results | ForEach-Object { $_.Type.Length } | Measure-Object -Maximum).Maximum)
|
|
104
|
-
|
|
105
|
-
foreach ($r in $results) {
|
|
106
|
-
$status = if ($r.Found) { "[PASS]" } else { "[FAIL]" }
|
|
107
|
-
$color = if ($r.Found) { "Green" } else { "Red" }
|
|
108
|
-
Write-Host "$status $($r.ClauseId)" -ForegroundColor $color
|
|
109
|
-
Write-Host " Type: $($r.Type) | Location: $($r.Location)" -ForegroundColor Gray
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
# Summary
|
|
114
|
-
Write-Host ""
|
|
115
|
-
Write-Host "========================================" -ForegroundColor Cyan
|
|
116
|
-
Write-Host " Result: $passCount passed, $failCount failed" -ForegroundColor $(if ($failCount -eq 0) { "Green" } else { "Yellow" })
|
|
117
|
-
Write-Host "========================================" -ForegroundColor Cyan
|
|
118
|
-
Write-Host ""
|
|
119
|
-
|
|
120
|
-
if ($failCount -gt 0) {
|
|
121
|
-
Write-Host "Missing clauses:" -ForegroundColor Yellow
|
|
122
|
-
$results | Where-Object { -not $_.Found } | ForEach-Object {
|
|
123
|
-
$shortText = if ($_.Text) { $_.Text.Substring(0, [Math]::Min(50, $_.Text.Length)) } else { "(no text)" }
|
|
124
|
-
Write-Host " [FAIL] $($_.ClauseId): $shortText" -ForegroundColor Red
|
|
125
|
-
}
|
|
126
|
-
Write-Host ""
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
# Exit code
|
|
130
|
-
exit $(if ($failCount -eq 0) { 0 } else { 1 })
|
package/lib/shared-converge.ps1
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
# SDD Shared Convergence Functions
|
|
2
|
-
# Common functions used by converge-task.ps1, sdd-check.ps1, and verify-patterns.ps1
|
|
3
|
-
# Version: 1.0.1 - 2026-08-29
|
|
4
|
-
|
|
5
|
-
function Get-SpecClauses {
|
|
6
|
-
param(
|
|
7
|
-
[Parameter(Mandatory=$true)]
|
|
8
|
-
[string]$File
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
if (-not (Test-Path $File)) {
|
|
12
|
-
Write-Error "SPEC file not found: $File"
|
|
13
|
-
return @()
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
$content = Get-Content $File -Raw
|
|
17
|
-
$clauses = @()
|
|
18
|
-
|
|
19
|
-
# Match all clause patterns
|
|
20
|
-
$patterns = @(
|
|
21
|
-
'\[(SC-[A-Z0-9]+-[0-9]+)\]',
|
|
22
|
-
'\[(SC-[0-9]+)\]',
|
|
23
|
-
'\[(SC-COLOR-[0-9]+)\]',
|
|
24
|
-
'\[(SC-DIM-[0-9]+)\]',
|
|
25
|
-
'\[(AC-[A-Z0-9]+-[0-9]+)\]',
|
|
26
|
-
'\[(AC-[0-9]+)\]'
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
foreach ($pattern in $patterns) {
|
|
30
|
-
$matches = [regex]::Matches($content, $pattern)
|
|
31
|
-
foreach ($m in $matches) {
|
|
32
|
-
$id = $m.Groups[1].Value
|
|
33
|
-
if ($id -notin $clauses.Id) {
|
|
34
|
-
$context = Get-ClauseContext -File $File -ClauseId $id
|
|
35
|
-
$clauses += @{
|
|
36
|
-
Id = $id
|
|
37
|
-
Type = Get-ClauseType -ClauseId $id
|
|
38
|
-
Text = $context
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return $clauses
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function Get-ClauseContext {
|
|
48
|
-
param(
|
|
49
|
-
[Parameter(Mandatory=$true)]
|
|
50
|
-
[string]$File,
|
|
51
|
-
|
|
52
|
-
[Parameter(Mandatory=$true)]
|
|
53
|
-
[string]$ClauseId
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
$content = Get-Content $File -Raw
|
|
57
|
-
# Match clause ID and get text on same line or next line
|
|
58
|
-
$pattern = "(?i)\[$ClauseId\]\s*`?([^`\n]+)"
|
|
59
|
-
$match = [regex]::Match($content, $pattern)
|
|
60
|
-
|
|
61
|
-
if ($match.Success) {
|
|
62
|
-
$text = $match.Groups[1].Value.Trim()
|
|
63
|
-
# Remove markdown table characters
|
|
64
|
-
$text = $text -replace '^\|', '' -replace '\|$', '' -replace '\|', ' '
|
|
65
|
-
$text = $text -replace '\s+', ' '
|
|
66
|
-
return $text.Substring(0, [Math]::Min(200, $text.Length))
|
|
67
|
-
}
|
|
68
|
-
return ""
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function Get-ClauseType {
|
|
72
|
-
param([string]$ClauseId)
|
|
73
|
-
|
|
74
|
-
if ($ClauseId -match 'COLOR') { return "Color" }
|
|
75
|
-
if ($ClauseId -match 'DIM') { return "Dimension" }
|
|
76
|
-
if ($ClauseId -match 'AC-') { return "Acceptance" }
|
|
77
|
-
return "Text"
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function Get-ClauseSearchText {
|
|
81
|
-
param([string]$ClauseText)
|
|
82
|
-
|
|
83
|
-
$searchText = $ClauseText -replace '\[.*?\]', '' -replace '\(.*?\)', '' -replace '`', ''
|
|
84
|
-
$searchText = $searchText.Trim()
|
|
85
|
-
|
|
86
|
-
return $searchText
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function Test-ClauseMatch {
|
|
90
|
-
param(
|
|
91
|
-
[Parameter(Mandatory=$true)]
|
|
92
|
-
[string]$ClauseId,
|
|
93
|
-
|
|
94
|
-
[string]$ClauseText,
|
|
95
|
-
|
|
96
|
-
[string]$ClauseType,
|
|
97
|
-
|
|
98
|
-
[string[]]$Files
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
if ($Files.Count -eq 0) {
|
|
102
|
-
return @{
|
|
103
|
-
ClauseId = $ClauseId
|
|
104
|
-
Found = $null
|
|
105
|
-
Location = "No implementation files"
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
$searchText = Get-ClauseSearchText -ClauseText $ClauseText
|
|
110
|
-
|
|
111
|
-
if ($searchText.Length -lt 2) {
|
|
112
|
-
return @{
|
|
113
|
-
ClauseId = $ClauseId
|
|
114
|
-
Found = $null
|
|
115
|
-
Location = "Text too short to search"
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if ($ClauseType -eq "Color") {
|
|
120
|
-
return Test-ColorMatch -ClauseId $ClauseId -SearchText $searchText -Files $Files
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return Test-TextMatch -ClauseId $ClauseId -SearchText $searchText -Files $Files
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function Test-TextMatch {
|
|
127
|
-
param(
|
|
128
|
-
[string]$ClauseId,
|
|
129
|
-
[string]$SearchText,
|
|
130
|
-
[string[]]$Files
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
foreach ($file in $Files) {
|
|
134
|
-
if (-not (Test-Path $file)) { continue }
|
|
135
|
-
$content = Get-Content $file -Raw -ErrorAction SilentlyContinue
|
|
136
|
-
if (-not $content) { continue }
|
|
137
|
-
|
|
138
|
-
if ($content -match [regex]::Escape($SearchText)) {
|
|
139
|
-
return @{
|
|
140
|
-
ClauseId = $ClauseId
|
|
141
|
-
Found = $true
|
|
142
|
-
Location = $file
|
|
143
|
-
Text = $SearchText
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return @{
|
|
149
|
-
ClauseId = $ClauseId
|
|
150
|
-
Found = $false
|
|
151
|
-
Location = "[Text not found]"
|
|
152
|
-
Text = $SearchText
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function Test-ColorMatch {
|
|
157
|
-
param(
|
|
158
|
-
[string]$ClauseId,
|
|
159
|
-
[string]$SearchText,
|
|
160
|
-
[string[]]$Files
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
# Extract hex from search text
|
|
164
|
-
$hexPattern = '#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\b'
|
|
165
|
-
$specHex = ($SearchText -split '#')[-1] -replace '[^A-Fa-f0-9]', ''
|
|
166
|
-
|
|
167
|
-
# Normalize 3-char hex to 6-char
|
|
168
|
-
if ($specHex.Length -eq 3) {
|
|
169
|
-
$specHex = $specHex[0] + $specHex[0] + $specHex[1] + $specHex[1] + $specHex[2] + $specHex[2]
|
|
170
|
-
}
|
|
171
|
-
$specHex = $specHex.ToUpper()
|
|
172
|
-
|
|
173
|
-
foreach ($file in $Files) {
|
|
174
|
-
if (-not (Test-Path $file)) { continue }
|
|
175
|
-
$content = Get-Content $file -Raw -ErrorAction SilentlyContinue
|
|
176
|
-
if (-not $content) { continue }
|
|
177
|
-
|
|
178
|
-
$implHexes = [regex]::Matches($content, $hexPattern) | ForEach-Object {
|
|
179
|
-
$h = $_.Groups[1].Value
|
|
180
|
-
if ($h.Length -eq 3) {
|
|
181
|
-
$h = $h[0] + $h[0] + $h[1] + $h[1] + $h[2] + $h[2]
|
|
182
|
-
}
|
|
183
|
-
$h.ToUpper()
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if ("#$specHex" -in $implHexes) {
|
|
187
|
-
return @{
|
|
188
|
-
ClauseId = $ClauseId
|
|
189
|
-
Found = $true
|
|
190
|
-
Location = $file
|
|
191
|
-
Text = $SearchText
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return @{
|
|
197
|
-
ClauseId = $ClauseId
|
|
198
|
-
Found = $false
|
|
199
|
-
Location = "[Color #$specHex not found]"
|
|
200
|
-
Text = $SearchText
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function Get-TaskFiles {
|
|
205
|
-
param(
|
|
206
|
-
[string]$TaskId,
|
|
207
|
-
[string]$TaskDir
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
if (-not $TaskDir) {
|
|
211
|
-
$dirs = Get-ChildItem -Filter "task-*-*" -Directory -ErrorAction SilentlyContinue
|
|
212
|
-
$TaskDir = ($dirs | Select-Object -First 1).Name
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (-not $TaskDir) { return @() }
|
|
216
|
-
|
|
217
|
-
$pattern = "$TaskId-*.md"
|
|
218
|
-
$files = Get-ChildItem -Path $TaskDir -Filter $pattern -ErrorAction SilentlyContinue
|
|
219
|
-
$files = $files | Where-Object { $_.Name -ne "MASTER-TASKS.md" }
|
|
220
|
-
|
|
221
|
-
if (-not $files) { return @() }
|
|
222
|
-
|
|
223
|
-
# Extract files from task content
|
|
224
|
-
$content = Get-Content $files[0].FullName -Raw
|
|
225
|
-
$implFiles = @()
|
|
226
|
-
|
|
227
|
-
# Match: - `path` or - [name](path)
|
|
228
|
-
$lines = $content -split '\n'
|
|
229
|
-
foreach ($line in $lines) {
|
|
230
|
-
# Match `- `path` or `- [name](path)`
|
|
231
|
-
if ($line -match '^\s*-\s+`([^`]+)`') {
|
|
232
|
-
$implFiles += $Matches[1]
|
|
233
|
-
} elseif ($line -match '^\s*-\s+\[[^\]]+\]\(([^)]+)\)') {
|
|
234
|
-
$implFiles += $Matches[1]
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return $implFiles
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function Write-ConvergeStatus {
|
|
242
|
-
param(
|
|
243
|
-
[Parameter(Mandatory=$true)]
|
|
244
|
-
[string]$Symbol,
|
|
245
|
-
|
|
246
|
-
[Parameter(Mandatory=$true)]
|
|
247
|
-
[string]$Text,
|
|
248
|
-
|
|
249
|
-
[ValidateSet("Pass", "Fail", "Warn", "Info", "Header", "White")]
|
|
250
|
-
[string]$Color = "White"
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
$colors = @{
|
|
254
|
-
"Pass" = "Green"
|
|
255
|
-
"Fail" = "Red"
|
|
256
|
-
"Warn" = "Yellow"
|
|
257
|
-
"Info" = "Cyan"
|
|
258
|
-
"Header" = "Magenta"
|
|
259
|
-
"White" = "White"
|
|
260
|
-
}
|
|
261
|
-
$fg = if ($colors[$Color]) { $colors[$Color] } else { "White" }
|
|
262
|
-
Write-Host "$Symbol $Text" -ForegroundColor $fg
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function Write-ConvergeHeader {
|
|
266
|
-
param([string]$Text)
|
|
267
|
-
|
|
268
|
-
Write-Host ""
|
|
269
|
-
Write-Host "--- $Text ---" -ForegroundColor Magenta
|
|
270
|
-
}
|
package/lib/verify-patterns.ps1
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
param(
|
|
2
|
-
[string]$PatternRepo = "converge/patterns/pattern-repository.md",
|
|
3
|
-
[string]$SrcDir = "src",
|
|
4
|
-
[bool]$Report = $true
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
$ErrorActionPreference = "Continue"
|
|
8
|
-
|
|
9
|
-
# Load pattern repository
|
|
10
|
-
if (-not (Test-Path $PatternRepo)) {
|
|
11
|
-
Write-Host "[WARN] Pattern repository not found: $PatternRepo" -ForegroundColor Yellow
|
|
12
|
-
Write-Host "Creating default pattern repository..." -ForegroundColor Yellow
|
|
13
|
-
New-Item -ItemType Directory -Path (Split-Path $PatternRepo -Parent) -Force | Out-Null
|
|
14
|
-
Copy-Item -Path "templates/pattern-repository.md" -Destination $PatternRepo -Force -ErrorAction SilentlyContinue
|
|
15
|
-
if (-not (Test-Path $PatternRepo)) {
|
|
16
|
-
Write-Host "[FAIL] Could not create pattern repository" -ForegroundColor Red
|
|
17
|
-
exit 1
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
Write-Host ""
|
|
22
|
-
Write-Host "========================================" -ForegroundColor Cyan
|
|
23
|
-
Write-Host " SDD Pattern Verification" -ForegroundColor Cyan
|
|
24
|
-
Write-Host "========================================" -ForegroundColor Cyan
|
|
25
|
-
Write-Host ""
|
|
26
|
-
|
|
27
|
-
# Parse patterns
|
|
28
|
-
$content = Get-Content $PatternRepo -Raw
|
|
29
|
-
$patternBlocks = [regex]::Matches($content, '(?s)\[PAT-(\d+)\](.*?)(?=\n### |\n## |\n---\Z)') | ForEach-Object {
|
|
30
|
-
@{
|
|
31
|
-
Id = "PAT-$($_.Groups[1].Value)"
|
|
32
|
-
Block = $_.Groups[2].Value
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
Write-Host "[INFO] Found $($patternBlocks.Count) patterns" -ForegroundColor Cyan
|
|
37
|
-
Write-Host ""
|
|
38
|
-
|
|
39
|
-
if ($patternBlocks.Count -eq 0) {
|
|
40
|
-
Write-Host "[WARN] No patterns found in repository" -ForegroundColor Yellow
|
|
41
|
-
Write-Host "Add patterns using [PAT-NNN] format" -ForegroundColor Yellow
|
|
42
|
-
exit 0
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
# Get source files
|
|
46
|
-
if (-not (Test-Path $SrcDir)) {
|
|
47
|
-
Write-Host "[WARN] Source directory not found: $SrcDir" -ForegroundColor Yellow
|
|
48
|
-
Write-Host "Searching in current directory..." -ForegroundColor Yellow
|
|
49
|
-
$SrcDir = "."
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
$extensions = @("*.ts", "*.tsx", "*.js", "*.jsx", "*.cs", "*.py", "*.go", "*.java")
|
|
53
|
-
$sourceFiles = Get-ChildItem -Path $SrcDir -Recurse -Include $extensions -File -ErrorAction SilentlyContinue
|
|
54
|
-
|
|
55
|
-
Write-Host "[INFO] Source files: $($sourceFiles.Count)" -ForegroundColor Cyan
|
|
56
|
-
|
|
57
|
-
# Verify patterns
|
|
58
|
-
$results = @()
|
|
59
|
-
$passCount = 0
|
|
60
|
-
$failCount = 0
|
|
61
|
-
|
|
62
|
-
foreach ($pattern in $patternBlocks) {
|
|
63
|
-
Write-Host ""
|
|
64
|
-
Write-Host "--- $($pattern.Id) ---" -ForegroundColor Magenta
|
|
65
|
-
|
|
66
|
-
# Extract description
|
|
67
|
-
if ($pattern.Block -match '(?s)\*\*Description:\*\*\s*(.*?)(?=\n\n|\n```)') {
|
|
68
|
-
$desc = $Matches[1].Trim()
|
|
69
|
-
$shortDesc = $desc.Substring(0, [Math]::Min(60, $desc.Length))
|
|
70
|
-
Write-Host " $shortDesc" -ForegroundColor Gray
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
# Extract regex pattern
|
|
74
|
-
$regex = $null
|
|
75
|
-
if ($pattern.Block -match 'Regex for verification:\s*```\s*([\s\S]*?)\s*```') {
|
|
76
|
-
$regex = $Matches[1].Trim()
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (-not $regex) {
|
|
80
|
-
Write-Host "[WARN] No regex defined for $($pattern.Id)" -ForegroundColor Yellow
|
|
81
|
-
continue
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
# Search for violations
|
|
85
|
-
$violations = @()
|
|
86
|
-
foreach ($file in $sourceFiles) {
|
|
87
|
-
$fileContent = Get-Content $file.FullName -Raw -ErrorAction SilentlyContinue
|
|
88
|
-
if (-not $fileContent) { continue }
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
if ($fileContent -match $regex) {
|
|
92
|
-
$match = $Matches[0]
|
|
93
|
-
# Check if it is a compliant example
|
|
94
|
-
$line = ($fileContent -split '\n' | Where-Object { $_ -match [regex]::Escape($match) } | Select-Object -First 1)
|
|
95
|
-
if ($line -and $line -notmatch "// Pattern: \[$($pattern.Id)\]") {
|
|
96
|
-
$violations += @{
|
|
97
|
-
File = $file.FullName -replace [regex]::Escape($PWD), '.'
|
|
98
|
-
Match = $match.Substring(0, [Math]::Min(80, $match.Length))
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
} catch {
|
|
103
|
-
# Skip invalid regex
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if ($violations.Count -eq 0) {
|
|
108
|
-
Write-Host "[PASS] $($pattern.Id): No violations" -ForegroundColor Green
|
|
109
|
-
$passCount++
|
|
110
|
-
$results += @{
|
|
111
|
-
Pattern = $pattern.Id
|
|
112
|
-
Status = "Pass"
|
|
113
|
-
Violations = 0
|
|
114
|
-
}
|
|
115
|
-
} else {
|
|
116
|
-
Write-Host "[FAIL] $($pattern.Id): $($violations.Count) violation(s)" -ForegroundColor Red
|
|
117
|
-
foreach ($v in $violations | Select-Object -First 3) {
|
|
118
|
-
Write-Host " -> $($v.File)" -ForegroundColor Red
|
|
119
|
-
Write-Host " $($v.Match)" -ForegroundColor DarkGray
|
|
120
|
-
}
|
|
121
|
-
if ($violations.Count -gt 3) {
|
|
122
|
-
Write-Host " ... and $($violations.Count - 3) more" -ForegroundColor Yellow
|
|
123
|
-
}
|
|
124
|
-
$failCount++
|
|
125
|
-
$results += @{
|
|
126
|
-
Pattern = $pattern.Id
|
|
127
|
-
Status = "Fail"
|
|
128
|
-
Violations = $violations.Count
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
# Summary
|
|
134
|
-
Write-Host ""
|
|
135
|
-
Write-Host "========================================" -ForegroundColor Cyan
|
|
136
|
-
Write-Host " Pattern Verification Summary" -ForegroundColor Cyan
|
|
137
|
-
Write-Host "========================================" -ForegroundColor Cyan
|
|
138
|
-
Write-Host ""
|
|
139
|
-
Write-Host " Patterns Checked: $($patternBlocks.Count)" -ForegroundColor Gray
|
|
140
|
-
Write-Host " Passed: $passCount" -ForegroundColor Green
|
|
141
|
-
Write-Host " Failed: $failCount" -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "Green" })
|
|
142
|
-
Write-Host ""
|
|
143
|
-
|
|
144
|
-
# Generate report
|
|
145
|
-
if ($Report) {
|
|
146
|
-
$reportDir = "converge/patterns"
|
|
147
|
-
if (-not (Test-Path $reportDir)) {
|
|
148
|
-
New-Item -ItemType Directory -Path $reportDir -Force | Out-Null
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
$reportFile = Join-Path $reportDir "verification-$(Get-Date -Format 'yyyy-MM-dd').md"
|
|
152
|
-
|
|
153
|
-
# Calculate pass rate safely
|
|
154
|
-
$totalPatterns = $patternBlocks.Count
|
|
155
|
-
$passRate = if ($totalPatterns -gt 0) { [Math]::Round(($passCount / $totalPatterns) * 100, 1) } else { 0 }
|
|
156
|
-
|
|
157
|
-
$report = @"
|
|
158
|
-
# Pattern Verification Report
|
|
159
|
-
|
|
160
|
-
Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
|
|
161
|
-
|
|
162
|
-
## Summary
|
|
163
|
-
|
|
164
|
-
| Metric | Value |
|
|
165
|
-
|--------|-------|
|
|
166
|
-
| Patterns Checked | $totalPatterns |
|
|
167
|
-
| Passed | $passCount |
|
|
168
|
-
| Failed | $failCount |
|
|
169
|
-
| Pass Rate | $passRate% |
|
|
170
|
-
|
|
171
|
-
## Results
|
|
172
|
-
|
|
173
|
-
| Pattern | Status | Violations |
|
|
174
|
-
|---------|--------|------------|
|
|
175
|
-
"@
|
|
176
|
-
|
|
177
|
-
foreach ($r in $results) {
|
|
178
|
-
$status = if ($r.Status -eq "Pass") { "PASS" } else { "FAIL" }
|
|
179
|
-
$report += "`n| $($r.Pattern) | $status | $($r.Violations) |"
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
$report += @"
|
|
183
|
-
|
|
184
|
-
## Failed Patterns
|
|
185
|
-
|
|
186
|
-
"@
|
|
187
|
-
|
|
188
|
-
$failedResults = $results | Where-Object { $_.Status -eq "Fail" }
|
|
189
|
-
if ($failedResults.Count -eq 0) {
|
|
190
|
-
$report += "`n_No violations found._"
|
|
191
|
-
} else {
|
|
192
|
-
foreach ($f in $failedResults) {
|
|
193
|
-
$report += "`n### $($f.Pattern)`n`nViolations: $($f.Violations)`n"
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
$report += @"
|
|
198
|
-
|
|
199
|
-
---
|
|
200
|
-
Generated by SDD Pattern Verification
|
|
201
|
-
"@
|
|
202
|
-
|
|
203
|
-
$report | Out-File -FilePath $reportFile -Encoding UTF8
|
|
204
|
-
Write-Host "[INFO] Report saved: $reportFile" -ForegroundColor Cyan
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
# Exit code
|
|
208
|
-
exit $(if ($failCount -eq 0) { 0 } else { 1 })
|