Reusable changelog-body linter (Layer 1 mechanical checks) + fragment scaffolding (Layer 2) #282

Closed
opened 2026-07-02 13:10:33 +02:00 by bosun · 1 comment
Owner

Motivation

The Cold-Read discipline (BookStack page 208, applied at release-toolkit#264 pre-cut) catches ~60-70% mechanical + structural issues in rolling-PR bodies. Automating those catches converts "manual Cold Read before every cut" into "automated linter on every PR + optional Cold Read for edge cases."

Scope

Layer 1 — Mechanical linter (this tracker's primary scope)

Reusable workflow reusable-changelog-body-check.yml — sibling to reusable-changelog-fragment-check.yml (#278). Runs at pull_request time on release-prep/rolling PRs. Checks:

  • Version header pattern: ## v?\d+\.\d+\.\d+ (or fenced equivalent) present + date matches expected format
  • All standard sections present: Added / Changed / Fixed / Removed / Deprecated / Upgrade — explicit None. allowed
  • No forward-references: no mention of version numbers above manifest.last_released_version + 1 (warn on suspected forward-refs like "since v0.22.0" in a v0.20.0 cut)
  • Body length ceiling: soft warn at 5000 chars (empirical target from release-toolkit#264 fix pass + tmux-tell v0.27.0 compression)
  • Bullet vs prose consistency: within a section, all entries same style
  • No duplicate ### Section headers: redundant guard on top of the #272 assembly fix

Fail-loud on violations; adopters wire it into their pull_request workflow same shape as fragment-check.yml.

Layer 2 — Fragment scaffolding (bundled scope)

  • Ship changelog.d/.template.md with pre-baked structure (Outcome + Required action placeholders)
  • Port the fragment-brevity docs from tmux-tell#628 CONTRIBUTING §CHANGELOG entries into release-toolkit CONTRIBUTING as the canonical guidance
  • Optionally: pre-commit hook / PR-body-check for per-fragment word count (deferred design)

Layer 3 — Reader-simulation (explicitly OUT of scope)

Severity ordering, semantic emphasis promotion, missing-context checks — these need judgment. Cold Read remains the opt-in discipline for major cuts + bodies exceeding the automated ceiling.

Empirical anchor

release-toolkit#264 Cold-Read pass (Pilot 2026-07-02, Bosun-routed) caught 5 structural issues that mostly fall under Layer 1:

  1. "since v0.22.0" forward-reference from a v0.19.0-current reader (Layer 1 forward-ref check)
  2. Severity ordering — bake-reset + head-ref filter buried at bottom of Fixed (Layer 3 — out of scope)
  3. Bullet/prose inconsistency in Fixed section (Layer 1 consistency check)
  4. Missing version header + one-line summary (Layer 1 header check)
  5. Cascade-failure narratives over-long (Layer 2 fragment discipline)

4 of 5 catches would fire automatically under Layer 1 + Layer 2.

Refs

  • Sibling reusable: #271 (fragment-kind check via #278)
  • Empirical: release-toolkit#264 Cold-Read pass
  • Prior art: BookStack page 208 (Cold Read Prompt, established via tmux-tell#628 2026-07-01)
  • Related fixes: #272 (duplicate Section headers) — Layer 1 check would be redundant guard
## Motivation The Cold-Read discipline (BookStack page 208, applied at release-toolkit#264 pre-cut) catches ~60-70% mechanical + structural issues in rolling-PR bodies. Automating those catches converts "manual Cold Read before every cut" into "automated linter on every PR + optional Cold Read for edge cases." ## Scope ### Layer 1 — Mechanical linter (this tracker's primary scope) Reusable workflow `reusable-changelog-body-check.yml` — sibling to `reusable-changelog-fragment-check.yml` (#278). Runs at pull_request time on release-prep/rolling PRs. Checks: - **Version header pattern**: `## v?\d+\.\d+\.\d+` (or fenced equivalent) present + date matches expected format - **All standard sections present**: Added / Changed / Fixed / Removed / Deprecated / Upgrade — explicit `None.` allowed - **No forward-references**: no mention of version numbers above `manifest.last_released_version + 1` (warn on suspected forward-refs like "since v0.22.0" in a v0.20.0 cut) - **Body length ceiling**: soft warn at 5000 chars (empirical target from release-toolkit#264 fix pass + tmux-tell v0.27.0 compression) - **Bullet vs prose consistency**: within a section, all entries same style - **No duplicate `### Section` headers**: redundant guard on top of the #272 assembly fix Fail-loud on violations; adopters wire it into their `pull_request` workflow same shape as fragment-check.yml. ### Layer 2 — Fragment scaffolding (bundled scope) - Ship `changelog.d/.template.md` with pre-baked structure (Outcome + Required action placeholders) - Port the fragment-brevity docs from tmux-tell#628 CONTRIBUTING §CHANGELOG entries into release-toolkit CONTRIBUTING as the canonical guidance - Optionally: pre-commit hook / PR-body-check for per-fragment word count (deferred design) ### Layer 3 — Reader-simulation (explicitly OUT of scope) Severity ordering, semantic emphasis promotion, missing-context checks — these need judgment. Cold Read remains the opt-in discipline for major cuts + bodies exceeding the automated ceiling. ## Empirical anchor release-toolkit#264 Cold-Read pass (Pilot 2026-07-02, Bosun-routed) caught 5 structural issues that mostly fall under Layer 1: 1. "since v0.22.0" forward-reference from a v0.19.0-current reader (Layer 1 forward-ref check) 2. Severity ordering — bake-reset + head-ref filter buried at bottom of Fixed (Layer 3 — out of scope) 3. Bullet/prose inconsistency in Fixed section (Layer 1 consistency check) 4. Missing version header + one-line summary (Layer 1 header check) 5. Cascade-failure narratives over-long (Layer 2 fragment discipline) 4 of 5 catches would fire automatically under Layer 1 + Layer 2. ## Refs - Sibling reusable: #271 (fragment-kind check via #278) - Empirical: release-toolkit#264 Cold-Read pass - Prior art: BookStack page 208 (Cold Read Prompt, established via tmux-tell#628 2026-07-01) - Related fixes: #272 (duplicate Section headers) — Layer 1 check would be redundant guard
bosun closed this issue 2026-07-02 16:22:43 +02:00

Reopened — auto-closed on PR #289 merge (Closes #282 Layer 1 close-keyword doesn't distinguish sub-scope). Per Bosun 16:22:58 ratification: "#282 stays open pending Layer 2 landing."

Layer 1 status: DONE at PR #289 (merged as ee679838). Delivered:

  • .forgejo/workflows/reusable-changelog-body-check.yml — reusable
  • scripts/changelog-body-check.sh — 6 mechanical checks (version header + date, standard sections present, forward-refs, length ceiling 5000, bullet/prose consistency, duplicate section headers)
  • tests/changelog-body-check.bats — 10 mutation-verify tests
  • docs/integration.md — consumer wiring stanza
  • Bake-list registrations across all 7 sites (2 loops in release-prep.sh, 1 in check-self-bootstrap.sh, 1 in build_bake.sh, 3 in reusable-release.yml)
  • [-] Toolkit-self dogfood wrapper — deferred to release-toolkit#290 post-v0.21.0-cut fold (pre-cut single-ref-alignment bind, per #281 pattern)

Layer 2 status: OPEN. Scope per issue body:

  • Ship changelog.d/.template.md with pre-baked structure (Outcome + Required action placeholders)
  • Port the fragment-brevity docs from tmux-tell#628 CONTRIBUTING §CHANGELOG entries into release-toolkit CONTRIBUTING as the canonical guidance
  • [-] Pre-commit hook / PR-body-check for per-fragment word count — deferred (design) per issue body

Layer 3: Explicit out-of-scope (reader-simulation, severity ordering, semantic emphasis, missing-context) — Cold Read remains opt-in.

Next PR against i/282-layer2-fragment-scaffolding will close this on merge.

Reopened — auto-closed on PR #289 merge (`Closes #282 Layer 1` close-keyword doesn't distinguish sub-scope). Per Bosun `16:22:58` ratification: "#282 stays open pending Layer 2 landing." **Layer 1 status**: ✅ **DONE** at [PR #289](https://git.frankenbit.de/frankenbit/release-toolkit/pulls/289) (merged as `ee679838`). Delivered: - [x] `.forgejo/workflows/reusable-changelog-body-check.yml` — reusable - [x] `scripts/changelog-body-check.sh` — 6 mechanical checks (version header + date, standard sections present, forward-refs, length ceiling 5000, bullet/prose consistency, duplicate section headers) - [x] `tests/changelog-body-check.bats` — 10 mutation-verify tests - [x] `docs/integration.md` — consumer wiring stanza - [x] Bake-list registrations across all 7 sites (2 loops in release-prep.sh, 1 in check-self-bootstrap.sh, 1 in build_bake.sh, 3 in reusable-release.yml) - [-] Toolkit-self dogfood wrapper — **deferred** to [release-toolkit#290](https://git.frankenbit.de/frankenbit/release-toolkit/issues/290) post-v0.21.0-cut fold (pre-cut single-ref-alignment bind, per #281 pattern) **Layer 2 status**: OPEN. Scope per issue body: - [ ] Ship `changelog.d/.template.md` with pre-baked structure (Outcome + Required action placeholders) - [ ] Port the fragment-brevity docs from [tmux-tell#628](https://git.frankenbit.de/frankenbit/tmux-tell/issues/628) CONTRIBUTING §CHANGELOG entries into release-toolkit CONTRIBUTING as the canonical guidance - [-] Pre-commit hook / PR-body-check for per-fragment word count — **deferred (design)** per issue body **Layer 3**: Explicit out-of-scope (reader-simulation, severity ordering, semantic emphasis, missing-context) — Cold Read remains opt-in. Next PR against `i/282-layer2-fragment-scaffolding` will close this on merge.
bosun closed this issue 2026-07-02 16:35:07 +02:00
Sign in to join this conversation.
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#282
No description provided.