fix(x): release caption uses curated CHANGELOG headlines, not commit subjects (#607)

draft_release_post was fed highlights=list(report.change_summary) — raw
per-commit subjects — so the announcement model parroted the top commit
('RoboCo API v0.26.0 is out: docs: curate the full Unreleased body #601').
New pure changelog_highlights() extracts the bold feature leads from the
curated release entry (report.drafted_changelog), stripping PR refs and
trailing periods; approve() prefers those and falls back to change_summary
only when the changelog yields nothing. The video captions were already
good because the authoring dev read the changelog — same source now.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-20 10:35:29 +02:00
committed by GitHub
co-authored by Renn F
parent bf1012e952
commit 57b9e76b12
3 changed files with 57 additions and 2 deletions
+8 -2
View File
@@ -283,11 +283,17 @@ class ReleaseProposalService(BaseService):
publish). Off/no-creds is itself a no-op inside the engine. The
proposal task's project scopes the draft to the released project."""
try:
from roboco.services.x_engine import get_x_engine
from roboco.services.x_engine import changelog_highlights, get_x_engine
# Prefer the curated CHANGELOG's feature headlines over raw
# per-commit subjects — the latter made the announcement caption
# parrot the top commit ("docs: curate the Unreleased body…").
highlights = changelog_highlights(report.drafted_changelog) or list(
report.change_summary
)
await get_x_engine(self.session).draft_release_post(
version=report.proposed_version,
highlights=list(report.change_summary),
highlights=highlights,
project_id=project_id,
)
except Exception as exc:
+25
View File
@@ -103,6 +103,31 @@ def _fallback_release_body(
return f"{product_name} v{version} is out: {lead}"
# Bold feature leads in a Keep-a-Changelog release body: "- **Headline (#N).**"
_CHANGELOG_LEAD_RE = re.compile(r"^- \*\*(?P<lead>.+?)\*\*", re.MULTILINE)
_CHANGELOG_PR_REF_RE = re.compile(r"\s*\(#\d+(?:,\s*#\d+)*\)")
def changelog_highlights(entry: str, *, limit: int = 8) -> list[str]:
"""Human-readable feature headlines from a curated CHANGELOG release body.
The release drafter's ``change_summary`` is raw per-commit subjects
("docs: curate the full 0.26.0 Unreleased body (#601)") feeding those to
the announcement model produces a lame parroted caption. The curated
changelog's bold leads ARE the feature story ("Telegram Mini App V5 — brand
voice"), so use those instead. Pure + best-effort: a body with no bold
leads yields an empty list and the caller falls back to change_summary.
"""
out: list[str] = []
for m in _CHANGELOG_LEAD_RE.finditer(entry):
lead = _CHANGELOG_PR_REF_RE.sub("", m.group("lead")).strip().rstrip(".").strip()
if lead:
out.append(lead)
if len(out) >= limit:
break
return out
def _release_prompt(
version: str, highlights: list[str], voice: str, product_name: str
) -> str:
+24
View File
@@ -1398,3 +1398,27 @@ async def test_draft_release_post_uses_project_name_when_set(
assert body is not None
assert "Acme Robotics" in body
assert "RoboCo" not in body
def test_changelog_highlights_extracts_feature_headlines() -> None:
entry = (
"## [0.26.0] - 2026-07-20\n\n"
"### Security\n\n"
"- **Orchestrator API is off the public internet (GHSA-4f7g).** Both "
"composes published :8000 on 0.0.0.0.\n\n"
"### Added\n\n"
"- **Telegram Mini App V5 — brand voice and an operations ring (#583).** "
"Share Tech Mono becomes the display face.\n"
"- **Forge program: GitHub, Gitea, and GitLab (#575, #581).** One API.\n"
)
hl = x_engine_module.changelog_highlights(entry)
assert hl[0] == "Orchestrator API is off the public internet (GHSA-4f7g)"
assert hl[1] == "Telegram Mini App V5 — brand voice and an operations ring"
assert hl[2] == "Forge program: GitHub, Gitea, and GitLab"
# No raw commit-subject noise, no trailing PR refs or periods.
assert all("#" not in h.split("(GHSA")[0] for h in hl)
def test_changelog_highlights_empty_on_no_leads() -> None:
body = "## [0.26.0]\n\nplain prose, no bold leads\n"
assert x_engine_module.changelog_highlights(body) == []