Activate when the user wants to merge a chain of dependent cross-repo PRs, orchestrate multi-repo PR merges, or says "merge chain", "merge PRs", "/merge-chain". Expects a logos-workspace PR as input.
Orchestrates merging a chain of dependent PRs across multiple repos in the Logos workspace. The user provides a workspace PR; this skill analyzes it, builds a merge plan, and executes it step by step.
Fetch the workspace PR details:
gh pr view <NUMBER> --repo logos-co/logos-workspace --json headRefName,baseRefName,state,title,body,url
Determine which submodules and flake inputs changed by diffing the PR:
gh pr diff <NUMBER> --repo logos-co/logos-workspace
Look for:
Subproject commit <hash> under )repos/<name>flake.lock changes (input URL/hash updates)For each changed submodule, find the corresponding upstream PR. Check for open or recently merged PRs on the same branch name:
gh pr list --repo logos-co/<repo-name> --state all --json number,title,headRefName,state,mergedAt --limit 20
Match by branch name (usually the same feature branch across repos). If a PR is already merged, note it — it can be skipped during execution.
For each upstream repo with a PR, check for temporary ?ref= branch references in its flake.nix. These are added during development to point to unmerged upstream feature branches:
grep '?ref=' repos/<repo-name>/flake.nix
IMPORTANT: First fetch the PR branch to check the version of flake.nix on that branch, not just what's on disk:
cd repos/<repo-name>
git fetch origin <pr-branch>
git show origin/<pr-branch>:flake.nix | grep '?ref='
Read the dependency graph from nix/dep-graph.nix. This maps each repo's flake input name to its deps list.
Compute topological merge order. From the repos that have PRs to merge, sort them so that dependencies come before dependents. Repos with no deps on other changed repos go first. The workspace PR is always last.
Map between repo directory names and flake input names. They usually match, but check the workspace flake.nix inputs section to confirm. The ws script's repo registry (in scripts/ws, the REPOS array) also has this mapping.
Check for vendor submodules in downstream repos. Some repos have vendor/<dep> git submodules (e.g., logos-liblogos has vendor/logos-cpp-sdk). These need updating alongside flake inputs:
git -C repos/<repo-name> submodule status | grep <dep-name>
Present the plan to the user. Format it clearly:
## Merge Chain Plan
Repos to merge (in dependency order):
1. logos-cpp-sdk#20 — "title" [ready to merge]
2. logos-liblogos#64 — "title" [depends on logos-cpp-sdk]
- flake.nix has ?ref=feat/branch for logos-cpp-sdk (will revert)
- vendor/logos-cpp-sdk submodule needs updating
3. logos-workspace#6 — "title" [aggregator, last]
Execution steps:
1. Merge logos-co/logos-cpp-sdk#20
2. On logos-liblogos branch <branch>:
a. Revert ?ref= in flake.nix
b. Update flake.lock: nix flake lock --update-input logos-cpp-sdk
c. Update vendor/logos-cpp-sdk submodule
d. Commit and push
3. [Optional] Wait for logos-liblogos CI
4. Merge logos-co/logos-liblogos#64
5. On workspace branch <branch>:
a. Update submodule pointers (repos/logos-cpp-sdk, repos/logos-liblogos)
b. Update flake.lock
c. Commit, rebase onto base branch, push
6. Merge logos-co/logos-workspace#<N>
Proceed?
Wait for user confirmation before proceeding to Phase 3.
Execute each step, asking for user confirmation before each merge and each push. Use gh CLI for GitHub operations and git for local operations.
Step A: Merge the PR
gh pr merge <NUMBER> --repo logos-co/<repo-name> --squash --delete-branch
Ask the user which merge strategy they prefer if not obvious (squash, merge commit, rebase). Default to squash.
After merging, note the merge commit hash on the target branch:
gh api repos/logos-co/<repo-name>/git/ref/heads/<base-branch> --jq '.object.sha'
Step B: Fix up each downstream repo in the chain
For each downstream repo that depends on the just-merged repo:
Checkout the PR branch locally:
cd repos/<downstream-repo>
git fetch origin
git checkout <pr-branch>
Revert ?ref= in flake.nix (if present). Edit flake.nix to change:
url = "github:logos-co/<repo>?ref=<branch>";
back to:
url = "github:logos-co/<repo>";
Update flake.lock to pick up the newly merged commit:
nix flake lock --update-input <input-name>
Use the flake input name as declared in THAT repo's own flake.nix (not the workspace input name — they may differ).
Update vendor submodule (if it exists):
# Check if vendor/<dep> exists
if [ -d vendor/<dep> ]; then
cd vendor/<dep>
git fetch origin
git checkout origin/<base-branch>
cd ../..
git add vendor/<dep>
fi
Commit and push:
git add flake.nix flake.lock
git commit -m "update <dep> to merged main"
git push origin <pr-branch>
Optionally wait for CI — ask the user:
gh pr checks <NUMBER> --repo logos-co/<downstream-repo> --watch
Or skip if the user wants to proceed immediately (the changes are mechanical).
Then merge this downstream PR and repeat for the next level.
Checkout the workspace PR branch:
git checkout <workspace-pr-branch>
Update each submodule pointer to the final merged commit:
cd repos/<repo-name>
git fetch origin
git checkout origin/<main-branch>
cd ../..
Update the workspace flake.lock using flake input names:
nix flake update <input1> <input2> ...
Stage and commit:
git add repos/<repo1> repos/<repo2> ... flake.lock
git commit -m "update flake references after merging upstream PRs"
Rebase onto base branch to keep history clean, then push:
git rebase <base-branch>
git push origin <workspace-pr-branch> --force-with-lease
Confirm with user before force-pushing.
Merge the workspace PR:
gh pr merge <NUMBER> --repo logos-co/logos-workspace --squash --delete-branch
Usually identical. The workspace flake.nix inputs section is the source of truth. Exceptions are in the inputToDirOverrides map in flake.nix (e.g., counter_qml, counter).
follows mechanismThe workspace flake.nix uses follows so overriding one input propagates downstream. But each repo's own flake.lock must be independently correct for standalone CI builds. That's why we must update each repo's flake.lock after its upstream deps merge — the workspace follows don't help individual repo CI.
?ref= patternDuring development, downstream repos add ?ref=<feature-branch> to flake.nix inputs to build against unmerged upstream code. Example:
logos-cpp-sdk.url = "github:logos-co/logos-cpp-sdk?ref=feat/logos-instance-id";
This MUST be reverted to "github:logos-co/logos-cpp-sdk" before or during the merge chain. The skill handles this automatically.
main vs mastergh repo view logos-co/<repo-name> --json defaultBranchRef --jq '.defaultBranchRef.name'
If any step fails (merge conflict, CI failure, permission denied):
Return to the workspace master branch, update submodules, and run ws update for all repos that changed. This updates the workspace flake.lock to point to the latest commits on each repo's default branch, which is essential after merging upstream PRs.
git checkout master
git pull
git submodule update
ws update <repo1> <repo2> ...
The ws update arguments should be the flake input names of ALL repos that were merged in the chain, plus any repos whose flake.lock was updated as part of the fixup steps. For example, if logos-cpp-sdk and logos-liblogos were merged:
ws update logos-cpp-sdk logos-liblogos
If other repos were also affected (e.g., logos-test-modules had its flake.lock updated because it depends on logos-liblogos), include those too:
ws update logos-cpp-sdk logos-liblogos logos-test-modules
This step ensures the workspace flake.lock is fully in sync with all the newly merged upstream commits. Without it, the workspace would still reference the old (pre-merge) commit hashes.