From beads-upstream-sync
Syncs beads forks with upstream steveyegge/beads repo, updates bd binary, and cleans merged PR branches in crew clone directories. Triggers on sync upstream or branch cleanup phrases.
npx claudepluginhub xexr/marketplace --plugin beads-upstream-syncThis skill uses the workspace's default tool permissions.
Full fork maintenance for a beads fork of steveyegge/beads.
Syncs Gastown forks with upstream, updates gt binary, cleans merged PR branches, and performs fork hygiene in crew clones using git and GitHub CLI.
Creates fix/feature branches from upstream/main for beads/gastown forks: selects bead via bd, implements changes, submits upstream PR, cherry-picks to fork.
Automates git branch cleanup: inventories local/remote branches, identifies merged/gone candidates, protects main/develop/release/current, confirms deletions, prunes remotes, provides recovery SHAs.
Share bugs, ideas, or general feedback.
Full fork maintenance for a beads fork of steveyegge/beads.
~/gt/beads/crew/<crew-name>)origin = your fork, upstream = steveyegge/beadsmain branch (the bleeding-edge branch)If resuming mid-skill from a handoff, ALWAYS re-read this skill file before continuing. The handoff mail provides context, but THIS FILE defines the steps. Do not execute from handoff notes alone.
PR branches are the source of truth. The cherry-picks on main are copies. Conflicts are always resolved on the PR branch first, then the fixed version is brought back to main.
IMPORTANT: Do NOT push at any step without presenting a summary and getting explicit user approval first. Every push (main, PR branches) requires a checkpoint.
Before starting, detect the local environment configuration:
# Detect fork identity from origin remote
git remote get-url origin
# Parse owner AND repo from: git@github.com:<OWNER>/<REPO>.git or https://github.com/<OWNER>/<REPO>.git
# Fork may not share the upstream repo name (users can rename forks)
# Verify upstream remote points to steveyegge/beads
git remote get-url upstream
# Should contain: steveyegge/beads
# Detect crew name from working directory
pwd
# Parse crew name from path: */crew/<CREW_NAME>
# Discover rig directories that have clones of this repo
ls -d ~/gt/*/mayor/rig 2>/dev/null
ls -d ~/gt/*/refinery/rig 2>/dev/null
# Filter to those whose origin matches the same fork
# Check for destructive command guard (DCG)
command -v dcg 2>/dev/null && echo "DCG detected" || echo "No DCG"
Present to user for confirmation:
Detected environment:
- Fork:
<OWNER>/<REPO>(origin) forked fromsteveyegge/beads(upstream)- Crew name:
<detected>- Rig directories: list of mayor/refinery rig paths with beads clones
- DCG installed: yes/no (affects which git reset patterns to use)
Please confirm or correct these values.
Wait for user confirmation before proceeding.
Use these values throughout the remaining steps:
FORK_OWNER — for gh pr list --author and fork referencesCREW_NAME — for navigating back to the crew working directoryRIG_DIRS — list of rig directories to update after push (step 8) and prune (step 9)HAS_DCG — if true, use safe reset patterns (mixed reset + stash) instead of git reset --hardCapture the current HEAD before any changes (needed later for config change review):
OLD_HEAD=$(git rev-parse --short HEAD)
Fetch latest from both remotes:
git fetch upstream && git fetch origin
Report to user:
# New upstream commits
git log --oneline origin/main..upstream/main
# Our cherry-picks on main
git log --oneline upstream/main..origin/main
# Check if any cherry-picked PRs were merged
gh pr list --repo steveyegge/beads --author FORK_OWNER --state all \
--json number,title,state,headRefName
# List active PR branches
git branch -r --list 'origin/fix/*' --list 'origin/feat/*'
Summarize:
git rebase upstream/main
If clean (no conflicts): Git auto-drops cherry-picks whose PRs were merged. Confirm:
Verify:
# Should show only remaining unmerged cherry-picks (or nothing)
git log --oneline upstream/main..main
Proceed to step 3.
If conflicts: A cherry-pick conflicts with upstream changes. This means the corresponding PR branch will also need updating. Follow the conflict resolution flow:
Abort the main rebase:
git rebase --abort
Go to the conflicting PR branch and rebase it first (PR branch is the source of truth):
git checkout fix/conflicting-feature
git rebase upstream/main
# Resolve conflicts here — this is the authoritative version
# Stage resolved files, git rebase --continue
Squash the PR branch to a single commit (if multi-commit):
# Interactive rebase is not available in agent sessions.
# Instead, soft-reset to the branch point and re-commit:
git reset --soft upstream/main
git commit -m "squashed: <feature description>"
Repeat for each conflicting PR branch.
Rebuild main from scratch — cleaner than patching old cherry-picks:
git checkout main
git stash # if any local changes
git pull origin main --ff-only # ensure we're at origin/main first
git rebase upstream/main # fast-forward to upstream (drops merged cherry-picks)
If the rebase still conflicts (because the old cherry-picks are stale), reset to upstream/main. If DCG is installed, use the safe reset pattern:
git rebase --abort
git reset upstream/main # mixed reset: moves HEAD, leaves changes in working tree
git stash # stashes the working tree diff (restores to new HEAD)
git stash drop # discard — now clean at upstream/main
If no DCG, you can use git reset --hard upstream/main directly.
Then cherry-pick each unmerged PR's squashed commit:
git cherry-pick <squashed-commit-from-fix/feature-A>
git cherry-pick <squashed-commit-from-fix/feature-B>
Return to step 2's verify check.
For each remaining PR branch with an open upstream PR that wasn't already rebased in step 2:
git checkout fix/some-feature
git rebase upstream/main
git rebase --abort), resolve on the PR branch using the same flow as step 2. Then rebuild main again — reset to upstream/main and re-cherry-pick all unmerged PRs (including the newly fixed one)Return to main when done:
git checkout main
MANDATORY before checkpoint. Catches compilation errors, package-level name collisions (invisible to git rebase), and test regressions.
Why this must happen before push: Git rebase resolves file-level merge conflicts, but it cannot detect package-level issues like duplicate function names across different files in the same Go package. Only the compiler catches these. If you push before building, you ship broken code and must fix + force-push again.
For each active PR branch:
git checkout <branch>
SKIP_UPDATE_CHECK=1 make build # build from non-main branch
go test ./... # run full test suite
git status # check for generated file drift
If git status shows changes after build, the build generated files that weren't
committed. Amend the branch commit to include them.
Then on main:
git checkout main
SKIP_UPDATE_CHECK=1 make build # SKIP because origin/main not yet pushed
go test ./...
git status # check for generated file drift
If build fails on main but passes on individual PR branches, the issue is likely a cross-branch collision (e.g., two PRs each adding a function with the same name). Fix on the PR branch first (source of truth), then rebuild main's cherry-pick.
If you amend any PR branch during this step (to fix build, include generated
files, or resolve a collision), main's cherry-pick of that branch is now stale.
You must rebuild main: reset to upstream/main and re-cherry-pick all PR branches
(step 2.5), then re-run this step for all branches.
All branches must build clean and tests must pass before proceeding.
STOP. Do NOT push yet. Present a full summary to the user:
Wait for user approval before proceeding to push.
Only after user approval:
Main:
# Force-with-lease if cherry-picks were dropped or main was rebuilt (history rewritten)
git push origin main --force-with-lease
# Regular push if no cherry-picks changed (pure fast-forward, rare)
git push origin main
Active PR branches (rebase always rewrites, so always force):
git push origin fix/some-feature --force-with-lease
Now that origin/main is updated, rebuild without the skip flag:
make install
Verify the build matches the expected commit:
bd version
git rev-parse --short HEAD
Compare the commit hash from bd version against the current HEAD. They must match. If they don't, the binary was built from a stale checkout — rebuild.
Mayor and refinery rig directories track origin/main. After pushing, update each
rig directory discovered in step 0.
Always check for dirty state first — these clones often have unstaged changes:
cd <rig-directory>
git status --porcelain # check if dirty
# If dirty: git stash
git fetch origin
git pull --rebase origin main
# If stashed: git stash pop
# If stash pop conflicts: git reset HEAD && git stash && git stash drop
# (conflicts from old state are safe to discard)
Repeat for each rig directory in RIG_DIRS (e.g. ~/gt/beads/mayor/rig, ~/gt/beads/refinery/rig).
Note: Refinery/rig accumulates stale local branches from old operations. If stash pop conflicts are messy, the simplest recovery is:
git reset HEAD # unstage everything
git stash # stash the mess (restores working tree to HEAD)
git stash drop # discard — clean at new HEAD
Verify all clones are at the same HEAD as the crew working copy.
Delete merged PR branches (remote + local):
# Remote
git push origin --delete <branch-name> [<branch-name> ...]
# Local (in crew working copy)
git branch -d <branch-name> [<branch-name> ...]
Delete stale branches (no open PR, no longer needed) — confirm with user first, then use -D for unmerged locals.
Prune stale refs in all clones:
# Crew working copy
git remote prune origin
# Each rig directory from RIG_DIRS
cd <rig-directory> && git remote prune origin
Also clean up stale local branches in refinery/rig (it accumulates them).
Return to the crew working copy before continuing:
cd ~/gt/beads/crew/CREW_NAME # use detected crew name
After the upgrade, run bd doctor to catch any issues introduced by upstream changes:
bd doctor --fix
This runs all checks and auto-fixes what it can in one pass. Report any changes detected and any issues found.
Only run additional doctor checks in other locations if the above reported issues:
# Only if bd doctor --fix showed problems:
cd ~/gt && bd doctor
# Also check each rig directory from RIG_DIRS
cd <rig-directory> && bd doctor
Review the upstream commits for changes that affect configuration. Use the OLD_HEAD captured in step 1:
git log --oneline --name-only $OLD_HEAD..upstream/main -- \
'*.yaml' '*.json' '*.toml' \
'internal/storage/migrations/' \
'cmd/bd/doctor/' \
'internal/beads/'
Also generate a human-readable summary of all upstream changes:
# Contributor summary
git shortlog --no-merges -s $OLD_HEAD..upstream/main
# Detailed commit list (non-merge)
git log --oneline --no-merges $OLD_HEAD..upstream/main
Report to user:
| Issue | Solution |
|---|---|
| Rebase conflict on main | Abort main rebase. Fix PR branch first (source of truth), then rebuild main. |
| Rebase conflict on PR branch | Resolve on the PR branch — this is the authoritative version. Then rebuild main with all updated cherry-picks. |
| Package-level name collision (compiles on branch, fails on main) | Two cherry-picks define the same symbol. Rename on the PR branch (source of truth), then rebuild main. Git rebase can't detect these — only the compiler can. |
| Force push needed on main | Cherry-pick dropped or main rebuilt. Use --force-with-lease. Explain in checkpoint. |
| PR branch rebase changes nothing | Upstream changes don't affect the PR's files. Still force-push to update the base. |
| Generated file drift after build | git status shows changes after make build. Amend the branch commit to include them. |
| Mayor/rig has local changes | git stash before pull, git stash pop after. If pop conflicts, discard with stash+drop. |
git branch -d refuses (not fully merged) | Confirm with user, then git branch -D. |
git push --delete fails (ref doesn't exist) | Already deleted remotely. Remove from batch and retry remaining. |
| Destructive git command blocked by hook | If DCG or similar is installed: use safe pattern — git reset <ref> (mixed) + git stash + git stash drop. If no hook: git reset --hard works directly. |
git checkout HEAD -- <path> blocked by hook | Use git stash + git stash drop to restore working tree to HEAD. |