Skip to content

ci: drive releases with release-please and Communique notes#143

Merged
ThomasK33 merged 6 commits into
mainfrom
ci/release-please-integration
Jun 12, 2026
Merged

ci: drive releases with release-please and Communique notes#143
ThomasK33 merged 6 commits into
mainfrom
ci/release-please-integration

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Jun 12, 2026

Copy link
Copy Markdown
Member

Summary

Replaces the changelog/release machinery (the [Unreleased] bot-PR workflow, the release/* changelog workflow, and the local release:prep / release:finalize scripts) with a single release-please-driven flow that keeps Communique as the changelog author:

  • One standing release PR (title chore(release): <version>) maintained on every push to main, carrying the version bump plus a Communique-written CHANGELOG.md section. Merging it is the release: the next workflow run creates the v<version> tag + GitHub Release and dispatches the existing tag-driven release.yml publish pipeline (which is unchanged).
  • release-please runs as a library, not the stock action: the action only supports its built-in changelog generators, so src/tools/release-please-runner.ts registers Communique via registerChangelogNotes('communique', ...) (communique generate HEAD <last-tag> --concise, previous tag passed explicitly so both tools agree on the range). Precedent: npm/template-oss runs release-please the same way.
  • ## [Unreleased] stays at the top of CHANGELOG.md — Communique hard-requires the heading and feeds its body to the LLM as draft material to reconcile into generated notes. Release-please's stock updater would insert new sections above it, so a custom updater (wired via a node-strategy subclass) inserts release sections below the anchor and clears any reconciled draft so it can't leak into later releases. Maintainers can stage draft wording under [Unreleased] in a small PR and Communique folds it into the next release's notes.
  • Version selection from Conventional Commits with bump-minor-pre-major + bump-patch-for-minor-pre-major, matching the project's pre-1.0 history (breaking → minor, feat/fix → patch). Release-As: footers override.
  • New CHANGELOG headings drop the v (## [0.4.2] - <date>): release-please recovers the version from the merged PR body with a parser that requires a digit right after the bracket — with a v there, merge would create no release. Unit tests pin this contract against the installed release-please internals. Generated bodies are also normalized against LLM drift (stray H2 section headings demoted to the house ### style).
  • Token-event workarounds reuse the repo's existing pattern: workflow-token pushes/tags trigger nothing, so the runner's outputs drive explicit gh workflow run dispatches (CI + validate-skills onto the release branch, release.yml by tag input).
  • Supply chain: release-please is an exact-pinned devDependency. Its CJS-only octokit 9.x line ships no npm provenance, so @octokit/endpoint@9.0.6 gets a documented trustPolicyExclude in the new pnpm-workspace.yaml (which also pins an explicit supportedArchitectures matrix so lockfile regeneration stays host-independent — aube add on macOS had silently dropped the linux/win32 optional-dep entries).
  • ADR 0009 records the decision; ADR 0002 (release-it for release prep) is superseded. docs/RELEASE-PROCESS.md rewritten for the new flow.

What releasing looks like now

  1. Review the standing release PR (version + changelog section).
  2. Approve + merge it — the bot authors it, so maintainer review satisfies the ruleset; no admin bypass, no local scripts, no manual tagging.
  3. The merge run tags, creates the GitHub Release, and dispatches release.yml (quality gates → verified tarball → editorial Communique notes + assets → npm trusted publish), all as before.

Verification

  • 1291 unit tests pass, including round-trip tests that exercise the installed release-please internals: PR-body → version/notes parse-back, the no-v heading constraint, chore(release): ${version} title parsing, and the Unreleased-aware updater (insert below the anchor, clear staged drafts, self-heal a missing heading).
  • actionlint + zizmor clean; format/lint/typecheck/build green.
  • Live --dry-run with a real ANTHROPIC_API_KEY against this branch: Communique generated real curated notes for these changes, the candidate PR came out as chore(release): 0.4.2 with a parseable body, and the changelogPreview showed exactly the intended file shape — empty [Unreleased] on top, new ## [0.4.2] section below it, historical ## [v0.4.1] untouched.

After merging (operational notes)

  • The first push to main after merge opens the first release PR — sanity-check its version + notes before merging it.
  • An open automation/update-unreleased-changelog PR (if any) should be closed; its workflow no longer exists.
  • Requires the existing ANTHROPIC_API_KEY secret (or OPENAI_API_KEY + COMMUNIQUE_MODEL var) — same contract as the retired workflows.

🤖 Generated with Claude Code

ThomasK33 and others added 6 commits June 12, 2026 12:07
Replace the Unreleased-changelog workflow, the release/* changelog
workflow, and the local release:prep/release:finalize scripts (release-it)
with a single release-please flow: a runner executes release-please as a
library with Communique registered as the changelog generator, maintaining
one release PR whose merge creates the tag + GitHub Release and dispatches
the existing tag-driven publish pipeline.

- src/tools/release-please-runner.ts: registerChangelogNotes('communique')
  shells out to `communique generate HEAD <last-tag> --concise`; outputs
  drive workflow dispatches (CI onto the release branch, release.yml by tag)
  since workflow-token pushes/tags never trigger other workflows.
- Heading style for new CHANGELOG sections is `## [<version>] - <date>`
  (no v: release-please's PR-body parser needs a digit after the bracket);
  unit tests pin that contract and the insertion point against the
  installed release-please internals.
- CHANGELOG.md drops the [Unreleased] section: the release PR is the
  unreleased view now.
- pnpm-workspace.yaml: aube supportedArchitectures matrix (keeps the
  lockfile host-independent) and a documented trustPolicyExclude for
  @octokit/endpoint@9.0.6 (last-CJS octokit line ships no provenance).
- ADR 0009 records the decision; ADR 0002 (release-it) is superseded.

Change-Id: If33cccf3ae5d1054992a619d2dab4c1539a01fbc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
The release branch carries a component suffix
(release-please--branches--main--components--agent-tty) because
getBranchComponent() ignores include-component-in-tag; resolve the branch
from the open PR instead of hardcoding it.

Change-Id: Ibf4b9480f96860317aa0bec4838f6d9d8715c4e0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Communique hard-fails when CHANGELOG.md lacks an Unreleased heading, and it
feeds that section's body to the LLM as draft material to reconcile into
generated notes. Restore the section and replace release-please's stock
Changelog updater (via a node-strategy subclass registered over the builtin
type) with one that inserts release sections below the anchor — the stock
insertion regex matches '## [Unreleased]' and would insert above it — and
clears the reconciled draft body so it cannot leak into later releases.

Also add a changelogPreview field to --dry-run output so the resulting
CHANGELOG.md can be inspected without pushing.

Change-Id: I634dd707083c5d53163e624760c9099b8dd1ff9d
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
A live run produced '## Changed' (H2) where the historical changelog nests
'### Changed' (H3) under each version heading; demote stray H2s and drop a
duplicate leading version heading if the LLM emits one.

Change-Id: I3f8ff8223d8482bffac556d90b32fd348597fab6
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Under the release-please flow the GitHub Release already exists when
release.yml runs, so the edit path is the normal route; without the flags a
prerelease (e.g. an rc rehearsal) would take over the Latest badge.

Change-Id: I82bf91dbb61c8751e8c13578f83ff8b95e8fc8a8
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
- Write release outputs before rebuilding the release PR and guard the
  workflow dispatch steps with !cancelled(): a notes failure (LLM outage)
  after createReleases() tagged a release previously meant release.yml was
  never dispatched, and a rerun could not recover it (the merged PR label
  has already flipped to autorelease: tagged).
- Make the changelog section-boundary scan and the H2 demotion fence-aware
  so '#'-prefixed lines inside fenced code blocks in staged drafts or LLM
  output are never mistaken for headings.
- Fail with an error naming communique when it exits 0 without writing the
  --output file, instead of a bare readFileSync ENOENT.
- Move formatChangelogSection's heading-contract JSDoc back onto the
  function it documents.

Change-Id: Ie5f78cb5e52338a1f9b55c4b5d7df0818c900fcc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33 ThomasK33 merged commit 7b4bed3 into main Jun 12, 2026
12 checks passed
@ThomasK33 ThomasK33 deleted the ci/release-please-integration branch June 12, 2026 12:03
@github-actions github-actions Bot mentioned this pull request Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant