feat(release-promote): auto-discover slug from merged PR roadmap labels#130
Merged
Conversation
The roadmap-tracking chain ends with the operator typing
`--roadmap-item-slug <slug>` on the release-promote command. That's
the last manual step — reconstruct "which roadmap item did this
release ship work for?" from human memory at release time, after
the fact. Fragile across reviewer hand-offs.
Replace that manual lookup with discovery: scan PRs merged between
the previous track tag and the release's target SHA, extract their
`roadmap/<slug>` labels (planted by the `pr-roadmap-link` skill at
PR-create time), and use the unique slug as the tag annotation
automatically.
Resolution priority (printed at promote time so the operator sees
the chosen path):
1. Explicit `--roadmap-item-slug <slug>` — wins, no discovery.
2. `--no-auto-slug` flag — skip discovery; unsigned tag.
3. Auto-discover (default):
- 0 slugs in range → no annotation (status quo)
- 1 slug → use it; log the discovery source
- >1 slugs → fail loud; demand explicit choice
Discovery details:
- Finds the previous track tag via `previousTrackTagBelow`
(channel-agnostic within the same track; rc.2 finds beta.2,
new minor's alpha.1 finds previous live).
- `gh pr list --state merged --base next --search merged:>{date}
--json number,mergeCommit,labels` — coarse date filter, then
narrows by `git merge-base --is-ancestor` to PRs whose merge
commit is in (prevTag, targetSha].
- `parseSlugsFromPrLabels` extracts unique `roadmap/<slug>`
suffixes from the PR labels.
- Network/auth failures degrade gracefully: any gh error skips
discovery (returns null), and the caller treats that the same
as "no slug to annotate" — release never blocks on the
discovery hop.
Two new pure functions exported for testing:
- `parseSlugsFromPrLabels(prs)` — dedupes slugs across PRs;
tolerates both gh's object-label shape and string-label shape.
- `previousTrackTagBelow(tagList, track, currentVersion)` —
track-scoped, channel-agnostic; returns the highest tag of the
same track strictly below currentVersion.
13 new tests cover the pure parts; the IO glue is exercised via
the existing live release flow.
Pairs with the `pr-roadmap-link` skill (PR #129). Together they
close the loop: pick slug at feature start → confirm at PR time →
auto-flow at release time → server records the channel transition.
The operator's only remaining decision is "is this PR tracked, and
which slug?" — exactly the kind of intent only a human knows.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
scripts/release-promote.mjsnow auto-discovers the roadmap slug fromroadmap/<slug>labels on PRs merged between the previous track tag and the release's target SHA. Eliminates the last manual operator step in the roadmap-tracking chain.--roadmap-item-slug <slug>flag →--no-auto-slugopt-out → auto-discover.parseSlugsFromPrLabels+previousTrackTagBelow.discoverSlugFromMergedPrs,resolveSlugForTag) is non-exported; failure-tolerant — any gh or git error returns null and the release proceeds with no annotation (status quo).Why
After PRs #113 + #125 + #128 + #129, the roadmap-tracking chain looked like:
roadmap-trackingskill stashes slugpr-roadmap-linkskill plantsRoadmap-Item:trailer +roadmap/<slug>label on the PR--roadmap-item-slug <slug>Step 3 was the brittle link. Reviewers hand-off branches; operators forget which feature shipped in which release; the slug placeholder
SScould sit in a repo variable for months. The whole chain was designed to make step 3 redundant — this PR cashes that in.Behaviour matrix
--roadmap-item-slug Xpassed--no-auto-slugpassedghCLI unavailable / 401 / network error[auto-slug] gh pr list failedwarning. Release proceeds.roadmap/*labelsslug: (none) — N PR(s) since <prevTag>, none with roadmap/* labels.slug: X (auto-discovered from PRs since <prevTag>).--roadmap-item-slug Xor--no-auto-slug.The "refuse on ambiguity" branch is deliberate — guessing one of multiple slugs would silently misattribute work; better to demand the operator's explicit pick.
Test plan
node --test scripts/release-promote.test.mjs(89/89 — 76 existing + 13 new, all pure-function tests)node scripts/release-promote.mjs --helprenders correctly with the new flag + resolution rulesnode -c scripts/release-promote.mjssyntax cleanroadmap/Xlabel, verify the tag annotation readsRoadmap-Item: Xslug: (none)--roadmap-item-slugPairs with #129 (the
pr-roadmap-linkskill that plants the labels this script reads). Should land after #129 so the labels exist on PRs before this discovery starts looking for them — but landing it earlier is fine, it just stays silent (zero matches) until the first labeled PR ships.