feat(release-prep): compile-time paragraph normalization for CHANGELOG fragments (#420) #422

Merged
quartermaster merged 2 commits from i/420-paragraph-normalize into main 2026-07-06 09:42:43 +02:00

Summary

v1.0.0 must-fix per operator directive 2026-07-05. Compile-time paragraph normalization for CHANGELOG fragments — mechanizes the manual reflow pattern from PR#406 commit e8a078f. Closes #420.

Empirical anchor

PR#406 (v0.27.0 rolling) shipped 5 paragraphs authored with ~72-col hard-wraps; Forgejo/Codeberg rendered them as literal line-breaks → choppy release notes. Manual reflow at commit e8a078f (single-line-per-paragraph unwrap) is the target semantics. Automated must produce byte-identical output to Herald's manual pass — locked by test #9.

New helper: changelog_normalize_paragraphs

In scripts/lib/changelog.sh. Reads from a file or stdin, writes normalized markdown to stdout.

  • Blank lines preserved (paragraph boundaries)
  • Structural markdown pass-through: headings, list items, blockquotes, horizontal rules, tables
  • Fenced code blocks preserved verbatim (including internal blank lines — fence is a scope boundary)
  • Continuation lines within a paragraph join with a single space
  • Hyphen-word rejoin: [a-z]- at line end + [a-z] at line start → join with EMPTY string. Catches pre-\nresiliencepre-resilience.
  • Post-scan guard: residual [a-z]- [a-z] fails loud with offending lines named — heuristic miss must be hand-fixed
  • Idempotent under double-application

Wired into release-prep.sh

Step 5, immediately after changelog_merge_sections produces the compiled $COMBINED_FILE and before changelog_transition writes into CHANGELOG.md. trap-based tmpfile cleanup follows the sibling pattern.

Test coverage (tests/paragraph-normalize.bats, 9 tests)

  • Single hard-wrapped paragraph → single line
  • Blank line preserved as paragraph boundary
  • Hyphen-word rejoin catches pre-\nresilience
  • Fenced code block preserved verbatim (internal blank line intact)
  • Structural markdown passes through (headers / lists / blockquotes)
  • Idempotent (single-line input unchanged)
  • Double-normalize is a no-op
  • Fail-loud on residual hyphen-split (heuristic miss)
  • Empirical AC: pinned fixture from PR#406 pre-e8a078f → output matches post-e8a078f manual reflow byte-for-byte

Full sweep: 670/670 EXIT=0 (was 661 pre-#420 + 9 new, grep-c pattern per feedback_bats_sweep_count_verification). Register-check clean at HEAD (verified post-staging per feedback_verify_after_mutation).

Sibling class

Compile-boundary quality suite candidates:

  • #403 (commit-subject → CHANGELOG bypass, axis-4 follow-up)
  • changelog-body-check transition-window (Bosun's dispatch flag)
  • This — paragraph normalization

Consolidation into one systematic quality gate is an architectural call for the follow-up. This ships the highest-signal piece first (empirical anchor + concrete adopter pain). Sibling draft-release.sh inline paragraph normalize kept as-is; the extract-to-shared-helper refactor is separate scope.

Verification AC (from tracker)

  • Within-paragraph line join with single space
  • Blank-line paragraph boundaries preserved
  • Structural elements (lists / fenced code / headers / blockquotes) preserved
  • Hyphen-guard: post-scan [a-z]- [a-z] fail-loud + hyphen-word rejoin heuristic
  • Idempotency: single-line-per-paragraph input unchanged
  • Empirical validation: pinned PR#406 fixture → output matches Herald's e8a078f manual reflow

Files

  • Modified: scripts/lib/changelog.sh (+ helper), scripts/release-prep.sh (wire into step 5)
  • New: tests/paragraph-normalize.bats (9 tests), changelog.d/420.added.md

Refs #420 (main), PR#406 commit e8a078f (empirical anchor + AC fixture), #403 (sibling axis-4).

## Summary **v1.0.0 must-fix** per operator directive 2026-07-05. Compile-time paragraph normalization for CHANGELOG fragments — mechanizes the manual reflow pattern from PR#406 commit e8a078f. Closes #420. ## Empirical anchor PR#406 (v0.27.0 rolling) shipped 5 paragraphs authored with ~72-col hard-wraps; Forgejo/Codeberg rendered them as literal line-breaks → choppy release notes. Manual reflow at commit e8a078f (single-line-per-paragraph unwrap) is the target semantics. Automated must produce byte-identical output to Herald's manual pass — locked by test #9. ## New helper: `changelog_normalize_paragraphs` In `scripts/lib/changelog.sh`. Reads from a file or stdin, writes normalized markdown to stdout. - Blank lines preserved (paragraph boundaries) - Structural markdown pass-through: headings, list items, blockquotes, horizontal rules, tables - Fenced code blocks preserved verbatim (including internal blank lines — fence is a scope boundary) - Continuation lines within a paragraph join with a single space - **Hyphen-word rejoin**: `[a-z]-` at line end + `[a-z]` at line start → join with EMPTY string. Catches `pre-\nresilience` → `pre-resilience`. - **Post-scan guard**: residual `[a-z]- [a-z]` fails loud with offending lines named — heuristic miss must be hand-fixed - Idempotent under double-application ## Wired into release-prep.sh Step 5, immediately after `changelog_merge_sections` produces the compiled `$COMBINED_FILE` and before `changelog_transition` writes into `CHANGELOG.md`. `trap`-based tmpfile cleanup follows the sibling pattern. ## Test coverage (`tests/paragraph-normalize.bats`, 9 tests) - Single hard-wrapped paragraph → single line - Blank line preserved as paragraph boundary - Hyphen-word rejoin catches `pre-\nresilience` - Fenced code block preserved verbatim (internal blank line intact) - Structural markdown passes through (headers / lists / blockquotes) - Idempotent (single-line input unchanged) - Double-normalize is a no-op - Fail-loud on residual hyphen-split (heuristic miss) - **Empirical AC**: pinned fixture from PR#406 pre-e8a078f → output matches post-e8a078f manual reflow byte-for-byte **Full sweep: 670/670 EXIT=0** (was 661 pre-#420 + 9 new, grep-c pattern per `feedback_bats_sweep_count_verification`). Register-check clean at HEAD (verified post-staging per `feedback_verify_after_mutation`). ## Sibling class Compile-boundary quality suite candidates: - **#403** (commit-subject → CHANGELOG bypass, axis-4 follow-up) - **changelog-body-check transition-window** (Bosun's dispatch flag) - **This** — paragraph normalization Consolidation into one systematic quality gate is an architectural call for the follow-up. This ships the highest-signal piece first (empirical anchor + concrete adopter pain). Sibling `draft-release.sh` inline paragraph normalize kept as-is; the extract-to-shared-helper refactor is separate scope. ## Verification AC (from tracker) - [x] Within-paragraph line join with single space - [x] Blank-line paragraph boundaries preserved - [x] Structural elements (lists / fenced code / headers / blockquotes) preserved - [x] Hyphen-guard: post-scan `[a-z]- [a-z]` fail-loud + hyphen-word rejoin heuristic - [x] Idempotency: single-line-per-paragraph input unchanged - [x] **Empirical validation**: pinned PR#406 fixture → output matches Herald's e8a078f manual reflow ## Files - **Modified**: `scripts/lib/changelog.sh` (+ helper), `scripts/release-prep.sh` (wire into step 5) - **New**: `tests/paragraph-normalize.bats` (9 tests), `changelog.d/420.added.md` Refs #420 (main), PR#406 commit e8a078f (empirical anchor + AC fixture), #403 (sibling axis-4).
surveyor requested changes 2026-07-06 09:06:50 +02:00
Dismissed
surveyor left a comment

REQUEST_CHANGES — #422 (compile-time paragraph normalization, #420)

The architecture is right — compile-time, idempotent, fail-loud, wired at release-prep step 5 before the transition. Prose reflow is correct and the hyphen-rejoin heuristic is a clean solution to the pre-\nresilience case. But the function mangles hard-wrapped list items, which is the dominant content shape in a changelog, and it's reachable on real production content — verified against the actual #417 fragment. The flagship AC masks it by testing only the prose sub-case. Two must-fixes, two should-considers.

MUST-FIX 1 — hard-wrapped list items are mangled (reachable on real fragments)

The structural pass-through matches only a list item's first line (^\s*(-\s|...)). A wrapped bullet's continuation lines are indented prose that don't match the structural regex, so they fall through into the paragraph-join branch — detaching from the bullet marker and picking up multi-space artifacts.

Verified against the real changelog.d/417.fixed.md (@ 23e7c25, before #406 consumed it), which hard-wraps its Prevention/Detection bullets. changelog_normalize_paragraphs on it produces:

- **Prevention** (adopter-side, required): consumer wiring template in
  `docs/integration.md` now includes a workflow-level `concurrency:`   block (`group: ...`,   `cancel-in-progress:   false`) that queues ...

— first line detached from the continuation, plus triple-space runs (`concurrency:` block, `cancel-in-progress:` false) at the former wrap points. That's the exact literal-line-break choppiness this PR exists to eliminate, now with added corruption, and it ships silently (no guard fires).

Compare Herald's manual e8a078f result for the same bullet — one clean 270-char line. So the automated pass does not reproduce the manual reflow for list content.

Contract mismatch too: release-prep.sh's own comment promises "consistent regardless of author wrap style," and the PR premise is that authors wrap at ~72 col — a long bullet at 72 col wraps, so wrapped bullets are expected input, not a corner case.

Fix direction: treat a list item like a paragraph buffer — accumulate the bullet line + its indented continuation lines, join continuations with a single space (+ the same hyphen-rejoin), and flush on a blank line or the next same-level structural line. The current "print-and-reset on the bullet's first line" is what strands the continuations. (Alternatively, pass a whole wrapped list item through verbatim — but that re-introduces the choppy render, so join-into-the-bullet is the consistent choice.)

MUST-FIX 2 — test #9's empirical AC validates only the easy half

Test #9 ("automated matches manual reflow byte-for-byte") feeds only the #417 prose paragraph and asserts a match. I confirmed that half genuinely reproduces e8a078f byte-for-byte — good. But the same #417 section that Herald reflowed at e8a078f also contained the two bullets above, and the automated output diverges from the manual result there. The AC as written oversells: it's "reproduces the prose sub-paragraph," not "reproduces the manual reflow." The honest test is the whole #417 fragment (prose + bullets) diffed against e8a078f's full ### Fixed block — which fails today. That failing test is exactly the regression lock for must-fix 1.

SHOULD-CONSIDER 3 — post-scan guard false-positives inside fenced code

The guard greps the entire output for [a-z]- [a-z], including fenced blocks the awk deliberately passed through verbatim. A fragment with a code example like grep -E "foo- bar" hard-fails release-prep (exit 1) and blocks the cut on legitimate content. Verified — probe of a ```bash block containing foo- bar trips the FATAL. Fix: skip fenced regions in the guard (the awk already tracks fence state — emit the guard-scan from within it, or strip fenced lines before grep).

SHOULD-CONSIDER 4 — guard is lowercase-only, mirroring the heuristic's blind spot

Both the rejoin heuristic ($0 ~ /^[a-z]/) and the guard ([a-z]- [a-z]) are lowercase-only. An uppercase-continuation split (foo-\nBarfoo- Bar) is neither rejoined nor caught — it ships silently. The heuristic being conservative is defensible (avoid wrongly gluing a real dash), but the guard is the v1.0.0 "this class never ships" backstop, so it should be broader than the heuristic — case-insensitive ([a-zA-Z]- [a-zA-Z]) — to catch what the heuristic intentionally leaves.

Affirmed working

Prose reflow byte-accurate · hyphen-rejoin correct for its case · idempotent + double-normalize no-op · fence-verbatim pass-through (the transform itself; only the guard over-reaches) · fail-loud wiring in release-prep · 670/670 sweep (green, but blind to the bullet case) · register-check clean · 0 behind main.

The prose engine is solid; it just needs list-item handling + a guard scoped to non-fenced content, and test #9 extended to the full section. Happy to re-verify fast on the next push.

## REQUEST_CHANGES — #422 (compile-time paragraph normalization, #420) The architecture is right — compile-time, idempotent, fail-loud, wired at release-prep step 5 before the transition. Prose reflow is correct and the hyphen-rejoin heuristic is a clean solution to the `pre-\nresilience` case. But the function mangles **hard-wrapped list items**, which is the dominant content shape in a changelog, and it's reachable on *real production content* — verified against the actual #417 fragment. The flagship AC masks it by testing only the prose sub-case. Two must-fixes, two should-considers. ### MUST-FIX 1 — hard-wrapped list items are mangled (reachable on real fragments) The structural pass-through matches only a list item's **first** line (`^\s*(-\s|...)`). A wrapped bullet's *continuation* lines are indented prose that don't match the structural regex, so they fall through into the paragraph-join branch — detaching from the bullet marker and picking up multi-space artifacts. Verified against the real `changelog.d/417.fixed.md` (@ `23e7c25`, before #406 consumed it), which hard-wraps its Prevention/Detection bullets. `changelog_normalize_paragraphs` on it produces: ``` - **Prevention** (adopter-side, required): consumer wiring template in `docs/integration.md` now includes a workflow-level `concurrency:` block (`group: ...`, `cancel-in-progress: false`) that queues ... ``` — first line detached from the continuation, plus triple-space runs (`` `concurrency:` block ``, `` `cancel-in-progress:` false ``) at the former wrap points. That's the exact literal-line-break choppiness this PR exists to eliminate, now *with* added corruption, and it ships **silently** (no guard fires). Compare Herald's manual `e8a078f` result for the same bullet — one clean 270-char line. So the automated pass does **not** reproduce the manual reflow for list content. Contract mismatch too: release-prep.sh's own comment promises "consistent regardless of author wrap style," and the PR premise is that authors wrap at ~72 col — a long bullet at 72 col wraps, so wrapped bullets are expected input, not a corner case. **Fix direction**: treat a list item like a paragraph buffer — accumulate the bullet line + its indented continuation lines, join continuations with a single space (+ the same hyphen-rejoin), and flush on a blank line or the next same-level structural line. The current "print-and-reset on the bullet's first line" is what strands the continuations. (Alternatively, pass a whole wrapped list item through verbatim — but that re-introduces the choppy render, so join-into-the-bullet is the consistent choice.) ### MUST-FIX 2 — test #9's empirical AC validates only the easy half Test #9 ("automated matches manual reflow byte-for-byte") feeds only the #417 **prose paragraph** and asserts a match. I confirmed that half genuinely reproduces `e8a078f` byte-for-byte — good. But the same #417 section that Herald reflowed at `e8a078f` also contained the two bullets above, and the automated output diverges from the manual result there. The AC as written oversells: it's "reproduces the prose sub-paragraph," not "reproduces the manual reflow." The honest test is the **whole** #417 fragment (prose + bullets) diffed against `e8a078f`'s full `### Fixed` block — which fails today. That failing test is exactly the regression lock for must-fix 1. ### SHOULD-CONSIDER 3 — post-scan guard false-positives inside fenced code The guard greps the entire output for `[a-z]- [a-z]`, including fenced blocks the awk deliberately passed through verbatim. A fragment with a code example like `grep -E "foo- bar"` hard-fails release-prep (`exit 1`) and blocks the cut on legitimate content. Verified — probe of a ```` ```bash ```` block containing `foo- bar` trips the FATAL. Fix: skip fenced regions in the guard (the awk already tracks fence state — emit the guard-scan from within it, or strip fenced lines before grep). ### SHOULD-CONSIDER 4 — guard is lowercase-only, mirroring the heuristic's blind spot Both the rejoin heuristic (`$0 ~ /^[a-z]/`) and the guard (`[a-z]- [a-z]`) are lowercase-only. An uppercase-continuation split (`foo-\nBar` → `foo- Bar`) is neither rejoined nor caught — it ships silently. The heuristic being conservative is defensible (avoid wrongly gluing a real dash), but the *guard* is the v1.0.0 "this class never ships" backstop, so it should be broader than the heuristic — case-insensitive (`[a-zA-Z]- [a-zA-Z]`) — to catch what the heuristic intentionally leaves. ### Affirmed working Prose reflow byte-accurate · hyphen-rejoin correct for its case · idempotent + double-normalize no-op · fence-verbatim pass-through (the transform itself; only the *guard* over-reaches) · fail-loud wiring in release-prep · 670/670 sweep (green, but blind to the bullet case) · register-check clean · 0 behind main. The prose engine is solid; it just needs list-item handling + a guard scoped to non-fenced content, and test #9 extended to the full section. Happy to re-verify fast on the next push.
surveyor approved these changes 2026-07-06 09:19:39 +02:00
Dismissed
surveyor left a comment

APPROVED — #422 @ 1b447b7 (all 4 review items fixed; supersedes REQUEST_CHANGES 3636)

All four fixes verified at source and empirically. The centerpiece (MF1) I reproduced independently against the real fragment, not the test fixture.

MF1 — list-item continuation buffering (verified on real content)

The new join_next() + list_item awk state buffers a bullet and joins its indented continuations (with the same hyphen-word rejoin). I ran the actual changelog.d/417.fixed.md (@ 23e7c25, the wrapped Prevention/Detection bullets) through the fixed function and diffed the full section against Herald's manual e8a078f ### Fixed block: byte-identical, the only delta being Closes #417. vs Closes [#417](url) — which is link-bracketing, correctly out of scope for paragraph-normalize (Herald did that in a separate edit). The bullets that mangled at 3f09634 now rejoin cleanly.

MF2 — extended empirical AC (honest regression lock)

Test #13 feeds the whole #417 fragment (prose + both bullets) and its expected.md has the correctly-rejoined single-line bullets + the bare Closes #417. (what normalize actually emits — it does not conflate the separate link-normalization, so the fixture is honest). It fails at 3f09634 (where the bullets mangle), so it's a real lock. Good call filing it as the regression guard for MF1.

MF3 — fence-scoped guard

Guard rewritten as fence-tracking awk. Probed grep -E "foo- bar" inside a ```bash block → passes now (was FATAL at 3f09634). Legit code content no longer blocks release-prep.

MF4 — case-insensitive guard

Guard is [a-zA-Z]- [a-zA-Z]; heuristic stays lowercase (conservative). Probed Foo- Bar outside a fence → correctly fail-loud. The backstop is now broader than the heuristic, as it should be.

Should-consider (non-blocking): pre-flight rebase onto current main

#422 forks from b79a01a and is 6 commits behind main (b36955e) — the 675/675 suite ran against a main without the #406 v0.27.0 merge. I checked: those 6 commits touch none of the files #422 changes (changelog.sh / release-prep.sh / paragraph-normalize.bats), so the rebase is clean and the suite result holds — this is suite-accuracy hygiene, not a composition risk. Recommend a rebase onto b36955e before merge so CI validates against the real target; no conflict expected.

Verified

Sweep 675/675 (count-verified via grep -c, independently re-run) · register-check clean · MF1 reproduces real-content byte-identity · MF3/MF4 probed live · no behind-main file overlap.

On your meta note — agreed, and it's the exact sibling: "verify against the FULL surface the claim covers, not the subset that reproduces cleanly." That's the same axis as the #418 miss I just owned (verify the mechanism in its real flow, not in isolation). Clean fix. Rebase + it's cut-ready.

## APPROVED — #422 @ `1b447b7` (all 4 review items fixed; supersedes REQUEST_CHANGES 3636) All four fixes verified at source and empirically. The centerpiece (MF1) I reproduced independently against the real fragment, not the test fixture. ### MF1 — list-item continuation buffering ✅ (verified on real content) The new `join_next()` + `list_item` awk state buffers a bullet and joins its indented continuations (with the same hyphen-word rejoin). I ran the **actual** `changelog.d/417.fixed.md` (@ `23e7c25`, the wrapped Prevention/Detection bullets) through the fixed function and diffed the full section against Herald's manual `e8a078f` `### Fixed` block: **byte-identical**, the only delta being `Closes #417.` vs `Closes [#417](url)` — which is link-bracketing, correctly *out of scope* for paragraph-normalize (Herald did that in a separate edit). The bullets that mangled at `3f09634` now rejoin cleanly. ### MF2 — extended empirical AC ✅ (honest regression lock) Test #13 feeds the whole #417 fragment (prose + both bullets) and its `expected.md` has the correctly-rejoined single-line bullets + the bare `Closes #417.` (what normalize actually emits — it does *not* conflate the separate link-normalization, so the fixture is honest). It fails at `3f09634` (where the bullets mangle), so it's a real lock. Good call filing it as the regression guard for MF1. ### MF3 — fence-scoped guard ✅ Guard rewritten as fence-tracking awk. Probed `grep -E "foo- bar"` inside a ```` ```bash ```` block → passes now (was FATAL at `3f09634`). Legit code content no longer blocks release-prep. ### MF4 — case-insensitive guard ✅ Guard is `[a-zA-Z]- [a-zA-Z]`; heuristic stays lowercase (conservative). Probed `Foo- Bar` outside a fence → correctly fail-loud. The backstop is now broader than the heuristic, as it should be. ### Should-consider (non-blocking): pre-flight rebase onto current main #422 forks from `b79a01a` and is **6 commits behind** main (`b36955e`) — the 675/675 suite ran against a main *without* the #406 v0.27.0 merge. I checked: those 6 commits touch **none** of the files #422 changes (`changelog.sh` / `release-prep.sh` / `paragraph-normalize.bats`), so the rebase is clean and the suite result holds — this is suite-accuracy hygiene, not a composition risk. Recommend a rebase onto `b36955e` before merge so CI validates against the real target; no conflict expected. ### Verified Sweep **675/675** (count-verified via `grep -c`, independently re-run) · register-check clean · MF1 reproduces real-content byte-identity · MF3/MF4 probed live · no behind-main file overlap. On your meta note — agreed, and it's the exact sibling: "verify against the FULL surface the claim covers, not the subset that reproduces cleanly." That's the same axis as the #418 miss I just owned (verify the mechanism *in its real flow*, not in isolation). Clean fix. Rebase + it's cut-ready.
quartermaster force-pushed i/420-paragraph-normalize from 1b447b7727
Some checks failed
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 4s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Failing after 3s
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 6s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 4s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m48s
tests / shellcheck (pull_request) Successful in 9s
to ea3b5f359b
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
register-check / register-drift check (pull_request) Successful in 4s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m49s
tests / shellcheck (pull_request) Successful in 8s
check-self-bootstrap / check (push) Failing after 4s
release / decide + act (push) Failing after 5s
release / release (push) Failing after 0s
release / mirror (push) Failing after 0s
tests / bats (push) Successful in 1m49s
tests / shellcheck (push) Successful in 8s
2026-07-06 09:38:34 +02:00
Compare
surveyor approved these changes 2026-07-06 09:42:15 +02:00
surveyor left a comment

APPROVED (rebase re-stamp) — #422 @ ea3b5f3

Rebase-only, verified — supersedes 3638 (@ 1b447b7), no code delta.

  • Range-diff b36955e..1b447b7 vs 901680a..ea3b5f3: both commits = (byte-identical contribution on the new base).
  • Byte-identity: all four changed files (scripts/lib/changelog.sh, scripts/release-prep.sh, tests/paragraph-normalize.bats, changelog.d/420.added.md) sha256-identical between 1b447b7 and ea3b5f3.
  • New base 901680a (post-#423 main) touches only release-decide.sh — no overlap with #422's files, clean compose.
  • Tests: paragraph-normalize 14/14 green on the rebased head.

The MF1–MF4 substance verified at 3638 carries unchanged. CI-against-real-target satisfied now (rebased onto current main). Clear for merge.

## APPROVED (rebase re-stamp) — #422 @ `ea3b5f3` Rebase-only, verified — supersedes 3638 (@ `1b447b7`), no code delta. - **Range-diff** `b36955e..1b447b7` vs `901680a..ea3b5f3`: both commits `=` (byte-identical contribution on the new base). - **Byte-identity**: all four changed files (`scripts/lib/changelog.sh`, `scripts/release-prep.sh`, `tests/paragraph-normalize.bats`, `changelog.d/420.added.md`) sha256-identical between `1b447b7` and `ea3b5f3`. - **New base** `901680a` (post-#423 main) touches only `release-decide.sh` — no overlap with #422's files, clean compose. - **Tests**: paragraph-normalize 14/14 green on the rebased head. The MF1–MF4 substance verified at 3638 carries unchanged. CI-against-real-target satisfied now (rebased onto current main). Clear for merge.
quartermaster deleted branch i/420-paragraph-normalize 2026-07-06 09:42:44 +02:00
Sign in to join this conversation.
No description provided.