[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6MQX62HMQR5CSN4FSC95ZJC":3,"skill-same:01M6MQX62HMQR5CSN4FSC95ZJC":72},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":27,"commitSha":46,"skillDirectory":47,"forksCount":48,"repositoryHtmlUrl":49,"repositoryHomepageUrl":6,"ownerHtmlUrl":50,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":51,"install":52,"resources":65},"01M6MQX62HMQR5CSN4FSC95ZJC","stack-submit",null,"Use when you need to push, submit, or create pull requests for a commit stack. Rebases only the current stack onto upstream main, validates, and pushes INSTEAD of manual git sync + git submit + PR\u002FMR creation. Handles branch creation, stacked PR\u002FMR creation with correct base branches.","higherorderfunctor\u002Fnix-agentic-tools","higherorderfunctor","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F34926636?v=4","01M6MQT3TQS16N1CBRZJYR2EMB","Christopher Aubut","01M6MQT419AF9ERC87M87NRXQV",2,"Unlicense","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fstacked-workflows\u002Fskills\u002Fstack-submit\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"stack","stack submit","submit","when","mr","git sync","false","use","main","only","branches","upstream","correct","manual","for","creation","pr","push","branch","pull","2026-11-10 10:12:30",false,"---\nname: stack-submit\ndescription: >-\n  Use when you need to push, submit, or create pull requests for a commit stack.\n  Rebases only the current stack onto upstream main, validates, and pushes\n  INSTEAD of manual git sync + git submit + PR\u002FMR creation. Handles branch\n  creation, stacked PR\u002FMR creation with correct base branches.\nargument-hint: \"[revset]\"\ndisable-model-invocation: false\ncompatibility: \"Requires git-branchless\"\n---\n\nSubmit this worktree's commit stack for review. If an argument is provided, use\nit as a revset to select which commits to submit. Default is this worktree's own\nstack (`$STACK`, pre-flight step 5).\n\n## Pre-flight\n\n1. **Load references** — read `references\u002Fgit-branchless.md` (relative to this\n   skill's directory) before proceeding.\n\n2. **Confirm repository initialization.** A devenv project with\n   `git.branchless.enable` runs `git branchless init` on every shell entry.\n   Anywhere else, check and stop if it is missing:\n\n   ```bash\n   git config --get branchless.core.mainBranch >\u002Fdev\u002Fnull ||\n     echo \"not initialized: ask the user to run git branchless init\"\n   ```\n\n   Do not run `git branchless init` yourself: it rewrites the repository's\n   shared configuration and hooks for every worktree.\n\n3. **Require a clean working tree**:\n\n   ```bash\n   test -z \"$(git status --porcelain --untracked-files=no)\" || echo \"dirty: commit first\"\n   ```\n\n   The rebase below carries uncommitted edits across and could strand the\n   checkout on a conflict, so if tracked files have changes, stop and ask the\n   user to commit them (see **Don't use `git stash`** in\n   `references\u002Fgit-branchless.md`). Untracked files do not affect the move.\n\n4. **Check for stale rebase state**:\n\n   ```bash\n   ls -d \"$(git rev-parse --git-path rebase-merge)\" \\\n     \"$(git rev-parse --git-path rebase-apply)\" 2>\u002Fdev\u002Fnull\n   ```\n\n   If either exists, run `git rebase --abort` before proceeding. Ask Git for the\n   paths: in a linked worktree `.git` is a file, and an on-disk rebase keeps its\n   state in that worktree's own git dir.\n\n5. **Derive and fetch the upstream ref, and select this worktree's stack.** If\n   `git remote` prints nothing, ask the user for the remote URL and add it\n   first. The upstream is the main branch's upstream, falling back to\n   `\u003Cremote>\u002F\u003Cmain>`:\n\n   ```bash\n   main_branch=\"$(git config branchless.core.mainBranch || echo main)\"\n   remote=\"$(git config \"branch.${main_branch}.remote\" || echo origin)\"\n   if [ \"$remote\" = . ]; then\n     # main tracks a local branch: that branch is the upstream; nothing to fetch\n     upstream=\"$(git rev-parse --abbrev-ref \"${main_branch}@{upstream}\")\"\n   else\n     upstream=\"$(git rev-parse --abbrev-ref \"${main_branch}@{upstream}\" 2>\u002Fdev\u002Fnull \\\n       || echo \"${remote}\u002F${main_branch}\")\"\n     git fetch \"$remote\"   # updates $upstream; no local branch moves\n   fi\n   if git rev-parse --verify -q \"${upstream}^{commit}\" >\u002Fdev\u002Fnull; then\n     base=\"$upstream\"\n   else\n     upstream=\"\"           # first push: the remote has no main branch yet\n     base=\"$main_branch\"\n   fi\n   STACK=\"descendants(roots((stack() & ::HEAD) - ::$base)) - ::$base\"\n   ```\n\n   `$STACK` is this worktree's own stack: HEAD's first commit past `$base` and\n   everything descended from it. Every later revset in this skill uses it\n   instead of `stack()`. Local `main` is never updated here, so while it is\n   behind `$upstream` the upstream commits count as draft, and `stack()` then\n   holds every stack that another worktree branched from them. See **Selecting\n   your own stack** in `references\u002Fgit-branchless.md`.\n\n   When `$upstream` does not resolve (a first push to a remote that has no main\n   branch yet), there is nothing to rebase onto. `$base` falls back to local\n   `main`, and step 3 is skipped.\n\n   `$remote` is also where the push steps below push. If it is `.` (main tracks\n   a local branch), ask the user which remote to push to and set `remote` to it.\n\n   In a new shell, re-derive these variables and redo step 2's selection and\n   checks. Preserve any lease snapshot captured before a rewrite; fetching again\n   never authorizes refreshing that snapshot.\n\n6. **Capture pinned leases before rewriting**, including step 3's initial move.\n   Run this block in the same Bash shell as the later push calls:\n\n   ```bash\n   set -euETo pipefail\n   shopt -s inherit_errexit 2>\u002Fdev\u002Fnull || :\n\n   lease_remote=\"$remote\"\n   lease_heads=\"$(git ls-remote --heads \"$lease_remote\")\" || exit 1\n\n   push_selected() {\n     : \"${lease_remote:?capture remote leases before rewriting}\"\n     : \"${lease_heads?capture remote leases before rewriting}\"\n     local b branches expected\n     local -a leases=() refs=()\n     branches=\"$(git query --branches \"$SELECTED\")\" || return 1\n     test -n \"$branches\" || { echo \"no selected branches\" >&2; return 1; }\n     for b in $branches; do\n       expected=\"$(printf '%s\\n' \"$lease_heads\" | awk -v ref=\"refs\u002Fheads\u002F$b\" '$2 == ref { print $1 }')\"\n       leases+=(\"--force-with-lease=refs\u002Fheads\u002F$b:$expected\")\n       refs+=(\"refs\u002Fheads\u002F$b:refs\u002Fheads\u002F$b\")\n     done\n     git push \"$@\" --set-upstream \"${leases[@]}\" \"$lease_remote\" \"${refs[@]}\"\n   }\n   ```\n\n   This records the remote's branch tips before any rewrite. A branch absent\n   from that snapshot gets an empty expected SHA: its lease requires that the\n   remote branch still does not exist. This also covers branches named in step 6\n   after the move. A failed remote read stops the workflow; it must never be\n   treated as an empty remote.\n\n   Keep `lease_heads`, `lease_remote` and the function in this shell until the\n   push finishes. Do not recapture after a rewrite or fetch, including when\n   resuming in a new shell: without the original snapshot, stop and recover it\n   before pushing. For a later operation, capture a fresh snapshot **before**\n   that operation's first rewrite. The push uses the captured remote and\n   explicit branch refs, independent of tracking or `push.default`.\n\n   The packaged `git submit` has no expected-SHA option. Its branch forge\n   fetches before pushing with bare `--force-with-lease`; even `git submit -c`\n   can update existing branches that way. See the **`git submit`** entry in\n   `references\u002Fgit-branchless.md` for the source evidence. Use `push_selected`\n   for both new branches and updates throughout this skill.\n\n## Steps\n\n1. **Visualize the stack** with `git sl` to understand what will be submitted.\n\n2. **Determine and check the commit range** from `$ARGUMENTS`:\n\n   ```bash\n   # If $ARGUMENTS is empty, this worktree's stack:\n   SELECTED=\"$STACK\"\n\n   # If $ARGUMENTS is a revset (use git sl or git branchless log, not git log).\n   # current() follows a commit hash to its rewritten copy after step 3's move:\n   SELECTED=\"current($ARGUMENTS) - ::$base\"\n\n   : \"${STACK:?run pre-flight step 5 first}\"\n   selected_commits=\"$(git query -r \"$SELECTED\")\" || exit 1\n   test -n \"$selected_commits\" || { echo \"empty selection\" >&2; exit 1; }\n   stack_commits=\"$(git query -r \"$STACK\")\" || exit 1\n   test -n \"$stack_commits\" || { echo \"empty stack selection\" >&2; exit 1; }\n   outside=\"$(git query -r \"only($SELECTED, $base) - ($STACK)\")\" || exit 1\n   test -z \"$outside\" || { printf '%s\\n' \"$outside\"; exit 1; }\n   current_worktree=\"$(git rev-parse --show-toplevel)\" || exit 1\n   other_heads=\"$(git worktree list --porcelain | awk -v current=\"$current_worktree\" '\n     $1 == \"worktree\" {\n       if (path != \"\" && path != current && head != \"\") print head\n       path = substr($0, 10); head = \"\"\n     }\n     $1 == \"HEAD\" { head = $2 }\n     END { if (path != \"\" && path != current && head != \"\") print head }\n   ')\" || exit 1\n   overlap=\"\"\n   for other_head in $other_heads; do\n     for stack_commit in $stack_commits; do\n       if git merge-base --is-ancestor \"$stack_commit\" \"$other_head\"; then\n         overlap=\"${overlap}${overlap:+ }${other_head}\"\n       fi\n     done\n   done\n   test -z \"$overlap\" || { printf '%s\\n' \"$overlap\"; exit 1; }\n   merges=\"$(git query -r \"merges() & ($STACK)\")\" || exit 1\n   test -z \"$merges\" || { printf '%s\\n' \"$merges\"; exit 1; }\n   ```\n\n   - **Empty selection:** stop and ask which commits to submit. HEAD has no\n     commits of its own past `$base`: you are on `main`, in a fresh worktree, or\n     everything selected is already upstream. Do **not** fall back to `draft()`\n     or `stack()`: in a repository with several worktrees both select other\n     worktrees' stacks. (A first push of a repository whose whole history is on\n     `main` also lands here; use step 8's explicit `SELECTED` and pinned\n     `push_selected` path.)\n   - **The second query prints commits:** stop, show those commits, and ask.\n     Either the revset reaches past this worktree's stack (`draft()` does, for\n     example), or local `main` has commits that are not on `$upstream` and the\n     stack is built on them. The move in step 3 would carry those commits along.\n   - **The worktree check prints a commit:** another worktree, including a\n     detached one, has its HEAD inside this selection. Stop and ask: step 3\n     would rewrite or abandon the commit under that worktree.\n   - **The merge query prints a commit:** the stack contains a merge commit, for\n     example from merging `origin\u002Fmain` into it. Stop and tell the user this\n     skill does not submit a stack with a merge in it. Step 3 would move only\n     the commits below the merge and leave the merge and everything above it,\n     HEAD included, on the old base, and it still exits 0.\n\n3. **Rebase the stack onto the latest upstream.** Pre-flight step 5 already\n   fetched and step 6 captured the leases, so move only the selected stack onto\n   the remote-tracking ref:\n\n   ```bash\n   git move -b \"$SELECTED\" -d \"$upstream\" || exit 1\n   selected_commits=\"$(git query -r \"$SELECTED\")\" || exit 1\n   test -n \"$selected_commits\" || { echo \"selection became empty\" >&2; exit 1; }\n   ```\n\n   `-b` accepts a revset and moves each selected commit's whole lineage from\n   where it leaves `$upstream`, so commits already upstream are never re-moved.\n   That holds for a stack without merge commits, which step 2 checked. If\n   `$upstream` has not moved, this is a no-op — that's fine. On a first push\n   (`$upstream` empty), skip this step. If the selection is empty after the\n   move, everything selected was already upstream: stop and say so instead of\n   testing and submitting nothing.\n\n   This skill does not use branchless sync because it targets local `main` and\n   can span other draft stacks. If the guarded move reports a conflict, it exits\n   1 and changes nothing. Stop and ask the user; do not start an on-disk repair.\n\n4. **Run tests across the stack** to validate each commit independently. This is\n   the one case where testing every commit is unambiguously right: every commit\n   here is about to become its own PR, so every commit has to stand alone.\n\n   ```bash\n   git test run -x '\u003Ctest-command>' --jobs \u003CN> \"$SELECTED\"\n   ```\n\n   Size `\u003CN>` by **memory**, not cores — see **Sizing `--jobs`** in\n   `references\u002Fgit-branchless.md`. Each job is a separate worktree with no\n   shared cache, so peak usage is `jobs x per-job footprint`. Never pass\n   `--jobs 0` — it means one job per physical CPU and overrides any bound the\n   machine's config set, which for a multi-GB command such as `nix flake check`\n   exhausts RAM.\n\n   Determine the test command from the project (package.json scripts, Makefile,\n   Cargo.toml, etc.). If no obvious test command exists, ask the user. If the\n   project has no tests, skip this step.\n\n   If any commit fails, stop and report which commit(s) failed. Do not submit a\n   stack with failing tests.\n\n5. **Verify commit messages** — review `git sl` output. Flag any commits with\n   vague messages (\"fix\", \"WIP\", \"update\") and ask the user before rewriting\n   them.\n\n6. **Ensure branches exist** on each commit. For commits without branches,\n   generate branch names from commit messages:\n\n   ```bash\n   # Pattern: type\u002Fscope or type\u002Fshort-description\n   # feat(flake): add minimal flake skeleton → feat\u002Fflake-skeleton\n   # docs(references): add git-branchless reference → docs\u002Fgit-branchless-reference\n   # chore: add TODO.md → chore\u002Fadd-todo\n   ```\n\n   Create branches:\n\n   ```bash\n   git branch \u003Cbranch-name> \u003Ccommit-hash>\n   ```\n\n   Present the branch list to the user for review before proceeding. Every local\n   branch on a selected commit is pushed, including incidental review or\n   worktree branches. Step 7's dry-run preview shows each remote ref that will\n   be published; delete or move an unwanted local branch before publishing.\n\n   The pinned push sets upstream tracking to each branch's own remote branch,\n   including branches that initially track `main`.\n\n7. **Confirm before pushing** — preview what will be pushed:\n\n   ```bash\n   set -euETo pipefail\n   shopt -s inherit_errexit 2>\u002Fdev\u002Fnull || :\n   push_selected --dry-run\n   ```\n\n   Show the dry-run output to the user and ask for confirmation:\n\n   ```\n   Ready to push N branches and create N PRs. Proceed? (y\u002Fn)\n   ```\n\n   **Wait for explicit user approval.** Pushing creates remote branches and PRs\n   — these are visible side effects that cannot be easily undone. This gate is\n   especially important when the skill is auto-invoked via\n   `disable-model-invocation: false`.\n\n8. **Push branches with the captured leases**:\n\n   ```bash\n   set -euETo pipefail\n   shopt -s inherit_errexit 2>\u002Fdev\u002Fnull || :\n   push_selected\n   ```\n\n   This creates absent branches and updates existing ones in the same push. If\n   any lease is rejected, stop and inspect the remote change with the user; do\n   not refresh the snapshot and retry or replace the command with `git submit`.\n   A dry-run does not reserve the remote refs, so the real push must use the\n   same leases.\n\n   For a first push whose entire history is on local `main`, step 2 reports an\n   empty selection. After agreeing which branches to publish, set `SELECTED` to\n   a revset union of their explicit names (separated by `|`, not spaces):\n\n   ```bash\n   SELECTED='feat\u002Fa|feat\u002Fb'\n   ```\n\n   Capture their remote snapshot with pre-flight step 6 before any rewrite, skip\n   step 3, and use the same preview and push in steps 7 and 8. This path\n   publishes public commits without rerunning step 2's empty-stack guard; there\n   is no unpinned fallback.\n\n9. **Create stacked PRs\u002FMRs** — for each branch, create a pull\u002Fmerge request\n   targeting the previous branch in the stack (or `main` for the first). Use the\n   commit message as the title. Keep the body minimal — the commit diff speaks\n   for itself.\n\n#### GitHub\n\n```bash\n# First commit after main:\ngh pr create --head \u003Cbranch-1> --base main \\\n  --title \"\u003Ccommit message>\" --body \"$(cat \u003C\u003C'EOF'\n## Summary\n\u003Cone-line description>\n\nStack: 1\u002FN\n\n🤖 Generated with [Claude Code](https:\u002F\u002Fclaude.com\u002Fclaude-code)\nEOF\n)\"\n\n# Subsequent commits:\ngh pr create --head \u003Cbranch-N> --base \u003Cbranch-N-1> \\\n  --title \"\u003Ccommit message>\" --body \"...\"\n```\n\n10. **Report results** — show a summary table:\n\n    ```\n    | # | Branch | PR\u002FMR | Base | Status |\n    |---|--------|----|------|--------|\n    | 1 | feat\u002Fflake-skeleton | #2 | main | created |\n    | 2 | chore\u002Fformatting | #3 | feat\u002Fflake-skeleton | created |\n    | ... | ... | ... | ... | ... |\n    | 25 | todo\u002Fpre-publish | — | (tracking only) | pushed |\n    ```\n\n## Subsequent Updates\n\n### Pinned force-push\n\nBefore any amend, fix, restack or move, run pre-flight step 6 to capture the\nremote tips and define `push_selected`. This applies to the initial step 3 move\nas well as later updates. After the rewrite, re-derive `$SELECTED` using\npre-flight step 5 and step 2's selection and checks, keeping the original lease\nsnapshot. Preview with `push_selected --dry-run`, then push with `push_selected`\nafter the required approval.\n\nNever push a base branch tip containing a still-open stacked PR's head. GitHub\nthen marks that PR merged, and deletes its head branch when the repository\ndeletes branches on merge.\n\nNever refresh the expected tips after the rewrite. Bare `--force-with-lease`\ncompares against remote-tracking refs, which any fetch (including another\nworktree's fetch) can update. The explicit SHA captured before rewriting still\nrejects a competing push after such a fetch. An empty expected SHA requires an\nabsent branch, so a competing branch creation is rejected too.\n\nPRs\u002FMRs auto-update when their branches are force-pushed. No need to recreate\nthem. Only branches downstream of the changed commit need updating, but pushing\nall is safe — unchanged branches are skipped automatically.\n\n### Before merging\n\nBefore the user or agent merges any PR\u002FMR, find every open PR whose base is the\nmerging PR's head branch:\n\n```bash\ngh pr list --base \u003Cbranch> --state open --limit 1000 --json number\n```\n\nHere `\u003Cbranch>` is the merging PR's head branch. Retarget each dependent PR to\nthe merging PR's current base BEFORE merging:\n\n```bash\ngh pr edit \u003Cdependent-PR-number> --base \u003Ccurrent-base-branch>\n```\n\nFor the bottom PR, the current base is `main`: use\n`gh pr edit \u003Cdependent-PR-number> --base main`. For an out-of-order merge of PR\nN+1 into branch N, use branch N instead. Confirm every affected PR is still open\nand targets that current base:\n\n```bash\ngh pr view \u003Cdependent-PR-number> --json state,baseRefName\n```\n\nIf any retarget or confirmation fails, stop before merging. Repeat this before\nevery PR\u002FMR merge. On GitLab or another forge, use its target-branch controls to\nmove every open dependent MR\u002FPR to the merging MR\u002FPR's current base and confirm\neach remains open and targets that base before merging.\n\nOn GitHub, an observed merge that deleted the base branch recorded a\n`BaseRefDeletedEvent`, followed by an `AutomaticBaseChangeSucceededEvent` (base\nmoved to `main`) and a `ClosedEvent` on the dependent PR. The retarget and close\nhappened in the same second, right after the deletion. That PR ended CLOSED with\nstate reason COMPLETED even though its base had moved to `main`. Do not rely on\nautomatic retargeting to keep dependent PRs open.\n\n### After each squash merge\n\nAfter each squash-merged PR\u002FMR, you MUST move the remaining stack onto the new\nupstream, confirm the next PR\u002FMR's base, and force-push the remaining branches.\nWithout this, downstream PRs\u002FMRs show the full diff of all prior commits (the\nsquash merge creates a new commit hash that downstream branches don't share). Do\nit after EVERY squash merge, not just the first: each merge changes main.\n\nMove only the remaining commits with `git move -s`, not `-b`. `-b` moves the\nwhole lineage of the selection from where it leaves `$upstream`, so it drags the\nmerged (now squashed) commits along; `-s \u003Cfirst remaining>` moves exactly that\ncommit and its descendants. Never run a bare `git restack` here: it selects\nother worktrees' stacks and stale branches repo-wide (see **Rewriting** in\n`references\u002Fgit-branchless.md`).\n\n1. **Fetch and detach review checkouts.** Re-run pre-flight step 5 (it fetches).\n   A worktree you own that has a stack commit checked out (for example a review\n   checkout) would be rewritten under you, and step 2's worktree check refuses\n   it: `git -C \u003Cpath> switch --detach \"$upstream\"`. For a worktree you do not\n   own, stop and ask.\n2. **Select the remaining commits.** `$STACK` still holds the merged commits,\n   because the squash created a new commit upstream. Name the first commit whose\n   PR\u002FMR is still open, select it and its descendants, then re-run step 2's\n   checks on that selection:\n\n   ```bash\n   first=\"\u003Cfirst remaining commit>\"\n   SELECTED=\"descendants($first) - ::$base\"\n   git query -r \"$SELECTED\"            # must list only the unmerged commits\n   tip=\"$(git query -r \"heads($SELECTED)\")\"\n   [ \"$tip\" = \"$(git rev-parse HEAD)\" ] || { echo \"check out the tip: $tip\"; exit 1; }\n   old_base=\"$(git rev-parse \"${first}^\")\"\n   old_tip=\"$(git rev-parse HEAD)\"\n   ```\n\n   Before the move, capture the lease for every remaining branch (Pinned\n   force-push).\n\n   If every PR\u002FMR in the stack merged, nothing remains to move: skip the rest of\n   this recipe and tear the worktree down instead of submitting again.\n\n3. **Push a backup** of the tip to the remote before rewriting, under a name of\n   its own so an earlier rewrite's backup is never overwritten, for example\n   `git push \"$remote\" \"HEAD:refs\u002Fheads\u002Farchive\u002Fpre-move-$(git rev-parse --short HEAD)\"`.\n   A local tag or branch is not a safe backup.\n4. **Move.** `git move --dry-run` only tests the in-memory attempt, so use it as\n   the preview and then run the real move:\n\n   ```bash\n   git move --dry-run -s \"$first\" -d \"$upstream\" || exit 1\n   git move -s \"$first\" -d \"$upstream\" || exit 1\n   ```\n\n   If it reports a conflict it exits 1 and changes nothing. Stop and ask the\n   user.\n\n5. **Verify nothing else changed.** `git range-diff` must show every remaining\n   patch unchanged (a `=` row for each commit):\n\n   ```bash\n   git range-diff \"$old_base..$old_tip\" \"$upstream..HEAD\"\n   git sl\n   ```\n\n6. **Force-push with pinned leases.** Push every remaining branch with the lease\n   step 2 captured (Pinned force-push). Do not fetch to refresh the lease: a\n   fetch is what makes the bare form pass.\n\n7. **Confirm the next PR\u002FMR is open and targets main.** Retargeting had to\n   happen before the merge (see **Before merging**); a base of `main` alone does\n   not prove the PR\u002FMR stayed open. If a dependent PR\u002FMR closed unexpectedly,\n   stop and report it before continuing.\n\nBecause `-s` starts at the first remaining commit, the move never has to skip an\nalready-merged patch, so a multi-commit squash needs no special handling. If a\nremaining commit's edits overlap the new upstream, the move conflicts (exit 1,\nnothing changed): stop and ask rather than hiding the still-live descendants\n(arxanas\u002Fgit-branchless#965).\n\nStep 3's `-b` move, by contrast, skips a commit whose patch is already upstream\nand deletes its branch. It detects a squash of several commits only when their\nedits do not overlap, each applying as an empty commit; when they overlap, it\nconflicts.\n\n### Out-of-order merge recovery\n\nBefore merging PR N+1 into branch N, apply **Before merging**: retarget every\nopen dependent PR\u002FMR (including N+2) from branch N+1 to branch N and confirm\neach remains open and targets branch N.\n\nIf a PR is merged out of order (e.g., PR N+1 merged before PR N), the squash\ngoes into the base branch — not main. Merge the base PR (N) next — it now\ncontains both changes. After merging, the commits for both PRs are upstream:\nre-run pre-flight step 5 (it fetches), then run the squash-merge recipe above\nwith `$SELECTED` limited to what remains, and `-s` on the first commit that is\nstill unmerged.\n\nDo not try to update local `main` with `git branch -f main origin\u002Fmain`: Git\nrefuses it whenever another worktree has `main` checked out, and the move does\nnot need it.\n\nIf a merged commit is still in the selection, stop and ask. Do not start the\nmove at a merged commit or hide orphaned commits; either can reach another\nworktree's line.\n\n## Addressing Review Feedback\n\nWhen a reviewer (human, Copilot, etc.) comments on a specific PR in the stack:\n\n1. **Identify the target commit** — the PR tells you which branch\u002Fcommit the\n   feedback applies to. Before step 2, capture the lease for the target and\n   every downstream branch (Pinned force-push).\n\n2. **Apply the fix** using `\u002Fstack-fix`. It handles both absorb (line-level\n   fixes) and manual amend (structural changes) paths, including dry-run\n   preview, conflict resolution, and restacking.\n\n3. **Force-push all downstream branches** (target + every branch after it):\n\n   Push with the pinned lease (Pinned force-push) for the target and downstream\n   branches.\n\n4. **Return to the stack tip** after pushing:\n\n   ```bash\n   git checkout \u003Ctip-branch>   # e.g. todo\u002Fpre-publish or the last PR branch\n   ```\n\n5. **Reply to and resolve** each review thread. Replying alone does NOT close\n   the conversation — you must explicitly resolve each thread in the UI or via\n   the platform's API\u002FCLI.\n\n   #### GitHub\n\n   ```bash\n   # Get unresolved thread IDs for a PR\n   gh api graphql -f query='{\n     repository(owner: \"\u003Cowner>\", name: \"\u003Crepo>\") {\n       pullRequest(number: \u003CN>) {\n         reviewThreads(first: 100) {\n           nodes { id isResolved }\n         }\n       }\n     }\n   }' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved | not) | .id'\n\n   # Resolve each thread\n   gh api graphql -f query='mutation {\n     resolveReviewThread(input: {threadId: \"\u003Cthread-id>\"}) {\n       thread { isResolved }\n     }\n   }'\n   ```\n\n## Tips\n\n- Always start with `git sl` to understand the stack before submitting.\n- Use one branch per commit so each stacked PR has a clear base.\n- For very large stacks (20+ PRs), consider batching — submit the first 5-10,\n  get them merged, then submit the next batch. Reviewers struggle with 20+ open\n  PRs at once.\n- **For stacks > 10 commits, script branch creation and PR creation.** Write a\n  standalone bash script (not inline shell — zsh doesn't support bashisms like\n  `${!array[@]}`). Use `#!\u002Fusr\u002Fbin\u002Fenv bash` with strict mode. Add `sleep 1`\n  between `gh pr create` calls to avoid GitHub rate limiting.\n\n  Template for scripted stacked PR creation:\n\n  ```bash\n  #!\u002Fusr\u002Fbin\u002Fenv bash\n  set -euETo pipefail\n  shopt -s inherit_errexit 2>\u002Fdev\u002Fnull || :\n\n  # Define stack: branch names and commit messages in order\n  BRANCHES=(\"feat\u002Ffirst\" \"feat\u002Fsecond\" \"feat\u002Fthird\")\n  TITLES=(\"feat: first change\" \"feat: second change\" \"feat: third change\")\n  TOTAL=${#BRANCHES[@]}\n\n  for i in \"${!BRANCHES[@]}\"; do\n    branch=\"${BRANCHES[$i]}\"\n    title=\"${TITLES[$i]}\"\n    pos=$((i + 1))\n    base=$([[ $i -eq 0 ]] && echo \"main\" || echo \"${BRANCHES[$((i - 1))]}\")\n\n    echo \"Creating PR ${pos}\u002F${TOTAL}: ${branch} -> ${base}\"\n    gh pr create --head \"${branch}\" --base \"${base}\" \\\n      --title \"${title}\" --body \"Stack: ${pos}\u002F${TOTAL}\"\n    sleep 1\n  done\n  ```\n","Submit this worktree's commit stack for review. If an argument is provided, use\nit as a revset to select which commits to submit. Default is this worktree's own\nstack (`$STACK`, pre-flight step 5).\n\n## Pre-flight\n\n1. **Load references** — read `references\u002Fgit-branchless.md` (relative to this\n   skill's directory) before proceeding.\n\n2. **Confirm repository initialization.** A devenv project with\n   `git.branchless.enable` runs `git branchless init` on every shell entry.\n   Anywhere else, check and stop if it is missing:\n\n   ```bash\n   git config --get branchless.core.mainBranch >\u002Fdev\u002Fnull ||\n     echo \"not initialized: ask the user to run git branchless init\"\n   ```\n\n   Do not run `git branchless init` yourself: it rewrites the repository's\n   shared configuration and hooks for every worktree.\n\n3. **Require a clean working tree**:\n\n   ```bash\n   test -z \"$(git status --porcelain --untracked-files=no)\" || echo \"dirty: commit first\"\n   ```\n\n   The rebase below carries uncommitted edits across and could strand the\n   checkout on a conflict, so if tracked files have changes, stop and ask the\n   user to commit them (see **Don't use `git stash`** in\n   `references\u002Fgit-branchless.md`). Untracked files do not affect the move.\n\n4. **Check for stale rebase state**:\n\n   ```bash\n   ls -d \"$(git rev-parse --git-path rebase-merge)\" \\\n     \"$(git rev-parse --git-path rebase-apply)\" 2>\u002Fdev\u002Fnull\n   ```\n\n   If either exists, run `git rebase --abort` before proceeding. Ask Git for the\n   paths: in a linked worktree `.git` is a file, and an on-disk rebase keeps its\n   state in that worktree's own git dir.\n\n5. **Derive and fetch the upstream ref, and select this worktree's stack.** If\n   `git remote` prints nothing, ask the user for the remote URL and add it\n   first. The upstream is the main branch's upstream, falling back to\n   `\u003Cremote>\u002F\u003Cmain>`:\n\n   ```bash\n   main_branch=\"$(git config branchless.core.mainBranch || echo main)\"\n   remote=\"$(git config \"branch.${main_branch}.remote\" || echo origin)\"\n   if [ \"$remote\" = . ]; then\n     # main tracks a local branch: that branch is the upstream; nothing to fetch\n     upstream=\"$(git rev-parse --abbrev-ref \"${main_branch}@{upstream}\")\"\n   else\n     upstream=\"$(git rev-parse --abbrev-ref \"${main_branch}@{upstream}\" 2>\u002Fdev\u002Fnull \\\n       || echo \"${remote}\u002F${main_branch}\")\"\n     git fetch \"$remote\"   # updates $upstream; no local branch moves\n   fi\n   if git rev-parse --verify -q \"${upstream}^{commit}\" >\u002Fdev\u002Fnull; then\n     base=\"$upstream\"\n   else\n     upstream=\"\"           # first push: the remote has no main branch yet\n     base=\"$main_branch\"\n   fi\n   STACK=\"descendants(roots((stack() & ::HEAD) - ::$base)) - ::$base\"\n   ```\n\n   `$STACK` is this worktree's own stack: HEAD's first commit past `$base` and\n   everything descended from it. Every later revset in this skill uses it\n   instead of `stack()`. Local `main` is never updated here, so while it is\n   behind `$upstream` the upstream commits count as draft, and `stack()` then\n   holds every stack that another worktree branched from them. See **Selecting\n   your own stack** in `references\u002Fgit-branchless.md`.\n\n   When `$upstream` does not resolve (a first push to a remote that has no main\n   branch yet), there is nothing to rebase onto. `$base` falls back to local\n   `main`, and step 3 is skipped.\n\n   `$remote` is also where the push steps below push. If it is `.` (main tracks\n   a local branch), ask the user which remote to push to and set `remote` to it.\n\n   In a new shell, re-derive these variables and redo step 2's selection and\n   checks. Preserve any lease snapshot captured before a rewrite; fetching again\n   never authorizes refreshing that snapshot.\n\n6. **Capture pinned leases before rewriting**, including step 3's initial move.\n   Run this block in the same Bash shell as the later push calls:\n\n   ```bash\n   set -euETo pipefail\n   shopt -s inherit_errexit 2>\u002Fdev\u002Fnull || :\n\n   lease_remote=\"$remote\"\n   lease_heads=\"$(git ls-remote --heads \"$lease_remote\")\" || exit 1\n\n   push_selected() {\n     : \"${lease_remote:?capture remote leases before rewriting}\"\n     : \"${lease_heads?capture remote leases before rewriting}\"\n     local b branches expected\n     local -a leases=() refs=()\n     branches=\"$(git query --branches \"$SELECTED\")\" || return 1\n     test -n \"$branches\" || { echo \"no selected branches\" >&2; return 1; }\n     for b in $branches; do\n       expected=\"$(printf '%s\\n' \"$lease_heads\" | awk -v ref=\"refs\u002Fheads\u002F$b\" '$2 == ref { print $1 }')\"\n       leases+=(\"--force-with-lease=refs\u002Fheads\u002F$b:$expected\")\n       refs+=(\"refs\u002Fheads\u002F$b:refs\u002Fheads\u002F$b\")\n     done\n     git push \"$@\" --set-upstream \"${leases[@]}\" \"$lease_remote\" \"${refs[@]}\"\n   }\n   ```\n\n   This records the remote's branch tips before any rewrite. A branch absent\n   from that snapshot gets an empty expected SHA: its lease requires that the\n   remote branch still does not exist. This also covers branches named in step 6\n   after the move. A failed remote read stops the workflow; it must never be\n   treated as an empty remote.\n\n   Keep `lease_heads`, `lease_remote` and the function in this shell until the\n   push finishes. Do not recapture after a rewrite or fetch, including when\n   resuming in a new shell: without the original snapshot, stop and recover it\n   before pushing. For a later operation, capture a fresh snapshot **before**\n   that operation's first rewrite. The push uses the captured remote and\n   explicit branch refs, independent of tracking or `push.default`.\n\n   The packaged `git submit` has no expected-SHA option. Its branch forge\n   fetches before pushing with bare `--force-with-lease`; even `git submit -c`\n   can update existing branches that way. See the **`git submit`** entry in\n   `references\u002Fgit-branchless.md` for the source evidence. Use `push_selected`\n   for both new branches and updates throughout this skill.\n\n## Steps\n\n1. **Visualize the stack** with `git sl` to understand what will be submitted.\n\n2. **Determine and check the commit range** from `$ARGUMENTS`:\n\n   ```bash\n   # If $ARGUMENTS is empty, this worktree's stack:\n   SELECTED=\"$STACK\"\n\n   # If $ARGUMENTS is a revset (use git sl or git branchless log, not git log).\n   # current() follows a commit hash to its rewritten copy after step 3's move:\n   SELECTED=\"current($ARGUMENTS) - ::$base\"\n\n   : \"${STACK:?run pre-flight step 5 first}\"\n   selected_commits=\"$(git query -r \"$SELECTED\")\" || exit 1\n   test -n \"$selected_commits\" || { echo \"empty selection\" >&2; exit 1; }\n   stack_commits=\"$(git query -r \"$STACK\")\" || exit 1\n   test -n \"$stack_commits\" || { echo \"empty stack selection\" >&2; exit 1; }\n   outside=\"$(git query -r \"only($SELECTED, $base) - ($STACK)\")\" || exit 1\n   test -z \"$outside\" || { printf '%s\\n' \"$outside\"; exit 1; }\n   current_worktree=\"$(git rev-parse --show-toplevel)\" || exit 1\n   other_heads=\"$(git worktree list --porcelain | awk -v current=\"$current_worktree\" '\n     $1 == \"worktree\" {\n       if (path != \"\" && path != current && head != \"\") print head\n       path = substr($0, 10); head = \"\"\n     }\n     $1 == \"HEAD\" { head = $2 }\n     END { if (path != \"\" && path != current && head != \"\") print head }\n   ')\" || exit 1\n   overlap=\"\"\n   for other_head in $other_heads; do\n     for stack_commit in $stack_commits; do\n       if git merge-base --is-ancestor \"$stack_commit\" \"$other_head\"; then\n         overlap=\"${overlap}${overlap:+ }${other_head}\"\n       fi\n     done\n   done\n   test -z \"$overlap\" || { printf '%s\\n' \"$overlap\"; exit 1; }\n   merges=\"$(git query -r \"merges() & ($STACK)\")\" || exit 1\n   test -z \"$merges\" || { printf '%s\\n' \"$merges\"; exit 1; }\n   ```\n\n   - **Empty selection:** stop and ask which commits to submit. HEAD has no\n     commits of its own past `$base`: you are on `main`, in a fresh worktree, or\n     everything selected is already upstream. Do **not** fall back to `draft()`\n     or `stack()`: in a repository with several worktrees both select other\n     worktrees' stacks. (A first push of a repository whose whole history is on\n     `main` also lands here; use step 8's explicit `SELECTED` and pinned\n     `push_selected` path.)\n   - **The second query prints commits:** stop, show those commits, and ask.\n     Either the revset reaches past this worktree's stack (`draft()` does, for\n     example), or local `main` has commits that are not on `$upstream` and the\n     stack is built on them. The move in step 3 would carry those commits along.\n   - **The worktree check prints a commit:** another worktree, including a\n     detached one, has its HEAD inside this selection. Stop and ask: step 3\n     would rewrite or abandon the commit under that worktree.\n   - **The merge query prints a commit:** the stack contains a merge commit, for\n     example from merging `origin\u002Fmain` into it. Stop and tell the user this\n     skill does not submit a stack with a merge in it. Step 3 would move only\n     the commits below the merge and leave the merge and everything above it,\n     HEAD included, on the old base, and it still exits 0.\n\n3. **Rebase the stack onto the latest upstream.** Pre-flight step 5 already\n   fetched and step 6 captured the leases, so move only the selected stack onto\n   the remote-tracking ref:\n\n   ```bash\n   git move -b \"$SELECTED\" -d \"$upstream\" || exit 1\n   selected_commits=\"$(git query -r \"$SELECTED\")\" || exit 1\n   test -n \"$selected_commits\" || { echo \"selection became empty\" >&2; exit 1; }\n   ```\n\n   `-b` accepts a revset and moves each selected commit's whole lineage from\n   where it leaves `$upstream`, so commits already upstream are never re-moved.\n   That holds for a stack without merge commits, which step 2 checked. If\n   `$upstream` has not moved, this is a no-op — that's fine. On a first push\n   (`$upstream` empty), skip this step. If the selection is empty after the\n   move, everything selected was already upstream: stop and say so instead of\n   testing and submitting nothing.\n\n   This skill does not use branchless sync because it targets local `main` and\n   can span other draft stacks. If the guarded move reports a conflict, it exits\n   1 and changes nothing. Stop and ask the user; do not start an on-disk repair.\n\n4. **Run tests across the stack** to validate each commit independently. This is\n   the one case where testing every commit is unambiguously right: every commit\n   here is about to become its own PR, so every commit has to stand alone.\n\n   ```bash\n   git test run -x '\u003Ctest-command>' --jobs \u003CN> \"$SELECTED\"\n   ```\n\n   Size `\u003CN>` by **memory**, not cores — see **Sizing `--jobs`** in\n   `references\u002Fgit-branchless.md`. Each job is a separate worktree with no\n   shared cache, so peak usage is `jobs x per-job footprint`. Never pass\n   `--jobs 0` — it means one job per physical CPU and overrides any bound the\n   machine's config set, which for a multi-GB command such as `nix flake check`\n   exhausts RAM.\n\n   Determine the test command from the project (package.json scripts, Makefile,\n   Cargo.toml, etc.). If no obvious test command exists, ask the user. If the\n   project has no tests, skip this step.\n\n   If any commit fails, stop and report which commit(s) failed. Do not submit a\n   stack with failing tests.\n\n5. **Verify commit messages** — review `git sl` output. Flag any commits with\n   vague messages (\"fix\", \"WIP\", \"update\") and ask the user before rewriting\n   them.\n\n6. **Ensure branches exist** on each commit. For commits without branches,\n   generate branch names from commit messages:\n\n   ```bash\n   # Pattern: type\u002Fscope or type\u002Fshort-description\n   # feat(flake): add minimal flake skeleton → feat\u002Fflake-skeleton\n   # docs(references): add git-branchless reference → docs\u002Fgit-branchless-reference\n   # chore: add TODO.md → chore\u002Fadd-todo\n   ```\n\n   Create branches:\n\n   ```bash\n   git branch \u003Cbranch-name> \u003Ccommit-hash>\n   ```\n\n   Present the branch list to the user for review before proceeding. Every local\n   branch on a selected commit is pushed, including incidental review or\n   worktree branches. Step 7's dry-run preview shows each remote ref that will\n   be published; delete or move an unwanted local branch before publishing.\n\n   The pinned push sets upstream tracking to each branch's own remote branch,\n   including branches that initially track `main`.\n\n7. **Confirm before pushing** — preview what will be pushed:\n\n   ```bash\n   set -euETo pipefail\n   shopt -s inherit_errexit 2>\u002Fdev\u002Fnull || :\n   push_selected --dry-run\n   ```\n\n   Show the dry-run output to the user and ask for confirmation:\n\n   ```\n   Ready to push N branches and create N PRs. Proceed? (y\u002Fn)\n   ```\n\n   **Wait for explicit user approval.** Pushing creates remote branches and PRs\n   — these are visible side effects that cannot be easily undone. This gate is\n   especially important when the skill is auto-invoked via\n   `disable-model-invocation: false`.\n\n8. **Push branches with the captured leases**:\n\n   ```bash\n   set -euETo pipefail\n   shopt -s inherit_errexit 2>\u002Fdev\u002Fnull || :\n   push_selected\n   ```\n\n   This creates absent branches and updates existing ones in the same push. If\n   any lease is rejected, stop and inspect the remote change with the user; do\n   not refresh the snapshot and retry or replace the command with `git submit`.\n   A dry-run does not reserve the remote refs, so the real push must use the\n   same leases.\n\n   For a first push whose entire history is on local `main`, step 2 reports an\n   empty selection. After agreeing which branches to publish, set `SELECTED` to\n   a revset union of their explicit names (separated by `|`, not spaces):\n\n   ```bash\n   SELECTED='feat\u002Fa|feat\u002Fb'\n   ```\n\n   Capture their remote snapshot with pre-flight step 6 before any rewrite, skip\n   step 3, and use the same preview and push in steps 7 and 8. This path\n   publishes public commits without rerunning step 2's empty-stack guard; there\n   is no unpinned fallback.\n\n9. **Create stacked PRs\u002FMRs** — for each branch, create a pull\u002Fmerge request\n   targeting the previous branch in the stack (or `main` for the first). Use the\n   commit message as the title. Keep the body minimal — the commit diff speaks\n   for itself.\n\n#### GitHub\n\n```bash\n# First commit after main:\ngh pr create --head \u003Cbranch-1> --base main \\\n  --title \"\u003Ccommit message>\" --body \"$(cat \u003C\u003C'EOF'\n## Summary\n\u003Cone-line description>\n\nStack: 1\u002FN\n\n🤖 Generated with [Claude Code](https:\u002F\u002Fclaude.com\u002Fclaude-code)\nEOF\n)\"\n\n# Subsequent commits:\ngh pr create --head \u003Cbranch-N> --base \u003Cbranch-N-1> \\\n  --title \"\u003Ccommit message>\" --body \"...\"\n```\n\n10. **Report results** — show a summary table:\n\n    ```\n    | # | Branch | PR\u002FMR | Base | Status |\n    |---|--------|----|------|--------|\n    | 1 | feat\u002Fflake-skeleton | #2 | main | created |\n    | 2 | chore\u002Fformatting | #3 | feat\u002Fflake-skeleton | created |\n    | ... | ... | ... | ... | ... |\n    | 25 | todo\u002Fpre-publish | — | (tracking only) | pushed |\n    ```\n\n## Subsequent Updates\n\n### Pinned force-push\n\nBefore any amend, fix, restack or move, run pre-flight step 6 to capture the\nremote tips and define `push_selected`. This applies to the initial step 3 move\nas well as later updates. After the rewrite, re-derive `$SELECTED` using\npre-flight step 5 and step 2's selection and checks, keeping the original lease\nsnapshot. Preview with `push_selected --dry-run`, then push with `push_selected`\nafter the required approval.\n\nNever push a base branch tip containing a still-open stacked PR's head. GitHub\nthen marks that PR merged, and deletes its head branch when the repository\ndeletes branches on merge.\n\nNever refresh the expected tips after the rewrite. Bare `--force-with-lease`\ncompares against remote-tracking refs, which any fetch (including another\nworktree's fetch) can update. The explicit SHA captured before rewriting still\nrejects a competing push after such a fetch. An empty expected SHA requires an\nabsent branch, so a competing branch creation is rejected too.\n\nPRs\u002FMRs auto-update when their branches are force-pushed. No need to recreate\nthem. Only branches downstream of the changed commit need updating, but pushing\nall is safe — unchanged branches are skipped automatically.\n\n### Before merging\n\nBefore the user or agent merges any PR\u002FMR, find every open PR whose base is the\nmerging PR's head branch:\n\n```bash\ngh pr list --base \u003Cbranch> --state open --limit 1000 --json number\n```\n\nHere `\u003Cbranch>` is the merging PR's head branch. Retarget each dependent PR to\nthe merging PR's current base BEFORE merging:\n\n```bash\ngh pr edit \u003Cdependent-PR-number> --base \u003Ccurrent-base-branch>\n```\n\nFor the bottom PR, the current base is `main`: use\n`gh pr edit \u003Cdependent-PR-number> --base main`. For an out-of-order merge of PR\nN+1 into branch N, use branch N instead. Confirm every affected PR is still open\nand targets that current base:\n\n```bash\ngh pr view \u003Cdependent-PR-number> --json state,baseRefName\n```\n\nIf any retarget or confirmation fails, stop before merging. Repeat this before\nevery PR\u002FMR merge. On GitLab or another forge, use its target-branch controls to\nmove every open dependent MR\u002FPR to the merging MR\u002FPR's current base and confirm\neach remains open and targets that base before merging.\n\nOn GitHub, an observed merge that deleted the base branch recorded a\n`BaseRefDeletedEvent`, followed by an `AutomaticBaseChangeSucceededEvent` (base\nmoved to `main`) and a `ClosedEvent` on the dependent PR. The retarget and close\nhappened in the same second, right after the deletion. That PR ended CLOSED with\nstate reason COMPLETED even though its base had moved to `main`. Do not rely on\nautomatic retargeting to keep dependent PRs open.\n\n### After each squash merge\n\nAfter each squash-merged PR\u002FMR, you MUST move the remaining stack onto the new\nupstream, confirm the next PR\u002FMR's base, and force-push the remaining branches.\nWithout this, downstream PRs\u002FMRs show the full diff of all prior commits (the\nsquash merge creates a new commit hash that downstream branches don't share). Do\nit after EVERY squash merge, not just the first: each merge changes main.\n\nMove only the remaining commits with `git move -s`, not `-b`. `-b` moves the\nwhole lineage of the selection from where it leaves `$upstream`, so it drags the\nmerged (now squashed) commits along; `-s \u003Cfirst remaining>` moves exactly that\ncommit and its descendants. Never run a bare `git restack` here: it selects\nother worktrees' stacks and stale branches repo-wide (see **Rewriting** in\n`references\u002Fgit-branchless.md`).\n\n1. **Fetch and detach review checkouts.** Re-run pre-flight step 5 (it fetches).\n   A worktree you own that has a stack commit checked out (for example a review\n   checkout) would be rewritten under you, and step 2's worktree check refuses\n   it: `git -C \u003Cpath> switch --detach \"$upstream\"`. For a worktree you do not\n   own, stop and ask.\n2. **Select the remaining commits.** `$STACK` still holds the merged commits,\n   because the squash created a new commit upstream. Name the first commit whose\n   PR\u002FMR is still open, select it and its descendants, then re-run step 2's\n   checks on that selection:\n\n   ```bash\n   first=\"\u003Cfirst remaining commit>\"\n   SELECTED=\"descendants($first) - ::$base\"\n   git query -r \"$SELECTED\"            # must list only the unmerged commits\n   tip=\"$(git query -r \"heads($SELECTED)\")\"\n   [ \"$tip\" = \"$(git rev-parse HEAD)\" ] || { echo \"check out the tip: $tip\"; exit 1; }\n   old_base=\"$(git rev-parse \"${first}^\")\"\n   old_tip=\"$(git rev-parse HEAD)\"\n   ```\n\n   Before the move, capture the lease for every remaining branch (Pinned\n   force-push).\n\n   If every PR\u002FMR in the stack merged, nothing remains to move: skip the rest of\n   this recipe and tear the worktree down instead of submitting again.\n\n3. **Push a backup** of the tip to the remote before rewriting, under a name of\n   its own so an earlier rewrite's backup is never overwritten, for example\n   `git push \"$remote\" \"HEAD:refs\u002Fheads\u002Farchive\u002Fpre-move-$(git rev-parse --short HEAD)\"`.\n   A local tag or branch is not a safe backup.\n4. **Move.** `git move --dry-run` only tests the in-memory attempt, so use it as\n   the preview and then run the real move:\n\n   ```bash\n   git move --dry-run -s \"$first\" -d \"$upstream\" || exit 1\n   git move -s \"$first\" -d \"$upstream\" || exit 1\n   ```\n\n   If it reports a conflict it exits 1 and changes nothing. Stop and ask the\n   user.\n\n5. **Verify nothing else changed.** `git range-diff` must show every remaining\n   patch unchanged (a `=` row for each commit):\n\n   ```bash\n   git range-diff \"$old_base..$old_tip\" \"$upstream..HEAD\"\n   git sl\n   ```\n\n6. **Force-push with pinned leases.** Push every remaining branch with the lease\n   step 2 captured (Pinned force-push). Do not fetch to refresh the lease: a\n   fetch is what makes the bare form pass.\n\n7. **Confirm the next PR\u002FMR is open and targets main.** Retargeting had to\n   happen before the merge (see **Before merging**); a base of `main` alone does\n   not prove the PR\u002FMR stayed open. If a dependent PR\u002FMR closed unexpectedly,\n   stop and report it before continuing.\n\nBecause `-s` starts at the first remaining commit, the move never has to skip an\nalready-merged patch, so a multi-commit squash needs no special handling. If a\nremaining commit's edits overlap the new upstream, the move conflicts (exit 1,\nnothing changed): stop and ask rather than hiding the still-live descendants\n(arxanas\u002Fgit-branchless#965).\n\nStep 3's `-b` move, by contrast, skips a commit whose patch is already upstream\nand deletes its branch. It detects a squash of several commits only when their\nedits do not overlap, each applying as an empty commit; when they overlap, it\nconflicts.\n\n### Out-of-order merge recovery\n\nBefore merging PR N+1 into branch N, apply **Before merging**: retarget every\nopen dependent PR\u002FMR (including N+2) from branch N+1 to branch N and confirm\neach remains open and targets branch N.\n\nIf a PR is merged out of order (e.g., PR N+1 merged before PR N), the squash\ngoes into the base branch — not main. Merge the base PR (N) next — it now\ncontains both changes. After merging, the commits for both PRs are upstream:\nre-run pre-flight step 5 (it fetches), then run the squash-merge recipe above\nwith `$SELECTED` limited to what remains, and `-s` on the first commit that is\nstill unmerged.\n\nDo not try to update local `main` with `git branch -f main origin\u002Fmain`: Git\nrefuses it whenever another worktree has `main` checked out, and the move does\nnot need it.\n\nIf a merged commit is still in the selection, stop and ask. Do not start the\nmove at a merged commit or hide orphaned commits; either can reach another\nworktree's line.\n\n## Addressing Review Feedback\n\nWhen a reviewer (human, Copilot, etc.) comments on a specific PR in the stack:\n\n1. **Identify the target commit** — the PR tells you which branch\u002Fcommit the\n   feedback applies to. Before step 2, capture the lease for the target and\n   every downstream branch (Pinned force-push).\n\n2. **Apply the fix** using `\u002Fstack-fix`. It handles both absorb (line-level\n   fixes) and manual amend (structural changes) paths, including dry-run\n   preview, conflict resolution, and restacking.\n\n3. **Force-push all downstream branches** (target + every branch after it):\n\n   Push with the pinned lease (Pinned force-push) for the target and downstream\n   branches.\n\n4. **Return to the stack tip** after pushing:\n\n   ```bash\n   git checkout \u003Ctip-branch>   # e.g. todo\u002Fpre-publish or the last PR branch\n   ```\n\n5. **Reply to and resolve** each review thread. Replying alone does NOT close\n   the conversation — you must explicitly resolve each thread in the UI or via\n   the platform's API\u002FCLI.\n\n   #### GitHub\n\n   ```bash\n   # Get unresolved thread IDs for a PR\n   gh api graphql -f query='{\n     repository(owner: \"\u003Cowner>\", name: \"\u003Crepo>\") {\n       pullRequest(number: \u003CN>) {\n         reviewThreads(first: 100) {\n           nodes { id isResolved }\n         }\n       }\n     }\n   }' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved | not) | .id'\n\n   # Resolve each thread\n   gh api graphql -f query='mutation {\n     resolveReviewThread(input: {threadId: \"\u003Cthread-id>\"}) {\n       thread { isResolved }\n     }\n   }'\n   ```\n\n## Tips\n\n- Always start with `git sl` to understand the stack before submitting.\n- Use one branch per commit so each stacked PR has a clear base.\n- For very large stacks (20+ PRs), consider batching — submit the first 5-10,\n  get them merged, then submit the next batch. Reviewers struggle with 20+ open\n  PRs at once.\n- **For stacks > 10 commits, script branch creation and PR creation.** Write a\n  standalone bash script (not inline shell — zsh doesn't support bashisms like\n  `${!array[@]}`). Use `#!\u002Fusr\u002Fbin\u002Fenv bash` with strict mode. Add `sleep 1`\n  between `gh pr create` calls to avoid GitHub rate limiting.\n\n  Template for scripted stacked PR creation:\n\n  ```bash\n  #!\u002Fusr\u002Fbin\u002Fenv bash\n  set -euETo pipefail\n  shopt -s inherit_errexit 2>\u002Fdev\u002Fnull || :\n\n  # Define stack: branch names and commit messages in order\n  BRANCHES=(\"feat\u002Ffirst\" \"feat\u002Fsecond\" \"feat\u002Fthird\")\n  TITLES=(\"feat: first change\" \"feat: second change\" \"feat: third change\")\n  TOTAL=${#BRANCHES[@]}\n\n  for i in \"${!BRANCHES[@]}\"; do\n    branch=\"${BRANCHES[$i]}\"\n    title=\"${TITLES[$i]}\"\n    pos=$((i + 1))\n    base=$([[ $i -eq 0 ]] && echo \"main\" || echo \"${BRANCHES[$((i - 1))]}\")\n\n    echo \"Creating PR ${pos}\u002F${TOTAL}: ${branch} -> ${base}\"\n    gh pr create --head \"${branch}\" --base \"${base}\" \\\n      --title \"${title}\" --body \"Stack: ${pos}\u002F${TOTAL}\"\n    sleep 1\n  done\n  ```\n",{"name":5,"description":7,"argument-hint":44,"compatibility":45,"disable-model-invocation":40},"[revset]","Requires git-branchless","2a93bbf3c2b149c332ef3e05030c5280c5f3c75b","packages\u002Fstacked-workflows\u002Fskills\u002Fstack-submit",0,"https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor","2026-11-11 10:23:17",{"source":8,"skillName":5,"commands":53,"prompt":63,"zipAvailable":64},[54,57,60],{"tool":55,"command":56},"npx","npx skills add higherorderfunctor\u002Fnix-agentic-tools --skill stack-submit",{"tool":58,"command":59},"bunx","bunx skills add higherorderfunctor\u002Fnix-agentic-tools --skill stack-submit",{"tool":61,"command":62},"pnpm","pnpm dlx skills add higherorderfunctor\u002Fnix-agentic-tools --skill stack-submit","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fstacked-workflows\u002Fskills\u002Fstack-submit\u002FSKILL.md",true,[66],{"path":67,"resourceType":68,"resourceTypeDesc":69,"sizeBytes":70,"sourceHtmlUrl":71},"references\u002Fgit-branchless.md","REFERENCE","参考文档",37,"https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fstacked-workflows\u002Fskills\u002Fstack-submit\u002Freferences\u002Fgit-branchless.md",[73,100,126,145,160,176],{"skillNo":74,"name":75,"nameZh":6,"description":76,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":77,"categories":78,"keywords":84,"publishedAt":39},"01M6MQXHSDNFXSN7TT5FFWPF3X","stack-test","Use when you need to run tests or formatters across commits in a stack. Use INSTEAD of manual git test run or looping git checkout + test. Prevents: testing every commit when only the tip has to pass, unbounded parallel jobs exhausting memory, cache misunderstandings.","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fstacked-workflows\u002Fskills\u002Fstack-test\u002FSKILL.md",[79],{"categoryNo":80,"categoryName":81,"taxonomyType":82,"taxonomyTypeDesc":83},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[85,19,86,87,88,89,90,91,32,92,93,94,28,26,25,95,96,97,98,99,22],"test","stack test","parallel","TIP","checkout","commits","memory","testing","fix","tests","you","cache","commit","across","command",{"skillNo":101,"name":102,"nameZh":6,"description":103,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":104,"categories":105,"keywords":106,"publishedAt":39},"01M6MQXAXVWG1RY9H2116G4SVX","stack-summary","Use when you need to audit, analyze, or review stack quality before restructuring. Produces a structured summary with per-commit classification, philosophy audit, and violation flags. Output feeds directly into \u002Fstack-plan restructure mode. Use INSTEAD of manual git log inspection.","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fstacked-workflows\u002Fskills\u002Fstack-summary\u002FSKILL.md",[],[19,107,108,109,110,33,32,111,112,26,25,95,113,97,114,115,116,117,118,119,120,22,121,122,123,124,125],"stack summary","summary","feeds","review stack","mode","classification","git log","log","review","output","analyze","with","philosophy","inspection","plan","worktree","audit","flags","git",{"skillNo":127,"name":128,"nameZh":6,"description":129,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":130,"categories":131,"keywords":132,"publishedAt":39},"01M6MQX2BNQ4MAV9KCV0RZ734G","stack-split","Use when you need to break up, split, or decompose a large commit into smaller atomic commits. Use INSTEAD of manual git rebase -i + edit or git reset HEAD^. Prevents: non-working intermediate commits, wrong split ordering, missed downstream restack.","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fstacked-workflows\u002Fskills\u002Fstack-split\u002FSKILL.md",[],[133,19,134,135,90,32,136,26,25,137,95,97,138,139,140,141,142,143,125,144,22],"split","stack split","decompose","up","atomic commits","reset","head","missed","break","atomic","edit","rebase",{"skillNo":146,"name":147,"nameZh":6,"description":148,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":149,"categories":150,"keywords":151,"publishedAt":39},"01M6MQWWM5P7FVQSF7EWHJ70C6","stack-plan","Use when you need to plan commits, restructure a stack, or commit uncommitted work as organized atomic commits. Builds a stack from a description, uncommitted work, or existing commits INSTEAD of dumping everything into one monolithic commit with git add -A && git commit, or manual git rebase -i, git reset --soft, or ad-hoc git move sequences. Prevents: wrong commit ordering, forward references, git move -F panics, untested intermediates.","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fstacked-workflows\u002Fskills\u002Fstack-plan\u002FSKILL.md",[],[19,121,152,153,154,90,33,155,32,156,26,157,25,137,95,97,158,138,118,159,22],"stack plan","tree","as","move","git commit","Existing","git move","references",{"skillNo":161,"name":162,"nameZh":6,"description":163,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":164,"categories":165,"keywords":166,"publishedAt":39},"01M6MQWP7CKTM5NF72MED1H8NS","stack-fix","Use when you need to fix, correct, or update content in an earlier commit — line-level edits, content moves, or structural changes. Absorbs staged changes into correct stack commits INSTEAD of running git absorb or git commit --fixup via Bash, or hand-running git prev + edit + git amend + git restack --merge. Prevents: missed dry-run preview, leftover staged changes going unnoticed, forgetting to restack. Falls back to guided manual amend when absorb cannot route hunks.","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fstacked-workflows\u002Fskills\u002Fstack-fix\u002FSKILL.md",[],[93,19,167,168,90,32,31,169,156,26,25,95,170,171,97,172,173,174,175,22],"stack fix","back","structural","Bash","preview","hand","dry","route","running",{"skillNo":177,"name":178,"nameZh":6,"description":179,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":180,"categories":181,"keywords":182,"publishedAt":39},"01M6MQWJS10XBJT1C1JW4QF1X9","peer-communication","How to write replies a person reads: answer first, plain words, easy to scan. Load before your first reply in an interactive session.","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fpeer-communication\u002Fskills\u002Fpeer-communication\u002FSKILL.md",[],[183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199],"peer","communication","peer communication","scan","answer","session","how to","load","interactive","replies","first","how","write","person","easy","reply","plain"]