Skip to content

ci(publish): organize GitHub Releases like burn#55

Merged
willwashburn merged 2 commits intomainfrom
release-notes-burn-style
May 8, 2026
Merged

ci(publish): organize GitHub Releases like burn#55
willwashburn merged 2 commits intomainfrom
release-notes-burn-style

Conversation

@willwashburn
Copy link
Copy Markdown
Member

Summary

The workforce GitHub Releases page (agentworkforce@0.14.0, 0.13.0, …) currently ships ### Released\n- vX.Y.Z stubs for every package — even though each package's CHANGELOG.md has hand-curated ## [Unreleased] bullets ready to ship. This PR ports the burn repo's release-notes infrastructure to workforce so the public Releases page mirrors burn's layout: ## Packages, ## Release Notes (top-level cross-package narrative), ## Package Changelogs with per-package ### <npm-name> / #### Added / #### Fixed / etc.

Concretely:

  • Per-package changelog generator now reads ## [Unreleased] and promotes hand-curated content verbatim into the new versioned block, resetting [Unreleased] to empty. The git-log fallback (with Conventional Commits + imperative-verb bucketing) only fires when [Unreleased] is empty.
  • New root CHANGELOG.md seed file + Generate root changelog step that performs the same Unreleased→[x.y.z] promotion anchored on the umbrella agentworkforce version. No git-log fallback — empty [Unreleased] just leaves the file alone.
  • Build combined release notes step now extracts the root version block as ## Release Notes and inlines per-package changelog bodies as ## Package Changelogs / ### <npm-name>.
  • publish job exposes release_version (the umbrella version) for the create-release job; bump step records it; commit step now stages root CHANGELOG.md alongside per-package files.

Smoke-tested locally against the current [Unreleased] content in each package's CHANGELOG and against a synthetic root [Unreleased] block — both paths produce the expected nested-heading layout.

Test plan

  • Trigger a dry-run publish workflow on this branch (workflow_dispatch with dry_run: true) and confirm the generated /tmp/release-notes.md shows real ### Added / ### Changed bullets rather than ### Released stubs.
  • On the next real publish from main after merge, confirm the GitHub Release shows ## Packages, ## Release Notes (if root [Unreleased] was non-empty), and ## Package Changelogs with curated per-package content — matching the layout of e.g. https://github.com/AgentWorkforce/burn/releases/tag/relayburn-v2.5.0.
  • Confirm the per-package CHANGELOG.md files have their [Unreleased] blocks reset to empty after the publish, with the new [x.y.z] - DATE block carrying the prior Unreleased bullets.

🤖 Generated with Claude Code

Mirror the burn repo's release-notes structure so each workforce GitHub
Release includes the actual curated changes — not just `Released v…`
stamps — and a top-level cross-package narrative.

- Per-package changelog generator now reads `## [Unreleased]` and
  promotes hand-curated content verbatim into the new versioned block,
  resetting `[Unreleased]` to empty. Falls back to bucketed git-log
  inference (Conventional Commits + imperative-verb heuristics, with
  unclassified commits landing in `Changed`) only when Unreleased is
  empty. This is why prior releases stamped `### Released - vX.Y.Z`
  even though every package had real curated bullets sitting in
  `[Unreleased]`.
- New `Generate root changelog` step performs the same Unreleased→
  `[x.y.z]` promotion for a root `CHANGELOG.md`, anchored on the
  umbrella `agentworkforce` version. No git-log fallback — empty
  `[Unreleased]` just means "no narrative-worthy changes this release"
  and the file is left alone.
- `Build combined release notes` step now extracts the root version
  block as `## Release Notes` (top-level cross-package narrative) and
  inlines per-package changelog bodies as `## Package Changelogs` /
  `### <npm-name>`, matching the burn release page layout.
- `publish` job exposes `release_version` (the umbrella version) for
  the create-release job; bump step records it; commit step now stages
  root `CHANGELOG.md` alongside per-package files.
- Add a root `CHANGELOG.md` seed so the new step has something to
  operate on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ebc033d8-d5af-42c9-bc79-bad643ed9326

📥 Commits

Reviewing files that changed from the base of the PR and between 89cf8e3 and aa557d4.

📒 Files selected for processing (1)
  • .github/workflows/publish.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/publish.yml

📝 Walkthrough

Walkthrough

Adds a canonical release_version extracted from the agentworkforce package and propagated through the publish workflow to promote per-package and root CHANGELOG.md entries (promoting curated Unreleased or inferring from git). The release-notes builder merges root and package changelogs to produce GitHub Release notes.

Changes

Changelog Promotion & Release Workflow

Layer / File(s) Summary
Root Changelog Data Structure
CHANGELOG.md
Root CHANGELOG.md introduces Keep a Changelog format with an ## [Unreleased] section for hand-curated release notes.
Workflow Output Contracts
.github/workflows/publish.yml
The publish job declares a new release_version output sourced from the bump step, propagating the canonical agentworkforce package version alongside per-package versions.
Canonical Version Capture
.github/workflows/publish.yml
During the version bump loop, the workflow captures RELEASE_VERSION only when processing the agentworkforce package and exports it as step output release_version for downstream jobs.
Changelog Generation & Promotion Logic
.github/workflows/publish.yml
Per-package changelog generator promotes curated ## [Unreleased] content into new version blocks, falls back to git-log inference when needed, skips prereleases/first-publish without prior tag, and avoids duplicate generation. Root changelog generator promotes non-empty root [Unreleased] into a versioned entry using the canonical release_version.
Release Artifacts & Integration
.github/workflows/publish.yml
Version-bump commit stages root and package CHANGELOG.md files alongside package.json. Release-notes builder receives RELEASE_VERSION, extracts root "Release Notes" when available, merges root and package changelog sections, and inserts a fallback if both are empty.

