Skip to content

Commit a68bc19

Browse files
scripts: add merge-branches Claude Code skill (#14019)
1 parent a7af171 commit a68bc19

1 file changed

Lines changed: 204 additions & 0 deletions

File tree

.claude/commands/merge-branches.md

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
---
2+
description: Merge a source branch into a destination branch via scripts/merge_branches.py, walk through each conflict interactively, commit as fix_conflicts, and post inline PR review comments tagging the authors who caused each conflict.
3+
---
4+
5+
# Merge Branches
6+
7+
Run a branch-merge end-to-end: kick off `scripts/merge_branches.py`, resolve each conflict interactively with the user, build-verify, commit, push, and document every resolution as an inline review comment on the resulting PR. Follow the steps in order. Do not batch-skip steps — interactivity is the point.
8+
9+
## Background
10+
11+
- The merge script `scripts/merge_branches.py` creates a fresh branch off the destination, merges the source, and pushes a PR with conflict markers committed in-place. It also adds a comment to the PR linking the conflicting files on each side.
12+
- This skill picks up from that point: it resolves the conflict markers, verifies the build, commits, and posts per-conflict inline review comments tagging the people whose changes collided.
13+
- Graphite (`gt`) is the project's git wrapper — use it for commits and pushes.
14+
15+
---
16+
17+
## Step 1: Gather inputs
18+
19+
Ask the user (use `AskUserQuestion`):
20+
21+
1. **Source branch** (the branch being merged *from*, e.g. `main-v0.14.2`).
22+
2. **Destination branch** (the branch being merged *into*, e.g. `main`). If the source has a default in `scripts/merge_paths.json`, offer it as a recommended option.
23+
3. **Use a worktree?** Recommend yes — merges leave the local branch in an unusual state. If yes, use `EnterWorktree` (creating one based on `origin/<dst>` via `git worktree add` if a custom base ref is needed).
24+
25+
Confirm the choices in one sentence before proceeding.
26+
27+
---
28+
29+
## Step 2: Fetch and run the merge script
30+
31+
```bash
32+
git fetch origin <src-branch> <dst-branch>
33+
python3 scripts/merge_branches.py --src <src-branch> --dst <dst-branch>
34+
```
35+
36+
Capture the script's output. From it, extract:
37+
- The new merge branch name (`<user>/merge-<src>-into-<dst>-<timestamp>`).
38+
- The PR URL / number created by `gh pr create`.
39+
- The list of conflicted files (printed under `git status -s | grep -E ...`).
40+
41+
If the script ran with `gt`-untracked state, run `gt track --parent <dst-branch>` so subsequent `gt modify`/`gt submit` work.
42+
43+
**If the script errors out or `git status` shows entries that aren't `UU` (both modified)** — e.g. `DU` / `UD` (delete-vs-modify), `AU` / `UA` (rename-vs-modify), `AA` (both added) — the merge produced conflicts with no in-file markers. The marker-grep in Step 3 will miss them entirely. Surface these to the user explicitly, propose a resolution per file (keep / drop / port the change), and apply with `git rm` or `git add` once decided. Only then proceed to Step 3 for the remaining `UU` files.
44+
45+
---
46+
47+
## Step 3: Enumerate conflicts
48+
49+
**Prerequisite** — before doing anything in this step, have the user mark all files in the PR as viewed on GitHub (or Reviewable). Otherwise the `fix_conflicts` commit will not appear as a separate revision in review, and the per-conflict inline comments in Step 8 will land in a diff that mixes the merge markers with the resolution, making the review unreadable. Confirm with the user that this is done before continuing.
50+
51+
Use `grep` to find conflict markers in every reported file:
52+
53+
```bash
54+
grep -n "<<<<<<\|>>>>>>\|||||||" <files>
55+
```
56+
57+
For each conflict region, capture:
58+
- The file and line range.
59+
- The **HEAD** (destination) section.
60+
- The **base** section (between `|||||||` and `=======`) — `git config merge.conflictstyle diff3` is set by the script, so the merge base is shown explicitly.
61+
- The **incoming** (source) section.
62+
63+
Also count conflicts per file so the user knows how many decisions to expect.
64+
65+
---
66+
67+
## Step 4: Identify authors for each conflict
68+
69+
For each conflicting hunk, identify who introduced each side so they can be tagged later. Run on the merge-base:
70+
71+
```bash
72+
MERGE_BASE=$(git merge-base origin/<src> origin/<dst>)
73+
# Author of dst-side change:
74+
git log --format='%h %an <%ae> %s' -S '<identifying-token>' "$MERGE_BASE..origin/<dst>" -- <file>
75+
# Author of src-side change:
76+
git log --format='%h %an <%ae> %s' -S '<identifying-token>' "$MERGE_BASE..origin/<src>" -- <file>
77+
```
78+
79+
Pick a distinctive token from each side of the conflict (a new identifier, a removed crate name, etc.). Look up the GitHub handle of the author by reading the PR (`mcp__github__pull_request_read` with the `#NNNN` from the commit subject) and capturing `user.login`.
80+
81+
Record `{ file, line, side, author_handle, pr_number }` for each conflict — this drives Step 7.
82+
83+
---
84+
85+
## Step 5: Resolve conflicts one by one
86+
87+
For **each** conflict region (in file order, top to bottom within a file):
88+
89+
1. Show the user a tight summary:
90+
- **File and line.**
91+
- **What the destination side did** (and which PR / who).
92+
- **What the source side did** (and which PR / who).
93+
- **Proposed resolution** with reasoning. Common patterns:
94+
- *Orthogonal changes* → keep both (e.g. two independent params added at the same position).
95+
- *One side removed something now unused, the other added something now used* → drop the removed item, keep the added one. Verify "unused" with `grep` before claiming it.
96+
- *Same semantic change in different terms* → pick one, ensure all call sites match.
97+
2. Use `AskUserQuestion` with options:
98+
- **Accept** the proposed resolution.
99+
- **Edit** / give feedback (free text via "Other").
100+
- **Skip** — leave the conflict markers, come back later.
101+
3. On accept, apply the change with `Edit` (replace the entire `<<<<<<<``>>>>>>>` block, including markers, with the resolved text).
102+
4. After all conflicts in a file are resolved, verify no markers remain in that file.
103+
104+
Do NOT batch all proposals before asking — propose, ask, apply, then move on. The user wants to think through each one.
105+
106+
---
107+
108+
## Step 6: Build-verify and catch silent merge skews
109+
110+
Run `cargo check` on every crate that owns a conflicted file:
111+
112+
```bash
113+
cargo check -p <crate>
114+
```
115+
116+
Textual conflicts are only half the story — the merge driver doesn't catch **silent API skews** where one side renamed a type or changed a signature and the other side's text merged cleanly but no longer typechecks. If `cargo check` fails:
117+
118+
1. Read the error.
119+
2. Identify which side introduced the breaking change (usually `main`-side refactors).
120+
3. Propose a fix to the user with the same Accept / Edit / Skip flow as Step 5.
121+
4. Record it as an additional "silent skew" entry for Step 7.
122+
123+
Repeat until the build is clean.
124+
125+
---
126+
127+
## Step 7: Commit and push
128+
129+
Stage the resolved files and create the `fix_conflicts` commit as a **new commit on top of the merge commit** (do not amend — the resolution needs to stay as its own diff in review). Then push.
130+
131+
The commit message must satisfy the project's commitlint config (`commitlint.config.js``scope: subject` format, scope must be in `AllowedScopes`). For a generic merge resolution use `workspace: fix merge conflicts`; if the conflicts were limited to one crate, narrow the scope (e.g. `blockifier: fix merge conflicts`).
132+
133+
Examples (pick what fits the user's workflow):
134+
135+
```bash
136+
# Graphite (project default — gt modify -c adds a new commit, doesn't amend)
137+
git add <resolved-files>
138+
gt modify -cam "workspace: fix merge conflicts"
139+
gt submit
140+
141+
# Plain git
142+
git add <resolved-files>
143+
git commit -m "workspace: fix merge conflicts"
144+
git push
145+
```
146+
147+
Confirm the PR was updated by checking the push output or `gh pr view <PR#>`.
148+
149+
---
150+
151+
## Step 8: Post per-file inline review comments
152+
153+
Build a single PR review with one inline comment per conflict (and per silent-skew fix). Use `gh api` to POST to `/repos/{owner}/{repo}/pulls/{PR#}/reviews`. Each comment object needs:
154+
155+
- `path` — repo-relative file path.
156+
- `line` — the line number of the resolved hunk in the **head** commit (the `fix_conflicts` commit). Use `git rev-parse HEAD` for `commit_id`.
157+
- `side: "RIGHT"`.
158+
- `body` — explain the conflict, the resolution, and cc the authors with `@<handle>`.
159+
160+
Suggested comment template:
161+
162+
```markdown
163+
**Conflict:** <one-line description>.
164+
- `<dst-branch>` (@<dst-author>, #<dst-pr>) <what they did>.
165+
- `<src-branch>` (@<src-author>, #<src-pr>) <what they did>.
166+
167+
**Resolution:** <what we did and why>.
168+
169+
cc @<dst-author> @<src-author>
170+
```
171+
172+
For silent skews (Step 6), label them as **"Silent merge skew (not a textual conflict)"** so reviewers know why a comment landed somewhere with no conflict markers.
173+
174+
Write the review payload to a tmp JSON file (because the bodies contain newlines and markdown that don't survive `-F` flags cleanly) and submit:
175+
176+
```bash
177+
gh api repos/<owner>/<repo>/pulls/<PR#>/reviews \
178+
--method POST --input /tmp/pr_review.json
179+
```
180+
181+
The top-level `body` of the review should be a short header: *"Per-file conflict resolution notes (commit `fix_conflicts`). Source side: @<src-author>. Destination side: @<dst-author>. Inline notes below."* Set `event: "COMMENT"` (not `APPROVE` or `REQUEST_CHANGES`).
182+
183+
Clean up `/tmp/pr_review.json` afterward.
184+
185+
---
186+
187+
## Step 9: Report back
188+
189+
Tell the user:
190+
- PR URL.
191+
- Commit SHA of `fix_conflicts`.
192+
- Number of conflicts resolved + number of silent skews fixed.
193+
- Link to the posted review.
194+
195+
Then stop. Do not merge the PR — that's a separate decision.
196+
197+
---
198+
199+
## Notes
200+
201+
- **Never** mask a failing `cargo check` with `#[ignore]` or by deleting code — investigate the skew (Step 6).
202+
- **Never** amend the merge commit — keep `fix_conflicts` as a separate commit so reviewers can see the resolution diff cleanly.
203+
- If the user says **Skip** on any conflict, do not commit until they come back and resolve it — leaving conflict markers in a commit will break CI.
204+
- If `mcp__github__pull_request_read` is unavailable, fall back to `gh pr view <PR#> --json author` to fetch the GitHub handle.

0 commit comments

Comments
 (0)