Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .claude/skills/pr-roadmap-link/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ description: Lock a StarStats roadmap-item slug onto a PR's body + label BEFORE

# pr-roadmap-link

You're the last line of defense between "ship a feature" and "telemetry knows what feature shipped." The release-promote.mjs script reads `Roadmap-Item: <slug>` from the tag annotation — but that annotation is set per-release by the operator typing `--roadmap-item-slug <slug>` on the promote command. If the slug decision wasn't made when the PR was merged, the operator has to reconstruct it later from git history, which is fragile.
You're the last line of defense between "ship a feature" and "telemetry knows what feature shipped." The release-promote.mjs script's `discoverSlugFromMergedPrs` reads `roadmap/<slug>` labels on PRs merged in the release window, so the slug travels with the PR rather than needing operator memory at release time.

Your job: at PR-create time, plant the slug on the PR (both as a body trailer and as a `roadmap/<slug>` label per spec §3.3) so the operator can read it off the PR when they later cut a release.

**Critical distinction — the `roadmap/<slug>` label is the canonical signal.** `release-promote.mjs` auto-discovery reads PR labels only, NOT body trailers. The `Roadmap-Item: <slug>` trailer is human-readable metadata for future-you reading the merged PR; it does not drive any automation. Always plant BOTH (this skill does that for you), but if anyone manually edits a PR later, the LABEL is the load-bearing one. Documented at `docs/RELEASING.md §11.4`.

## When to fire

Fire when ALL of these are true:
Expand Down
142 changes: 136 additions & 6 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,16 +367,25 @@ gh workflow run promote.yml \

Inputs:

| Input | Type | Required | Notes |
|-----------|----------|----------|----------------------------------------------------|
| `channel` | choice | yes | `alpha`, `beta`, `rc`, `live` |
| `sha` | string | no | Target SHA on `next`. Defaults to `next` HEAD. |
| `n` | string | no | Explicit pre-release N. Must advance forward. |
| `dry_run` | boolean | no | Prints actions without pushing (default: `false`). |
| Input | Type | Required | Notes |
|---------------------|----------|----------|---------------------------------------------------------------------------------------------|
| `channel` | choice | yes | `alpha`, `beta`, `rc`, `live` |
| `sha` | string | no | Target SHA on `next`. Defaults to `next` HEAD. |
| `n` | string | no | Explicit pre-release N. Must advance forward. |
| `dry_run` | boolean | no | Prints actions without pushing (default: `false`). |
| `roadmap_item_slug` | string | no | Explicit slug for the tag annotation. Overrides auto-discovery from merged PR labels (§11). |

`channel=live` runs both a dry-run preview and the real promotion,
gated by the `production-release` Environment between them.

The `roadmap_item_slug` input is usually unnecessary because the
release-promote script auto-discovers the slug from `roadmap/<slug>`
labels on PRs merged since the previous track tag — see **§11 Roadmap
tracking pipeline** for the full mechanism. Pass `roadmap_item_slug`
when you want to override auto-discovery (e.g., bulk releases that
ship multiple roadmap items, or releases where the labels weren't
applied at PR-create time).

### 6.3. Automatic alpha cadence

Every push to `next` fires the workflow, which:
Expand Down Expand Up @@ -702,3 +711,124 @@ Three behaviour changes from the pre-2026-05-23 model:
Detailed examples for the new subcommands live in the body of this
document with the legacy single-track examples flagged where they
need a track argument added.

---

## 11. Roadmap tracking pipeline

