|
| 1 | +--- |
| 2 | +name: changelog-draft |
| 3 | +description: Generate a reviewable changelog draft from PRs merged in a release range. Extracts explicit CHANGELOG markers, classifies unmarked PRs, adds external contributor attribution, and outputs markdown + JSON artifacts. Does NOT mutate channel_versions.json. |
| 4 | +--- |
| 5 | + |
| 6 | +# Changelog Draft Generator |
| 7 | + |
| 8 | +## Inputs |
| 9 | + |
| 10 | +| Parameter | Required | Description | |
| 11 | +|-----------|----------|-------------| |
| 12 | +| `channel` | yes | Release channel: `stable`, `preview`, or `dev` | |
| 13 | +| `release_tag` | yes | The release tag to generate the changelog for (e.g. `v0.2026.05.06.09.12.stable_00`) | |
| 14 | +| `output_dir` | no | Directory to write output files. Defaults to `$RUNNER_TEMP` or `/tmp/changelog-draft` | |
| 15 | +| `attribution` | no | Attribution mode: `external-only` (default), `all`, or `none` | |
| 16 | + |
| 17 | +## Workflow |
| 18 | + |
| 19 | +### Step 1 — Determine the release range |
| 20 | + |
| 21 | +Infer the previous release **cut** for comparison. Release tags follow the pattern `v0.YYYY.MM.DD.HH.MM.<channel>_NN`, where `_NN` is the RC/hotfix number within that release cut. Multiple tags can share the same date prefix (e.g. `_00`, `_01`, `_02` are all part of one release cut). |
| 22 | + |
| 23 | +The base tag must be the `_00` tag of the **previous** release cut (i.e. a different date), not just the previous tag. For example, if generating a changelog for `v0.2026.04.29.08.57.stable_01`, the base should be `v0.2026.04.22.08.57.stable_00`, not `v0.2026.04.29.08.57.stable_00`. |
| 24 | + |
| 25 | +```bash |
| 26 | +# 1. Extract the date prefix from the release_tag (everything before _NN) |
| 27 | +release_date_prefix="${release_tag%_*}" |
| 28 | + |
| 29 | +# 2. List all _00 tags for the channel (these are release cut points), sorted descending |
| 30 | +git tag --list "v0.*.${channel}_00" --sort=-version:refname |
| 31 | + |
| 32 | +# 3. Pick the first _00 tag whose date prefix differs from release_date_prefix |
| 33 | +``` |
| 34 | + |
| 35 | +Record the range as `previous_cut_tag..release_tag`. |
| 36 | + |
| 37 | +### Step 2 — Fetch PR data |
| 38 | + |
| 39 | +Run the `fetch_prs.py` script to collect all PRs merged in the release range and extract explicit changelog markers: |
| 40 | + |
| 41 | +```bash |
| 42 | +python3 .agents/skills/changelog-draft/scripts/fetch_prs.py \ |
| 43 | + --repo warpdotdev/warp \ |
| 44 | + --base-ref <previous_tag> \ |
| 45 | + --head-ref <release_tag> |
| 46 | +``` |
| 47 | + |
| 48 | +The script outputs JSON to stdout with this structure: |
| 49 | +```json |
| 50 | +{ |
| 51 | + "range": { "base": "<previous_tag>", "head": "<release_tag>" }, |
| 52 | + "prs": [ |
| 53 | + { |
| 54 | + "number": 1234, |
| 55 | + "title": "...", |
| 56 | + "author": "username", |
| 57 | + "body": "...", |
| 58 | + "labels": ["..."], |
| 59 | + "merged_at": "2026-05-01T...", |
| 60 | + "explicit_entries": [ |
| 61 | + { "category": "NEW-FEATURE", "text": "Added dark mode" } |
| 62 | + ], |
| 63 | + "linked_issues": [5678], |
| 64 | + "changed_files": ["app/src/ai/agent.rs", "crates/warp_features/src/lib.rs"] |
| 65 | + } |
| 66 | + ] |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +### Step 3 — Classify contributors |
| 71 | + |
| 72 | +Run the `classify_contributors.py` script with the unique author logins from Step 2: |
| 73 | + |
| 74 | +```bash |
| 75 | +python3 .agents/skills/changelog-draft/scripts/classify_contributors.py \ |
| 76 | + --org warpdotdev \ |
| 77 | + --authors author1,author2,author3 |
| 78 | +``` |
| 79 | + |
| 80 | +Output JSON: |
| 81 | +```json |
| 82 | +{ |
| 83 | + "internal": ["author1"], |
| 84 | + "external": ["author3"], |
| 85 | + "bot": ["author2"], |
| 86 | + "unknown": [] |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +### Step 4 — Extract feature flags |
| 91 | + |
| 92 | +Run the `extract_feature_flags.py` script to get the current flag gate lists: |
| 93 | + |
| 94 | +```bash |
| 95 | +python3 .agents/skills/changelog-draft/scripts/extract_feature_flags.py \ |
| 96 | + --file crates/warp_features/src/lib.rs |
| 97 | +``` |
| 98 | + |
| 99 | +Output JSON: |
| 100 | +```json |
| 101 | +{ |
| 102 | + "release_flags": ["Autoupdate", "Changelog", ...], |
| 103 | + "preview_flags": ["Orchestration", ...], |
| 104 | + "dogfood_flags": ["LogExpensiveFramesInSentry", ...] |
| 105 | +} |
| 106 | +``` |
| 107 | + |
| 108 | +### Step 5 — Fetch issue reporters |
| 109 | + |
| 110 | +Collect all unique `linked_issues` from Step 2 and fetch the original reporter for each. Pass `--org` so the script checks org membership and filters out internal reporters automatically: |
| 111 | + |
| 112 | +```bash |
| 113 | +python3 .agents/skills/changelog-draft/scripts/fetch_issue_reporters.py \ |
| 114 | + --repo warpdotdev/warp \ |
| 115 | + --org warpdotdev \ |
| 116 | + --issues 5678,9012 |
| 117 | +``` |
| 118 | + |
| 119 | +Output JSON (only external reporters are included): |
| 120 | +```json |
| 121 | +{ |
| 122 | + "issue_reporters": [ |
| 123 | + { |
| 124 | + "issue_number": 5678, |
| 125 | + "title": "Crash when opening large file", |
| 126 | + "reporter": "community-user", |
| 127 | + "url": "https://github.com/warpdotdev/warp/issues/5678" |
| 128 | + } |
| 129 | + ] |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +The `--org` flag checks each reporter's org membership via the GitHub API, filtering out internal members so they aren't misattributed as external community reporters. These reporters will be credited in the "Community" section of the changelog. |
| 134 | + |
| 135 | +### Step 6 — Classify unmarked PRs |
| 136 | + |
| 137 | +For each PR that has no explicit `CHANGELOG-*` entries, decide whether to include it and under which category. |
| 138 | + |
| 139 | +Follow the classification guidance in `.agents/skills/classify-changelog-pr/SKILL.md`. |
| 140 | + |
| 141 | +For each unmarked PR, produce a classification: |
| 142 | +```json |
| 143 | +{ |
| 144 | + "pr_number": 1234, |
| 145 | + "include": true, |
| 146 | + "category": "IMPROVEMENT", |
| 147 | + "text": "Proposed changelog line", |
| 148 | + "confidence": "high", |
| 149 | + "rationale": "...", |
| 150 | + "feature_flag": null, |
| 151 | + "needs_review": false |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +**Key rules:** |
| 156 | +- PRs that only touch CI, tests, docs, or internal tooling → `include: false` |
| 157 | +- PRs behind dogfood-only feature flags → `include: false` for stable channel |
| 158 | +- PRs behind preview flags → `include: false` for stable, `include: true` for preview |
| 159 | +- When in doubt, set `needs_review: true` and `confidence: "low"` |
| 160 | +- Bot PRs (dependabot, renovate, etc.) → `include: false` |
| 161 | + |
| 162 | +**Feature-flag detection:** Use the `changed_files` list from Step 2 to check if any PR touches `crates/warp_features/src/lib.rs` or references a `FeatureFlag` variant in its title/body. Cross-reference with the flag lists from Step 4 to determine channel visibility. |
| 163 | + |
| 164 | +**Unknown contributors:** Authors in the `unknown` bucket (org membership check failed due to auth) should be treated conservatively — do not attribute them as external. Note them in the output for manual verification. |
| 165 | + |
| 166 | +### Step 7 — Assemble the draft |
| 167 | + |
| 168 | +Combine explicit entries (Step 2) and inferred entries (Step 6) into the final report. Group by category in this order: |
| 169 | + |
| 170 | +1. `NEW-FEATURE` — New Features |
| 171 | +2. `IMPROVEMENT` — Improvements |
| 172 | +3. `BUG-FIX` — Bug Fixes |
| 173 | +4. `OZ` — Oz Updates |
| 174 | + |
| 175 | +PRs marked with `CHANGELOG-NONE` are explicitly opted out and must never appear in the changelog markdown. |
| 176 | + |
| 177 | +### Step 8 — Write output files |
| 178 | + |
| 179 | +Write two files to `output_dir`: |
| 180 | + |
| 181 | +**`changelog-draft.md`** — Human-reviewable markdown, ready for Slack/Notion: |
| 182 | + |
| 183 | +```markdown |
| 184 | +# Changelog Draft |
| 185 | +**Channel:** stable |
| 186 | +**Range:** v0.2026.05.01... → v0.2026.05.06... |
| 187 | +**Generated:** 2026-05-06T15:00:00Z |
| 188 | + |
| 189 | +## New Features |
| 190 | +- Added dark mode ([#1234](https://github.com/warpdotdev/warp/pull/1234)) — @external-contributor ✨ |
| 191 | + |
| 192 | +## Improvements |
| 193 | +- Faster tab switching ([#1235](https://github.com/warpdotdev/warp/pull/1235)) |
| 194 | + |
| 195 | +## Bug Fixes |
| 196 | +- Fixed crash on startup ([#1236](https://github.com/warpdotdev/warp/pull/1236)) |
| 197 | + |
| 198 | +## Oz Updates |
| 199 | +- Improved agent memory ([#1237](https://github.com/warpdotdev/warp/pull/1237)) |
| 200 | + |
| 201 | +## Community |
| 202 | +### Contributors |
| 203 | +- @contributor1 — [#1234](https://github.com/warpdotdev/warp/pull/1234) ✨ |
| 204 | + |
| 205 | +### Issue Reporters |
| 206 | +Thanks to the community members who reported issues fixed in this release: |
| 207 | +- @reporter1 — [#5678](https://github.com/warpdotdev/warp/issues/5678) "Crash when opening large file" |
| 208 | +``` |
| 209 | + |
| 210 | +The markdown draft must **not** include "Needs Review" or "Skipped PRs" sections — those are internal details that belong only in the JSON audit artifact. |
| 211 | + |
| 212 | +**`changelog-draft.json`** — Machine-readable audit artifact (internal only): |
| 213 | + |
| 214 | +```json |
| 215 | +{ |
| 216 | + "channel": "stable", |
| 217 | + "range": { "base": "v0...", "head": "v0..." }, |
| 218 | + "generated_at": "2026-05-06T15:00:00Z", |
| 219 | + "entries": [ |
| 220 | + { |
| 221 | + "pr_number": 1234, |
| 222 | + "category": "NEW-FEATURE", |
| 223 | + "text": "Added dark mode", |
| 224 | + "source": "explicit", |
| 225 | + "author": "external-contributor", |
| 226 | + "is_external": true, |
| 227 | + "confidence": "high", |
| 228 | + "rationale": null, |
| 229 | + "feature_flag": null |
| 230 | + } |
| 231 | + ], |
| 232 | + "skipped": [...], |
| 233 | + "needs_review": [...], |
| 234 | + "issue_reporters": [...] |
| 235 | +} |
| 236 | +``` |
| 237 | + |
| 238 | +The JSON artifact retains `skipped`, `needs_review`, and `issue_reporters` for audit purposes — every PR in the range must appear in either `entries`, `skipped`, or `needs_review`. |
| 239 | + |
| 240 | +## Constraints |
| 241 | + |
| 242 | +- **Never** write to `channel_versions.json` or any production config file. |
| 243 | +- **Never** push commits, create branches, or open PRs. |
| 244 | +- All output goes to `output_dir` only. |
| 245 | +- The markdown draft should be copy-pasteable into Slack or Notion for review. |
| 246 | +- Keep the JSON artifact complete enough for audit: every PR in the range should appear in either `entries`, `skipped`, or `needs_review`. |
| 247 | + |
| 248 | +## Validation |
| 249 | + |
| 250 | +After generating output, verify: |
| 251 | +1. Every PR in the range is accounted for (entries + skipped + needs_review = total PRs). |
| 252 | +2. Explicit marker entries match what `fetch_prs.py` extracted (no dropped markers). |
| 253 | +3. No duplicate PR numbers across sections. |
| 254 | +4. The markdown renders cleanly (no broken links or formatting). |
0 commit comments