gum-monthly-release
SkillDocs & knowledgeDrafts the end-of-month Gum release notes from PRs and commits since the last release. Outputs a draft in /temp/ in the hybrid format, curated Breaking Changes / Biggest Changes / Gum Tool / Gum Runtimes / Tutorials and Templates highlights on top, then a complete per-PR "What's Changed" list, then the Full Changelog placeholder, with image placeholders. User-triggered near month end.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the gum-monthly-release skill
What this skill tells your AI
The instructions your AI receives, as published by vchelaru/gum in .claude/skills/gum-monthly-release/SKILL.md and read by ahel’s review.
Invoking coder agent to draft the monthly Gum release notes.
Ask first, don't guess
This skill is a collaboration. The user has explicitly said: when anything is unclear at any point — categorization, intent of a sparse commit, whether a change cross-cuts Tool and Runtimes, which features deserve the spotlight — stop and ask. Do not invent user-impact descriptions from a one-line commit message. When the diff is genuinely ambiguous, surface the ambiguity rather than papering over it. The point of this skill is to save the user keystrokes, not to silently misrepresent the release.
Anything you can't resolve mid-draft, capture in the ## Open Questions block at the bottom of the markdown file. The user will work through that block with you after the file is generated.
Not for hotfixes. This skill's fan-out is built for monthly-scale PR volume. A hotfix (emergency single-bug release) gets a short manual note instead — see "Hotfixes" in docs/contributing/building-and-releasing-gum.md.
Step 1: Front-loaded questions
Before doing any work, ask the user (in a single message, as a numbered list — they can answer all at once):
-
Release tag / date. What's the release tag name? (e.g.
Release_April_29_2026) The skill uses this to name the output file and to draft the Full Changelog placeholder. -
Previous-release boundary. What should I diff against? The user may give a tag (
PreRelease_March_26_2026), a commit hash, a date, or acompare/...URL. If they have no preference, propose the most recent tag fromgh release list --repo vchelaru/Gum --limit 5and ask them to confirm.Always cross-check the boundary against
gh release list. If a newer published release exists between the user's boundary andmain, flag it before proceeding — using the older boundary will double-count PRs that already shipped in the newer release. Show the user the newer tag, list how many PRs it would absorb, and ask which boundary to use. Do not silently accept a stale boundary, even if the user supplied acompare/...URL with it. -
Breaking-changes migration doc URL. Is there one for this month? (e.g.
https://docs.flatredball.com/gum/gum-tool/upgrading/migrating-to-2026-april) If none, the Breaking Changes section is omitted.
Wait for answers before proceeding. If they answer some but not others, ask the rest.
Baked-in defaults — do not ask:
- Primary author for
(thanks)exclusion is alwaysvchelaru. Every PR by anyone else gets(thanks @author)(still skipping bot accounts per Step 4). - Biggest Changes selection — propose Top 4 + 4 alternates by default. The user may choose to expand the Top to 5+ during review — this is common on big months (April 2026 shipped 5; May 2026 shipped 6). When the release is clearly large (many net-new features, not just one dominant theme), say so and offer 6–7 up front rather than making the user pull every alternate up one at a time. Still don't pad with filler — only promote genuinely user-visible, "talkable" features; leave architectural-but-invisible changes (e.g. a controls-moved-to-a-shared-library refactor with backward-compat forwarders) as a regular section bullet, not a spotlight. List remaining alternates in Open Questions so the user can promote with one line.
Step 2: Gather PRs and commits
With the boundary established, gather all merged PRs and commits since that point.
git log is the canonical PR set — not the merged:>=DATE search. A release tag is often published days after the commit it points to (e.g. Release_May_02_2026 was published May 2 but tags a late-April commit). A merged:>=<publish-date> filter then silently drops every PR merged between the tagged commit and the publish date — this burned a release once, undercounting by ~70 PRs. So derive the authoritative list from the commits actually reachable since the tag, and parse the squash-merge PR number from each subject:
# Canonical set: every squashed PR since the boundary tag. The trailing (#N) is the merge PR.
git fetch origin --tags -q
git log --no-merges <prev-tag>..origin/main --format="%s" \
| grep -oE '\(#[0-9]+\)$' | grep -oE '[0-9]+' | sort -un # → the PR numbers to cover
# Full clean subjects (already end in "(#N)") — used directly for the What's Changed list (Step 7.5):
git log --no-merges <prev-tag>..origin/main --format="%s"
Use the merged:>=DATE search only as a secondary source for files/labels/author metadata, and reconcile it against the git-log set — anything in git log but missing from the search is a real PR the date filter dropped.
# Metadata for categorization (Tool vs Runtimes) — widen the date well before the tag to avoid undercounting:
gh pr list --repo vchelaru/Gum --state merged --search "merged:>=YYYY-MM-DD" --limit 400 --json number,title,author,files,labels
# For details on a specific PR (description + commits)
gh pr view <number> --repo vchelaru/Gum --json title,body,commits,author,files
Look at — and this is where past drafts went wrong, so read carefully:
- PR title — the headline, but never the only signal. Many PR titles are roll-ups: "Styling improvements", "FRB fixes", "Apos Shapes work". The actual user-facing changes are in the individual commit messages, not the title.
- Individual commits in every PR — mandatory, not optional. PR bodies in this repo are almost always empty, but the merged commit list inside each PR usually contains a per-commit changelog (one bullet per real user-visible change). A PR titled "Styling improvements" might contain commits for six different new features and fixes that each deserve their own release-notes bullet. Skipping the per-PR commit list will silently drop most of the release content.
- Files touched — used for categorization (Tool vs Runtimes). Pull these via
--json fileson the bulkpr listcall rather than per-PR. - Author — for
(thanks @author)attribution.
Do NOT dump every PR's commits into one file for yourself to read. The previous version of this skill looped all ~250 PRs' commits into a single text file and had the main agent expand them. That single-context consumption is the root cause of the "it just summarizes" complaint — one agent holding hundreds of PRs' worth of raw commits will compress, no matter how many times the surrounding text says "mandatory." The per-PR commit fetch and expansion is therefore delegated to parallel subagents in Step 3; each subagent holds only a handful of PRs, so it has no pressure to summarize.
What you produce in this step is just the batched PR-number list for that fan-out (plus the metadata above for categorization). Two constraints to carry into Step 3:
- Subagents fetch commits per-PR (
gh pr view <N> --repo vchelaru/Gum --json number,title,author,files,commits), never the bulkgh pr list … --json …,commitscall — that one blows the GraphQL 500,000-node limit on a busy month (the commits connection multiplies by author sub-connections × page size). - Standalone
jqandpythonare not installed in this environment; usegh's built-in--jqonly. Issue/non-PR numbers in the set just error and are skipped, which is fine.
Heuristic for when a PR's commits will add content vs. just confirm the title:
- "Bump version to ...", "GITBOOK-NNN",
Merge pull request— commits add nothing; trust the title. - Specific bug-fix PRs ("Fixed parent-height bug when toggling visibility") — commits often confirm; check anyway.
- Roll-up PRs ("Styling improvements", "Font improvements", "FRB fixes", "Apos Shapes work", "More work on X", "Sokol forms2") — commits almost certainly contain multiple distinct changes. Always expand these.
Filter out GitBook auto-sync commits (GITBOOK-NNN: ...) — these are docs auto-syncs from the GitBook integration, not changelog material.
Filter out FRB-integration PRs entirely — and that includes the What's Changed list, not just the curated sections. This covers more than PRs literally titled "FRB fixes" / "Oops fixed FRB": any FlatRedBall-1 build-compat patch qualifies — e.g. titles like "Fix FRB build: ...", PRs that gate Gum code under #if !FRB, or "Added #ifs and file includes into FRB". FRB1 has a different syntax than the rest of Gum and breaks under refactors from time to time; the maintainer has stated these never need to be called out in Gum release notes — not in the curated highlights and not in the complete What's Changed list. Always omit them, do not put them in Open Questions, and do not ask the user whether to keep them.
Filter out internal-only plugin/code-organization renames unless the maintainer explicitly says otherwise. Renames like InternalPlugin → PriorityPlugin are internal even if the symbol looks public — first-party plugin classes are not part of the consumer API. When in doubt, surface in Open Questions, not as a Breaking Change.
If a commit message is sparse and intent is genuinely unclear from the diff, add it to the Open Questions block rather than guessing.
Step 3: Fan out per-PR expansion to parallel subagents
This is the structural fix for the "it just summarizes" problem — treat it as the heart of the skill, not an optimization. The main agent never reads the raw commits for the whole release. Each PR is handed to a general-purpose subagent that reads that PR's commits (and its diff when the commits are sparse) and returns finished user-impact bullets. Because each subagent holds only a handful of PRs, it faithfully expands every commit instead of compressing — which is exactly what a single agent reading hundreds of PRs cannot do.
First, grab the tone reference so the subagents can match last month's voice. Use the most recent prior release (don't hard-code a tag — it ages):
gh release list --repo vchelaru/Gum --limit 5 # find the previous tag
gh release view <prior-release-tag> --repo vchelaru/Gum
Batching. Group the canonical PR numbers into batches of ~8–10. Spawn one subagent per batch, issuing batches in parallel (single message, multiple Agent calls). The harness caps concurrency, so on a big month send them in waves until the entire canonical set is covered — every PR is expanded by exactly one subagent, not just the "unclear" ones. (A ~250-PR month is ~25–30 subagents; that's expected and is the point.)
Subagent prompt template (fill in the batch's PR numbers):
You are expanding Gum PRs into release-notes bullets. For each PR number in this batch — [N1, N2, …] — run
gh pr view <N> --repo vchelaru/Gum --json number,title,author,files,commits. If the commit list is sparse or the intent is unclear, also readgh pr diff <N> --repo vchelaru/Gum, and for PRs whose title references an issue number,gh issue view <issue-num> --repo vchelaru/Gumfor the user-facing symptom. Standalonejq/pythonare not installed; usegh --jqonly.The style is user-impact-first, not mechanical: the reader is a Gum customer deciding whether this release matters to them. Translate, e.g. "Refactored TextRuntime font loading" → "Better error messages when fonts fail to load because Gum wasn't initialized"; "Added IsTilingMiddleSections to NineSlice" → "NineSlices can now optionally tile the middle section in tool and at runtime."
Humans are lazy. One short sentence per bullet, no exceptions. State the change and stop — cut every qualifying clause ("this only affects you if...", "instead of the old behavior of...", "note that...") unless deleting it would make the bullet actively wrong. A bullet with a semicolon or a second sentence is a compression failure, not thoroughness. If detail matters (who's affected by a breaking change, how to migrate), that detail belongs in the linked doc, not the bullet — link docs (
https://docs.flatredball.com/gum/...) instead of inlining them.Bad: "V1 and V2 default Forms visuals are removed. DefaultVisualsVersion.V1/.V2 and their backing classes no longer exist. Passing either value, or referencing a V1/V2 class directly, is now a compile error instead of a CS0618 warning. This only affects you if you explicitly requested V1/V2 or subclassed a V1/V2 visual directly; every backend already defaulted to V3 in practice." Good: "V1 and V2 default Forms visuals removed, replaced by V3."
Expand roll-ups — this is the single most important instruction. A PR titled "Styling improvements" / "Font improvements" / "FRB fixes" / "Apos Shapes work" / "More work on X" almost always contains several distinct user-visible changes in its commit list. Emit one bullet per distinct change, never one bullet for the PR. The PR title is never the only signal; the per-commit changelog is. ("Bump version",
GITBOOK-NNN,Merge pull requestcommits add nothing — for those, trust the title.)Skip entirely (return nothing): GitBook auto-syncs (
GITBOOK-NNN), FRB-integration fix PRs ("FRB fixes" / "Oops fixed FRB" — FlatRedBall-1 patches the maintainer never wants in notes), internal-only first-party plugin/code-organization renames (e.g.InternalPlugin→PriorityPlugin), documentation-only PRs (new or updated docs pages, troubleshooting sections, doc reorganization, broken-link fixes) — the maintainer does not include documentation in the release notes, and internal diagnostic logging (e.g. per-phase timing added to the Output window, debug instrumentation) — not a user-visible capability change, so skip it without routing to Open Questions. For a mixed PR that also changes code, still surface the non-documentation user-facing change; only the documentation/diagnostic portion is dropped.Clarity bar — every bullet must answer "what changed and why do I care?" without opening the PR or knowing the codebase:
- No dangling internal class/method names. "Property paths shared via the relocated
PropertyPathObserver" is unacceptable — name the user scenario or drop the class name.- Fixes name the symptom and trigger: "Fixed Android crash where Gum projects failed to load when bundled in an APK", not "Fixed Android issue."
- Features name the scenario the user can now do, not the API that was added.
- Omit changes gated behind a compile flag (
FULL_DIAGNOSTICS), CI-only, or internal first-chance noise the user never sees. Do not invent detail you can't support from the diff — mark it low-confidence instead.Return a JSON array, one object per PR you did not skip:
{ "number": N, "author": "login", "section": "tool" | "runtimes" | "both" | "templates" | "omit", "bullets": ["…"], "confidence": "high" | "medium" | "low", "note": "what's unclear, only if medium/low" }section: "both"is for cross-cuttingGumCommonchanges that affect Tool and Runtimes. Preferconfidence: "low"over guessing.
Collect every subagent's array into one combined list. That pre-digested list — not the raw commits — is what you categorize and consolidate in Steps 4–5. You should not need to re-read individual PR commits in the main context; if one specific bullet is unclear, re-read that one PR, don't re-pull the set.
If the draft still summarizes: escalate to a Workflow
This fan-out is a prose-described pipeline — the running agent still has to choose to batch the PRs, spawn the waves, and not shortcut. That's a softer guarantee than a deterministic harness. If a future run still comes back compressed (roll-up PRs collapsed to one line, whole sections thinner than the commit history warrants), that's the signal this prose step wasn't enough, and the next step is to promote Step 3 to a Workflow rather than to add more "be thorough" wording (which won't help — see the diagnosis in the changelog comment that introduced this step).
A Workflow makes the fan-out deterministic: pipeline(prNumbers, fetchCommits, expandToBullets, tagSection) loops over the canonical PR set with no opportunity to skip or summarize the batch, returning the same structured {number, section, bullets, confidence} objects this step's subagents return. The main loop then runs Steps 4–5 over the workflow's output exactly as it does today. Note that a Workflow spawns dozens of agents and costs more tokens, so it requires the user's explicit opt-in each run — surface the option, don't auto-launch it.
Step 4: Categorize
Work from the combined bullet list the Step 3 subagents returned, not the raw commits. Each bullet already carries a proposed section; your job here is to file, consolidate, and de-duplicate them.
Documentation-only changes are excluded from the curated sections — the maintainer does not want docs called out in the highlight bullets. The Step 3 subagents already drop them, but double-check that none slipped through as a Gum Tool or Gum Runtimes bullet. (Documentation PRs are only excluded from the curated highlights — they still appear in the complete What's Changed list per Step 7.5.)
One bullet = one change, one short sentence — never stitch bullets together to save space. Each Step 3 subagent already returns one bullet per distinct change. When filing them into a section, keep them as separate list items, and keep each to a single short sentence — no semicolons chaining a second fact, no "this only affects you if..." caveats, no parenthetical PR-number lists like "(#4426, #4436, #4498)" tacked onto a combined sentence. A bullet that names more than one PR, or that runs past one sentence, is a compression failure — split it back into separate bullets, even when the changes are thematically related (a shared intro sentence is fine; the fixes below it still get one bullet each). No em dashes — use a period or comma instead. This is the single most common way a draft goes wrong: it's easy to re-merge Step 3's already-good short bullets into a longer "complete" paragraph while filing them, and that's exactly the failure to avoid.
Sections, in order:
- Breaking Changes — one short sentence per change, naming what was removed/changed and its replacement (e.g. "X removed, replaced by Y"), plus a "See the upgrade guide for who's affected and how to fix it: " line. Who's affected, why, and how to migrate are the doc's job, not the bullet's — do not restate them inline. Omit the section entirely if the user said no breaking changes this month.
- Biggest Changes — see Step 5.
- Gum Tool — anything affecting the Gum WPF tool (paths under
Tool/,Gum/, plugins, tool-side projects). - Gum Runtimes — anything affecting shipped runtime libraries (
MonoGameGum,KniGum,FnaGum,SkiaGum,RaylibGum,GumCommon's runtime-facing pieces). - Tutorials and Templates — sample/template/tutorial changes. Often empty — omit if empty.
- What's Changed — the complete, verifiable per-PR list (see Step 7.5). This is the "full diff below the highlights" half of the hybrid format.
- Full Changelog — placeholder line (see Step 7).
The output is a hybrid, not curated-only. Sections 1–5 are curated highlights that consolidate and explain (e.g. ~80 shape PRs collapse into a handful of capability bullets). Curated-only drafts read as "missing tons" because the reader can't verify coverage — and aggressive consolidation does drop detail. The What's Changed list (Step 7.5) fixes this: every PR is listed, one line each, so coverage is provable. Always produce both halves.
Cross-cutting changes (e.g. GumCommon changes that affect both sides): duplicate the bullet in both Tool and Runtimes sections. The user explicitly wants this — customers only read the section that applies to them and shouldn't have to guess whether a change in the other section affects them.
When you can't tell which section a change belongs to, ask or add it to Open Questions.
Attribution: every PR not authored by the primary author gets (thanks @author) appended. Skip bot accounts: claude, claude-code, dependabot, dependabot[bot], github-actions, github-actions[bot], and any other obviously-automated handle.
Step 5: Biggest Changes — Top 4 + candidates
The user picks the spotlight features by gut feel: coolest / most impactful for end users. Your job is to propose, not decide.
- Choose the Top 4 from the combined Step 3 bullets. Lean toward: net-new features (new controls, new variables, new tools), high-visibility UX changes, cross-platform additions, and things users would talk about. Lean away from: bug fixes, refactors, internal improvements.
- Identify up to 4 additional candidates — also strong but didn't make your top 4.
- Place the Top 4 in the Biggest Changes section in the markdown, each with:
### <Feature name>heading- 1–2 short sentences of user-impact description, same "cut every caveat" bar as regular bullets (see Step 3) — a spotlight section earns a heading and an image, not extra words
- Doc link if available
PLACEHOLDER!!!! IMAGE/GIF for <feature name>line where the image goes
- Put the additional candidates in the Open Questions block at the bottom (see Step 9) so the user can swap or re-rank.
Step 6: Image placeholders
Use a plain-text loud placeholder, not an HTML comment. The user reports HTML comments sometimes don't render and they want the placeholders to be impossible to miss.
PLACEHOLDER!!!! IMAGE/GIF for Hot Reload Support
Place these on their own line where each image should go in the Biggest Changes section.
Step 7: Full Changelog placeholder
Place this directly below the What's Changed list (Step 7.5) and above the Open Questions block:
PLACEHOLDER!!!! Full Changelog link
The user fills this in after they cut the tag — it's the GitHub compare link (https://github.com/vchelaru/Gum/compare/<prev-tag>...<new-tag>), which only resolves once the new tag exists. It is not missing content; don't try to fill it.
Step 7.5: Build the complete "What's Changed" list
Below the curated sections (and above the Full Changelog placeholder), emit a ## What's Changed section: one bullet per PR in the canonical set, so the release is fully verifiable.
Build it from git log subjects, not the PR-title API. The squash-merge subject is the full PR title already suffixed with (#N) — clean and untruncated. The gh pr list --json title field, by contrast, comes back truncated with … for some older PRs, which leaks ellipses into the list. So:
git log --no-merges <prev-tag>..origin/main --format="%s" \
| grep -vE '^GITBOOK-' \
| grep -vE '^FRB fixes' \
| grep -vE '^Oops fixed FRB' \
| grep -vE '^Fix FRB build' \
| grep -vE '#if !FRB' \
| grep -vE '^Added #ifs and file includes into FRB' \
| sed 's/^/- /'
Then append (thanks @author) to the lines whose PR is not authored by vchelaru. There are usually only a handful of external contributors; get them in one pass and sed the specific (#N) lines:
gh pr list --repo vchelaru/Gum --state merged --search "merged:>=YYYY-MM-DD" --limit 400 \
--json number,author --jq '.[] | select(.author.login != "vchelaru") | "\(.number) \(.author.login)"'
# → for each, sed -i -E 's/\(#NNNN\)$/(#NNNN) (thanks @login)/' on the list file
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 620
- Forks
- 80
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
gum-monthly-release- Source
- github.com/vchelaru/gum