Sequence Diagram

sequenceDiagram
  participant Workflow as Publish Workflow
  participant BumpStep as bump step
  participant PkgGen as Per-Pkg Changelog Gen
  participant RootGen as Root Changelog Gen
  participant CommitStep as Commit Step
  participant ReleaseBuilder as Release Notes Builder
  participant GitHub as GitHub Release
  
  Workflow->>BumpStep: Iterate packages
  BumpStep->>BumpStep: Capture agentworkforce as RELEASE_VERSION
  BumpStep->>Workflow: Output release_version
  Workflow->>PkgGen: Process each package with RELEASE_VERSION context
  PkgGen->>PkgGen: Promote curated [Unreleased] or infer from git log
  Workflow->>RootGen: Generate root changelog using RELEASE_VERSION
  RootGen->>RootGen: Promote root [Unreleased] to versioned entry
  RootGen->>CommitStep: Mark CHANGELOG.md ready
  CommitStep->>CommitStep: Stage root + package CHANGELOG.md & package.json
  CommitStep->>GitHub: Push version bump commit
  Workflow->>ReleaseBuilder: Pass RELEASE_VERSION
  ReleaseBuilder->>ReleaseBuilder: Extract root [Release Notes]
  ReleaseBuilder->>ReleaseBuilder: Merge root + package changelog content
  ReleaseBuilder->>GitHub: Create GitHub Release with merged notes
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • AgentWorkforce/workforce#37: Both PRs modify the publish workflow and release-notes generation to centralize canonical release/version handling and changelog assembly.

Poem

🐰 I hopped through Unreleased, nibbling notes with care,
Promoted every crumb into versions fair,
One agentworkforce number tied them in a bow,
Now releases sing and changelogs glow,
Hooray — the workflow lets them share!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'ci(publish): organize GitHub Releases like burn' clearly identifies the main change: refactoring the GitHub Release generation to mirror the burn repository's layout and release-notes infrastructure.
Description check ✅ Passed The description thoroughly explains the changes: per-package changelog promotion, root changelog generation, release notes formatting, and includes a concrete test plan for validation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release-notes-burn-style

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
.github/workflows/publish.yml (1)

280-295: 💤 Low value

Consider extracting the shared splitAtUnreleased function.

This function is duplicated verbatim in the root changelog generator (lines 477–492). While inline scripts in YAML make sharing difficult, you could extract it to a small .mjs file in .github/scripts/ and import it from both generators to reduce maintenance burden.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/publish.yml around lines 280 - 295, Extract the duplicated
splitAtUnreleased function into a shared module (e.g.,
.github/scripts/changelog-utils.mjs) and export it so both YAML inline scripts
can import and reuse it; update the workflows that currently define
splitAtUnreleased (the function named splitAtUnreleased in the publish.yml
generator and the identical one in the root changelog generator) to replace the
inline definition with an import of the shared function, ensuring the exported
symbol name matches (splitAtUnreleased) and adjusting any relative paths or
module import syntax used by the workflow step runtimes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.github/workflows/publish.yml:
- Around line 280-295: Extract the duplicated splitAtUnreleased function into a
shared module (e.g., .github/scripts/changelog-utils.mjs) and export it so both
YAML inline scripts can import and reuse it; update the workflows that currently
define splitAtUnreleased (the function named splitAtUnreleased in the
publish.yml generator and the identical one in the root changelog generator) to
replace the inline definition with an import of the shared function, ensuring
the exported symbol name matches (splitAtUnreleased) and adjusting any relative
paths or module import syntax used by the workflow step runtimes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b55bfff2-5a17-49bb-b779-3c832da46e91

📥 Commits

Reviewing files that changed from the base of the PR and between 3d89c92 and 89cf8e3.

📒 Files selected for processing (2)
  • .github/workflows/publish.yml
  • CHANGELOG.md

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

if (type === 'test' || type === 'ci') return 'reliability';
if (type === 'chore' && scope === 'release') return 'release';
if (type === 'chore') return 'deps';
return 'other';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Conventional docs: commits misclassified as other instead of docs

The getType function matches docs in the conventional commit regex (line 339) but the if-chain (lines 343–349) has no branch for type === 'docs', so it falls through to return 'other' at line 350. This routes conventional docs: commits into cats.other, which is merged into the Changed section (line 374). Meanwhile, the imperative-verb fallback at line 360 correctly returns 'docs' for subjects like "Document …", routing them into the Documentation section (line 376). The result is an inconsistency: docs: update README → Changed, but Document the API → Documentation. The cats.docs bucket and Documentation section were clearly added to capture documentation commits, but the conventional-commit code path bypasses them.

Suggested change
return 'other';
if (type === 'docs') return 'docs';
return 'other';
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in aa557d4. Added if (type === 'docs') return 'docs'; before the final return 'other'; so the conventional-commit path matches what the imperative-verb fallback was already doing.

The getType conventional-commit branch matched `docs` in the regex but
had no `if (type === 'docs')` clause, so `docs:` subjects fell through
to `'other'` and ended up in Changed. The imperative-verb fallback
already routed "Document …" subjects to `'docs'`/Documentation, so the
two paths disagreed. Add the missing branch so both classify the same.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@willwashburn willwashburn merged commit 947d513 into main May 8, 2026
2 checks passed
@willwashburn willwashburn deleted the release-notes-burn-style branch May 8, 2026 14:17
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