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/converge-task.ps1
DELETED
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
param(
|
|
2
|
-
[Parameter(Mandatory=$true)]
|
|
3
|
-
[string]$TaskId,
|
|
4
|
-
|
|
5
|
-
[string]$SpecFile = "",
|
|
6
|
-
|
|
7
|
-
[string]$TaskDir = "",
|
|
8
|
-
|
|
9
|
-
[string]$ConvergeDir = "",
|
|
10
|
-
|
|
11
|
-
[switch]$Strict
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
$ErrorActionPreference = "Continue"
|
|
15
|
-
$script:issuesFound = @()
|
|
16
|
-
|
|
17
|
-
# ============================================================
|
|
18
|
-
# SDD Path Resolution
|
|
19
|
-
# ============================================================
|
|
20
|
-
$SDD_OUTPUT_BASE = "sdd"
|
|
21
|
-
$SDD_SPEC_FILE = "sdd/SPEC.md"
|
|
22
|
-
$SDD_TASKS_DIR = "sdd/tasks"
|
|
23
|
-
$SDD_CONVERGE_DIR = "sdd/converge"
|
|
24
|
-
|
|
25
|
-
if (Test-Path ".sdd/config.json") {
|
|
26
|
-
try {
|
|
27
|
-
$config = Get-Content ".sdd/config.json" -Raw | ConvertFrom-Json
|
|
28
|
-
if ($config.output) {
|
|
29
|
-
if ($config.output.baseDir) { $SDD_OUTPUT_BASE = $config.output.baseDir }
|
|
30
|
-
if ($config.output.spec) { $SDD_SPEC_FILE = $config.output.spec }
|
|
31
|
-
if ($config.output.tasksDir) { $SDD_TASKS_DIR = $config.output.tasksDir }
|
|
32
|
-
if ($config.output.convergeDir) { $SDD_CONVERGE_DIR = $config.output.convergeDir }
|
|
33
|
-
}
|
|
34
|
-
} catch {}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
# Legacy fallback
|
|
38
|
-
if (-not $SpecFile) {
|
|
39
|
-
if (Test-Path "SPEC.md") { $SpecFile = "SPEC.md" }
|
|
40
|
-
elseif (Test-Path $SDD_SPEC_FILE) { $SpecFile = $SDD_SPEC_FILE }
|
|
41
|
-
else { $SpecFile = $SDD_SPEC_FILE }
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (-not $ConvergeDir) {
|
|
45
|
-
if (Test-Path "converge") { $ConvergeDir = "converge" }
|
|
46
|
-
else { $ConvergeDir = $SDD_CONVERGE_DIR }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
# Ensure converge directory exists
|
|
50
|
-
if (-not (Test-Path $ConvergeDir)) {
|
|
51
|
-
New-Item -ItemType Directory -Path $ConvergeDir -Force | Out-Null
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
# Import shared functions
|
|
55
|
-
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
56
|
-
. "$scriptDir/shared-converge.ps1"
|
|
57
|
-
|
|
58
|
-
# Find Task File
|
|
59
|
-
function Get-TaskFile {
|
|
60
|
-
param([string]$TaskId, [string]$Dir)
|
|
61
|
-
|
|
62
|
-
if (-not $Dir) {
|
|
63
|
-
# Check new location first
|
|
64
|
-
if (Test-Path $SDD_TASKS_DIR) {
|
|
65
|
-
$dirs = Get-ChildItem -Path $SDD_TASKS_DIR -Filter "task-*-*" -Directory -ErrorAction SilentlyContinue
|
|
66
|
-
if ($dirs) {
|
|
67
|
-
$Dir = Join-Path $SDD_TASKS_DIR ($dirs | Select-Object -First 1).Name
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
# Fallback to legacy
|
|
71
|
-
if (-not $Dir) {
|
|
72
|
-
$dirs = Get-ChildItem -Filter "task-*-*" -Directory -ErrorAction SilentlyContinue
|
|
73
|
-
if ($dirs) { $Dir = ($dirs | Select-Object -First 1).Name }
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (-not $Dir) { return $null, $null }
|
|
77
|
-
|
|
78
|
-
$pattern = "$TaskId-*.md"
|
|
79
|
-
$files = Get-ChildItem -Path $Dir -Filter $pattern -ErrorAction SilentlyContinue
|
|
80
|
-
$files = $files | Where-Object { $_.Name -ne "MASTER-TASKS.md" }
|
|
81
|
-
return $Dir, ($files | Select-Object -First 1)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
# Generate Report
|
|
85
|
-
function New-ConvergeReport {
|
|
86
|
-
param(
|
|
87
|
-
[string]$TaskId,
|
|
88
|
-
[string[]]$Clauses,
|
|
89
|
-
[object[]]$Issues,
|
|
90
|
-
[int]$PassCount,
|
|
91
|
-
[int]$FailCount,
|
|
92
|
-
[string]$SpecFile,
|
|
93
|
-
[string]$OutputDir
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
$reportDir = Join-Path $OutputDir "task-$TaskId"
|
|
97
|
-
if (-not (Test-Path $reportDir)) {
|
|
98
|
-
New-Item -ItemType Directory -Path $reportDir -Force | Out-Null
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
$reportFile = Join-Path $reportDir "report.md"
|
|
102
|
-
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|
103
|
-
|
|
104
|
-
$report = @"
|
|
105
|
-
# Converge Report: $TaskId
|
|
106
|
-
|
|
107
|
-
Generated: $timestamp by SDD Converge
|
|
108
|
-
|
|
109
|
-
## Summary
|
|
110
|
-
|
|
111
|
-
| Metric | Value |
|
|
112
|
-
|--------|-------|
|
|
113
|
-
| Clauses Found | $($Clauses.Count) |
|
|
114
|
-
| Clauses Passed | $PassCount |
|
|
115
|
-
| Clauses Failed | $FailCount |
|
|
116
|
-
| Status | $(if ($FailCount -eq 0) { 'PASS' } else { 'FAIL' }) |
|
|
117
|
-
|
|
118
|
-
## Clause Results
|
|
119
|
-
|
|
120
|
-
| Clause ID | Type | Status | Notes |
|
|
121
|
-
|-----------|------|--------|-------|
|
|
122
|
-
"@
|
|
123
|
-
|
|
124
|
-
foreach ($clauseId in $Clauses) {
|
|
125
|
-
$clauseType = Get-ClauseType -ClauseId $clauseId
|
|
126
|
-
$isFail = $clauseId -in $Issues.Clause
|
|
127
|
-
$status = if ($isFail) { "FAIL" } else { "PASS" }
|
|
128
|
-
$report += "`n| $clauseId | $clauseType | $status | [Text spec] |"
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
$report += @"
|
|
132
|
-
|
|
133
|
-
## Issues Found
|
|
134
|
-
|
|
135
|
-
| Clause | Issue | Location |
|
|
136
|
-
|--------|-------|----------|
|
|
137
|
-
"@
|
|
138
|
-
|
|
139
|
-
if ($Issues.Count -eq 0) {
|
|
140
|
-
$report += "`n| - | No issues found | - |"
|
|
141
|
-
} else {
|
|
142
|
-
foreach ($issue in $Issues) {
|
|
143
|
-
$report += "`n| $($issue.Clause) | $($issue.Issue) | $($issue.Location) |"
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
$report += @"
|
|
148
|
-
|
|
149
|
-
## Next Steps
|
|
150
|
-
|
|
151
|
-
$(if ($FailCount -eq 0) {
|
|
152
|
-
"- [ ] **PROCEED:** All checks pass -> Move to next task"
|
|
153
|
-
} else {
|
|
154
|
-
"- [ ] **FIX:** Address issues above -> Re-run converge
|
|
155
|
-
- [ ] **IF SPEC ISSUE:** Update SPEC.md clause -> Re-generate task"
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
Generated by SDD Per-Task Converge v1.1.0
|
|
160
|
-
"@
|
|
161
|
-
|
|
162
|
-
$report | Out-File -FilePath $reportFile -Encoding UTF8
|
|
163
|
-
return $reportFile
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
# Main Execution
|
|
167
|
-
Write-Host ""
|
|
168
|
-
Write-Host "========================================" -ForegroundColor Cyan
|
|
169
|
-
Write-Host " SDD Per-Task Converge: $TaskId" -ForegroundColor Cyan
|
|
170
|
-
Write-Host "========================================" -ForegroundColor Cyan
|
|
171
|
-
Write-Host ""
|
|
172
|
-
Write-Host "Output: $SDD_OUTPUT_BASE/" -ForegroundColor Gray
|
|
173
|
-
Write-Host ""
|
|
174
|
-
|
|
175
|
-
# Step 1: Find task file
|
|
176
|
-
$taskDir, $taskFile = Get-TaskFile -TaskId $TaskId -Dir $TaskDir
|
|
177
|
-
if (-not $taskFile) {
|
|
178
|
-
Write-Host "[FAIL] Task file for $TaskId not found" -ForegroundColor Red
|
|
179
|
-
exit 1
|
|
180
|
-
}
|
|
181
|
-
Write-Host "[PASS] Task file: $([System.IO.Path]::GetFileName($taskDir))/$($taskFile.Name)" -ForegroundColor Green
|
|
182
|
-
|
|
183
|
-
# Step 2: Find SPEC.md
|
|
184
|
-
if (-not (Test-Path $SpecFile)) {
|
|
185
|
-
Write-Host "[FAIL] SPEC file not found: $SpecFile" -ForegroundColor Red
|
|
186
|
-
exit 1
|
|
187
|
-
}
|
|
188
|
-
Write-Host "[PASS] SPEC file: $SpecFile" -ForegroundColor Green
|
|
189
|
-
|
|
190
|
-
# Step 3: Extract clauses from SPEC.md
|
|
191
|
-
$clauses = Get-SpecClauses -File $SpecFile
|
|
192
|
-
Write-Host "[INFO] Found $($clauses.Count) clause IDs in SPEC.md" -ForegroundColor Cyan
|
|
193
|
-
|
|
194
|
-
# Step 4: Get implementation files
|
|
195
|
-
$implFiles = Get-TaskFiles -TaskId $TaskId -TaskDir $taskDir
|
|
196
|
-
Write-Host "[INFO] Implementation files: $($implFiles.Count)" -ForegroundColor Cyan
|
|
197
|
-
foreach ($f in $implFiles) {
|
|
198
|
-
Write-Host " -> $f" -ForegroundColor Gray
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
# Step 5: Validate each clause
|
|
202
|
-
Write-Host ""
|
|
203
|
-
Write-Host "--- Clause Validation ---" -ForegroundColor Magenta
|
|
204
|
-
|
|
205
|
-
$passCount = 0
|
|
206
|
-
$failCount = 0
|
|
207
|
-
|
|
208
|
-
foreach ($clause in $clauses) {
|
|
209
|
-
$result = Test-ClauseMatch -ClauseId $clause.Id -ClauseText $clause.Text -ClauseType $clause.Type -Files $implFiles
|
|
210
|
-
|
|
211
|
-
if ($result.Found) {
|
|
212
|
-
$passCount++
|
|
213
|
-
Write-Host "[PASS] $($clause.Id)" -ForegroundColor Green
|
|
214
|
-
} else {
|
|
215
|
-
$failCount++
|
|
216
|
-
Write-Host "[FAIL] $($clause.Id)" -ForegroundColor Red
|
|
217
|
-
$script:issuesFound += @{
|
|
218
|
-
Clause = $clause.Id
|
|
219
|
-
Issue = "Not found in implementation"
|
|
220
|
-
Location = $result.Location
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
# Summary
|
|
226
|
-
Write-Host ""
|
|
227
|
-
Write-Host "========================================" -ForegroundColor Cyan
|
|
228
|
-
Write-Host " Summary" -ForegroundColor Cyan
|
|
229
|
-
Write-Host "========================================" -ForegroundColor Cyan
|
|
230
|
-
Write-Host ""
|
|
231
|
-
Write-Host " Clauses Passed: $passCount" -ForegroundColor Green
|
|
232
|
-
Write-Host " Clauses Failed: $failCount" -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "Green" })
|
|
233
|
-
|
|
234
|
-
if ($failCount -gt 0) {
|
|
235
|
-
Write-Host ""
|
|
236
|
-
Write-Host " Issues Found:" -ForegroundColor Yellow
|
|
237
|
-
foreach ($issue in $script:issuesFound) {
|
|
238
|
-
Write-Host " - $($issue.Clause): $($issue.Issue)" -ForegroundColor Yellow
|
|
239
|
-
Write-Host " Location: $($issue.Location)" -ForegroundColor DarkGray
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
# Step 6: Generate report
|
|
244
|
-
$reportFile = New-ConvergeReport -TaskId $TaskId -Clauses $clauses.Id -Issues $script:issuesFound -PassCount $passCount -FailCount $failCount -SpecFile $SpecFile -OutputDir $ConvergeDir
|
|
245
|
-
Write-Host ""
|
|
246
|
-
Write-Host "[PASS] Report saved: $reportFile" -ForegroundColor Green
|
|
247
|
-
|
|
248
|
-
# Exit code
|
|
249
|
-
if ($failCount -gt 0) {
|
|
250
|
-
if ($Strict) {
|
|
251
|
-
Write-Host "[FAIL] Converge FAILED (strict mode)" -ForegroundColor Red
|
|
252
|
-
exit 1
|
|
253
|
-
} else {
|
|
254
|
-
Write-Host "[WARN] Converge completed with issues (non-strict)" -ForegroundColor Yellow
|
|
255
|
-
exit 0
|
|
256
|
-
}
|
|
257
|
-
} else {
|
|
258
|
-
Write-Host "[PASS] Converge PASSED" -ForegroundColor Green
|
|
259
|
-
exit 0
|
|
260
|
-
}
|
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
|
-
}
|