diff --git a/CHANGELOG.md b/CHANGELOG.md index 74e1d4b..1c6b64b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- `scripts/validate.{ps1,sh}` Check 6 now treats an empty `[Unreleased]` section as passing when the + section immediately below it is a dated `[x.y.z] - ` release heading (the freshly cut version), + so a clean post-release CHANGELOG no longer fails CI. A non-release-state empty `[Unreleased]` still + fails, preserving the "every PR adds a changelog line" invariant. + +--- + +## [1.3.0] - 2026-06-18 + +Release-tooling and CI hardening. Adds the `/sd:release` command (10th), a single-command repo invariant +validator with a Windows + Ubuntu CI matrix, an uninstaller, and a batch of command/agent refinements. + ### Added - `/sd:release` command (10th command) - generates release notes from completed specs: collects every spec in `done` status (feature / bug / refactor / perf; RCA excluded), groups them into @@ -229,6 +242,8 @@ Each hook ships in two flavours: - Operating systems: Windows 11 + PowerShell 5.1 / 7.x, macOS 13+, Ubuntu 22.04+. - Optional MCP servers: Atlassian, Context7, sequential-thinking, GitNexus, MSSQL, Playwright, Tavily. -[Unreleased]: https://github.com/developzoneio/specwright/compare/v1.1.0...HEAD +[Unreleased]: https://github.com/developzoneio/specwright/compare/v1.3.0...HEAD +[1.3.0]: https://github.com/developzoneio/specwright/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/developzoneio/specwright/compare/v1.1.0...v1.2.0 [1.1.0]: https://github.com/developzoneio/specwright/compare/v1.0.0...v1.1.0 [1.0.0]: https://github.com/developzoneio/specwright/releases/tag/v1.0.0 diff --git a/scripts/validate.ps1 b/scripts/validate.ps1 index b21aa7f..ee9f18b 100644 --- a/scripts/validate.ps1 +++ b/scripts/validate.ps1 @@ -238,7 +238,7 @@ try { # ---- Check 6: CHANGELOG [Unreleased] non-empty ----------------------------- -Write-Section 'Check 6/6: CHANGELOG [Unreleased] non-empty' +Write-Section 'Check 6/6: CHANGELOG [Unreleased] gate' $changelog = Join-Path $repoRoot 'CHANGELOG.md' $lines = Get-Content -LiteralPath $changelog $start = -1 @@ -250,12 +250,19 @@ if ($start -lt 0) { Add-Failure 'changelog: no [Unreleased] header' } else { $hasEntry = $false + $nextHeader = $null for ($i = $start + 1; $i -lt $lines.Count; $i++) { - if ($lines[$i] -match '^##\s+\[') { break } + if ($lines[$i] -match '^##\s+\[') { $nextHeader = $lines[$i]; break } if ($lines[$i] -match '^\s*-\s+\S') { $hasEntry = $true; break } } + # An empty [Unreleased] passes only immediately after a release: the next + # section must be a dated [x.y.z] - heading (the freshly cut + # version). Otherwise every PR must add an entry under [Unreleased]. + $justReleased = ($null -ne $nextHeader) -and ($nextHeader -match '^##\s+\[\d+\.\d+\.\d+\]\s+-\s+\S') if ($hasEntry) { Write-Ok '[Unreleased] has at least one entry' + } elseif ($justReleased) { + Write-Ok '[Unreleased] empty but sits directly above a dated release (just cut)' } else { Write-FailMsg '[Unreleased] section is empty (add a changelog entry)' Add-Failure 'changelog: [Unreleased] empty' diff --git a/scripts/validate.sh b/scripts/validate.sh index 9e06df8..0a2ad4e 100755 --- a/scripts/validate.sh +++ b/scripts/validate.sh @@ -171,15 +171,24 @@ trap - EXIT # ---- Check 6: CHANGELOG [Unreleased] non-empty ----------------------------- -section "Check 6/6: CHANGELOG [Unreleased] non-empty" +section "Check 6/6: CHANGELOG [Unreleased] gate" changelog="$repo_root/CHANGELOG.md" block="$(awk ' /^##[[:space:]]+\[Unreleased\]/ { f=1; next } /^##[[:space:]]+\[/ { f=0 } f { print } ' "$changelog")" +# Header of the section immediately below [Unreleased]. +next_header="$(awk ' + /^##[[:space:]]+\[Unreleased\]/ { f=1; next } + f && /^##[[:space:]]+\[/ { print; exit } +' "$changelog")" if printf '%s\n' "$block" | grep -qE '^[[:space:]]*-[[:space:]]+[^[:space:]]'; then ok "[Unreleased] has at least one entry" +elif printf '%s\n' "$next_header" \ + | grep -qE '^##[[:space:]]+\[[0-9]+\.[0-9]+\.[0-9]+\][[:space:]]+-[[:space:]]+[^[:space:]]'; then + # Empty [Unreleased] is allowed only directly above a freshly cut release. + ok "[Unreleased] empty but sits directly above a dated release (just cut)" else fail "[Unreleased] section is empty (add a changelog entry)" add_failure "changelog: [Unreleased] empty"