diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b0c51f..caf72d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ Please choose versions by [Semantic Versioning](http://semver.org/). * MINOR version when you add functionality in a backwards-compatible manner, and * PATCH version when you make backwards-compatible bug fixes. +## Unreleased + +- build: add `scripts/check-changelog.sh` (wired into `make check` → `precommit`) that fails the build when a `##` section is placed above the CHANGELOG preamble — guards against the malformed-CHANGELOG class of error a changelog edit can otherwise introduce undetected + ## v0.96.3 - docs(dod): specify `## Unreleased` placement in the Definition of Done — must sit below the preamble and above the newest version section, never above the preamble (prevents the malformed-CHANGELOG class of error a dark-factory prompt can otherwise introduce) diff --git a/Makefile b/Makefile index 7743d29..f566c73 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,10 @@ precommit: ensure format generate test check addlicense check-versions: @bash scripts/check-versions.sh +.PHONY: check-changelog +check-changelog: + @bash scripts/check-changelog.sh + .PHONY: release-check release-check: precommit check-versions @echo "ready to release" @@ -64,7 +68,7 @@ test: go test -mod=mod -count=1 -p=$${GO_TEST_PARALLEL:-1} -cover $(TESTFLAGS_RACE) $(shell go list -mod=mod ./... | grep -v /vendor/) .PHONY: check -check: lint vet vulncheck osv-scanner trivy +check: lint vet vulncheck osv-scanner trivy check-changelog .PHONY: lint lint: diff --git a/scripts/check-changelog.sh b/scripts/check-changelog.sh new file mode 100755 index 0000000..3cfa303 --- /dev/null +++ b/scripts/check-changelog.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# +# Verifies CHANGELOG.md structure. The "# Changelog" title and the +# "All notable changes…" preamble must precede every "## " section +# (## Unreleased or ## vX.Y.Z). Guards against a section inserted above +# the preamble — the malformed shape a changelog edit can otherwise +# introduce without any other check catching it. +# +# Exits non-zero with a clear message on violation. +# Run from repo root (Makefile target `check-changelog`). + +set -euo pipefail + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +cd "$ROOT" + +FILE=CHANGELOG.md + +# `|| true` is required: under `set -euo pipefail` a no-match grep in a +# command substitution would abort the script before the emptiness checks run. +PREAMBLE_LINE=$(grep -n -m1 '^All notable changes to this project' "$FILE" | cut -d: -f1 || true) +FIRST_SECTION_LINE=$(grep -n -m1 '^## ' "$FILE" | cut -d: -f1 || true) + +if [ -z "$PREAMBLE_LINE" ]; then + echo "❌ CHANGELOG structure: missing preamble line 'All notable changes to this project…' in $FILE" >&2 + exit 1 +fi + +if [ -n "$FIRST_SECTION_LINE" ] && [ "$FIRST_SECTION_LINE" -lt "$PREAMBLE_LINE" ]; then + echo "❌ CHANGELOG structure: a '## ' section at line $FIRST_SECTION_LINE appears before the preamble (line $PREAMBLE_LINE) in $FILE" >&2 + echo " Expected order: '# Changelog' → preamble → '## Unreleased' → '## vX.Y.Z' (newest first)." >&2 + exit 1 +fi + +echo "CHANGELOG structure OK"