diff --git a/.cursor/skills/sdk-backmerge/SKILL.md b/.cursor/skills/sdk-backmerge/SKILL.md new file mode 100644 index 0000000000..030c3c92e0 --- /dev/null +++ b/.cursor/skills/sdk-backmerge/SKILL.md @@ -0,0 +1,204 @@ +--- +name: sdk-backmerge +description: Open the follow-up "backmerge" PR that lands a release's version bump + changelog onto main. Use after (or alongside) creating a release PR for an SDK pod package. +--- + +# SDK Pod Backmerge PR Creation + +Create the backmerge PR that keeps `main` aligned with what shipped on a `release--` branch, per `docs/gitflow.md` "Keep main aligned" sections. + +## When to use this skill + +**Applies to SDK pod packages** as defined in `.cursor/rules/sdk/sdk-pod-packages.mdc`. + +**Use when:** + +- A release PR has been (or is being) created for `release--` +- User invokes `/sdk-backmerge` +- `sdk-pr-create` chains into this flow automatically when the target is a release branch (see that skill's "Release Target Dual-PR Flow") + +The backmerge PR carries the version bump + changelog metadata from the release branch onto `main` so future development sees it. It is tagged `[skiplog]` to keep it out of subsequent changelogs. + +## Inputs (resolve in priority order) + +1. **Active release-PR context** (when chained from `sdk-pr-create`): release PR number/URL, release branch, source fork branch, ticket +2. **Explicit args** when invoked standalone: + - Release PR URL/number, OR + - `--package= --version=` and one of `--source=` or `--commit=` +3. **Inferred from current branch** when no args given: if currently on a fork branch that targets `release--`, derive package, version, and source from it +4. ASK only if still ambiguous after the steps above + +## Workflow + +### Step 1: Pre-flight + +- `gh` is installed and authenticated (`gh auth status`) +- Working tree is clean (`git status` empty); if not, ASK whether to stash +- Identify upstream remote: scan `git remote -v` for the canonical org repo (e.g., `tetherto/qvac.git`); fall back to a remote literally named `upstream`. ASK if neither found. +- Identify fork remote: typically `origin` + +### Step 2: Resolve the cherry-pick source + +| Scenario | Source range | +| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | +| Chained from `sdk-pr-create` | `upstream/release--..` | +| Standalone with merged release PR | The PR's merge/squash commit on `upstream/release--` (`gh pr view --json mergeCommit,headRefName`) | +| Standalone with `--commit=` | That single commit | + +**Sanity check:** the source range must reflect release metadata only (version bump, changelog files, NOTICE, optional model registry/history). If `git diff --stat` shows broad unrelated changes (suggesting the branch was based off `main`, not the release branch), STOP and ASK how to proceed — do not silently cherry-pick unrelated work onto `main`. + +### Step 3: Detect no-op (main already aligned) + +Before creating the backmerge branch, check whether `main` already contains the release content. + +```bash +git fetch upstream main +git fetch upstream release-- +git merge-tree --write-tree --merge-base=^ upstream/main +git rev-parse upstream/main^{tree} +``` + +The first command prints the tree SHA produced by simulating the cherry-pick. The second prints `main`'s current tree SHA. If they are identical, every change in the source range is already on `main`: + +1. STOP. Do not create a branch, do not push, do not open a PR. +2. Find the commit that landed the release content directly on `main` so you can cite it: + ```bash + git log upstream/main --oneline -1 -- packages//changelog// + ``` +3. Report to the user, e.g.: + ``` + No backmerge PR needed — main is already aligned with release--. + The release content landed on main via (). + ``` + +This avoids pushing an empty branch and a `gh pr create` failure. + +### Step 4: Sync and create the backmerge branch + +```bash +git fetch upstream main +git fetch upstream release-- +git checkout -b backmerge/release-- upstream/main +``` + +If a local branch with that name already exists, ASK before overwriting. + +### Step 5: Cherry-pick + +```bash +git cherry-pick -x +``` + +For a true merge commit (not squashed), add `-m 1`. + +### Step 6: Conflict triage + +**Auto-resolvable** (resolve, `git add`, then `git cherry-pick --continue`): + +- `packages//package.json` — version field conflict: take release-side. + ```bash + git checkout --theirs packages//package.json + git add packages//package.json + ``` +- `packages//CHANGELOG.md` (top-level aggregated): regenerate deterministically from the version folders, which were just cherry-picked in. + ```bash + node scripts/sdk/generate-changelog-sdk-pod.cjs --package= + git add packages//CHANGELOG.md + ``` + +**Anything else → STOP. Hand control back to the user.** Do not force-resolve, skip, or abort the cherry-pick on the user's behalf. + +When stopping, print: + +- `git status -sb` +- The list of unresolved files +- Resume instructions: + ``` + # After resolving manually: + git add + git cherry-pick --continue + # Then re-run: /sdk-backmerge --resume + ``` + +### Step 7: Push the fork branch + +Re-verify that the cherry-pick produced commits that actually change `upstream/main`. Defensive only — Step 3 catches the common no-op case; this guards against rarer paths (e.g. a `--commit=` arg that turned out to already be on `main`, or a `-m 1` cherry-pick of a merge commit that resolved to nothing): + +```bash +git diff --stat upstream/main..HEAD +``` + +If the output is empty, treat it as a late no-op and STOP (same handling as Step 3 — report and exit). Otherwise push: + +```bash +git push -u origin backmerge/release-- +``` + +### Step 8: Build PR title and body + +**Title** (default — release PRs typically have a `QVAC-####` ticket): + +``` +TICKET chore[skiplog]: backmerge release-- +``` + +**Title (tickless fallback)** — only when there is genuinely no ticket; combine the two tags inside a single bracket pair separated by `|` per `.cursor/rules/sdk/commit-and-pr-format.mdc`: + +``` +chore[skiplog|notask]: backmerge release-- +``` + +- Reuse the ticket from the companion release PR whenever possible. +- `` lists what is being landed (e.g. `version bump, changelog, NOTICE`). + +**Body** (concise, copy-ready): + +~~~markdown +## What this PR does + +Lands the release metadata for `@` on `main`, per [gitflow.md](../docs/gitflow.md) "Keep main aligned". No functional changes — tagged `[skiplog]` so it does not appear in future changelogs. + +## Companion release PR + +- + +## Files + +- `packages//package.json` — version `` → `` +- `packages//changelog//` — generated changelog files +- `packages//CHANGELOG.md` — aggregated changelog +- `packages//NOTICE` — updated dependency attributions (if present) +- (any other release-metadata files included in the cherry-pick) +~~~ + +### Step 9: Open the PR + +```bash +gh pr create \ + --repo / \ + --base main \ + --head :backmerge/release-- \ + --title "" \ + --body "<body>" +``` + +Print the new PR URL as a clickable hyperlink. When chained from `sdk-pr-create`, the parent prints both URLs side by side. + +## Quality Checklist + +Before completing: + +- [ ] Branch name is exactly `backmerge/release-<pkg>-<x.y.z>` +- [ ] Title contains `[skiplog]` tag (combined as `[skiplog|notask]` if tickless) +- [ ] Body links the companion release PR +- [ ] Cherry-pick used `-x` (so the original SHA is recorded in commit messages) +- [ ] No conflicts remain; any non-trivial conflicts were resolved by the user, not the skill +- [ ] `gh pr view` confirms target is `upstream:main` and head is `<fork>:backmerge/...` + +## References + +- `.cursor/skills/sdk-pr-create/SKILL.md` — companion skill, auto-chains into this one for release targets +- `.cursor/skills/sdk-changelog/SKILL.md` — changelog regeneration used during conflict resolution +- `.cursor/rules/sdk/commit-and-pr-format.mdc` — title format and `[skiplog]` semantics +- `.cursor/rules/sdk/sdk-pod-packages.mdc` — packages this skill applies to +- `docs/gitflow.md` — release flow and "Keep main aligned" rules diff --git a/.cursor/skills/sdk-pr-create/SKILL.md b/.cursor/skills/sdk-pr-create/SKILL.md index 264ea204ae..850524b881 100644 --- a/.cursor/skills/sdk-pr-create/SKILL.md +++ b/.cursor/skills/sdk-pr-create/SKILL.md @@ -18,14 +18,15 @@ Generate PR titles and descriptions for SDK pod packages, following the team's t ## Workflow -1. Identify base (main) and current branch -2. Collect commits/diff from `main...origin/<branch>` +1. Identify base and current branch — note whether the base is `main` or a `release-<pkg>-<x.y.z>` branch +2. Collect commits/diff from `<base>...origin/<branch>` 3. Infer ticket, prefix, and tags from changes (see Inference Strategy) 4. Only ask user for input when inference confidence is low 5. Generate title: `TICKET prefix[tags]: subject` 6. Fill template sections based on changes 7. Validate tag requirements ([bc]/[api]/[mod]) 8. Output complete PR description +9. If base is a release branch, chain into the dual-PR flow (see "Release Target Dual-PR Flow" below) ## Inference Strategy @@ -113,6 +114,32 @@ gh pr view --repo UPSTREAM_ORG/REPO BRANCH --web 6. If gh not available, output the copy-ready markdown format above 7. As part of the output, provide a clickable hyperlink (not plain text) to the PR on GitHub. +## Release Target Dual-PR Flow + +**Trigger:** the just-created PR's base is `release-<pkg>-<x.y.z>` for any SDK pod package. + +When triggered, automatically chain into the `sdk-backmerge` skill so a follow-up PR is also opened against `main` with the same version-bump + changelog metadata. This applies the gitflow.md "Keep main aligned" rule at PR-creation time so nobody has to remember a follow-up step after the release PR merges. + +### Steps (after Step 5 of gh CLI Integration above) + +1. Capture context for the backmerge: + - Just-created release PR number and URL + - Release branch name (`release-<pkg>-<x.y.z>`) and parsed `<pkg>` / `<x.y.z>` + - Source fork branch (the head of the release PR) + - Ticket number from the title +2. Invoke the `sdk-backmerge` workflow inline with these inputs (read `.cursor/skills/sdk-backmerge/SKILL.md` and follow it). +3. **Fail-stop policy** — if the backmerge cherry-pick produces a conflict outside `sdk-backmerge`'s auto-resolve list, STOP. Print: + - The release PR URL (success — PR #1 is open) + - The current `git status -sb` from the conflicted cherry-pick + - Resume instructions: `git add <files> && git cherry-pick --continue`, then run `/sdk-backmerge --resume` +4. On success, print **both** PR URLs as clickable hyperlinks, ordered: + - Release PR (target: `release-<pkg>-<x.y.z>`) + - Backmerge PR (target: `main`) + +### Opt-out + +To skip the backmerge for a single run, the user can invoke `/sdk-pr-create --no-backmerge`. The skill still creates PR #1 normally and prints a reminder pointing to `/sdk-backmerge` for later. + ## Quality Checklist Before outputting the PR description, verify: @@ -125,9 +152,12 @@ Before outputting the PR description, verify: - [ ] `[api]` tag has usage example - [ ] `[mod]` tag has Added/Removed models list - [ ] Description is concise - bullet points, no fluff +- [ ] If base is `release-<pkg>-<x.y.z>`, the dual-PR flow ran (or `--no-backmerge` was set), and both PR URLs are reported ## References - SDK pod packages: `.cursor/rules/sdk/sdk-pod-packages.mdc` - PR template: `.github/PULL_REQUEST_TEMPLATE/sdk-pod.md` - Format rules: `.cursor/rules/sdk/commit-and-pr-format.mdc` +- Backmerge skill: `.cursor/skills/sdk-backmerge/SKILL.md` +- GitFlow: `docs/gitflow.md`