forked from heavy-duty/ceremony
feat: changelog_shape_problem reads the declarable anchor changelog.d/shape
The sentinel pins the set's shape, outranking the newest-published-section inference (#182 D2); malformed content is a red diagnosis naming the file. bin/changelog-assemble skips 'shape' in the stray-file loop so the sentinel survives the consumption (#182 D5). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
4debf53872
commit
7d29acdeb0
2 changed files with 42 additions and 3 deletions
|
|
@ -62,12 +62,15 @@ done
|
|||
|
||||
# Every entry in the directory must be a publishable fragment. A stray file
|
||||
# in a machine-assembled directory is a mistake to surface, never to skip —
|
||||
# except README.md, the directory's marker (#112 D1). This runs before the
|
||||
# zero-fragments check so a directory holding only 'notes.txt' names the
|
||||
# stray file instead of claiming emptiness.
|
||||
# except README.md, the directory's marker (#112 D1), and 'shape', the
|
||||
# declared anchor (#182), which changelog_shape_problem validates below and
|
||||
# which deliberately survives the consumption: it is the declaration a
|
||||
# reader in the directory finds, and it must still be there after the
|
||||
# release empties the fragments out.
|
||||
for f in "$dir"/*; do
|
||||
[ -e "$f" ] || continue
|
||||
[ "${f##*/}" = "README.md" ] && continue
|
||||
[ "${f##*/}" = "shape" ] && continue
|
||||
if ! diagnosis="$(changelog_fragment_problem "$f")"; then
|
||||
refuse "$diagnosis"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -199,9 +199,31 @@ changelog_fragment_problem() {
|
|||
# definition shared by the PR-time guard and the release-time assembler:
|
||||
# fragments may not mix grouped headings with ungrouped bullets, and a
|
||||
# non-empty set must match the newest published section when one exists.
|
||||
#
|
||||
# The anchor is declarable (#182): an optional sentinel '<dir>/shape',
|
||||
# holding exactly 'flat' or 'grouped' on one line, pins the set's shape and
|
||||
# outranks the newest-published-section inference — the door a deliberate
|
||||
# flip walks through, while undeclared drift stays red (#159). Absent, the
|
||||
# inference binds unchanged. Any other content — empty, trailing junk, an
|
||||
# unknown word — is a diagnosis naming the file, never a silent fallback.
|
||||
# The sentinel lives in the fragments dir so it binds in both callers: the
|
||||
# assembler calls with changelog="" and still sees it. It is not a fragment
|
||||
# — changelog_fragments matches *.md only, so 'shape' never enters the list.
|
||||
changelog_shape_problem() {
|
||||
local changelog="$1" dir="$2"
|
||||
local fragments f grouped_in="" ungrouped_in="" published="" published_body=""
|
||||
local sentinel="$dir/shape" declared=""
|
||||
|
||||
if [ -f "$sentinel" ]; then
|
||||
declared="$(cat "$sentinel")"
|
||||
case "$declared" in
|
||||
flat | grouped) ;;
|
||||
*)
|
||||
printf "'%s' declares neither shape — its whole content must be 'flat' or 'grouped', one line\n" "$sentinel"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
fragments="$(changelog_fragments "$dir")"
|
||||
[ -n "$fragments" ] || return 0
|
||||
|
|
@ -227,6 +249,20 @@ changelog_shape_problem() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
if [ -n "$declared" ]; then
|
||||
if [ "$declared" = "grouped" ] && [ -n "$ungrouped_in" ]; then
|
||||
printf "fragment '%s' is flat but '%s' declares grouped — a repo is one shape or the other\n" \
|
||||
"$ungrouped_in" "$sentinel"
|
||||
return 1
|
||||
fi
|
||||
if [ "$declared" = "flat" ] && [ -n "$grouped_in" ]; then
|
||||
printf "fragment '%s' is grouped but '%s' declares flat — a repo is one shape or the other\n" \
|
||||
"$grouped_in" "$sentinel"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -f "$changelog" ]; then
|
||||
published="$(awk '$1 == "##" && $2 != "Unreleased" { print $2; exit }' "$changelog")"
|
||||
fi
|
||||
|
|
|
|||
Loading…
Reference in a new issue