Skip to content

Add changelog-release-pr composite action#66

Draft
yonib05 wants to merge 29 commits into
strands-agents:mainfrom
yonib05:changelog-release-action
Draft

Add changelog-release-pr composite action#66
yonib05 wants to merge 29 commits into
strands-agents:mainfrom
yonib05:changelog-release-action

Conversation

@yonib05

@yonib05 yonib05 commented Jun 11, 2026

Copy link
Copy Markdown
Member

Description

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

Deterministic, no LLM. Pipeline: fetch release(s) → derive structured entries from the GitHub compare API (every merged commit between the prior tag and this one, each resolved to its PR — independent of release-note format) → enrich each from its linked PR (area-* labels → areas, breaking change label, merge-commit SHA, author, and — for monorepo releases — the changed-file language gate) → render site/src/content/changelog/<sdk>/<file>.md matching the harness-sdk Zod schema (preserving human-written highlights/body on re-sync) → open a PR via peter-evans/create-pull-request.

Built to match devtools house style: dependency-free .cjs modules invoked through actions/github-script (like strands-command/scripts/javascript/process-input.cjs); pure logic modules with injected fetchers/fs; tests via Node's built-in runner.

Notable behaviors

  • Language gating for monorepo releases: PR changed-files decide 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.
  • Old-repo handling: historical harness releases link PRs on sdk-python/sdk-typescript; enrichment + links use each PR's own repo.
  • skip-existing mode for the daily cron backstop (zero API cost for existing files, never regresses enrichment).
  • Resilience: prerelease/draft skip, fail-fast on empty single-mode tag, memoized PR fetches, graceful degradation on PR-fetch errors.
  • github.action_ref scripts checkout so callers pinning @<sha> pin the scripts too; create-pull-request pinned to a commit SHA.

Type of Change

New feature

Testing

cd changelog-release-pr/scripts && node --test81 tests, all pass. Pure logic modules with injected fetchers/fs, so the whole suite runs offline; coverage spans tag→meta mapping, semver ordering, compare-API entry derivation (commit→PR resolution, dedup, direct-push skip, truncation warning), PR enrichment (areas/breaking/commit, language + docs-only gating, graceful degradation), title classification, new-contributor parsing, markdown rendering (YAML-quoting edge cases, highlights/body preservation on re-sync), and the full build orchestration (language/docs gating, skip-existing, memoization, drafts/prereleases). End-to-end validated against the harness-sdk changelog schema (astro sync + full site build).

Review follow-up (latest commit)

After moving entry derivation to the compare API, the body-parsing path (parseReleaseBody, countChangelogBullets, and their TAIL/LOOSE_ENTRY regexes) had no production caller — only classifyTitle and parseNewContributors remain live. Removed the dead code and reworked the affected tests onto a small local bullet-body helper (built on classifyTitle), explicitly labeled as a test convenience rather than a production path. How verified: confirmed zero non-test references to the removed symbols via grep before deletion, then re-ran node --test (81 pass / 0 fail). Net −5 tests = dead-function tests dropped, live-function tests added.

Consumers / merge order

Called by harness-sdk#2765 and evals#259. This merges first (the uses: path doesn't exist on devtools main until then); after merge, pin those workflows' refs from @main to the merge SHA.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

yonib05 added 29 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.
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