|
| 1 | +--- |
| 2 | +name: graphite |
| 3 | +description: Work with Graphite (gt) for stacked PRs - creating, navigating, and managing PR stacks. |
| 4 | +allowed-tools: |
| 5 | + - "Bash(gt *)" |
| 6 | + - "Bash(git add *)" |
| 7 | + - "Bash(git reset *)" |
| 8 | + - "Bash(git diff *)" |
| 9 | + - "Bash(git status *)" |
| 10 | + - "Bash(git stash *)" |
| 11 | + - "Bash(git checkout *)" |
| 12 | + - "Bash(git rebase *)" |
| 13 | + - "Bash(git branch *)" |
| 14 | + - "Bash(gh pr *)" |
| 15 | +--- |
| 16 | + |
| 17 | +# Graphite Skill |
| 18 | + |
| 19 | +Work with Graphite (`gt`) for creating, navigating, and managing stacked pull requests. |
| 20 | + |
| 21 | +## Quick Reference |
| 22 | + |
| 23 | +| I want to... | Command | |
| 24 | +|--------------|---------| |
| 25 | +| Create a new branch/PR | `gt create branch-name -m "message"` | |
| 26 | +| Amend current branch | `gt modify -m "message"` | |
| 27 | +| Navigate up the stack | `gt up` | |
| 28 | +| Navigate down the stack | `gt down` | |
| 29 | +| Jump to top of stack | `gt top` | |
| 30 | +| Jump to bottom of stack | `gt bottom` | |
| 31 | +| View stack structure | `gt ls` | |
| 32 | +| Submit stack for review | `gt submit --no-interactive` | |
| 33 | +| Rebase stack on trunk | `gt restack` | |
| 34 | +| Change branch parent | `gt track --parent <branch>` | |
| 35 | +| Rename current branch | `gt rename <new-name>` | |
| 36 | +| Move branch in stack | `gt move` | |
| 37 | + |
| 38 | +--- |
| 39 | + |
| 40 | +## What Makes a Good PR? |
| 41 | + |
| 42 | +In roughly descending order of importance: |
| 43 | + |
| 44 | +- **Atomic/hermetic** - independent of other changes; will pass CI and be safe to deploy on its own |
| 45 | +- **Narrow semantic scope** - changes only to module X, or the same change across modules X, Y, Z |
| 46 | +- **Small diff** - (heuristic) small total diff line count |
| 47 | + |
| 48 | +**Do NOT worry about creating TOO MANY pull requests.** It is **always** preferable to create more pull requests than fewer. |
| 49 | + |
| 50 | +**NO CHANGE IS TOO SMALL:** tiny PRs allow for the medium/larger-sized PRs to have more clarity. |
| 51 | + |
| 52 | +Always argue in favor of creating more PRs, as long as they independently pass build. |
| 53 | + |
| 54 | +--- |
| 55 | + |
| 56 | +## Branch Naming Conventions |
| 57 | + |
| 58 | +When naming PRs in a stack, follow this syntax: |
| 59 | + |
| 60 | +`terse-stack-feature-name/terse-description-of-change` |
| 61 | + |
| 62 | +For example, a 4 PR stack: |
| 63 | + |
| 64 | +``` |
| 65 | +auth-bugfix/reorder-args |
| 66 | +auth-bugfix/improve-logging |
| 67 | +auth-bugfix/improve-documentation |
| 68 | +auth-bugfix/handle-401-status-codes |
| 69 | +``` |
| 70 | + |
| 71 | +--- |
| 72 | + |
| 73 | +## Creating a Stack |
| 74 | + |
| 75 | +### Basic Workflow |
| 76 | + |
| 77 | +1. Make changes to files |
| 78 | +2. Stage changes: `git add <files>` |
| 79 | +3. Create branch: `gt create branch-name -m "commit message"` |
| 80 | +4. Repeat for each PR in the stack |
| 81 | +5. Submit: `gt submit --no-interactive` |
| 82 | + |
| 83 | +### Handle Untracked Branches (common with worktrees) |
| 84 | + |
| 85 | +Before creating branches, check if the current branch is tracked: |
| 86 | + |
| 87 | +```bash |
| 88 | +gt branch info |
| 89 | +``` |
| 90 | + |
| 91 | +If you see "ERROR: Cannot perform this operation on untracked branch": |
| 92 | + |
| 93 | +**Option A (Recommended): Track temporarily, then re-parent** |
| 94 | +1. Track current branch: `gt track -p main` |
| 95 | +2. Create your stack normally with `gt create` |
| 96 | +3. After creating ALL branches, re-parent your first new branch onto main: |
| 97 | + ```bash |
| 98 | + gt checkout <first-branch-of-your-stack> |
| 99 | + gt track -p main |
| 100 | + gt restack |
| 101 | + ``` |
| 102 | + |
| 103 | +**Option B: Stash changes and start from main** |
| 104 | +1. `git stash` |
| 105 | +2. `git checkout main && git pull` |
| 106 | +3. Create new branch and unstash: `git checkout -b temp-working && git stash pop` |
| 107 | +4. Proceed with `gt track -p main` and `gt create` |
| 108 | + |
| 109 | +--- |
| 110 | + |
| 111 | +## Navigating a Stack |
| 112 | + |
| 113 | +```bash |
| 114 | +# Move up one branch (toward top of stack) |
| 115 | +gt up |
| 116 | + |
| 117 | +# Move down one branch (toward trunk) |
| 118 | +gt down |
| 119 | + |
| 120 | +# Jump to top of stack |
| 121 | +gt top |
| 122 | + |
| 123 | +# Jump to bottom of stack (first branch above trunk) |
| 124 | +gt bottom |
| 125 | + |
| 126 | +# View the full stack structure |
| 127 | +gt ls |
| 128 | +``` |
| 129 | + |
| 130 | +--- |
| 131 | + |
| 132 | +## Modifying a Stack |
| 133 | + |
| 134 | +### Amend Current Branch |
| 135 | + |
| 136 | +```bash |
| 137 | +git add <files> |
| 138 | +gt modify -m "updated commit message" |
| 139 | +``` |
| 140 | + |
| 141 | +### Reorder Branches |
| 142 | + |
| 143 | +Use `gt move` to reorder branches in the stack. This is simpler than trying to use `gt create --insert`. |
| 144 | + |
| 145 | +### Re-parent a Stack |
| 146 | + |
| 147 | +If you created a stack on top of a feature branch but want it based on main: |
| 148 | + |
| 149 | +```bash |
| 150 | +# Go to first branch of your stack |
| 151 | +gt checkout <first-branch> |
| 152 | + |
| 153 | +# Change its parent to main |
| 154 | +gt track --parent main |
| 155 | + |
| 156 | +# Rebase the entire stack |
| 157 | +gt restack |
| 158 | +``` |
| 159 | + |
| 160 | +### Rename a Branch |
| 161 | + |
| 162 | +```bash |
| 163 | +gt rename new-branch-name |
| 164 | +``` |
| 165 | + |
| 166 | +--- |
| 167 | + |
| 168 | +## Resetting Commits to Unstaged Changes |
| 169 | + |
| 170 | +If changes are already committed but you want to re-stack them differently: |
| 171 | + |
| 172 | +```bash |
| 173 | +# Reset the last commit, keeping changes unstaged |
| 174 | +git reset HEAD^ |
| 175 | + |
| 176 | +# Reset multiple commits (e.g., last 2 commits) |
| 177 | +git reset HEAD~2 |
| 178 | + |
| 179 | +# View the diff to understand what you're working with |
| 180 | +git diff HEAD |
| 181 | +``` |
| 182 | + |
| 183 | +--- |
| 184 | + |
| 185 | +## Before Submitting |
| 186 | + |
| 187 | +### Verify Stack is Rooted on Main |
| 188 | + |
| 189 | +Before running `gt submit`, verify the first PR is parented on `main`: |
| 190 | + |
| 191 | +```bash |
| 192 | +gt ls |
| 193 | +``` |
| 194 | + |
| 195 | +If the first branch has a parent other than `main`: |
| 196 | +```bash |
| 197 | +gt checkout <first-branch> |
| 198 | +gt track -p main |
| 199 | +gt restack |
| 200 | +``` |
| 201 | + |
| 202 | +### Run Validation |
| 203 | + |
| 204 | +After creating each PR, run appropriate linting, building, and testing: |
| 205 | + |
| 206 | +1. Refer to the project's CLAUDE.md for specific commands |
| 207 | +2. If validation fails, fix the issue, stage changes, and use `gt modify` |
| 208 | + |
| 209 | +--- |
| 210 | + |
| 211 | +## Submitting and Updating PRs |
| 212 | + |
| 213 | +### Submit the Stack |
| 214 | + |
| 215 | +```bash |
| 216 | +gt submit --no-interactive |
| 217 | +``` |
| 218 | + |
| 219 | +### Update PR Descriptions |
| 220 | + |
| 221 | +After submitting, use `gh pr edit` to set proper titles and descriptions. |
| 222 | + |
| 223 | +**IMPORTANT:** Never use Bash heredocs for PR descriptions - shell escaping breaks markdown tables, code blocks, etc. Instead: |
| 224 | + |
| 225 | +1. Use the `Write` tool to create `/tmp/pr-body.md` with the full markdown content |
| 226 | +2. Use `gh pr edit` with `--body-file`: |
| 227 | + |
| 228 | +```bash |
| 229 | +gh pr edit <PR_NUMBER> --title "stack-name: description" --body-file /tmp/pr-body.md |
| 230 | +``` |
| 231 | + |
| 232 | +PR descriptions must include: |
| 233 | +- **Stack Context**: What is the bigger goal of this stack? |
| 234 | +- **What?** (optional for small changes): Super terse, focus on what not why |
| 235 | +- **Why?**: What prompted the change? Why this solution? How does it fit into the stack? |
| 236 | + |
| 237 | +**Example** (for a PR in a 3-PR stack adding a warning feature): |
| 238 | + |
| 239 | +```markdown |
| 240 | +## Stack Context |
| 241 | + |
| 242 | +This stack adds a warning on the merge button when users are bypassing GitHub rulesets. |
| 243 | + |
| 244 | +## Why? |
| 245 | + |
| 246 | +Users who can bypass rulesets (via org admin or team membership) currently see no indication |
| 247 | +they're circumventing branch protection. This PR threads the bypass data from the server to |
| 248 | +enable the frontend warning (PR 2) to display it. |
| 249 | +``` |
| 250 | + |
| 251 | +--- |
| 252 | + |
| 253 | +## Troubleshooting |
| 254 | + |
| 255 | +| Problem | Solution | |
| 256 | +|---------|----------| |
| 257 | +| "Cannot perform this operation on untracked branch" | Run `gt track -p main` first | |
| 258 | +| Stack parented on wrong branch | Use `gt track -p main` then `gt restack` | |
| 259 | +| Need to reorder PRs | Use `gt move` | |
| 260 | +| Conflicts during restack | Resolve conflicts, then `git rebase --continue` | |
| 261 | +| Want to split a PR | Reset commits (`git reset HEAD^`), re-stage selectively, create new branches | |
| 262 | +| Need to delete a branch (non-interactive) | `gt delete <branch> -f -q` | |
| 263 | +| `gt restack` hitting unrelated conflicts | Use targeted `git rebase <target>` instead (see below) | |
| 264 | +| Rebase interrupted mid-conflict | Check if files are resolved but unstaged, then `git add` + `git rebase --continue` | |
| 265 | + |
| 266 | +--- |
| 267 | + |
| 268 | +## Advanced: Surgical Rebasing in Complex Stacks |
| 269 | + |
| 270 | +In deeply nested stacks with many sibling branches, `gt restack` can be problematic: |
| 271 | +- It restacks ALL branches that need it, not just your stack |
| 272 | +- Can hit conflicts in completely unrelated branches |
| 273 | +- Is all-or-nothing - hard to be surgical |
| 274 | + |
| 275 | +### When to Use `git rebase` Instead of `gt restack` |
| 276 | + |
| 277 | +Use direct `git rebase` when: |
| 278 | +- You only want to update specific branches in your stack |
| 279 | +- `gt restack` is hitting conflicts in unrelated branches |
| 280 | +- You need to skip obsolete commits during the rebase |
| 281 | + |
| 282 | +### Targeted Rebase Workflow |
| 283 | + |
| 284 | +```bash |
| 285 | +# 1. Checkout the branch you want to rebase |
| 286 | +git checkout my-feature-branch |
| 287 | + |
| 288 | +# 2. Rebase onto the target (e.g., updated parent branch) |
| 289 | +git rebase target-branch |
| 290 | + |
| 291 | +# 3. If you hit conflicts: |
| 292 | +# - Resolve the conflict in the file |
| 293 | +# - Stage it: git add <file> |
| 294 | +# - Continue: git rebase --continue |
| 295 | + |
| 296 | +# 4. If a commit is obsolete and should be skipped: |
| 297 | +git rebase --skip |
| 298 | + |
| 299 | +# 5. After rebase, use gt modify to sync graphite's tracking |
| 300 | +gt modify --no-edit |
| 301 | +``` |
| 302 | + |
| 303 | +### Recovering from Interrupted Rebase (Context Reset) |
| 304 | + |
| 305 | +If a rebase was interrupted (e.g., Claude session ran out of context): |
| 306 | + |
| 307 | +1. **Check status:** |
| 308 | + ```bash |
| 309 | + git status |
| 310 | + # Look for "interactive rebase in progress" and "Unmerged paths" |
| 311 | + ``` |
| 312 | + |
| 313 | +2. **Read the "unmerged" files** - they may already be resolved (no conflict markers) |
| 314 | + |
| 315 | +3. **If already resolved, just stage and continue:** |
| 316 | + ```bash |
| 317 | + git add <resolved-files> |
| 318 | + git rebase --continue |
| 319 | + ``` |
| 320 | + |
| 321 | +4. **If still has conflict markers**, resolve them first, then stage and continue |
| 322 | + |
| 323 | +### Deleting Branches from a Stack |
| 324 | + |
| 325 | +```bash |
| 326 | +# Delete a branch (non-interactive, even if not merged) |
| 327 | +gt delete branch-to-delete -f -q |
| 328 | + |
| 329 | +# Also delete all children (upstack) |
| 330 | +gt delete branch-to-delete -f -q --upstack |
| 331 | + |
| 332 | +# Also delete all ancestors (downstack) |
| 333 | +gt delete branch-to-delete -f -q --downstack |
| 334 | +``` |
| 335 | + |
| 336 | +**Flags:** |
| 337 | +- `-f` / `--force`: Delete even if not merged or closed |
| 338 | +- `-q` / `--quiet`: Implies `--no-interactive`, minimizes output |
| 339 | + |
| 340 | +**After deleting intermediate branches**, children are automatically restacked onto the parent. If you need to manually update tracking: |
| 341 | +```bash |
| 342 | +gt checkout child-branch |
| 343 | +gt track --parent new-parent-branch |
| 344 | +``` |
0 commit comments