wip(test): fixture sweep, first pass — over-reaches onto section fixtures

'- Fixed entry.' is shared between the dangling-heading fragment fixture
and the section-predicate fixture, so the global replace crossed D4's
line. Next commit filters to fragments that actually red on the cite rule.

Refs #262
This commit is contained in:
cndgrr 2026-08-04 12:06:29 +00:00
parent 46b80fb6b2
commit c40d9412f6
4 changed files with 101 additions and 101 deletions

View file

@ -275,7 +275,7 @@ fragment_tree fragments-dev-flat 1.2.4-dev <<'EOF'
- The shipped entry.
EOF
printf '%s\n' "- Added fragment mode." >"$TMP/fragments-dev-flat/changelog.d/115.md"
printf '%s\n' "- Added fragment mode (#115)." >"$TMP/fragments-dev-flat/changelog.d/115.md"
check "fragment -dev + well-formed flat fragment passes" 0 "fragment mode" \
in_tree fragments-dev-flat
@ -310,7 +310,7 @@ EOF
cat >"$TMP/fragments-dev-grouped/changelog.d/115.md" <<'EOF'
### Changed
- Added fragment mode.
- Added fragment mode (#115).
EOF
check "fragment -dev + well-formed grouped fragment passes" 0 "fragment mode" \
in_tree fragments-dev-grouped
@ -322,11 +322,11 @@ fragment_tree fragments-dev-mixed 1.2.4-dev <<'EOF'
- The shipped entry.
EOF
printf '%s\n' "- Flat fragment." >"$TMP/fragments-dev-mixed/changelog.d/114.md"
printf '%s\n' "- Flat fragment (#115)." >"$TMP/fragments-dev-mixed/changelog.d/114.md"
cat >"$TMP/fragments-dev-mixed/changelog.d/115.md" <<'EOF'
### Fixed
- Grouped fragment.
- Grouped fragment (#115).
EOF
check "fragment mode refuses mixed shapes with the shared assembler diagnosis" 1 \
"fragment 'changelog.d/115.md' is grouped but fragment 'changelog.d/114.md' is not" \
@ -342,7 +342,7 @@ EOF
cat >"$TMP/fragments-dev-all-grouped-over-flat/changelog.d/115.md" <<'EOF'
### Fixed
- Grouped fragment.
- Grouped fragment (#115).
EOF
check "fragment mode refuses an all-grouped set over a flat published section" 1 \
"changelog.d/115.md' is grouped but newest published section '1.2.3'" \
@ -357,7 +357,7 @@ fragment_tree fragments-dev-flat-over-grouped 1.2.4-dev <<'EOF'
- The shipped entry.
EOF
printf '%s\n' "- Flat fragment." >"$TMP/fragments-dev-flat-over-grouped/changelog.d/115.md"
printf '%s\n' "- Flat fragment (#115)." >"$TMP/fragments-dev-flat-over-grouped/changelog.d/115.md"
check "fragment mode refuses a flat set over a grouped published section" 1 \
"changelog.d/115.md' is flat but newest published section '1.2.3'" \
in_tree fragments-dev-flat-over-grouped
@ -376,14 +376,14 @@ printf '%s\n' "grouped" >"$TMP/fragments-dev-flip/changelog.d/shape"
cat >"$TMP/fragments-dev-flip/changelog.d/115.md" <<'EOF'
### Fixed
- Grouped fragment.
- Grouped fragment (#115).
EOF
check "fragment mode: 'grouped' sentinel admits the flip tree over a flat published section" 0 \
"fragment mode" in_tree fragments-dev-flip
# Post-flip drift is refused on its own PR: a flat probe fragment atop the
# flip tree goes red — beside grouped fragments the mix rule names it first.
printf '%s\n' "- Flat probe." >"$TMP/fragments-dev-flip/changelog.d/116.md"
printf '%s\n' "- Flat probe (#116)." >"$TMP/fragments-dev-flip/changelog.d/116.md"
check "fragment mode: a flat probe atop the flip tree is refused" 1 \
"changelog.d/115.md' is grouped but fragment 'changelog.d/116.md' is not" \
in_tree fragments-dev-flip
@ -393,7 +393,7 @@ rm "$TMP/fragments-dev-flip/changelog.d/116.md"
# holds the shape: an all-flat set under 'grouped' is refused, sentinel
# named — the published-section inference never gets a say.
rm "$TMP/fragments-dev-flip/changelog.d/115.md"
printf '%s\n' "- Flat probe." >"$TMP/fragments-dev-flip/changelog.d/116.md"
printf '%s\n' "- Flat probe (#116)." >"$TMP/fragments-dev-flip/changelog.d/116.md"
check "fragment mode: a flat set under the 'grouped' sentinel refused, sentinel named" 1 \
"changelog.d/116.md' is flat but 'changelog.d/shape' declares grouped" \
in_tree fragments-dev-flip
@ -417,7 +417,7 @@ EOF
cat >"$TMP/fragments-dev-no-published/changelog.d/115.md" <<'EOF'
### Fixed
- Grouped fragment.
- Grouped fragment (#115).
EOF
check "fragment mode accepts a consistent set with no published section" 0 \
"fragment mode" in_tree fragments-dev-no-published
@ -452,7 +452,7 @@ fragment_tree fragments-bad-name 1.2.4-dev <<'EOF'
- The shipped entry.
EOF
printf '%s\n' "- An entry." >"$TMP/fragments-bad-name/changelog.d/notes.md"
printf '%s\n' "- An entry (#1)." >"$TMP/fragments-bad-name/changelog.d/notes.md"
check "fragment mode quotes malformed-fragment diagnosis and file" 1 \
"fragment 'changelog.d/notes.md' is not named for its issue" \
in_tree fragments-bad-name
@ -531,7 +531,7 @@ check "same changelog fails in legacy mode" 1 "development tree" \
mkdir -p "$TMP/env-tree"
printf '1.2.4-dev\n' >"$TMP/env-tree/VERSION"
printf '# Changelog\n\n## Unreleased\n\n- Pending.\n' >"$TMP/env-tree/NOTES.md"
printf '# Changelog\n\n## Unreleased\n\n- Pending (#1).\n' >"$TMP/env-tree/NOTES.md"
# A non-default changelog name proves the env var is honored, not the default.
env_tree() {
(cd "$TMP/env-tree" && CHANGELOG=NOTES.md VERSION_SOURCE=file bash "$SCRIPT")

View file

@ -55,13 +55,13 @@ tree flat-one <<EOF
$BASE_CHANGELOG
EOF
frag flat-one 12.md <<'EOF'
- Twelve landed.
- Twelve landed (#12).
EOF
check "flat: one fragment assembles and stamps" 0 "consumed 1 fragment" \
in_tree flat-one 0.2.0 2026-07-24
check "flat: preamble and shipped section stay byte-identical around the insert" 0 "" \
assert_file "$TMP/flat-one/CHANGELOG.md" \
$'# Changelog\n\nPreamble prose belongs to no section.\n\n## 0.2.0 — 2026-07-24\n\n- Twelve landed.\n\n## 0.1.0 — 2026-07-01\n\n- The shipped entry.'
$'# Changelog\n\nPreamble prose belongs to no section.\n\n## 0.2.0 — 2026-07-24\n\n- Twelve landed (#12).\n\n## 0.1.0 — 2026-07-01\n\n- The shipped entry.'
check "flat: the consumed fragment is deleted" 1 "" \
test -e "$TMP/flat-one/changelog.d/12.md"
check "flat: README.md survives consumption" 0 "" \
@ -73,17 +73,17 @@ tree flat-many <<EOF
$BASE_CHANGELOG
EOF
frag flat-many 2.md <<'EOF'
- Two.
- Two (#2).
EOF
frag flat-many 9.md <<'EOF'
- Nine.
- Nine (#9).
EOF
frag flat-many 10.md <<'EOF'
- Ten.
- Ten (#10).
EOF
frag flat-many ceremony-14.md <<'EOF'
- Fourteen crossed over — naïve reflows would mangle this café's
continuation line, so it must survive verbatim.
continuation line, so it must survive verbatim (#14).
EOF
assert_check() {
@ -95,7 +95,7 @@ assert_check() {
}
}
check "flat: numeric-descending order (10.md before 9.md), cross-repo name beside local" 0 "" \
assert_check flat-many $'- Fourteen crossed over — naïve reflows would mangle this café'"'"$'s\n continuation line, so it must survive verbatim.\n- Ten.\n- Nine.\n- Two.'
assert_check flat-many $'- Fourteen crossed over — naïve reflows would mangle this café'"'"$'s\n continuation line, so it must survive verbatim (#14).\n- Ten (#10).\n- Nine (#9).\n- Two (#2).'
# --- grouped write: canonical order, unnamed group appended ------------------
@ -113,28 +113,28 @@ EOF
frag grouped 21.md <<'EOF'
### Fixed
- Fixed twenty-one.
- Fixed twenty-one (#21).
EOF
frag grouped 20.md <<'EOF'
### Added
- Added twenty.
- Added twenty, second bullet.
- Added twenty (#20).
- Added twenty, second bullet (#20).
### Docs
- Docs twenty.
- Docs twenty (#20).
EOF
frag grouped 19.md <<'EOF'
### Security
- Security nineteen.
- Security nineteen (#19).
### Added
- Added nineteen.
- Added nineteen (#19).
EOF
GROUPED_BODY=$'### Added\n\n- Added twenty.\n- Added twenty, second bullet.\n- Added nineteen.\n\n### Fixed\n\n- Fixed twenty-one.\n\n### Security\n\n- Security nineteen.\n\n### Docs\n\n- Docs twenty.'
GROUPED_BODY=$'### Added\n\n- Added twenty (#20).\n- Added twenty, second bullet (#20).\n- Added nineteen (#19).\n\n### Fixed\n\n- Fixed twenty-one (#21).\n\n### Security\n\n- Security nineteen (#19).\n\n### Docs\n\n- Docs twenty (#20).'
check "grouped: --check shows canonical order, multi-bullet group, unnamed group last" 0 "" \
assert_check grouped "$GROUPED_BODY"
check "grouped: write mode assembles the same section" 0 "consumed 3 fragment" \
@ -156,13 +156,13 @@ printf 'grouped\n' >"$TMP/flip/changelog.d/shape"
frag flip 40.md <<'EOF'
### Added
- Forty landed.
- Forty landed (#40).
EOF
check "sentinel: the flip release assembles grouped over a flat published section" 0 \
"consumed 1 fragment" in_tree flip 0.2.0 2026-07-24
check "sentinel: the written flip section is exact" 0 "" \
assert_file "$TMP/flip/CHANGELOG.md" \
$'# Changelog\n\nPreamble prose belongs to no section.\n\n## 0.2.0 — 2026-07-24\n\n### Added\n\n- Forty landed.\n\n## 0.1.0 — 2026-07-01\n\n- The shipped entry.'
$'# Changelog\n\nPreamble prose belongs to no section.\n\n## 0.2.0 — 2026-07-24\n\n### Added\n\n- Forty landed (#40).\n\n## 0.1.0 — 2026-07-01\n\n- The shipped entry.'
check "sentinel: changelog.d/shape survives consumption" 0 "" \
test -e "$TMP/flip/changelog.d/shape"
@ -171,7 +171,7 @@ $BASE_CHANGELOG
EOF
printf 'grouped\n' >"$TMP/flip-flat-frag/changelog.d/shape"
frag flip-flat-frag 41.md <<'EOF'
- Flat forty-one.
- Flat forty-one (#41).
EOF
check "sentinel: a flat fragment under 'grouped' refuses, sentinel named" 1 \
"changelog.d/shape' declares grouped" in_tree flip-flat-frag 0.2.0 2026-07-24
@ -183,7 +183,7 @@ printf 'Grouped\n' >"$TMP/flip-malformed/changelog.d/shape"
frag flip-malformed 42.md <<'EOF'
### Added
- Forty-two.
- Forty-two (#42).
EOF
check "sentinel: a malformed sentinel refuses, file named" 1 \
"changelog.d/shape' declares neither shape" in_tree flip-malformed 0.2.0 2026-07-24
@ -196,13 +196,13 @@ tree preamble-only <<'EOF'
Only preamble so far.
EOF
frag preamble-only 1.md <<'EOF'
- The first entry ever.
- The first entry ever (#1).
EOF
check "a changelog with no section yet gets the section after the preamble" 0 "" \
in_tree preamble-only 0.1.0 2026-07-24
check "preamble-only write is exact" 0 "" \
assert_file "$TMP/preamble-only/CHANGELOG.md" \
$'# Changelog\n\nOnly preamble so far.\n\n## 0.1.0 — 2026-07-24\n\n- The first entry ever.'
$'# Changelog\n\nOnly preamble so far.\n\n## 0.1.0 — 2026-07-24\n\n- The first entry ever (#1).'
# --- --check is provably read-only -------------------------------------------
@ -210,7 +210,7 @@ tree check-readonly <<EOF
$BASE_CHANGELOG
EOF
frag check-readonly 5.md <<'EOF'
- Five.
- Five (#5).
EOF
cp -R "$TMP/check-readonly" "$TMP/check-readonly.before"
check "--check prints the assembled body" 0 "Five." \
@ -228,12 +228,12 @@ check "the defaulted stamp is a UTC date" 0 "" \
# --- --changelog and --dir override the defaults -----------------------------
mkdir -p "$TMP/flagged/frags"
printf '# Changelog\n\n## 0.1.0 — 2026-07-01\n\n- Shipped.\n' >"$TMP/flagged/NOTES.md"
printf -- '- Flagged entry.\n' >"$TMP/flagged/frags/2.md"
printf '# Changelog\n\n## 0.1.0 — 2026-07-01\n\n- Shipped (#1).\n' >"$TMP/flagged/NOTES.md"
printf -- '- Flagged entry (#2).\n' >"$TMP/flagged/frags/2.md"
check "--changelog and --dir override the defaults" 0 "" \
"$TOOL" 0.2.0 2026-07-24 --changelog "$TMP/flagged/NOTES.md" --dir "$TMP/flagged/frags"
check "the flag-driven write landed in the named changelog" 0 "" \
grep -qF -- "- Flagged entry." "$TMP/flagged/NOTES.md"
grep -qF -- "- Flagged entry (#2)." "$TMP/flagged/NOTES.md"
# --- refusals: each names the file responsible -------------------------------
@ -270,7 +270,7 @@ frag dangling 4.md <<'EOF'
### Fixed
- Fixed entry.
- Fixed entry (#4).
EOF
check "a dangling grouped heading refuses, file and heading named" 1 \
"fragment 'changelog.d/4.md' has an empty heading: '### Added'" \
@ -282,7 +282,7 @@ EOF
frag smuggled 6.md <<'EOF'
## 0.2.0 — 2026-07-24
- An entry under a smuggled heading.
- An entry under a smuggled heading (#6).
EOF
check "a fragment carrying a '## ' line refuses, file named" 1 \
"fragment 'changelog.d/6.md' carries a '## ' heading" \
@ -292,7 +292,7 @@ tree stray-txt <<EOF
$BASE_CHANGELOG
EOF
frag stray-txt 7.md <<'EOF'
- Seven.
- Seven (#7).
EOF
frag stray-txt notes.txt <<'EOF'
A stray scratchpad.
@ -313,7 +313,7 @@ tree stray-case <<EOF
$BASE_CHANGELOG
EOF
frag stray-case Fix-12.md <<'EOF'
- Uppercase prefix.
- Uppercase prefix (#12).
EOF
check "Fix-12.md refuses on the name pattern" 1 "Fix-12.md" \
in_tree stray-case 0.2.0
@ -322,12 +322,12 @@ tree mixed <<EOF
$BASE_CHANGELOG
EOF
frag mixed 5.md <<'EOF'
- Flat five.
- Flat five (#5).
EOF
frag mixed 6.md <<'EOF'
### Added
- Grouped six.
- Grouped six (#6).
EOF
check "grouped + flat mixed refuses, both files named" 1 "6.md" \
in_tree mixed 0.2.0
@ -340,7 +340,7 @@ EOF
frag grouped-over-flat 6.md <<'EOF'
### Added
- Grouped six.
- Grouped six (#6).
EOF
check "an all-grouped set over a flat published section refuses before assembly" 1 \
"fragment 'changelog.d/6.md' is grouped but newest published section '0.1.0'" \
@ -354,7 +354,7 @@ tree already <<'EOF'
- Already shipped.
EOF
frag already 4.md <<'EOF'
- A late fragment.
- A late fragment (#4).
EOF
check "an already-present section refuses — the ceremony was already run" 1 \
"already has a section for '0.2.0'" \
@ -370,13 +370,13 @@ tree rc-present <<'EOF'
- The candidate's entry.
EOF
frag rc-present 8.md <<'EOF'
- The real release entry.
- The real release entry (#8).
EOF
check "an rc section does not block assembling the bare version" 0 "" \
in_tree rc-present 0.2.0 2026-07-24
mkdir -p "$TMP/no-changelog/changelog.d"
printf -- '- Entry.\n' >"$TMP/no-changelog/changelog.d/2.md"
printf -- '- Entry (#2).\n' >"$TMP/no-changelog/changelog.d/2.md"
check "a missing changelog refuses" 1 "no such file" \
in_tree no-changelog 0.2.0
@ -404,12 +404,12 @@ frag round-trip 30.md <<'EOF'
### Added
- Thirty — wraps onto a
continuation line with a naïve café.
continuation line with a naïve café (#30).
EOF
frag round-trip 29.md <<'EOF'
### Fixed
- Fixed twenty-nine.
- Fixed twenty-nine (#29).
EOF
CHECKED="$(in_tree round-trip 0.2.0 2026-07-24 --check)"
check "round trip: write mode succeeds after --check" 0 "" \

View file

@ -58,8 +58,8 @@ Preamble prose belongs to no section.
- The shipped entry.
EOF
printf '0.1.1-dev\n' >"$dir/VERSION"
printf -- '- Twelve landed.\n' >"$dir/changelog.d/12.md"
printf -- '- Nine landed, and its prose wraps onto a\n continuation line.\n' >"$dir/changelog.d/9.md"
printf -- '- Twelve landed (#12).\n' >"$dir/changelog.d/12.md"
printf -- '- Nine landed, and its prose wraps onto a\n continuation line (#9).\n' >"$dir/changelog.d/9.md"
commit_base "$name"
}
@ -86,8 +86,8 @@ check "faithful flat ceremony: the section is byte-for-byte the assembly" 0 \
seed_flat faithful-grouped
sed -i '/^- The shipped entry/i ### Fixed\\\n' "$TMP/faithful-grouped/CHANGELOG.md"
printf -- '### Fixed\n\n- Fixed twenty-one.\n' >"$TMP/faithful-grouped/changelog.d/21.md"
printf -- '### Added\n\n- Added twenty.\n\n### Docs\n\n- Docs twenty.\n' >"$TMP/faithful-grouped/changelog.d/20.md"
printf -- '### Fixed\n\n- Fixed twenty-one (#21).\n' >"$TMP/faithful-grouped/changelog.d/21.md"
printf -- '### Added\n\n- Added twenty (#20).\n\n### Docs\n\n- Docs twenty (#20).\n' >"$TMP/faithful-grouped/changelog.d/20.md"
rm "$TMP/faithful-grouped/changelog.d/12.md" "$TMP/faithful-grouped/changelog.d/9.md"
git -C "$TMP/faithful-grouped" add -A
git -C "$TMP/faithful-grouped" commit -qm regroup
@ -108,7 +108,7 @@ check "the stamp's date never enters the comparison" 0 "byte-for-byte" \
# --- inapplicable trees: green NOTICE, never a silent skip -------------------
seed_flat ordinary-add
printf -- '- Thirteen incoming.\n' >"$TMP/ordinary-add/changelog.d/13.md"
printf -- '- Thirteen incoming (#13).\n' >"$TMP/ordinary-add/changelog.d/13.md"
commit_head ordinary-add
check "-dev PR adding a fragment: green NOTICE" 0 "NOTICE" run ordinary-add base
@ -195,8 +195,8 @@ Preamble prose belongs to no section.
## 0.2.0 — 2026-07-24
- Nine landed, and its prose wraps onto a
continuation line.
- Twelve landed.
continuation line (#9).
- Twelve landed (#12).
## 0.1.0 — 2026-07-01
@ -210,7 +210,7 @@ check "re-ordered entries fail" 1 "NOT what the fragments" run reordered base
# directory is not — only the survivor refusal fires.
seed_flat survivor
ceremony survivor 0.2.0 2026-07-24
printf -- '- Nine landed, and its prose wraps onto a\n continuation line.\n' >"$TMP/survivor/changelog.d/9.md"
printf -- '- Nine landed, and its prose wraps onto a\n continuation line (#9).\n' >"$TMP/survivor/changelog.d/9.md"
commit_head survivor
check "a surviving fragment with its entry present fails" 1 "STILL PRESENT" \
run survivor base
@ -316,9 +316,9 @@ check "merge base IS HEAD: vacuous, named honestly" 0 "vacuous" run vacuous HEAD
# the env vars are honored the way the composite sets them.
init_repo env-tree
mkdir -p "$TMP/env-tree/frags"
printf '# Changelog\n\n## 0.1.0 — 2026-07-01\n\n- Shipped.\n' >"$TMP/env-tree/NOTES.md"
printf '# Changelog\n\n## 0.1.0 — 2026-07-01\n\n- Shipped (#1).\n' >"$TMP/env-tree/NOTES.md"
printf '0.1.1-dev\n' >"$TMP/env-tree/VERSION"
printf -- '- Flagged entry.\n' >"$TMP/env-tree/frags/2.md"
printf -- '- Flagged entry (#2).\n' >"$TMP/env-tree/frags/2.md"
git -C "$TMP/env-tree" add -A
git -C "$TMP/env-tree" commit -qm base
git -C "$TMP/env-tree" branch fixture-base

View file

@ -87,7 +87,7 @@ cat >"$PROBLEM_FIXTURE" <<'EOF'
### Fixed
- Fixed entry.
- Fixed entry (#22).
## 1.3.0
@ -101,7 +101,7 @@ cat >"$PROBLEM_FIXTURE" <<'EOF'
### Fixed
- Fixed entry.
- Fixed entry (#22).
## 1.4.0
@ -115,7 +115,7 @@ cat >"$PROBLEM_FIXTURE" <<'EOF'
### Fixed
- Fixed entry.
- Fixed entry (#22).
EOF
assert_problem Unreleased 0 ""
@ -182,11 +182,11 @@ printf 'marker\n' >"$FRAG/README.md"
check "fragments: README.md is the directory marker, never a fragment" 0 "" \
changelog_fragments "$FRAG"
printf -- '- Two.\n' >"$FRAG/2.md"
printf -- '- Nine.\n' >"$FRAG/9.md"
printf -- '- Ten.\n' >"$FRAG/10.md"
printf -- '- Cross.\n' >"$FRAG/ceremony-14.md"
printf -- '- Local fourteen.\n' >"$FRAG/14.md"
printf -- '- Two (#2).\n' >"$FRAG/2.md"
printf -- '- Nine (#9).\n' >"$FRAG/9.md"
printf -- '- Ten (#10).\n' >"$FRAG/10.md"
printf -- '- Cross (#14).\n' >"$FRAG/ceremony-14.md"
printf -- '- Local fourteen (#14).\n' >"$FRAG/14.md"
assert_fragments_order() {
local expected="$1" actual
@ -205,39 +205,39 @@ check "fragments: issue number descending (numeric, 10 before 9), filename tie-b
PF="$TMP/frag-problems"
mkdir -p "$PF"
printf -- '- Fine.\n' >"$PF/7.md"
printf -- '- Fine (#7).\n' >"$PF/7.md"
check "fragment predicate: a flat fragment passes" 0 "" \
changelog_fragment_problem "$PF/7.md"
cat >"$PF/8.md" <<'EOF'
### Added
- Grouped fine.
- Grouped fine (#8).
EOF
check "fragment predicate: a grouped fragment passes" 0 "" \
changelog_fragment_problem "$PF/8.md"
printf -- '- Cross-repo.\n' >"$PF/ceremony-14.md"
printf -- '- Cross-repo (#14).\n' >"$PF/ceremony-14.md"
check "fragment predicate: a cross-repo name passes" 0 "" \
changelog_fragment_problem "$PF/ceremony-14.md"
printf -- '- Bad name.\n' >"$PF/Fix-12.md"
printf -- '- Bad name (#12).\n' >"$PF/Fix-12.md"
check "fragment predicate: an uppercase prefix is refused, file named" 1 "Fix-12.md" \
changelog_fragment_problem "$PF/Fix-12.md"
printf -- '- Bad name.\n' >"$PF/notes.txt"
printf -- '- Bad name (#12).\n' >"$PF/notes.txt"
check "fragment predicate: a non-.md file is refused, file named" 1 "notes.txt" \
changelog_fragment_problem "$PF/notes.txt"
printf -- '- Bad name.\n' >"$PF/12.markdown"
printf -- '- Bad name (#12).\n' >"$PF/12.markdown"
check "fragment predicate: .markdown is refused, file named" 1 "12.markdown" \
changelog_fragment_problem "$PF/12.markdown"
printf -- '- No number.\n' >"$PF/notes.md"
printf -- '- No number (#1).\n' >"$PF/notes.md"
check "fragment predicate: a name with no trailing issue number is refused" 1 "notes.md" \
changelog_fragment_problem "$PF/notes.md"
cat >"$PF/20.md" <<'EOF'
## 1.0.0 — 2026-07-24
- Smuggled heading.
- Smuggled heading (#20).
EOF
check "fragment predicate: a '## ' line is refused — the heading is the assembler's" 1 \
"the section heading is the assembler's to write" \
@ -253,7 +253,7 @@ cat >"$PF/22.md" <<'EOF'
### Fixed
- Fixed entry.
- Fixed entry (#22).
EOF
check "fragment predicate: a dangling grouped heading is refused, heading named" 1 \
"has an empty heading: '### Added'" \
@ -347,11 +347,11 @@ mkdir -p "$AF"
printf 'marker\n' >"$AF/README.md"
cat >"$AF/3.md" <<'EOF'
- Three — an em dash, and prose that
wraps onto a continuation line.
wraps onto a continuation line (#3).
EOF
printf -- '- Ten.\n- Ten again.\n' >"$AF/10.md"
printf -- '- Ten (#10).\n- Ten again (#10).\n' >"$AF/10.md"
check "assemble: flat fragments, newest issue first, prose verbatim" 0 "" \
assert_assemble "$AF" $'- Ten.\n- Ten again.\n- Three — an em dash, and prose that\n wraps onto a continuation line.'
assert_assemble "$AF" $'- Ten (#10).\n- Ten again (#10).\n- Three — an em dash, and prose that\n wraps onto a continuation line (#3).'
check "assemble: an empty directory is empty output — refusing is the caller's stance" 0 "" \
changelog_assemble "$TMP/no-such-dir"
@ -361,36 +361,36 @@ mkdir -p "$AG"
cat >"$AG/21.md" <<'EOF'
### Fixed
- Fixed twenty-one.
- Fixed twenty-one (#21).
EOF
cat >"$AG/20.md" <<'EOF'
### Added
- Added twenty.
- Added twenty (#20).
### Docs
- Docs twenty.
- Docs twenty (#20).
EOF
cat >"$AG/19.md" <<'EOF'
### Security
- Security nineteen.
- Security nineteen (#19).
### Added
- Added nineteen.
- Added nineteen (#19).
EOF
check "assemble: canonical group order, unnamed group appended, fragment order inside a group" 0 "" \
assert_assemble "$AG" $'### Added\n\n- Added twenty.\n- Added nineteen.\n\n### Fixed\n\n- Fixed twenty-one.\n\n### Security\n\n- Security nineteen.\n\n### Docs\n\n- Docs twenty.'
assert_assemble "$AG" $'### Added\n\n- Added twenty (#20).\n- Added nineteen (#19).\n\n### Fixed\n\n- Fixed twenty-one (#21).\n\n### Security\n\n- Security nineteen (#19).\n\n### Docs\n\n- Docs twenty (#20).'
AM="$TMP/assemble-mixed"
mkdir -p "$AM"
printf -- '- Flat five.\n' >"$AM/5.md"
printf -- '- Flat five (#5).\n' >"$AM/5.md"
cat >"$AM/6.md" <<'EOF'
### Added
- Grouped six.
- Grouped six (#6).
EOF
check "assemble: mixed shapes refused, grouped side named" 1 "6.md" \
changelog_assemble "$AM"
@ -400,11 +400,11 @@ check "assemble: mixed shapes refused, flat side named too" 1 "5.md" \
AX="$TMP/assemble-selfmixed"
mkdir -p "$AX"
cat >"$AX/7.md" <<'EOF'
- Ungrouped lead.
- Ungrouped lead (#7).
### Added
- Grouped follow.
- Grouped follow (#7).
EOF
check "assemble: one fragment mixing both shapes is refused, file named" 1 \
"'$AX/7.md' mixes grouped headings and ungrouped bullets" \
@ -429,14 +429,14 @@ cat >"$SHAPE_CHANGELOG" <<'EOF'
- Older section is grouped.
EOF
printf -- '- Flat fragment.\n' >"$SHAPE_DIR/1.md"
printf -- '- Flat fragment (#1).\n' >"$SHAPE_DIR/1.md"
check "shape: flat set matches newest flat published section" 0 "" \
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
cat >"$SHAPE_DIR/1.md" <<'EOF'
### Fixed
- Grouped fragment.
- Grouped fragment (#1).
EOF
check "shape: grouped set names its conflict with newest flat published section" 1 \
"fragment '$SHAPE_DIR/1.md' is grouped but newest published section '2.0.0' in '$SHAPE_CHANGELOG' is flat" \
@ -451,7 +451,7 @@ cat >"$SHAPE_CHANGELOG" <<'EOF'
- Newest section is grouped.
EOF
printf -- '- Flat fragment.\n' >"$SHAPE_DIR/1.md"
printf -- '- Flat fragment (#1).\n' >"$SHAPE_DIR/1.md"
check "shape: flat set names its conflict with newest grouped published section" 1 \
"fragment '$SHAPE_DIR/1.md' is flat but newest published section '2.0.0' in '$SHAPE_CHANGELOG' is grouped" \
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
@ -459,7 +459,7 @@ check "shape: flat set names its conflict with newest grouped published section"
cat >"$SHAPE_DIR/1.md" <<'EOF'
### Fixed
- Grouped fragment.
- Grouped fragment (#1).
EOF
check "shape: grouped set matches newest grouped published section" 0 "" \
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
@ -484,7 +484,7 @@ EOF
cat >"$SHAPE_DIR/1.md" <<'EOF'
### Fixed
- Grouped fragment.
- Grouped fragment (#1).
EOF
printf 'grouped\n' >"$SHAPE_DIR/shape"
check "shape: 'grouped' sentinel admits a grouped set over a flat published section" 0 "" \
@ -492,7 +492,7 @@ check "shape: 'grouped' sentinel admits a grouped set over a flat published sect
check "shape: the sentinel binds with no changelog at all — the assembler's call" 0 "" \
changelog_shape_problem "" "$SHAPE_DIR"
printf -- '- Flat fragment.\n' >"$SHAPE_DIR/1.md"
printf -- '- Flat fragment (#1).\n' >"$SHAPE_DIR/1.md"
check "shape: flat fragment under a 'grouped' sentinel refused, fragment and sentinel named" 1 \
"fragment '$SHAPE_DIR/1.md' is flat but '$SHAPE_DIR/shape' declares grouped" \
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
@ -512,14 +512,14 @@ check "shape: 'flat' sentinel admits a flat set over a grouped published section
cat >"$SHAPE_DIR/1.md" <<'EOF'
### Fixed
- Grouped fragment.
- Grouped fragment (#1).
EOF
check "shape: grouped fragment under a 'flat' sentinel refused, fragment and sentinel named" 1 \
"fragment '$SHAPE_DIR/1.md' is grouped but '$SHAPE_DIR/shape' declares flat" \
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
printf 'grouped\n' >"$SHAPE_DIR/shape"
printf -- '- Flat two.\n' >"$SHAPE_DIR/2.md"
printf -- '- Flat two (#2).\n' >"$SHAPE_DIR/2.md"
check "shape: a mixed set is refused regardless of the sentinel" 1 \
"fragment '$SHAPE_DIR/1.md' is grouped but fragment '$SHAPE_DIR/2.md' is not" \
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
@ -558,7 +558,7 @@ printf 'grouped\n' >"$SHAPE_DIR/shape"
cat >"$SHAPE_DIR/1.md" <<'EOF'
### Fixed
- Grouped fragment.
- Grouped fragment (#1).
EOF
assert_fragments_exclude_sentinel() {
local out
@ -579,17 +579,17 @@ printf 'grouped\n' >"$AS/shape"
cat >"$AS/30.md" <<'EOF'
### Fixed
- Fixed thirty.
- Fixed thirty (#30).
EOF
cat >"$AS/31.md" <<'EOF'
### Added
- Added thirty-one.
- Added thirty-one (#31).
EOF
check "assemble: the sentinel never assembles, and canonical order holds under it" 0 "" \
assert_assemble "$AS" $'### Added\n\n- Added thirty-one.\n\n### Fixed\n\n- Fixed thirty.'
assert_assemble "$AS" $'### Added\n\n- Added thirty-one (#31).\n\n### Fixed\n\n- Fixed thirty (#30).'
rm "$AS/30.md" "$AS/31.md"
printf -- '- Flat probe.\n' >"$AS/29.md"
printf -- '- Flat probe (#29).\n' >"$AS/29.md"
check "assemble: a flat set under a 'grouped' sentinel refuses to assemble" 1 \
"declares grouped" \
changelog_assemble "$AS"