sdd-pipeline 1.2.3 → 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/package.json +12 -9
- package/lib/converge-task.ps1 +0 -260
- package/lib/generate-tasks.ps1 +0 -450
- 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/output-dirs.ps1
DELETED
|
@@ -1,461 +0,0 @@
|
|
|
1
|
-
# SDD Output Directory Management
|
|
2
|
-
# Centralized helpers for managing output file locations
|
|
3
|
-
# Version: 1.0.0 - 2026-08-29
|
|
4
|
-
|
|
5
|
-
$ErrorActionPreference = "Stop"
|
|
6
|
-
|
|
7
|
-
# ============================================================
|
|
8
|
-
# Configuration
|
|
9
|
-
# ============================================================
|
|
10
|
-
|
|
11
|
-
# Default output base directory
|
|
12
|
-
$script:SDD_OUTPUT_BASE = "sdd"
|
|
13
|
-
|
|
14
|
-
# Subdirectories per phase
|
|
15
|
-
$script:SDD_DIRS = @{
|
|
16
|
-
Brief = "" # sdd/brief.md (root of sdd/)
|
|
17
|
-
Spec = "" # sdd/SPEC.md (root of sdd/)
|
|
18
|
-
Tasks = "tasks" # sdd/tasks/
|
|
19
|
-
Converge = "converge" # sdd/converge/
|
|
20
|
-
Plans = "plans" # sdd/plans/
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
# Legacy locations (for migration detection)
|
|
24
|
-
$script:LEGACY_FILES = @{
|
|
25
|
-
Brief = "BMAD-brief.md"
|
|
26
|
-
Spec = "SPEC.md"
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
# ============================================================
|
|
30
|
-
# Core Functions
|
|
31
|
-
# ============================================================
|
|
32
|
-
|
|
33
|
-
function Get-SddOutputBase {
|
|
34
|
-
<#
|
|
35
|
-
.SYNOPSIS
|
|
36
|
-
Get the SDD output base directory, defaulting to 'sdd/'
|
|
37
|
-
#>
|
|
38
|
-
param(
|
|
39
|
-
[string]$BaseDir = ""
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
if ($BaseDir -and $BaseDir -ne "") {
|
|
43
|
-
return $BaseDir
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
# Check config for custom output directory
|
|
47
|
-
if (Test-Path ".sdd/config.json") {
|
|
48
|
-
try {
|
|
49
|
-
$config = Get-Content ".sdd/config.json" -Raw | ConvertFrom-Json
|
|
50
|
-
if ($config.output -and $config.output.baseDir) {
|
|
51
|
-
return $config.output.baseDir
|
|
52
|
-
}
|
|
53
|
-
} catch {
|
|
54
|
-
# Ignore config errors
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return $script:SDD_OUTPUT_BASE
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function Get-SddOutputPath {
|
|
62
|
-
<#
|
|
63
|
-
.SYNOPSIS
|
|
64
|
-
Get full path to an SDD output file
|
|
65
|
-
|
|
66
|
-
.PARAMETER Type
|
|
67
|
-
Output type: Brief, Spec, Tasks, Converge, Plans
|
|
68
|
-
|
|
69
|
-
.PARAMETER SubPath
|
|
70
|
-
Optional subpath within the output directory
|
|
71
|
-
#>
|
|
72
|
-
param(
|
|
73
|
-
[Parameter(Mandatory=$true)]
|
|
74
|
-
[ValidateSet("Brief", "Spec", "Tasks", "Converge", "Plans")]
|
|
75
|
-
[string]$Type,
|
|
76
|
-
|
|
77
|
-
[string]$SubPath = ""
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
$base = Get-SddOutputBase
|
|
81
|
-
$subDir = $script:SDD_DIRS[$Type]
|
|
82
|
-
|
|
83
|
-
$path = $base
|
|
84
|
-
if ($subDir) {
|
|
85
|
-
$path = Join-Path $path $subDir
|
|
86
|
-
}
|
|
87
|
-
if ($SubPath) {
|
|
88
|
-
$path = Join-Path $path $SubPath
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return $path
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function Initialize-SddOutputDirs {
|
|
95
|
-
<#
|
|
96
|
-
.SYNOPSIS
|
|
97
|
-
Create all SDD output directories if they don't exist
|
|
98
|
-
#>
|
|
99
|
-
param(
|
|
100
|
-
[string]$BaseDir = ""
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
$base = Get-SddOutputBase -BaseDir $BaseDir
|
|
104
|
-
$created = @()
|
|
105
|
-
|
|
106
|
-
# Create base directory
|
|
107
|
-
if (-not (Test-Path $base)) {
|
|
108
|
-
New-Item -ItemType Directory -Path $base -Force | Out-Null
|
|
109
|
-
$created += $base
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
# Create subdirectories
|
|
113
|
-
foreach ($dir in $script:SDD_DIRS.Values) {
|
|
114
|
-
if ($dir) {
|
|
115
|
-
$fullPath = Join-Path $base $dir
|
|
116
|
-
if (-not (Test-Path $fullPath)) {
|
|
117
|
-
New-Item -ItemType Directory -Path $fullPath -Force | Out-Null
|
|
118
|
-
$created += $fullPath
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return $created
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function Test-SddOutputInitialized {
|
|
127
|
-
<#
|
|
128
|
-
.SYNOPSIS
|
|
129
|
-
Check if SDD output directories exist
|
|
130
|
-
#>
|
|
131
|
-
param(
|
|
132
|
-
[string]$BaseDir = ""
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
$base = Get-SddOutputBase -BaseDir $BaseDir
|
|
136
|
-
return Test-Path $base
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function Get-LegacyFiles {
|
|
140
|
-
<#
|
|
141
|
-
.SYNOPSIS
|
|
142
|
-
Find legacy files in project root that should be migrated
|
|
143
|
-
#>
|
|
144
|
-
param([switch]$Detailed)
|
|
145
|
-
|
|
146
|
-
$found = @{}
|
|
147
|
-
|
|
148
|
-
foreach ($name in $script:LEGACY_FILES.Keys) {
|
|
149
|
-
$path = $script:LEGACY_FILES[$name]
|
|
150
|
-
if (Test-Path $path) {
|
|
151
|
-
if ($Detailed) {
|
|
152
|
-
$found[$name] = @{
|
|
153
|
-
LegacyPath = $path
|
|
154
|
-
NewPath = Get-SddOutputPath -Type $name
|
|
155
|
-
Exists = $true
|
|
156
|
-
}
|
|
157
|
-
} else {
|
|
158
|
-
$found[$name] = $path
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return $found
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function Move-LegacyFile {
|
|
167
|
-
<#
|
|
168
|
-
.SYNOPSIS
|
|
169
|
-
Move a legacy file to new output directory
|
|
170
|
-
#>
|
|
171
|
-
param(
|
|
172
|
-
[Parameter(Mandatory=$true)]
|
|
173
|
-
[ValidateSet("Brief", "Spec")]
|
|
174
|
-
[string]$Type,
|
|
175
|
-
|
|
176
|
-
[switch]$Force
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
$legacyPath = $script:LEGACY_FILES[$Type]
|
|
180
|
-
if (-not (Test-Path $legacyPath)) {
|
|
181
|
-
Write-Verbose "No legacy file found for $Type"
|
|
182
|
-
return $false
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
$newPath = Get-SddOutputPath -Type $Type
|
|
186
|
-
|
|
187
|
-
# Check if new path already exists
|
|
188
|
-
if ((Test-Path $newPath) -and -not $Force) {
|
|
189
|
-
Write-Warning "New file already exists: $newPath"
|
|
190
|
-
Write-Host "Use -Force to overwrite" -ForegroundColor Yellow
|
|
191
|
-
return $false
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
# Initialize directories
|
|
195
|
-
$base = Get-SddOutputBase
|
|
196
|
-
$subDir = $script:SDD_DIRS[$Type]
|
|
197
|
-
if ($subDir) {
|
|
198
|
-
$targetDir = Join-Path $base $subDir
|
|
199
|
-
} else {
|
|
200
|
-
$targetDir = $base
|
|
201
|
-
}
|
|
202
|
-
if (-not (Test-Path $targetDir)) {
|
|
203
|
-
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
# Move file
|
|
207
|
-
Move-Item -Path $legacyPath -Destination $newPath -Force
|
|
208
|
-
Write-Host "[MIGRATED] $legacyPath -> $newPath" -ForegroundColor Green
|
|
209
|
-
|
|
210
|
-
return $true
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function Migrate-AllLegacyFiles {
|
|
214
|
-
<#
|
|
215
|
-
.SYNOPSIS
|
|
216
|
-
Migrate all legacy files to new output directory structure
|
|
217
|
-
#>
|
|
218
|
-
param(
|
|
219
|
-
[switch]$DryRun,
|
|
220
|
-
[switch]$Force
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
$legacy = Get-LegacyFiles -Detailed
|
|
224
|
-
|
|
225
|
-
if ($legacy.Count -eq 0) {
|
|
226
|
-
Write-Host "No legacy files to migrate" -ForegroundColor Green
|
|
227
|
-
return
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
Write-Host ""
|
|
231
|
-
Write-Host "=== Legacy File Migration ===" -ForegroundColor Cyan
|
|
232
|
-
Write-Host ""
|
|
233
|
-
Write-Host "Found $($legacy.Count) legacy file(s):" -ForegroundColor Yellow
|
|
234
|
-
Write-Host ""
|
|
235
|
-
|
|
236
|
-
foreach ($name in $legacy.Keys) {
|
|
237
|
-
$info = $legacy[$name]
|
|
238
|
-
Write-Host " $name :" -ForegroundColor Gray
|
|
239
|
-
Write-Host " Legacy: $($info.LegacyPath)" -ForegroundColor White
|
|
240
|
-
Write-Host " New: $($info.NewPath)" -ForegroundColor White
|
|
241
|
-
Write-Host ""
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if ($DryRun) {
|
|
245
|
-
Write-Host "[DRY RUN] No files were moved" -ForegroundColor Yellow
|
|
246
|
-
return
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
$confirm = Read-Host "Proceed with migration? (y/N)"
|
|
250
|
-
if ($confirm -ne "y" -and $confirm -ne "Y") {
|
|
251
|
-
Write-Host "Migration cancelled" -ForegroundColor Yellow
|
|
252
|
-
return
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
Write-Host ""
|
|
256
|
-
Write-Host "Migrating files..." -ForegroundColor Cyan
|
|
257
|
-
|
|
258
|
-
foreach ($name in $legacy.Keys) {
|
|
259
|
-
Move-LegacyFile -Type $name -Force:$Force
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
Write-Host ""
|
|
263
|
-
Write-Host "[OK] Migration complete" -ForegroundColor Green
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function Get-SddStructure {
|
|
267
|
-
<#
|
|
268
|
-
.SYNOPSIS
|
|
269
|
-
Get the current SDD output structure as a tree
|
|
270
|
-
#>
|
|
271
|
-
param(
|
|
272
|
-
[string]$BaseDir = "",
|
|
273
|
-
[switch]$IncludeEmpty
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
$base = Get-SddOutputBase -BaseDir $BaseDir
|
|
277
|
-
$structure = @{
|
|
278
|
-
Base = $base
|
|
279
|
-
Exists = Test-Path $base
|
|
280
|
-
Brief = $null
|
|
281
|
-
Spec = $null
|
|
282
|
-
Tasks = @()
|
|
283
|
-
Converge = @()
|
|
284
|
-
Plans = @()
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (-not $structure.Exists) {
|
|
288
|
-
return $structure
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
# Check brief
|
|
292
|
-
$briefPath = Get-SddOutputPath -Type Brief
|
|
293
|
-
if (Test-Path $briefPath) {
|
|
294
|
-
$structure.Brief = @{
|
|
295
|
-
Path = $briefPath
|
|
296
|
-
Modified = (Get-Item $briefPath).LastWriteTime
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
# Check spec
|
|
301
|
-
$specPath = Get-SddOutputPath -Type Spec
|
|
302
|
-
if (Test-Path $specPath) {
|
|
303
|
-
$structure.Spec = @{
|
|
304
|
-
Path = $specPath
|
|
305
|
-
Modified = (Get-Item $specPath).LastWriteTime
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
# Check tasks directory
|
|
310
|
-
$tasksPath = Get-SddOutputPath -Type Tasks
|
|
311
|
-
if (Test-Path $tasksPath) {
|
|
312
|
-
$taskDirs = Get-ChildItem -Path $tasksPath -Directory -ErrorAction SilentlyContinue
|
|
313
|
-
foreach ($dir in $taskDirs) {
|
|
314
|
-
$masterPath = Join-Path $dir.FullName "MASTER-TASKS.md"
|
|
315
|
-
$taskFiles = Get-ChildItem -Path $dir.FullName -Filter "T-*.md" -File -ErrorAction SilentlyContinue
|
|
316
|
-
$taskFiles = $taskFiles | Where-Object { $_.Name -ne "MASTER-TASKS.md" }
|
|
317
|
-
|
|
318
|
-
$structure.Tasks += @{
|
|
319
|
-
Name = $dir.Name
|
|
320
|
-
Path = $dir.FullName
|
|
321
|
-
Modified = $dir.LastWriteTime
|
|
322
|
-
HasMaster = Test-Path $masterPath
|
|
323
|
-
TaskCount = $taskFiles.Count
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
# Check converge directory
|
|
329
|
-
$convergePath = Get-SddOutputPath -Type Converge
|
|
330
|
-
if (Test-Path $convergePath) {
|
|
331
|
-
$files = Get-ChildItem -Path $convergePath -File -ErrorAction SilentlyContinue
|
|
332
|
-
$structure.Converge = $files | ForEach-Object {
|
|
333
|
-
@{
|
|
334
|
-
Name = $_.Name
|
|
335
|
-
Path = $_.FullName
|
|
336
|
-
Modified = $_.LastWriteTime
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return $structure
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function Show-SddStructure {
|
|
345
|
-
<#
|
|
346
|
-
.SYNOPSIS
|
|
347
|
-
Display SDD output structure as a tree
|
|
348
|
-
#>
|
|
349
|
-
param([string]$BaseDir = "")
|
|
350
|
-
|
|
351
|
-
$structure = Get-SddStructure -BaseDir $BaseDir
|
|
352
|
-
|
|
353
|
-
Write-Host ""
|
|
354
|
-
Write-Host "=== SDD Output Structure ===" -ForegroundColor Cyan
|
|
355
|
-
Write-Host ""
|
|
356
|
-
|
|
357
|
-
if (-not $structure.Exists) {
|
|
358
|
-
Write-Host " $($structure.Base)/ (not initialized)" -ForegroundColor Gray
|
|
359
|
-
Write-Host ""
|
|
360
|
-
Write-Host " Run /sdd init to create the structure" -ForegroundColor Yellow
|
|
361
|
-
return
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
# Brief
|
|
365
|
-
$briefIcon = if ($structure.Brief) { "[V]" } else { "[ ]" }
|
|
366
|
-
$briefColor = if ($structure.Brief) { "Green" } else { "Gray" }
|
|
367
|
-
Write-Host " $($briefIcon) $($structure.Base)/brief.md" -ForegroundColor $briefColor
|
|
368
|
-
|
|
369
|
-
# Spec
|
|
370
|
-
$specIcon = if ($structure.Spec) { "[V]" } else { "[ ]" }
|
|
371
|
-
$specColor = if ($structure.Spec) { "Green" } else { "Gray" }
|
|
372
|
-
Write-Host " $($specIcon) $($structure.Base)/SPEC.md" -ForegroundColor $specColor
|
|
373
|
-
|
|
374
|
-
# Tasks
|
|
375
|
-
Write-Host " [ ] $($structure.Base)/tasks/" -ForegroundColor Gray
|
|
376
|
-
if ($structure.Tasks.Count -gt 0) {
|
|
377
|
-
foreach ($task in $structure.Tasks) {
|
|
378
|
-
$status = if ($task.HasMaster) { "[V]" } else { "[ ]" }
|
|
379
|
-
Write-Host " $($status) $($task.Name)/" -ForegroundColor Gray
|
|
380
|
-
Write-Host " MASTER-TASKS.md ($($task.TaskCount) tasks)" -ForegroundColor DarkGray
|
|
381
|
-
}
|
|
382
|
-
} else {
|
|
383
|
-
Write-Host " (no task directories)" -ForegroundColor DarkGray
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
# Converge
|
|
387
|
-
Write-Host " [ ] $($structure.Base)/converge/" -ForegroundColor Gray
|
|
388
|
-
if ($structure.Converge.Count -gt 0) {
|
|
389
|
-
foreach ($file in $structure.Converge) {
|
|
390
|
-
Write-Host " [V] $($file.Name)" -ForegroundColor Green
|
|
391
|
-
}
|
|
392
|
-
} else {
|
|
393
|
-
Write-Host " (no converge reports)" -ForegroundColor DarkGray
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
Write-Host ""
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
# ============================================================
|
|
400
|
-
# Config Helpers
|
|
401
|
-
# ============================================================
|
|
402
|
-
|
|
403
|
-
function Set-SddOutputConfig {
|
|
404
|
-
<#
|
|
405
|
-
.SYNOPSIS
|
|
406
|
-
Update .sdd/config.json with output directory paths
|
|
407
|
-
#>
|
|
408
|
-
param(
|
|
409
|
-
[string]$BaseDir = ""
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
$base = Get-SddOutputBase -BaseDir $BaseDir
|
|
413
|
-
|
|
414
|
-
if (Test-Path ".sdd/config.json") {
|
|
415
|
-
$config = Get-Content ".sdd/config.json" -Raw | ConvertFrom-Json
|
|
416
|
-
|
|
417
|
-
# Add output section
|
|
418
|
-
$config | Add-Member -NotePropertyName "output" -NotePropertyValue ([ordered]@{
|
|
419
|
-
baseDir = $base
|
|
420
|
-
brief = Get-SddOutputPath -Type Brief
|
|
421
|
-
spec = Get-SddOutputPath -Type Spec
|
|
422
|
-
tasks = Get-SddOutputPath -Type Tasks
|
|
423
|
-
converge = Get-SddOutputPath -Type Converge
|
|
424
|
-
}) -Force
|
|
425
|
-
|
|
426
|
-
$config | ConvertTo-Json | Set-Content ".sdd/config.json"
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
function Get-SddOutputConfig {
|
|
431
|
-
<#
|
|
432
|
-
.SYNOPSIS
|
|
433
|
-
Get output paths from config
|
|
434
|
-
#>
|
|
435
|
-
if (Test-Path ".sdd/config.json") {
|
|
436
|
-
$config = Get-Content ".sdd/config.json" -Raw | ConvertFrom-Json
|
|
437
|
-
if ($config.output) {
|
|
438
|
-
return $config.output
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return $null
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
# ============================================================
|
|
445
|
-
# Export for use in other scripts
|
|
446
|
-
# ============================================================
|
|
447
|
-
|
|
448
|
-
# When dot-sourced, return functions
|
|
449
|
-
return @{
|
|
450
|
-
GetBase = ${function:Get-SddOutputBase}
|
|
451
|
-
GetPath = ${function:Get-SddOutputPath}
|
|
452
|
-
Init = ${function:Initialize-SddOutputDirs}
|
|
453
|
-
TestInit = ${function:Test-SddOutputInitialized}
|
|
454
|
-
GetLegacy = ${function:Get-LegacyFiles}
|
|
455
|
-
MoveLegacy = ${function:Move-LegacyFile}
|
|
456
|
-
MigrateAll = ${function:Migrate-AllLegacyFiles}
|
|
457
|
-
GetStructure = ${function:Get-SddStructure}
|
|
458
|
-
ShowStructure = ${function:Show-SddStructure}
|
|
459
|
-
SetConfig = ${function:Set-SddOutputConfig}
|
|
460
|
-
GetConfig = ${function:Get-SddOutputConfig}
|
|
461
|
-
}
|
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 })
|