Skip to content

feat(changelog): add changelog-release-pr composite action#66

Open
yonib05 wants to merge 31 commits into
strands-agents:mainfrom
yonib05:changelog-release-action
Open

feat(changelog): add changelog-release-pr composite action#66
yonib05 wants to merge 31 commits into
strands-agents:mainfrom
yonib05:changelog-release-action

Conversation

@yonib05

@yonib05 yonib05 commented Jun 11, 2026

Copy link
Copy Markdown
Member

Description

Adds the changelog-release-pr/ composite action — the automation half of the Strands changelog feature. It turns GitHub Releases into structured changelog markdown for the harness-sdk docs site (#2717) and opens a PR there.

Deterministic, no LLM. Pipeline:

  1. Fetch the release(s).
  2. Derive structured entries from the GitHub compare API — every merged commit between the prior tag (auto-detected, same stream, semver-ordered) and this one, each resolved to its PR via the commit→PR API. This is independent of release-note prose, so it's immune to the note-format drift that broke body-parsing.
  3. Enrich each entry from its linked PR: area-* labels → areas, breaking change label, merge-commit SHA, author, and — for monorepo releases — a changed-file language gate (python vs typescript).
  4. Render site/src/content/changelog/<sdk>/<file>.md matching the harness-sdk Zod schema, preserving human-written highlights/body on re-sync.
  5. Open a PR via peter-evans/create-pull-request.

Built to devtools house style: dependency-free .cjs modules invoked through actions/github-script; pure logic with injected fetchers/fs; tests via Node's built-in runner (node --test).

Notable behaviors

  • Compare-API entries: format-independent breakdown; squash and merge-commit repos both resolve correctly.
  • Language gating (monorepo): PR changed-files decide the python vs typescript stream (both → both; site/CI/docs-only → omitted; new contributors with neither → kept in both).
  • New Contributors parsed into structured frontmatter (incl. bracket-suffixed bots like dependabot[bot]), never leaked into entries, language-gated like entries.
  • Pre-monorepo / archived repos: maps python/v*, typescript/v*, and bare v* tags (incl. the archived sdk-typescript) to the right stream for the one-time backfill.
  • Fork-based PR flow (push-to-fork): an account without write access to the target repo opens the PR from its own fork; a classic PAT with public_repo is sufficient. PRs authored this way (a real user) trigger the required CI Gate, which GITHUB_TOKEN-authored PRs do not.
  • Stable backfill branches: the backfill branch name is a content hash of the written release set, so an open-but-unmerged sync PR isn't clobbered by a later run carrying a different set of releases.
  • Cheap cron backstop (skip-existing): only generates missing files, never regresses existing enrichment.

Tests

cd changelog-release-pr/scripts && node --test — 87 tests, all green.

Merge order

First in the stack: #66 (this) → #2717 → #2761 → #2765. The sync workflow (#2765) pins this action at @main, so this must land first.

yonib05 added 30 commits June 11, 2026 15:30
…po gating, PR memoization, fail-fast inputs, action_ref pinning, prerelease skip, docs
Drop PRs that only touch docs/website dirs (site/, docs/) from every stream,
so the changelog stays focused on SDK+language work. Derive a docsOnly flag
from changed-file paths in enrich, and gate entries and new contributors on it.

Fix language gating for early python releases re-tagged python/v* whose PRs
live in the old flat sdk-python repo (code under src/, no strands-py/ dir):
only language-gate when the PR lives in the monorepo repo itself (prRepo ===
repo), since cross-repo PRs have no dir-based language signal and were being
dropped wholesale, emptying those releases. Fetch PR files for all repos now,
since the docs-only gate needs them on single-language repos too.
… gating

Document the curated narrative-release-notes convention (highlights + markdown
body, both preserved across re-syncs) with a template and the rules that keep
them valid: don't restate the structured data, start body headings at h3.

Add tests for the new-contributors language gate: a monorepo release whose
first-contributor PR lives in the old flat repo is not dir-gated, and a
docs-only contributor is dropped even on a monorepo stream. Fix the stale
deps.enrich JSDoc to include languages and docsOnly.
…otation

Newer harness release notes reference PRs as "by @author in #2740" (short form,
no full URL) under emoji section headers, which the URL-only TAIL regex missed
entirely (parsed 0 bullets). Accept the short form too — prRepo stays null so
the caller defaults it to the release's own repo. Also strip the cross-SDK
"_(shared with TS/Python)_" annotation from titles; language gating already
decides stream membership from the PR's changed files.
…s-only

Check out the scripts from github.action_repository (not a hard-coded
strands-agents/devtools), so pinning the action to a fork/mirror sha actually
runs that fork's scripts instead of upstream.

Extend the docs-only filter to top-level documentation files — repo-root
Markdown and well-known root docs (README, CONTRIBUTING, AGENTS.md, etc.) — so a
PR confined to them is dropped from the changelog like site/ and docs/ PRs
already are. A root doc PR that also touches code is still kept.
Only `branch` and `warnings` are consumed by the composite action; the written
count is already logged via core.info, so the unused setOutput was misleading
API surface.
… body

Source structured entries from GitHub's compare API (every merged commit
between the prior tag in the same stream and this release, each resolved to its
PR via the commit->PR API) instead of parsing the auto-generated "What's
Changed" bullet list. This makes the breakdown deterministic and immune to
release-note format drift — we'd already hit three body formats (standard
bullets, short "in #N", and hand-written prose) that broke or defeated parsing.
The body is now curated narrative only (highlights + prose), preserved on
re-sync; "New Contributors" is still read from it.

