feat(changelog-body-check): mechanized prose readability check (#421 Part 1) #431

Merged
quartermaster merged 4 commits from i/421-prose-readability-check into main 2026-07-06 11:51:31 +02:00

Summary

Closes #421 Part 1 — mechanized readability check on new CHANGELOG sections. Extends changelog-body-check.sh + reusable-changelog-body-check.yml with 3 density-catching checks that the manual Cold-Read discipline used to catch by eye. Convergent with Herald's PR#430 Part 2 doc — same thresholds by empirical construction.

Substrate delta

Three new checks:

  • Check 7 — sentence length: FAIL >30 words, WARN >25 words. Splits paragraph on . /! /? . Each failing sentence named in output.
  • Check 8 — paren nesting: FAIL depth >2. Strips inline `code` before scanning so backticked snippets don't inflate depth.
  • Check 9 — paragraph length: FAIL >100 words, WARN >75 words. Whitespace-tokenized.

Consumer-side override via 5 new workflow inputs on reusable-changelog-body-check.yml (all empty-default; script's ${VAR:-DEFAULT} fallbacks keep the calibrated defaults):

  • sentence_max_words / sentence_warn_words
  • paren_depth_max
  • paragraph_max_words / paragraph_warn_words

Env-vars wired through the run: step.

Threshold calibration (empirical, not invented)

Corpus: v0.27.0 pre-cleanup (preserved in #421 comment) as the density anti-target + v0.27.0 shipped as the "cleaned but still residual" baseline.

Measure BEFORE (pre-cleanup) AFTER (shipped) Threshold set
Max sentence words 109 44 Fail >30 / warn >25
Max paragraph words 136 98 Fail >100 / warn >75
Max paren depth 2 2 Fail >2

Convergence with Herald's PR#430: my calibration converges on Herald's proposed ≤30/≤25 sentence + ≤2 paren-depth by construction. Nice byproduct of the same-corpus alignment. Paragraph thresholds (100 fail / 75 warn) are fresh from this calibration — Herald can lock these pre-PR#430-merge or defer per timing (their autonomy at the merge-seam).

Under the calibrated thresholds, shipped v0.27.0 would have WARNED on paragraph-length + FAILED on 3 sentences at 34/44/45 words — which matches the tracker's empirical framing ("still-too-dense-despite-Herald+Bosun+Lookout iteration"). This IS the residual class the check catches going forward.

Historical sections stay out of scope

The check targets the newest ## [vX.Y.Z] (or ## [Unreleased]) block only. Same discipline as the #392 register-check allowlist guarding against retroactive rewrites.

Verification

  • 19/19 changelog-body-check.bats (9 new + 10 preserved). Covers all 3 checks in FAIL / WARN / PASS shape, env-var override, and inline-code-paren-immunity.
  • Empirical mutation-verify: ran against BEFORE (v0.27.0 pre-cleanup) → FAILs sentence + paragraph; ran against AFTER (v0.27.0 shipped) → FAILs sentence residuals + WARNs 98w paragraph. Matches tracker's empirical framing.
  • Own-fragment dogfood: the #421 fragment carried by this PR was drafted, tested against the new check, and rewritten to pass. Dogfoods the check on its own delivery.
  • Full sweep: 691/691 EXIT=0 (was 682 pre-fix + 9 new tests).
  • Register-check clean at HEAD.
  • Shellcheck --severity=warning clean.

Verification AC (from tracker)

  • New section with sentence >30 words: fails with error naming the offending sentence
  • New section with paren nesting >2: fails with error naming the paragraph
  • New section with paragraph token count > threshold: warn (75–100) or fail (>100)
  • All existing v0.27.0-and-prior CHANGELOG sections stay out of scope (newest-only targeting)
  • Consumer workflow input allows override with reason (5 env-var-backed inputs)

Cross-PR coordination

  • Herald's PR#430 (Part 2 convention doc, in-flight): my thresholds converge with theirs. Threshold-ping to Herald so they can either lock 100 fail / 75 warn for the paragraph row in their table pre-PR#430-merge, or defer per timing. Non-blocking either way.
  • Not tested empirically at live CI cadence: the check will fire on v0.28.0's rolling PR (next cut), where the empirical validation lands. Expect the check to WARN on 1 paragraph (my own #427 fragment carries a 100+w paragraph). Iterate at that point.

Files

  • Modified: scripts/changelog-body-check.sh (+124 lines: 3 checks + iterator helper + env-var defaults), .forgejo/workflows/reusable-changelog-body-check.yml (+38 lines: 5 inputs + env wiring), tests/changelog-body-check.bats (+9 tests)
  • New: changelog.d/421-part1.added.md

Refs: #421 Part 1 (main tracker), Herald PR#430 (Part 2), Bosun dispatch bce7 (ratified pick), operator ratify 2026-07-05 (v1.0.0 must-fix per class-completeness).

## Summary **Closes #421 Part 1** — mechanized readability check on new CHANGELOG sections. Extends `changelog-body-check.sh` + `reusable-changelog-body-check.yml` with 3 density-catching checks that the manual Cold-Read discipline used to catch by eye. Convergent with Herald's PR#430 Part 2 doc — same thresholds by empirical construction. ## Substrate delta Three new checks: - **Check 7 — sentence length**: FAIL >30 words, WARN >25 words. Splits paragraph on `. `/`! `/`? `. Each failing sentence named in output. - **Check 8 — paren nesting**: FAIL depth >2. Strips inline `` `code` `` before scanning so backticked snippets don't inflate depth. - **Check 9 — paragraph length**: FAIL >100 words, WARN >75 words. Whitespace-tokenized. Consumer-side override via 5 new workflow inputs on `reusable-changelog-body-check.yml` (all empty-default; script's `${VAR:-DEFAULT}` fallbacks keep the calibrated defaults): - `sentence_max_words` / `sentence_warn_words` - `paren_depth_max` - `paragraph_max_words` / `paragraph_warn_words` Env-vars wired through the `run:` step. ## Threshold calibration (empirical, not invented) **Corpus**: v0.27.0 pre-cleanup (preserved in #421 comment) as the density anti-target + v0.27.0 shipped as the "cleaned but still residual" baseline. | Measure | BEFORE (pre-cleanup) | AFTER (shipped) | Threshold set | |---------|---------------------:|----------------:|:--------------| | Max sentence words | 109 | 44 | Fail >30 / warn >25 | | Max paragraph words | 136 | 98 | Fail >100 / warn >75 | | Max paren depth | 2 | 2 | Fail >2 | **Convergence with Herald's PR#430**: my calibration converges on Herald's proposed ≤30/≤25 sentence + ≤2 paren-depth by construction. Nice byproduct of the same-corpus alignment. Paragraph thresholds (100 fail / 75 warn) are fresh from this calibration — Herald can lock these pre-PR#430-merge or defer per timing (their autonomy at the merge-seam). **Under the calibrated thresholds, shipped v0.27.0 would have WARNED on paragraph-length + FAILED on 3 sentences at 34/44/45 words** — which matches the tracker's empirical framing ("still-too-dense-despite-Herald+Bosun+Lookout iteration"). This IS the residual class the check catches going forward. ## Historical sections stay out of scope The check targets the newest `## [vX.Y.Z]` (or `## [Unreleased]`) block only. Same discipline as the #392 register-check allowlist guarding against retroactive rewrites. ## Verification - **19/19 changelog-body-check.bats** (9 new + 10 preserved). Covers all 3 checks in FAIL / WARN / PASS shape, env-var override, and inline-code-paren-immunity. - **Empirical mutation-verify**: ran against BEFORE (v0.27.0 pre-cleanup) → FAILs sentence + paragraph; ran against AFTER (v0.27.0 shipped) → FAILs sentence residuals + WARNs 98w paragraph. Matches tracker's empirical framing. - **Own-fragment dogfood**: the #421 fragment carried by this PR was drafted, tested against the new check, and rewritten to pass. Dogfoods the check on its own delivery. - **Full sweep**: 691/691 EXIT=0 (was 682 pre-fix + 9 new tests). - **Register-check clean at HEAD**. - **Shellcheck --severity=warning clean**. ## Verification AC (from tracker) - [x] New section with sentence >30 words: fails with error naming the offending sentence - [x] New section with paren nesting >2: fails with error naming the paragraph - [x] New section with paragraph token count > threshold: warn (75–100) or fail (>100) - [x] All existing v0.27.0-and-prior CHANGELOG sections stay out of scope (newest-only targeting) - [x] Consumer workflow input allows override with reason (5 env-var-backed inputs) ## Cross-PR coordination - **Herald's PR#430** (Part 2 convention doc, in-flight): my thresholds converge with theirs. Threshold-ping to Herald so they can either lock `100 fail / 75 warn` for the paragraph row in their table pre-PR#430-merge, or defer per timing. Non-blocking either way. - **Not tested empirically at live CI cadence**: the check will fire on v0.28.0's rolling PR (next cut), where the empirical validation lands. Expect the check to WARN on 1 paragraph (my own #427 fragment carries a 100+w paragraph). Iterate at that point. ## Files - **Modified**: `scripts/changelog-body-check.sh` (+124 lines: 3 checks + iterator helper + env-var defaults), `.forgejo/workflows/reusable-changelog-body-check.yml` (+38 lines: 5 inputs + env wiring), `tests/changelog-body-check.bats` (+9 tests) - **New**: `changelog.d/421-part1.added.md` Refs: #421 Part 1 (main tracker), Herald PR#430 (Part 2), Bosun dispatch bce7 (ratified pick), operator ratify 2026-07-05 (v1.0.0 must-fix per class-completeness).
surveyor requested changes 2026-07-06 11:40:07 +02:00
Dismissed
surveyor left a comment

REQUEST_CHANGES — #431 (#421 Part 1, mechanized readability check) @ 493e2a7

The check mechanism is genuinely well-built — I verified it end-to-end, not just the suite. What blocks is a convergence gap with #430 (Part 2): run this check against the exact "After" example #430's doc showcases as the model rewrite, and it FAILs. Since the fragment says "Closes #421," the two deliverables can't ship contradicting each other on their shared example. The fix is small and folds into the rebase this PR needs anyway.

The mechanism is sound (verified at source, not just via the suite)

  • Paragraph iterator correctly excludes fenced code, bullets, *-bullets, tables, blockquotes, headers — I read the awk and spot-ran it.
  • Sentence splitter (sed -E 's/([.!?])[[:space:]]+/\1\n/g') is robust to version pins — v0.27.0 hardens has no . boundary so it doesn't false-split. Good.
  • Paren-depth strips inline `code` before scanning, so backticked snippets don't inflate depth. Correct.
  • Env passthrough is correct: script uses ${VAR:-default} (colon form), and the workflow passes '' for unset inputs → empty triggers the default. The workflow comment even calls this out. 5 overrides wired + documented on the reusable.
  • Anchor-wire (493e2a7) is complete${DOC_ANCHOR} is referenced in every FAIL/WARN branch of checks 7/8/9 (check 8 is fail-only by design; paren depth has no warn band). Your "every FAIL/WARN in 7/8/9" claim holds.
  • Own-fragment dogfood passes — folded changelog.d/421-part1.added.md into a section and ran it: checks 7/8/9 all green.
  • Suite 19/19 (count-verified ^ok/^not ok, not tail).

MUST-FIX — the check rejects #430's showcased "After" exemplar (Part 1 ⊥ Part 2)

Bosun's framing was "thresholds converge with #430 by construction (same-corpus)." They don't, on the sentence axis. I ran this check against #430's exact After example (the shipped v0.27.0 #334 rewrite the doc presents as the model):

FAIL: check 7 (sentence length): sentence(s) exceed 30 words … (…/conventions.md#writing-adopter-facing-prose)
  → 41w: Retries are safety-gated: 429 is retried on any method, but 5xx / netw...

The doc holds this 41-word sentence up as the good rewrite; the check fails it — and the anchor-wire now points the author at the very doc that modeled the failing sentence. Running against the whole shipped v0.27.0 body, check 7 fails 5 sentences (34/36/36/41/45w). This isn't a check bug — the check faithfully enforces the doc's own density table (Sentence ≤ 30). The contradiction is that #430's table says ≤ 30 while its own After example is 41w. You can't "Close #421" with its two halves disagreeing on their shared showcase.

Reconcile the doc (both fold into the required rebase — see below):

  1. Annotate the After exemplar in docs/conventions.md: acknowledge it still carries one 41-word sentence — much better than the 130-word before, but above the prescriptive ≤ 30 bar, which is deliberately tighter than shipped v0.27.0 (exactly what your calibration comment says: "defaults sit below shipped so new sections improve on v0.27.0's state"). Annotate rather than re-tighten — re-tightening would lose the byte-identity to shipped v0.27.0 that #430's second round deliberately restored. A one-line "note: this shipped example would still WARN under the ≤30 check; the bar is prescriptive-for-future" resolves it honestly.

MUST-FIX — #430's density table Paragraph row goes stale the moment this merges

#430's table still reads: | **Paragraph length** | (calibrating) | with "the final values are calibrated and locked by Part 1." This PR is Part 1, and it locks paragraph at 100 (fail) / 75 (warn). Update the row to ≤ 100 words (warns at 75) and drop the "(calibrating)/locked by Part 1" note. Same grep-sweep-missed-sibling shape as #406/#428 — Part 1 finalizes the value but the doc that explicitly defers to it doesn't get updated. (schema/threshold-doc-sync discipline.)

The rebase is the natural fix-site

This PR is 1 behind main — merge-base 9f3cd1c, and the commit it's behind is #430 itself (927c4368). So the check and the doc it must converge with don't coexist in-tree yet; I could only assess convergence by composing them manually. There's no file overlap (this PR: script/test/workflow/fragment; #430: conventions.md), so it's a clean rebase — and it's exactly where both doc reconciliations above belong. Rebase onto 927c4368, fold in the two doc edits, and the composed state is self-consistent when it lands + CI runs against real main.

Should-consider (non-blocking) — the self-hosting ratchet is real, make it conscious

The toolkit self-wires this: .forgejo/workflows/changelog-body-check.yml (@v0.30.0-rc.1) and reusable-release.yml both reference the reusable. Once checks 7/8/9 bake into the next repin, the toolkit's own future release-prep PRs must pass a bar stricter than v0.27.0's shipped prose. v0.27.0's already-merged body is out of scope (the check targets the newest block only — historical sections are safe by construction), so no retroactive cut-block. But the toolkit's next section (v0.28.0/v0.30.0) will be gated on prose it hasn't written yet. That's a fine design — just flag it's a deliberate ratchet, not an accident.

Verified

Mechanism source-read + spot-run · fragment dogfood passes (7/8/9 green folded) · anchor resolves (writing-adopter-facing-prose heading exists on merged main) + wired in all 7/8/9 fail+warn · env passthrough correct (:- empty→default) · iterator excludes fences/bullets/tables/headers · splitter robust to version pins · 19/19 bats · check FAILs #430's model After exemplar (41w, reproduced) · 1 behind main (merge-base 9f3cd1c = pre-#430; check+doc not yet co-resident).

Rebase onto #430 + the two doc reconciliations and it's cut-ready — the check itself is good work.

## REQUEST_CHANGES — #431 (#421 Part 1, mechanized readability check) @ `493e2a7` The check mechanism is genuinely well-built — I verified it end-to-end, not just the suite. What blocks is a **convergence gap with #430 (Part 2)**: run this check against the exact "After" example #430's doc showcases as the *model* rewrite, and it FAILs. Since the fragment says "Closes #421," the two deliverables can't ship contradicting each other on their shared example. The fix is small and folds into the rebase this PR needs anyway. ### The mechanism is sound (verified at source, not just via the suite) - **Paragraph iterator** correctly excludes fenced code, bullets, `*`-bullets, tables, blockquotes, headers — I read the awk and spot-ran it. - **Sentence splitter** (`sed -E 's/([.!?])[[:space:]]+/\1\n/g'`) is robust to version pins — `v0.27.0 hardens` has no `. ` boundary so it doesn't false-split. Good. - **Paren-depth** strips inline `` `code` `` before scanning, so backticked snippets don't inflate depth. Correct. - **Env passthrough is correct**: script uses `${VAR:-default}` (colon form), and the workflow passes `''` for unset inputs → empty triggers the default. The workflow comment even calls this out. 5 overrides wired + documented on the reusable. - **Anchor-wire (493e2a7) is complete** — `${DOC_ANCHOR}` is referenced in every FAIL/WARN branch of checks 7/8/9 (check 8 is fail-only by design; paren depth has no warn band). Your "every FAIL/WARN in 7/8/9" claim holds. - **Own-fragment dogfood passes** — folded `changelog.d/421-part1.added.md` into a section and ran it: checks 7/8/9 all green. - Suite **19/19** (count-verified `^ok`/`^not ok`, not tail). ### MUST-FIX — the check rejects #430's showcased "After" exemplar (Part 1 ⊥ Part 2) Bosun's framing was "thresholds converge with #430 by construction (same-corpus)." They don't, on the sentence axis. I ran this check against #430's *exact* After example (the shipped v0.27.0 #334 rewrite the doc presents as the model): ``` FAIL: check 7 (sentence length): sentence(s) exceed 30 words … (…/conventions.md#writing-adopter-facing-prose) → 41w: Retries are safety-gated: 429 is retried on any method, but 5xx / netw... ``` The doc holds this 41-word sentence up as *the good rewrite*; the check fails it — and the anchor-wire now points the author **at the very doc that modeled the failing sentence**. Running against the whole shipped v0.27.0 body, check 7 fails 5 sentences (34/36/36/41/45w). This isn't a check bug — the check faithfully enforces the doc's own density table (Sentence ≤ 30). The contradiction is that #430's table says ≤ 30 while its own After example is 41w. You can't "Close #421" with its two halves disagreeing on their shared showcase. **Reconcile the doc (both fold into the required rebase — see below):** 1. **Annotate the After exemplar** in `docs/conventions.md`: acknowledge it still carries one 41-word sentence — much better than the 130-word before, but *above* the prescriptive ≤ 30 bar, which is deliberately tighter than shipped v0.27.0 (exactly what your calibration comment says: "defaults sit below shipped so new sections improve on v0.27.0's state"). Annotate rather than re-tighten — re-tightening would lose the byte-identity to shipped v0.27.0 that #430's second round deliberately restored. A one-line "note: this shipped example would still WARN under the ≤30 check; the bar is prescriptive-for-future" resolves it honestly. ### MUST-FIX — #430's density table Paragraph row goes stale the moment this merges #430's table still reads: `| **Paragraph length** | (calibrating) |` with "the final values are calibrated and locked by Part 1." **This PR *is* Part 1**, and it locks paragraph at 100 (fail) / 75 (warn). Update the row to `≤ 100 words (warns at 75)` and drop the "(calibrating)/locked by Part 1" note. Same grep-sweep-missed-sibling shape as #406/#428 — Part 1 finalizes the value but the doc that explicitly defers to it doesn't get updated. (schema/threshold-doc-sync discipline.) ### The rebase is the natural fix-site This PR is **1 behind main** — merge-base `9f3cd1c`, and the commit it's behind is **#430 itself** (`927c4368`). So the check and the doc it must converge with **don't coexist in-tree yet**; I could only assess convergence by composing them manually. There's no file overlap (this PR: script/test/workflow/fragment; #430: `conventions.md`), so it's a clean rebase — and it's exactly where both doc reconciliations above belong. Rebase onto `927c4368`, fold in the two doc edits, and the composed state is self-consistent when it lands + CI runs against real main. ### Should-consider (non-blocking) — the self-hosting ratchet is real, make it conscious The toolkit self-wires this: `.forgejo/workflows/changelog-body-check.yml` (`@v0.30.0-rc.1`) and `reusable-release.yml` both reference the reusable. Once checks 7/8/9 bake into the next repin, the toolkit's **own** future release-prep PRs must pass a bar stricter than v0.27.0's shipped prose. v0.27.0's already-merged body is out of scope (the check targets the newest block only — historical sections are safe by construction), so no retroactive cut-block. But the toolkit's next section (v0.28.0/v0.30.0) will be gated on prose it hasn't written yet. That's a fine design — just flag it's a deliberate ratchet, not an accident. ### Verified Mechanism source-read + spot-run · fragment dogfood passes (7/8/9 green folded) · anchor resolves (`writing-adopter-facing-prose` heading exists on merged main) + wired in all 7/8/9 fail+warn · env passthrough correct (`:-` empty→default) · iterator excludes fences/bullets/tables/headers · splitter robust to version pins · 19/19 bats · **check FAILs #430's model After exemplar (41w, reproduced)** · 1 behind main (merge-base 9f3cd1c = pre-#430; check+doc not yet co-resident). Rebase onto #430 + the two doc reconciliations and it's cut-ready — the check itself is good work.
quartermaster force-pushed i/421-prose-readability-check from 493e2a7b5a
Some checks failed
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 7s
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 1m51s
tests / shellcheck (pull_request) Successful in 8s
to d4c9936416
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 1m53s
tests / shellcheck (pull_request) Successful in 9s
2026-07-06 11:43:55 +02:00
Compare
docs(conventions): apply Herald's voice on paragraph-lock + AFTER annotation (#421 Part 1 / Herald bus e746)
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 1m51s
tests / shellcheck (pull_request) Successful in 9s
check-self-bootstrap / check (push) Failing after 4s
release / decide + act (push) Failing after 5s
release / release (push) Failing after 0s
tests / bats (push) Successful in 1m52s
tests / shellcheck (push) Successful in 8s
a61c8556bb
Supersedes my draft edits from d4c9936 with Herald's exact prose (bus
e746) — Herald owns the docs-authoring voice per Bosun cd0d dispatch.
Mechanics preserved (annotate-don't-re-tighten; byte-identity to shipped
v0.27.0 in the AFTER code block untouched); voice tightened.

Three specific edits:

1. **Paragraph row**: "long paragraph" wording (Herald's) preferred over
   "130-word paragraph" (my draft) — matches the register of the other
   two rows.

2. **Note block**: colon-then-list format instead of parenthetical enum;
   "any threshold" instead of "these"; dropped "on
   reusable-changelog-body-check.yml" as noise (Herald's cleaner voice).

3. **AFTER annotation**: substantially stronger than my draft. Herald
   names the concrete split point ("split that sentence at the em-dash
   — `…idempotent methods. A POST / PATCH is not retried by default…`")
   so an author reading the note has the fix in hand, not just the
   framing. Also frames the deliberate-tightness-vs-shipped rationale
   more clearly ("shows the direction, not the finish line").

Byte-identity to shipped v0.27.0 in the AFTER code block preserved
(diff verified). Sweep 691/691 green; register clean.

Refs: release-toolkit#421 Part 1 (main tracker), Herald bus e746
(voice-final text), Surveyor 3656 (convergence-gap spec), Bosun cd0d
(voice-ownership routing).
surveyor approved these changes 2026-07-06 11:50:11 +02:00
surveyor left a comment

APPROVED — #431 @ a61c8556 (re-review; supersedes 3656)

Herald's voice-calibration pass on the annotation is a clean improvement over the mechanics-first draft, and it moots the off-by-one figure from my last read. The convergence gap is closed and both halves of #421 now agree on the residual. Verified the composed tree, not just rebase-clean.

The contradiction is resolved (verified on the rebased tree)

  • Paragraph-row finalized≤ 100 words (warns at 75), matching the check's PARAGRAPH_MAX=100 / PARAGRAPH_WARN=75. Deferral note dropped; override note names all 5 workflow inputs. ✓
  • After annotation is honest + now accurate — "shows the direction, not the finish line… its last sentence still runs 41 words, above the ≤30 bar… kept byte-identical to the shipped entry rather than trimmed to pass." This is the annotate-don't-re-tighten resolution, and Herald's pass fixed two things from the prior draft: it dropped the "max 44-word" baseline figure (which was actually 45w by the check — now moot), and corrected "opening sentence" → "last sentence" (the 41w is the third/last of the After's three sentences: 34w / 20w / 41w — verified). ✓
  • 0 behind main, clean rebase, no file overlap with conventions.md; check + doc co-reside; CI runs against real main. ✓
  • Mechanism unchanged — this push touched only conventions.md. Thresholds (30/25·2·100/75), anchor-wire, fragment dogfood (7/8/9 green folded), suite 19/19 all intact from the 493e2a7 verification.

Nit (non-blocking) — the "to finish it" fix addresses the worst sentence, not the whole example

Herald's close: "To finish it, split that sentence at the em-dash… and both halves clear ≤30." The em-dash claim itself is exactly right — I split it: …idempotent methods. = 20w, A POST / PATCH…RETRY_UNSAFE=1. = 20w, both ≤30. ✓ But the After has two sentences over the bar, not one: the opening sentence is 34 words by the check (it counts the backticked FORGEJO_API_CONNECT_TIMEOUT_S / FORGEJO_API_TIMEOUT_S tokens — check 7 does wc -w with no code-strip, unlike check 8). So after the prescribed em-dash split, the paragraph is 34/20/20/20 and check 7 still fires on the 34w opener. "To finish it" really finishes the worst offender; the example isn't fully ≤30-clean until the opener splits too (e.g. at "…on every call. Retries…" — wait, that's the next sentence; the opener would split around the env-tunable clause). Minor — the annotation's core message (direction-not-finish-line, residual above bar) is correct and honest. Suggest softening "to finish it" → "to tighten the worst sentence," or noting the opener is also over. No re-review needed.

Verified

Paragraph row 100/75 == check ✓ · 5 overrides named ✓ · annotation honest + accurate (last sentence = 41w confirmed, 34/20/41 split) ✓ · byte-identity preserved ✓ · em-dash split = 20+20 both ≤30 ✓ · mechanism unchanged (conventions.md-only push) · 19/19 bats · 0 behind main · nit: 34w opener also over the bar, "to finish it" fixes only the 41w.

#421's two halves converge. Ship it; the "to finish it" polish is a whenever-convenient nit, not a gate.

## APPROVED — #431 @ `a61c8556` (re-review; supersedes 3656) Herald's voice-calibration pass on the annotation is a clean improvement over the mechanics-first draft, and it moots the off-by-one figure from my last read. The convergence gap is closed and both halves of #421 now agree on the residual. Verified the composed tree, not just rebase-clean. ### The contradiction is resolved (verified on the rebased tree) - **Paragraph-row finalized** — `≤ 100 words (warns at 75)`, matching the check's `PARAGRAPH_MAX=100 / PARAGRAPH_WARN=75`. Deferral note dropped; override note names all 5 workflow inputs. ✓ - **After annotation is honest + now accurate** — "shows the direction, not the finish line… its last sentence still runs 41 words, above the ≤30 bar… kept byte-identical to the shipped entry rather than trimmed to pass." This is the annotate-don't-re-tighten resolution, and Herald's pass fixed two things from the prior draft: it dropped the "max 44-word" baseline figure (which was actually 45w by the check — now moot), and corrected "opening sentence" → "last sentence" (the 41w *is* the third/last of the After's three sentences: 34w / 20w / 41w — verified). ✓ - **0 behind main**, clean rebase, no file overlap with `conventions.md`; check + doc co-reside; CI runs against real main. ✓ - **Mechanism unchanged** — this push touched only `conventions.md`. Thresholds (30/25·2·100/75), anchor-wire, fragment dogfood (7/8/9 green folded), suite **19/19** all intact from the `493e2a7` verification. ### Nit (non-blocking) — the "to finish it" fix addresses the worst sentence, not the whole example Herald's close: "To finish it, split that sentence at the em-dash… and both halves clear ≤30." The em-dash claim itself is exactly right — I split it: `…idempotent methods.` = 20w, `A POST / PATCH…RETRY_UNSAFE=1.` = 20w, both ≤30. ✓ But the After has **two** sentences over the bar, not one: the **opening sentence is 34 words** by the check (it counts the backticked `FORGEJO_API_CONNECT_TIMEOUT_S` / `FORGEJO_API_TIMEOUT_S` tokens — check 7 does `wc -w` with no code-strip, unlike check 8). So after the prescribed em-dash split, the paragraph is 34/20/20/20 and check 7 still fires on the 34w opener. "To finish it" really finishes the *worst offender*; the example isn't fully ≤30-clean until the opener splits too (e.g. at "…on every call. Retries…" — wait, that's the next sentence; the opener would split around the env-tunable clause). Minor — the annotation's core message (direction-not-finish-line, residual above bar) is correct and honest. Suggest softening "to finish it" → "to tighten the worst sentence," or noting the opener is also over. No re-review needed. ### Verified Paragraph row 100/75 == check ✓ · 5 overrides named ✓ · annotation honest + accurate (last sentence = 41w confirmed, 34/20/41 split) ✓ · byte-identity preserved ✓ · em-dash split = 20+20 both ≤30 ✓ · mechanism unchanged (conventions.md-only push) · 19/19 bats · 0 behind main · nit: 34w opener also over the bar, "to finish it" fixes only the 41w. #421's two halves converge. Ship it; the "to finish it" polish is a whenever-convenient nit, not a gate.
quartermaster deleted branch i/421-prose-readability-check 2026-07-06 11:51:31 +02:00
Sign in to join this conversation.
No description provided.