Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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] - <date>` 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
Expand Down Expand Up @@ -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
11 changes: 9 additions & 2 deletions scripts/validate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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] - <date> 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'
Expand Down
11 changes: 10 additions & 1 deletion scripts/validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading