@@ -11,11 +11,181 @@ param(
1111
1212. (Join-Path $PSScriptRoot ' common.ps1' )
1313
14+ # Multi-app support (T086)
15+ if (-not (Get-Command Detect- DevSparkMode - ErrorAction SilentlyContinue)) {
16+ . " $PSScriptRoot /common.ps1"
17+ }
18+
19+ function Test-CompletedTasks {
20+ param ([string ]$Content )
21+
22+ if (-not $Content ) {
23+ return $false
24+ }
25+
26+ $unchecked = ([regex ]::Matches($Content , ' ^\s*- \[ \]' , ' Multiline' )).Count
27+ $checked = ([regex ]::Matches($Content , ' ^\s*- \[[xX]\]' , ' Multiline' )).Count
28+ return ($unchecked -eq 0 -and $checked -gt 0 )
29+ }
30+
31+ function Get-ArchiveRecovery {
32+ param (
33+ [string ]$RepoRoot ,
34+ [string ]$FromDate ,
35+ [string ]$ToDate
36+ )
37+
38+ $archiveRoot = Join-Path $RepoRoot ' .archive'
39+ $completedSpecs = @ ()
40+ $quickfixes = @ ()
41+ $fromCutoffDate = $null
42+ $toCutoffDate = $null
43+
44+ if ($FromDate ) {
45+ try {
46+ $fromCutoffDate = ([datetime ]$FromDate ).Date
47+ } catch {
48+ $fromCutoffDate = $null
49+ }
50+ }
51+
52+ if ($ToDate ) {
53+ try {
54+ $toCutoffDate = ([datetime ]$ToDate ).Date
55+ } catch {
56+ $toCutoffDate = $null
57+ }
58+ }
59+
60+ if (-not (Test-Path $archiveRoot )) {
61+ return @ {
62+ Specs = @ ()
63+ Quickfixes = @ ()
64+ }
65+ }
66+
67+ Get-ChildItem - Path $archiveRoot - Directory - ErrorAction SilentlyContinue | ForEach-Object {
68+ $batchDate = $null
69+ if ($_.Name -match ' ^\d{4}-\d{2}-\d{2}$' ) {
70+ try {
71+ $batchDate = ([datetime ]::ParseExact($_.Name , ' yyyy-MM-dd' , $null )).Date
72+ } catch {
73+ $batchDate = $null
74+ }
75+ }
76+
77+ if ($fromCutoffDate -and $batchDate -and $batchDate -lt $fromCutoffDate ) {
78+ return
79+ }
80+
81+ if ($toCutoffDate -and $batchDate -and $batchDate -gt $toCutoffDate ) {
82+ return
83+ }
84+
85+ $specArchiveRoot = Join-Path $_.FullName ' .documentation/specs'
86+ if (Test-Path $specArchiveRoot ) {
87+ Get-ChildItem - Path $specArchiveRoot - Directory - ErrorAction SilentlyContinue | ForEach-Object {
88+ $tasksFile = Join-Path $_.FullName ' tasks.md'
89+ if (-not (Test-Path $tasksFile )) {
90+ return
91+ }
92+
93+ $content = Get-Content $tasksFile - Raw - ErrorAction SilentlyContinue
94+ if (Test-CompletedTasks - Content $content ) {
95+ $completedSpecs += $_.Name
96+ }
97+ }
98+ }
99+
100+ $quickfixArchiveRoot = Join-Path $_.FullName ' .documentation/quickfixes'
101+ if (Test-Path $quickfixArchiveRoot ) {
102+ $quickfixes += Get-ChildItem - Path $quickfixArchiveRoot - Filter ' QF-*.md' - ErrorAction SilentlyContinue |
103+ ForEach-Object { $_.BaseName }
104+ }
105+ }
106+
107+ return @ {
108+ Specs = @ ($completedSpecs | Where-Object { $_ } | Sort-Object - Unique)
109+ Quickfixes = @ ($quickfixes | Where-Object { $_ } | Sort-Object - Unique)
110+ }
111+ }
112+
113+ function Get-HistoryRecovery {
114+ param (
115+ [string ]$ScriptPath ,
116+ [string ]$BaseRef ,
117+ [string ]$FromDate ,
118+ [string ]$ToDate
119+ )
120+
121+ $empty = @ {
122+ Specs = @ ()
123+ Quickfixes = @ ()
124+ ArchiveMovesDetected = $false
125+ ReleaseFrom = $FromDate
126+ ReleaseTo = $ToDate
127+ Commits = @ ()
128+ Contributors = @ ()
129+ MergedPrNumbers = @ ()
130+ MergedPrCount = 0
131+ PrReviews = @ ()
132+ PrReviewSummary = @ {
133+ matched_reviews = 0
134+ files_changed = 0
135+ tests_added = 0
136+ breaking_changes = 0
137+ resolved_high_findings = 0
138+ }
139+ }
140+
141+ if (-not (Test-Path $ScriptPath ) -or -not (Test-HasGit )) {
142+ return $empty
143+ }
144+
145+ try {
146+ $historyJson = if ($BaseRef ) {
147+ & $ScriptPath - BaseRef $BaseRef - FromDate $FromDate - ToDate $ToDate - Json
148+ } else {
149+ & $ScriptPath - FromDate $FromDate - ToDate $ToDate - Json
150+ }
151+
152+ $history = $historyJson | ConvertFrom-Json
153+ return @ {
154+ Specs = @ ($history.RECOVERED_SPECS | Where-Object { $_.completed } | ForEach-Object { $_.name } | Sort-Object - Unique)
155+ Quickfixes = @ ($history.RECOVERED_QUICKFIXES | ForEach-Object { $_.id } | Sort-Object - Unique)
156+ ArchiveMovesDetected = [bool ]$history.ARCHIVE_MOVES_DETECTED
157+ ReleaseFrom = $history.RELEASE_FROM
158+ ReleaseTo = $history.RELEASE_TO
159+ Commits = @ ($history.COMMITS )
160+ Contributors = @ ($history.CONTRIBUTORS )
161+ MergedPrNumbers = @ ($history.MERGED_PR_NUMBERS )
162+ MergedPrCount = [int ]$history.MERGED_PR_COUNT
163+ PrReviews = @ ($history.PR_REVIEWS )
164+ PrReviewSummary = $history.PR_REVIEW_SUMMARY
165+ }
166+ } catch {
167+ return $empty
168+ }
169+ }
170+
14171# Parse arguments
15172$versionArg = " "
16- foreach ($arg in $Arguments ) {
173+ $releaseFromArg = " "
174+ for ($index = 0 ; $index -lt $Arguments.Count ; $index ++ ) {
175+ $arg = $Arguments [$index ]
17176 if ($arg -match ' ^v?\d+\.\d+' ) {
18177 $versionArg = $arg -replace ' ^v' , ' '
178+ continue
179+ }
180+
181+ if ($arg -eq ' --from' -and $index + 1 -lt $Arguments.Count ) {
182+ $releaseFromArg = $Arguments [$index + 1 ]
183+ $index ++
184+ continue
185+ }
186+
187+ if ($arg -match ' ^--from=(.+)$' ) {
188+ $releaseFromArg = $matches [1 ]
19189 }
20190}
21191
@@ -83,14 +253,19 @@ if (Test-HasGit) {
83253 }
84254}
85255
256+ # Get timestamp
257+ $timestamp = (Get-Date ).ToUniversalTime().ToString(" yyyy-MM-ddTHH:mm:ssZ" )
258+ $releaseDate = Get-Date - Format " yyyy-MM-dd"
259+ $releaseFrom = if ($releaseFromArg ) { $releaseFromArg } elseif ($lastReleaseDate ) { ([datetime ]$lastReleaseDate ).ToString(' yyyy-MM-dd' ) } else { ' ' }
260+ $releaseTo = $releaseDate
261+
86262# Find completed and pending specs
87263$completedSpecs = @ ()
88264$pendingSpecs = @ ()
89265
90266if (Test-Path $specsDir ) {
91267 Get-ChildItem - Path $specsDir - Directory | ForEach-Object {
92268 $specName = $_.Name
93- # Skip pr-review directory
94269 if ($specName -eq " pr-review" ) { return }
95270
96271 $tasksFile = Join-Path $_.FullName " tasks.md"
@@ -121,6 +296,26 @@ if (Test-Path $quickfixDir) {
121296 ForEach-Object { $_.BaseName }
122297}
123298
299+ $activeCompletedSpecs = @ ($completedSpecs | Sort-Object - Unique)
300+ $activeQuickfixes = @ ($quickfixes | Sort-Object - Unique)
301+
302+ $archiveRecovery = Get-ArchiveRecovery - RepoRoot $repoRoot - FromDate $releaseFrom - ToDate $releaseTo
303+ $historyRecovery = Get-HistoryRecovery - ScriptPath (Join-Path $PSScriptRoot ' release-history-context.ps1' ) - BaseRef $lastTag - FromDate $releaseFrom - ToDate $releaseTo
304+
305+ $completedSpecs = @ ($activeCompletedSpecs + $archiveRecovery.Specs + $historyRecovery.Specs | Where-Object { $_ } | Sort-Object - Unique)
306+ $quickfixes = @ ($activeQuickfixes + $archiveRecovery.Quickfixes + $historyRecovery.Quickfixes | Where-Object { $_ } | Sort-Object - Unique)
307+
308+ $recoveredCompletedSpecs = @ ($completedSpecs | Where-Object { $_ -notin $activeCompletedSpecs })
309+ $recoveredQuickfixes = @ ($quickfixes | Where-Object { $_ -notin $activeQuickfixes })
310+ $archiveRecoveryUsed = [bool ](($archiveRecovery.Specs.Count + $archiveRecovery.Quickfixes.Count ) -gt 0 )
311+ $historyRecoveryUsed = [bool ](($historyRecovery.Specs.Count + $historyRecovery.Quickfixes.Count ) -gt 0 )
312+ $contributors = @ ($historyRecovery.Contributors | Where-Object { $_ } | Sort-Object - Unique)
313+ $commitsSince = $historyRecovery.Commits.Count
314+ $mergedPrNumbers = @ ($historyRecovery.MergedPrNumbers | Sort-Object - Unique)
315+ $mergedPrCount = [int ]$historyRecovery.MergedPrCount
316+ $prReviews = @ ($historyRecovery.PrReviews )
317+ $prReviewSummary = $historyRecovery.PrReviewSummary
318+
124319# Calculate next version if not provided
125320$nextVersion = $versionArg
126321$versionBump = " patch"
@@ -145,9 +340,7 @@ if (-not $nextVersion) {
145340 }
146341}
147342
148- # Get contributors
149- $contributors = @ ()
150- if (Test-HasGit ) {
343+ if ($contributors.Count -eq 0 -and (Test-HasGit )) {
151344 try {
152345 if ($lastTag ) {
153346 $contributors = git log " $lastTag ..HEAD" -- format= ' %aN' 2> $null | Sort-Object - Unique
@@ -158,9 +351,19 @@ if (Test-HasGit) {
158351 } catch { }
159352}
160353
161- # Get timestamp
162- $timestamp = (Get-Date ).ToUniversalTime().ToString(" yyyy-MM-ddTHH:mm:ssZ" )
163- $releaseDate = Get-Date - Format " yyyy-MM-dd"
354+ # DevSpark version stamp info
355+ $versionStampPath = Join-Path $repoRoot " .devspark/VERSION"
356+ $legacyVersionStampPath = Join-Path $repoRoot " .documentation/DEVSPARK_VERSION"
357+ $installedVersion = " "
358+ if (Test-Path $versionStampPath ) {
359+ try {
360+ $installedVersion = ((Get-Content $versionStampPath - ErrorAction SilentlyContinue) | Select-String ' ^version:\s*(.+)$' | Select-Object - First 1 ).Matches.Groups[1 ].Value.Trim()
361+ } catch { }
362+ } elseif (Test-Path $legacyVersionStampPath ) {
363+ try {
364+ $installedVersion = (Get-Content $legacyVersionStampPath - TotalCount 1 - ErrorAction SilentlyContinue).Trim()
365+ } catch { }
366+ }
164367
165368# Output
166369if ($Json ) {
@@ -175,17 +378,33 @@ if ($Json) {
175378 VERSION_SOURCE = $versionSource
176379 NEXT_VERSION = $nextVersion
177380 VERSION_BUMP = $versionBump
381+ RELEASE_FROM = $releaseFrom
382+ RELEASE_TO = $releaseTo
383+ ACTIVE_COMPLETED_SPECS = $activeCompletedSpecs
178384 COMPLETED_SPECS = $completedSpecs
385+ RECOVERED_COMPLETED_SPECS = $recoveredCompletedSpecs
179386 PENDING_SPECS = $pendingSpecs
387+ ACTIVE_QUICKFIXES = $activeQuickfixes
180388 QUICKFIXES = $quickfixes
389+ RECOVERED_QUICKFIXES = $recoveredQuickfixes
181390 LAST_TAG = $lastTag
182391 LAST_RELEASE_DATE = $lastReleaseDate
183392 COMMITS_SINCE_RELEASE = $commitsSince
184393 CONTRIBUTORS = $contributors
394+ MERGED_PR_NUMBERS = $mergedPrNumbers
395+ MERGED_PR_COUNT = $mergedPrCount
396+ PR_REVIEWS = $prReviews
397+ PR_REVIEW_SUMMARY = $prReviewSummary
398+ ARCHIVE_RECOVERY_USED = $archiveRecoveryUsed
399+ HISTORY_RECOVERY_USED = $historyRecoveryUsed
400+ HISTORY_ARCHIVE_MOVES_DETECTED = [bool ]$historyRecovery.ArchiveMovesDetected
185401 TIMESTAMP = $timestamp
186402 RELEASE_DATE = $releaseDate
187403 DRY_RUN = [bool ]$DryRun
188- } | ConvertTo-Json - Compress
404+ DEVSPARK_VERSION_PATH = $versionStampPath
405+ LEGACY_DEVSPARK_VERSION_PATH = $legacyVersionStampPath
406+ INSTALLED_VERSION = $installedVersion
407+ } | ConvertTo-Json - Depth 6
189408}
190409else {
191410 Write-Output " Release Context"
@@ -194,12 +413,25 @@ else {
194413 Write-Output " Current Version: $currentVersion (from $versionSource )"
195414 Write-Output " Next Version: $nextVersion ($versionBump bump)"
196415 Write-Output " Last Release: $lastTag ($lastReleaseDate )"
416+ Write-Output " Release Window: $releaseFrom -> $releaseTo "
197417 Write-Output " Commits Since: $commitsSince "
198418 Write-Output " "
199419 Write-Output " Completed Specs: $ ( $completedSpecs.Count ) "
200420 Write-Output " Pending Specs: $ ( $pendingSpecs.Count ) "
201421 Write-Output " Quickfixes: $ ( $quickfixes.Count ) "
202422 Write-Output " Contributors: $ ( $contributors.Count ) "
423+ Write-Output " Merged PRs: $mergedPrCount "
424+ if ($recoveredCompletedSpecs.Count -gt 0 -or $recoveredQuickfixes.Count -gt 0 ) {
425+ Write-Output ' '
426+ Write-Output " Recovered Specs: $ ( $recoveredCompletedSpecs.Count ) "
427+ Write-Output " Recovered Quickfixes: $ ( $recoveredQuickfixes.Count ) "
428+ }
429+ Write-Output " "
430+ if ($installedVersion ) {
431+ Write-Output " Installed DevSpark Version: $installedVersion "
432+ } else {
433+ Write-Output " Installed DevSpark Version: (VERSION stamp not found)"
434+ }
203435 if ($DryRun ) {
204436 Write-Output " "
205437 Write-Output " ** DRY RUN MODE - No changes will be made **"
0 commit comments