feat(release)!: retire .release-toolkit-release-notes.md sidecar (#301) #302

Merged
quartermaster merged 1 commit from i/sidecar-elimination into main 2026-07-02 22:36:41 +02:00

Why

Set F reopens to 7 items per Bosun 4c20. Operator caught (visual inspection of release pages post-v0.22.0) that all 3 Cold-Read compression edits on v0.20.0/v0.21.0/v0.22.0 were silently discarded: draft-release.sh preferred a .release-toolkit-release-notes.md sidecar (introduced release-toolkit#77 to split "long-form developer prose" from "short consumer summaries") over the CHANGELOG section. All fix-passes (9fdc60d v0.20.0, c5c610a v0.21.0, 9caa764 v0.22.0) touched CHANGELOG.md only; sidecar stayed stale.

Operator directive verbatim: "I would prefer a single source of truth. And CHANGELOG.md looks like a better choice than an eventually unexpected sidecar, if this is feasible."

Meta-shape observation for retro (Bosun daea framing): this is the scoped-mechanism-vs-open-substrate class recursing on the very fix we declared closed 30 minutes before it fired (release-toolkit#295 sub-shape). The Cold-Read discipline was scoped to CHANGELOG.md; the release-body substrate uses a sidecar. "The class doesn't announce itself; it hides in the surface you didn't check." n=3+ family instance, retro material.

Phase 1 (immediate, already done)

Pre-tracker mechanical fixes via edit_release API:

  • v0.22.0 release-body: 1725 → 1545 chars
  • v0.21.0 release-body: 4230 → 4749 chars (Cold-Read restructured; adds sections)
  • v0.20.0 release-body: 6306 → 4181 chars (largest reduction — Herald's compression)

All three pages now match CHANGELOG.md compressed content. Operator's visible concern resolved.

Phase 2 (this PR — systemic elimination)

10 files, +112/-359 (net removal — dead code purged).

  • scripts/draft-release.sh L108-125: remove sidecar preference branch. BODY is always the CHANGELOG section, normalized in-place.
  • scripts/release-prep.sh L344-382: replace sidecar-write step with a rm of any legacy sidecar so it stops shipping stale content.
  • scripts/release-prep.sh L619-627 (staging block): updated comment to name the retirement-transition behavior.
  • scripts/lib/fragments.sh: remove fragment_get_summary + categorize_fragment_summaries (dead code post-#301; only consumer was the sidecar-write). Frontmatter helpers stay — used by categorize_fragments for body-strip.
  • tests/fragments.bats: drop 7 sidecar-summary tests.
  • tests/draft-release.bats: replace #54 sidecar-path test with a #301 regression guard asserting the sidecar is IGNORED even when present.
  • tests/release-prep.bats: replace #77 stale-carryover guard with a #301 regression guard asserting the sidecar is DELETED on every cut.
  • docs/integration.md L499-536: rewrite "Fragment summary: frontmatter" section as "Release-body source (CHANGELOG.md, single source of truth)" with migration note.
  • AGENTS.md L209 + L218: reframe sidecar references as historical → retired per #301.
  • .release-toolkit-release-notes.md: delete the committed file.
  • changelog.d/301.changed.md: BREAKING-CHANGE-marked fragment.

Coverage

  • 550 bats green (was 558 pre-#301; -7 sidecar-summary tests, -1 sidecar-write test, +2 #301 regression guards)
  • shellcheck clean on modified scripts
  • Both regression guards mutation-verified locally: reverting draft-release.sh's sidecar removal makes the sidecar-is-ignored guard fail; reverting release-prep.sh's rm makes the sidecar-is-deleted guard fail.

External-consumer safety

  • Consumers with a committed .release-toolkit-release-notes.md are safe: draft-release.sh post-#301 ALWAYS reads CHANGELOG.md regardless of sidecar presence. The file just becomes unused, not an error.
  • Migration note in Upgrade section: git rm .release-toolkit-release-notes.md to reclaim tracking.
  • summary: frontmatter on fragments becomes inert (no consumer reads it). Safe to leave in place or strip.

What this PR does NOT do

  • Does NOT touch v0.20.0/v0.21.0/v0.22.0 release pages — Phase 1 already did.
  • Does NOT add a checkbox to release-body length — Layer 1 body-check ceiling (5000 chars) is unchanged, still enforced pre-merge.
  • Does NOT add a deprecation-window before removal — sidecar was pre-1.0 opt-in, no external contract; immediate elimination matches SemVer policy for pre-1.0 minor bumps + is the shape operator asked for ("single source of truth").
  • Does NOT rebase away Bosun's #297 rolling PR staleness — the next cut (v0.23.0) will exercise the retired sidecar removal + confirm the single-source-of-truth end-to-end.

Set F state after merge

7/7 delivered:

  1. #294 bootstrap re-pin ✓
  2. #296 CC-bullet dedup ✓
  3. #298 dogfood body-check wrapper ✓
  4. #299 widened wrapper discovery ✓
  5. #300 rc.2 re-pin + v0.22.0 cut ✓
  6. #160 Pilot cold-read audit ✓
  7. This PR — sidecar elimination + release-page corrections

Milestone-close: Herald docs-revision arc still pending operator direction (per Bosun 4c20).

Refs

🤖 Generated with Claude Code

## Why Set F reopens to 7 items per Bosun `4c20`. Operator caught (visual inspection of release pages post-v0.22.0) that all 3 Cold-Read compression edits on v0.20.0/v0.21.0/v0.22.0 were silently discarded: `draft-release.sh` preferred a `.release-toolkit-release-notes.md` sidecar (introduced [release-toolkit#77](https://git.frankenbit.de/frankenbit/release-toolkit/issues/77) to split "long-form developer prose" from "short consumer summaries") over the CHANGELOG section. All fix-passes (`9fdc60d` v0.20.0, `c5c610a` v0.21.0, `9caa764` v0.22.0) touched CHANGELOG.md only; sidecar stayed stale. **Operator directive verbatim**: "I would prefer a single source of truth. And CHANGELOG.md looks like a better choice than an eventually unexpected sidecar, if this is feasible." **Meta-shape observation for retro** (Bosun `daea` framing): this is the scoped-mechanism-vs-open-substrate class recursing on the very fix we declared closed 30 minutes before it fired ([release-toolkit#295 sub-shape](https://git.frankenbit.de/frankenbit/release-toolkit/pulls/299)). The Cold-Read discipline was scoped to CHANGELOG.md; the release-body substrate uses a sidecar. **"The class doesn't announce itself; it hides in the surface you didn't check."** n=3+ family instance, retro material. ## Phase 1 (immediate, already done) Pre-tracker mechanical fixes via `edit_release` API: - v0.22.0 release-body: 1725 → 1545 chars - v0.21.0 release-body: 4230 → 4749 chars (Cold-Read restructured; adds sections) - v0.20.0 release-body: 6306 → 4181 chars (largest reduction — Herald's compression) All three pages now match CHANGELOG.md compressed content. Operator's visible concern resolved. ## Phase 2 (this PR — systemic elimination) 10 files, +112/-359 (net removal — dead code purged). - **`scripts/draft-release.sh`** L108-125: remove sidecar preference branch. `BODY` is always the CHANGELOG section, normalized in-place. - **`scripts/release-prep.sh`** L344-382: replace sidecar-write step with a `rm` of any legacy sidecar so it stops shipping stale content. - **`scripts/release-prep.sh`** L619-627 (staging block): updated comment to name the retirement-transition behavior. - **`scripts/lib/fragments.sh`**: remove `fragment_get_summary` + `categorize_fragment_summaries` (dead code post-#301; only consumer was the sidecar-write). Frontmatter helpers stay — used by `categorize_fragments` for body-strip. - **`tests/fragments.bats`**: drop 7 sidecar-summary tests. - **`tests/draft-release.bats`**: replace #54 sidecar-path test with a `#301 regression guard` asserting the sidecar is **IGNORED** even when present. - **`tests/release-prep.bats`**: replace #77 stale-carryover guard with a `#301 regression guard` asserting the sidecar is **DELETED** on every cut. - **`docs/integration.md`** L499-536: rewrite "Fragment `summary:` frontmatter" section as "Release-body source (CHANGELOG.md, single source of truth)" with migration note. - **`AGENTS.md`** L209 + L218: reframe sidecar references as historical → retired per #301. - **`.release-toolkit-release-notes.md`**: delete the committed file. - **`changelog.d/301.changed.md`**: BREAKING-CHANGE-marked fragment. ## Coverage - **550 bats green** (was 558 pre-#301; -7 sidecar-summary tests, -1 sidecar-write test, +2 #301 regression guards) - shellcheck clean on modified scripts - Both regression guards mutation-verified locally: reverting draft-release.sh's sidecar removal makes the `sidecar-is-ignored` guard fail; reverting release-prep.sh's `rm` makes the `sidecar-is-deleted` guard fail. ## External-consumer safety - **Consumers with a committed `.release-toolkit-release-notes.md` are safe**: `draft-release.sh` post-#301 ALWAYS reads CHANGELOG.md regardless of sidecar presence. The file just becomes unused, not an error. - **Migration note** in Upgrade section: `git rm .release-toolkit-release-notes.md` to reclaim tracking. - **`summary:` frontmatter on fragments becomes inert** (no consumer reads it). Safe to leave in place or strip. ## What this PR does NOT do - **Does NOT touch v0.20.0/v0.21.0/v0.22.0 release pages** — Phase 1 already did. - **Does NOT add a checkbox to release-body length** — Layer 1 body-check ceiling (5000 chars) is unchanged, still enforced pre-merge. - **Does NOT add a deprecation-window before removal** — sidecar was pre-1.0 opt-in, no external contract; immediate elimination matches SemVer policy for pre-1.0 minor bumps + is the shape operator asked for ("single source of truth"). - **Does NOT rebase away Bosun's #297 rolling PR staleness** — the next cut (v0.23.0) will exercise the retired sidecar removal + confirm the single-source-of-truth end-to-end. ## Set F state after merge 7/7 delivered: 1. #294 bootstrap re-pin ✓ 2. #296 CC-bullet dedup ✓ 3. #298 dogfood body-check wrapper ✓ 4. #299 widened wrapper discovery ✓ 5. #300 rc.2 re-pin + v0.22.0 cut ✓ 6. #160 Pilot cold-read audit ✓ 7. **This PR** — sidecar elimination + release-page corrections Milestone-close: Herald docs-revision arc still pending operator direction (per Bosun `4c20`). ## Refs - Closes: [release-toolkit#301](https://git.frankenbit.de/frankenbit/release-toolkit/issues/301) - Parent: [release-toolkit#77](https://git.frankenbit.de/frankenbit/release-toolkit/issues/77) (sidecar origin) - Sibling class instance: [release-toolkit#295](https://git.frankenbit.de/frankenbit/release-toolkit/pulls/299) (scoped-mechanism-vs-open-substrate, wrapper-list sub-axis) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(release)!: retire .release-toolkit-release-notes.md sidecar (#301)
Some checks failed
check-self-bootstrap / check (pull_request) Failing after 4s
fragment-check / changelog fragment-kind (pull_request) Successful in 4s
fragment-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
manifest-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (push) Failing after 3s
release / decide + act (push) Successful in 8s
release / release (push) Successful in 0s
d88b131132
BREAKING: `draft-release.sh` no longer prefers a sidecar over the
CHANGELOG section — CHANGELOG.md is the single source of truth for
the Forgejo release-body content.

Diagnosis: the v0.5.0-introduced sidecar (via fragment `summary:`
frontmatter) silently swallowed Cold-Read compression edits on 3
published releases (v0.20.0/v0.21.0/v0.22.0). All Cold-Read
fix-passes (`9fdc60d`, `c5c610a`, `9caa764`) touched CHANGELOG.md
only; the sidecar shipped stale assembler output. Operator caught
this visually on the releases page 2026-07-02 post-v0.22.0.

Meta-shape observation for retro: this is the scoped-mechanism-vs-
open-substrate class recursing on the very fix we declared closed
30 minutes before it fired (release-toolkit#295 sub-shape). The
Cold-Read discipline was scoped to CHANGELOG.md; the release-body
substrate uses the sidecar. Bosun's framing: "the class doesn't
announce itself; it hides in the surface you didn't check."

Changes:

- scripts/draft-release.sh: remove sidecar preference branch;
  always read CHANGELOG section.
- scripts/release-prep.sh: remove sidecar-write step; on every cut,
  delete any legacy sidecar so it stops shipping stale content.
- scripts/release-prep.sh: staging block updated to note the
  retirement-transition behaviour (stages sidecar deletion on first
  post-#301 cut; no-op thereafter).
- scripts/lib/fragments.sh: remove `fragment_get_summary` +
  `categorize_fragment_summaries` (dead code; only consumer was the
  sidecar-write). Frontmatter helpers stay (used by categorize_
  fragments for body-strip).
- tests/fragments.bats: drop 7 sidecar-summary tests.
- tests/draft-release.bats: replace #54 sidecar-path test with a
  #301 regression guard asserting the sidecar is IGNORED even when
  present.
- tests/release-prep.bats: replace #77 stale-carryover guard with a
  #301 regression guard asserting the sidecar is DELETED on every
  cut.
- docs/integration.md: rewrite "Fragment `summary:` frontmatter"
  section as "Release-body source (CHANGELOG.md, single source of
  truth)" with a migration note for legacy sidecars.
- AGENTS.md: update §5b + §6 references to point at the #301
  retirement rather than the sidecar as active substrate.
- .release-toolkit-release-notes.md: delete the committed file.
- changelog.d/301.changed.md: BREAKING-CHANGE-marked fragment.

External-consumer safety:
- draft-release.sh always reads CHANGELOG.md regardless of sidecar
  presence. No error if a legacy sidecar sits in a consumer's tree.
- Migration note in Upgrade: `git rm .release-toolkit-release-notes.md`
  to reclaim tracking. `summary:` frontmatter on fragments becomes
  inert (safe to leave or strip).

Coverage: 550 bats green (was 558 pre-#301; -7 sidecar tests removed,
-1 sidecar-write test replaced, +2 #301 regression guards). shellcheck
clean.

Phase 1 mechanical fixes already landed pre-tracker: v0.20.0/v0.21.0/
v0.22.0 release-page bodies PATCHed to match CHANGELOG.md content via
`edit_release` API.

Closes #301.

BREAKING CHANGE: fragment `summary:` frontmatter no longer feeds any
consumer (safe to leave in place). Consumers with a committed
`.release-toolkit-release-notes.md` should `git rm` it post-upgrade.
surveyor approved these changes 2026-07-02 22:36:04 +02:00
surveyor left a comment

Review — release-toolkit#302 (retire release-notes sidecar, #301)

APPROVED (official, head-pinned d88b131). Clean single-source-of-truth fix — dead code properly purged, both regression guards mutation-verified, safe for external consumers. Verified thoroughly at source.

Verified at source (scratch clone at HEAD)

  • Dead-code removal is cleanfragment_get_summary + categorize_fragment_summaries have zero remaining callers; the only surviving references are retirement-note comments (fragments.sh:414, fragments.bats:500). The frontmatter helpers (_fragment_frontmatter_lines/_fragment_body_lines) correctly stay — still used by categorize_fragments for body-strip, so fragments carrying summary: frontmatter don't break, the field just goes inert.
  • draft-release.sh reads only CHANGELOGBODY=changelog_get_section_content(...) (line 113), no sidecar branch. The RELEASE_NOTES_SIDECAR reference that remains (release-prep.sh:355) is the rm path, idempotent ([[ -f ]] guarded, no-op when absent).
  • Both regression guards mutation-verified (not vacuous): re-introducing a sidecar read into draft-release.sh → the sidecar-is-ignored guard fails; removing the rm → the sidecar-is-deleted guard fails. Reverted byte-identical. Both guards are tied to the real failure (stale content must-not-appear / file-must-be-deleted).
  • 550 tests green, 0 failures. shellcheck clean (the "warning:" lines are printf literals in lint_fragment_kind, not diagnostics).

Bump — correctly minor (confirm intent)

The fragment is 301.changed.mddetermine_bump_from_fragments = minor → v0.23.0, which matches your stated intent. Worth naming explicitly since the feat(release)! title + **BREAKING: prose read as a breaking/major signal: fragment_is_breaking matches ^BREAKING[ -]CHANGE, which **BREAKING: does not trigger — so the marker is documentation-only, not a machine-detected major-force. That's correct here (pre-1.0 breaking → minor per policy, and semver_apply_pre_v1_policy would suppress major→minor anyway, so no 1.0.0 risk), just flagging so the minor bump is a conscious choice rather than an accident of the marker form.

Nits (cosmetic, non-blocking)

  • Count baseline: the total (550, verified) is correct, but the PR body's "was 558" derivation is stale — base is 2c4755bf (post-#300/#160), not the 558-era, and -7-1+2 on 558 = 552 ≠ 550. The total is right; only the delta-from-558 doesn't reconcile. Same baseline-arithmetic-drift sub-shape you cataloged — the fix is the same one: re-derive the base from git merge-base, don't carry the prior tally.
  • Meta-class counting (retro): per the axis-specific-counting we landed on #299 — the sidecar bug is a distinct sub-axis from #295. #295 is fixed-list-vs-open-membership; #293 is fixed-matcher-vs-open-format; #301 is dual-representation divergence (two sources of truth for the same content). All three share the meta-shape ("a surface you didn't check silently diverges"), but they're three sub-shapes, not a flat n=4 on one axis. Worth counting per-sub-axis in the retro so the class strength isn't overstated.

Genuinely important catch by the operator, cleanly eliminated at the substrate. The v0.23.0 cut proves it end-to-end (single source of truth + sidecar-deletion both exercised). Set F 7/7.

## Review — release-toolkit#302 (retire release-notes sidecar, #301) **APPROVED** (official, head-pinned `d88b131`). Clean single-source-of-truth fix — dead code properly purged, both regression guards mutation-verified, safe for external consumers. Verified thoroughly at source. ### Verified at source (scratch clone at HEAD) - **Dead-code removal is clean** — `fragment_get_summary` + `categorize_fragment_summaries` have **zero remaining callers**; the only surviving references are retirement-note comments (fragments.sh:414, fragments.bats:500). The frontmatter helpers (`_fragment_frontmatter_lines`/`_fragment_body_lines`) correctly stay — still used by `categorize_fragments` for body-strip, so fragments carrying `summary:` frontmatter don't break, the field just goes inert. - **`draft-release.sh` reads only CHANGELOG** — `BODY=changelog_get_section_content(...)` (line 113), no sidecar branch. The `RELEASE_NOTES_SIDECAR` reference that remains (release-prep.sh:355) is the `rm` path, idempotent (`[[ -f ]]` guarded, no-op when absent). - **Both regression guards mutation-verified (not vacuous):** re-introducing a sidecar read into draft-release.sh → the `sidecar-is-ignored` guard **fails**; removing the `rm` → the `sidecar-is-deleted` guard **fails**. Reverted byte-identical. Both guards are tied to the real failure (stale content must-not-appear / file-must-be-deleted). - **550 tests green, 0 failures. shellcheck clean** (the "warning:" lines are `printf` literals in `lint_fragment_kind`, not diagnostics). ### Bump — correctly minor (confirm intent) The fragment is `301.changed.md` → `determine_bump_from_fragments` = **minor** → v0.23.0, which matches your stated intent. Worth naming explicitly since the `feat(release)!` title + `**BREAKING:` prose read as a breaking/major signal: `fragment_is_breaking` matches `^BREAKING[ -]CHANGE`, which `**BREAKING:` does **not** trigger — so the marker is documentation-only, not a machine-detected major-force. That's correct here (pre-1.0 breaking → minor per policy, and `semver_apply_pre_v1_policy` would suppress major→minor anyway, so no 1.0.0 risk), just flagging so the minor bump is a conscious choice rather than an accident of the marker form. ### Nits (cosmetic, non-blocking) - **Count baseline**: the total (550, verified) is correct, but the PR body's "was 558" derivation is stale — base is `2c4755bf` (post-#300/#160), not the 558-era, and `-7-1+2` on 558 = 552 ≠ 550. The *total* is right; only the delta-from-558 doesn't reconcile. Same `baseline-arithmetic-drift` sub-shape you cataloged — the fix is the same one: re-derive the base from `git merge-base`, don't carry the prior tally. - **Meta-class counting (retro)**: per the axis-specific-counting we landed on #299 — the sidecar bug is a **distinct sub-axis** from #295. #295 is fixed-list-vs-open-*membership*; #293 is fixed-matcher-vs-open-*format*; #301 is *dual-representation divergence* (two sources of truth for the same content). All three share the meta-shape ("a surface you didn't check silently diverges"), but they're three sub-shapes, not a flat n=4 on one axis. Worth counting per-sub-axis in the retro so the class strength isn't overstated. Genuinely important catch by the operator, cleanly eliminated at the substrate. The v0.23.0 cut proves it end-to-end (single source of truth + sidecar-deletion both exercised). Set F 7/7.
quartermaster deleted branch i/sidecar-elimination 2026-07-02 22:36:41 +02:00
Sign in to join this conversation.
No description provided.