New: derive-entries.cjs (deriveEntries + previousTagInStream), semver-compare.cjs
(stream-ordering, ported from the site's compareVersionDesc). parse-release-body
exposes classifyTitle (shared conventional-commit classifier); its bullet/TAIL
entry path is now unused but kept. run-action adds listTags/compareCommits/
commitPulls; run wires prior-tag resolution (backfill from the release list,
single via previousTagInStream). 85 tests pass.
…ignal

Paginate the compare API's commits array (100/page) when deriving entries —
a single page silently dropped every commit past the first 100, producing an
incomplete changelog for any release range over 100 commits. Walk all pages and
reserve the `truncated` warning for GitHub's genuine 250-commit cap.

Also fix language gating: only drop a PR when it has a POSITIVE dir signal for
the other language (touches strands-py/ or strands-ts/). PRs with empty
languages — root/CI changes, or pre-monorepo flat-layout PRs whose code lived
under src/ before the strands-py/ dir existed — are kept, not dropped. Gating on
empty languages wrongly emptied pre-monorepo releases re-tagged as python/v*.
…e from compare API

parseReleaseBody and countChangelogBullets (plus the TAIL/LOOSE_ENTRY
regexes) had no production caller after entries moved to the compare API;
only classifyTitle and parseNewContributors are still live. Rework the
build-release-file test stub onto a local bullet-body helper and refocus
the parse-release-body tests on the two remaining functions.

Also document the full-resync reformat churn in the README and note that
semver-compare.cjs must stay in sync with the site's compareVersionDesc.
The entries loop and the new-contributors loop applied the same docs-only +
language gate independently. Extract dropFromStream(enr) and reuse it in both,
so the keep/drop policy lives in one place.
Add a push-to-fork input so an account without write access to target-repo
(an outside collaborator) can open changelog PRs: the branch lands on its
fork and the cross-repo PR is opened against target-repo. A classic PAT with
the public_repo scope is sufficient for this flow.

Derive the backfill branch name from a content hash of the written release
set rather than a constant, so an open-but-unmerged sync PR isn't clobbered
by a later run that contains a different set of releases.
@yonib05 yonib05 changed the title Add changelog-release-pr composite action feat(changelog): add changelog-release-pr composite action Jun 22, 2026
@yonib05 yonib05 marked this pull request as ready for review June 22, 2026 21:41
…le list

The authoring-template example nested a ```python block inside a ```markdown
block at the same fence depth, so GitHub closed the outer block early and the
rest of the section rendered as broken loose text. Use a 4-backtick outer fence
so the 3-backtick inner fence nests legally.

Also correct the curated-file list: it claimed six files but seven carry
hand-written bodies (evals/v0.1.0 was missing). Drop the hard-coded count so it
doesn't drift as more curated notes are added.
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