Files
ai-memory/scripts/check-changelog-sections.sh
AkitaOnRailsandClaude Opus 5 4c87a32fda ci: reject duplicate CHANGELOG section headings
`bin/release` copies `## [Unreleased]` into the new version section
verbatim, so a repeated `### ` heading there ships release notes with the
entries split across two identical headings. It is valid Markdown and
invisible in review, which is why it kept happening — I introduced one
merging #429, fixed it in #436, then reintroduced it in #439 and caught it
only when `bin/release` produced a visibly wrong 1.29.0 section.

The check found the same defect in four already-released sections
(1.19.0, 1.13.0, 1.4.0, 1.1.1), so this predates today by months. Those are
folded here so the check can be enforced repo-wide instead of carrying an
exemption list.

The fold is deliberately minimal: duplicate headings are merged into their
first occurrence and section order is left exactly as it was, rather than
re-sorting into Keep a Changelog order. Verified by comparing every
non-heading line before and after — the only delta is the five blank lines
that separated the five removed headings. No entry text moved between
versions, no entry altered, all 92 version headings intact.

Fails loudly rather than merging automatically, so the second author sees
the collision instead of having it silently resolved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 17:52:25 -03:00

65 lines
2.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Fail if any CHANGELOG version section repeats a `### ` heading.
#
# `bin/release` copies `## [Unreleased]` into the new version section
# verbatim. A duplicated heading there ships release notes with, say, two
# separate "Fixed" lists and the entries split between them — and nothing
# else catches it, because a duplicate heading is valid Markdown.
#
# This is not hypothetical: it happened twice in one day, both times from
# a maintainer adding a section at the top of `[Unreleased]` while an
# equivalent one already sat further down after an earlier merge.
#
# Merging on merge, rather than rejecting, would hide the conflict from
# whoever wrote the second entry; failing loudly keeps the choice with a
# human.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CHANGELOG="${1:-$REPO_ROOT/CHANGELOG.md}"
if [[ ! -f "$CHANGELOG" ]]; then
echo "check-changelog-sections: $CHANGELOG not found" >&2
exit 2
fi
status=0
current=""
while IFS= read -r line; do
case "$line" in
'## ['*)
current="${line#\#\# }"
seen=" "
;;
'### '*)
[[ -n "$current" ]] || continue
heading="${line#\#\#\# }"
# Trailing whitespace should not create a distinct heading.
heading="${heading%"${heading##*[![:space:]]}"}"
if [[ "$seen" == *" ${heading} "* ]]; then
echo "error: ${current} repeats the '### ${heading}' heading." >&2
status=1
else
seen="${seen}${heading} "
fi
;;
esac
done < "$CHANGELOG"
if [[ $status -ne 0 ]]; then
cat >&2 <<'EOF'
Each version section may use a given heading only once. Merge the entries
under a single heading, in Keep a Changelog order:
Added, Changed, Deprecated, Removed, Fixed, Security
(this project also uses Improved, placed after Changed).
EOF
exit 1
fi
echo "CHANGELOG section headings are unique within every version."