Shipped 2026-05-27 → 2026-05-29 (PRs #112, #113, #125, #126, #128,
#129, #130, #133). Closes the loop between "this PR ships feature X"
and "the tray 'What's new' panel publishes when X reaches a channel"
with zero operator typing between feature start and tray notification.

### 11.1. The six-step chain

1. **Feature start** — `roadmap-tracking` skill
(`.claude/skills/roadmap-tracking/SKILL.md`) fires post-brainstorming,
pre-TDD. Asks "link existing item / create new / skip tracking" and
stashes the decision at `.claude/session-roadmap-slug`.

2. **PR create** — `pr-roadmap-link` skill
(`.claude/skills/pr-roadmap-link/SKILL.md`) fires when about to
`gh pr create --base next`. Plants two attribution marks per spec §3.3:
- **`roadmap/<slug>` label on the PR** — **canonical**. Auto-discovery
reads this.
- `Roadmap-Item: <slug>` trailer in the PR body — human-readable
only. NOT read by any automation; future-you reading the merged
PR can see the slug without clicking through to labels.

3. **Release time** — `scripts/release-promote.mjs` auto-discovers the
slug from `roadmap/*` labels on PRs merged in the range `(prev_tag,
target_sha]`. Resolution priority:
1. `--roadmap-item-slug X` explicit flag wins.
2. `--no-auto-slug` flag → skip discovery, tag unannotated.
3. Auto-discover (default): 0 slugs → no annotation; 1 → use it;
2+ → refuse, demand explicit choice (so multi-slug releases
can't silently mis-attribute).

4. **Tag annotation** — release-promote writes
`Roadmap-Item: <slug>` into the annotated tag body. The release
workflow's "Resolve roadmap item slug" step parses it via
`git tag -l --format='%(contents)'`.

5. **CI emit** — `scripts/roadmap-emit-event.mjs` POSTs a signed CI
event (HMAC-SHA256 over `v1.<ts>.<body>`) to
`POST /v1/internal/roadmap/events`. Server creates a draft
changelog entry on the channel's first-time-Shipped transition.

6. **CI auto-publish** — `scripts/auto-publish-changelog.mjs` POSTs
another signed request to `POST /v1/internal/roadmap/changelog/publish`
(the HMAC endpoint shipped in #128). Server publishes the draft,
fans out subscriber notifications, fires the Discord webhook, and
the tray "What's new" card surfaces on next refresh.

Steps 1–4 are operator-facing; 5–6 happen in CI without intervention.

### 11.2. When to skip tracking

The `pr-roadmap-link` skill silently skips itself when:

- Branch name starts with `docs/`, `chore/`, `style/`, `refactor/`,
`release/`, or `revert/`.
- All commits use Conventional Commits prefixes that don't ship
user-visible behavior (`chore:`, `docs:`, `style:`, `test:`, `ci:`).
- You say "no roadmap" / "skip tracking" / "untracked" in the same turn.
- PR body already carries a `Roadmap-Item:` trailer (idempotent re-runs).

A skipped PR contributes no slug to the eventual release's discovery
window. If the release contains only skipped PRs, you'll see
`[track] slug: (none) — N PR(s) since <prev_tag>, none with roadmap/* labels`
in the promote output and the tag will be unannotated. That's the
right outcome for infrastructure releases.

### 11.3. Manual overrides

```bash
# Override auto-discovery with an explicit slug
node scripts/release-promote.mjs live tray --roadmap-item-slug my-feature

# Skip discovery entirely (multi-item release, or unlabeled work)
node scripts/release-promote.mjs live tray --no-auto-slug
```

The CI workflow has the equivalent input
(`workflow_dispatch.roadmap_item_slug`) — see §6.2.

### 11.4. Auto-discovery details

`discoverSlugFromMergedPrs` in `scripts/release-promote.mjs`:

1. Finds the previous track tag via `previousTrackTagBelow(tagList,
track, currentVersion)` — channel-agnostic but track-scoped.
2. Gets the prev tag's commit date for a coarse `merged:>{date}`
filter on `gh pr list`.
3. Calls `gh pr list --state merged --base next --search merged:>{date}
--json number,mergeCommit,labels,title`.
4. Narrows by exact ancestry: keeps PRs whose `mergeCommit.oid` is
reachable from `targetSha` AND NOT reachable from `prevTag`.
5. Extracts unique `roadmap/<slug>` label suffixes via
`parseSlugsFromPrLabels`.

Failure-tolerant: any gh / git error returns null and the release
proceeds with no annotation. Network issues never block a release.

### 11.5. Server-side dependencies

- HMAC key: `ROADMAP_CI_EVENT_HMAC_KEY` (shared between emit + publish
endpoints — one secret, one auth scheme).
- API base: `STARSTATS_API_URL` (shared with the existing admin-CLI
publish script; one secret per environment, paths appended in code).
- Webhook subscription: the org-level GitHub webhook MUST be
subscribed to BOTH `Projects v2 item` AND `Issues` events. Without
the Issues subscription, label changes on Issues don't propagate to
the server until the 5-min reconciler tick (see PR #112).

### 11.6. Operator quick reference

| Symptom | Where to look |
|---|---|
| Release shipped, no changelog drafted | Tag annotation missing `Roadmap-Item:`? Check `git tag -l --format='%(contents)' <tag>`. |
| Release shipped, draft created, not published | Auto-publish job log. `ROADMAP_ITEM_SLUG: ` empty → slug not in tag annotation. `[auto-publish] no-op: STARSTATS_API_URL not set` → secret missing. |
| Auto-discovery picked wrong slug | Multiple labeled PRs in range. Pass `--roadmap-item-slug X` explicitly. |
| Auto-discovery picked NO slug | Check `gh pr list --base next --search "merged:>{date}" --json labels` against the prev-tag's date. If labels are missing on PRs, the `pr-roadmap-link` skill didn't fire (or skipped per §11.2). |
| Label exists on PR but discovery still misses | Verify `gh` CLI is on PATH for the release-promote script. Auto-discovery skips silently on `gh` errors. |
Loading