--- /dev/null
+#!/bin/sh
+# Copyright (c) 2026 Jakub Czajka <jakub@ekhem.eu.org>
+# License: GPL-3.0 or later.
+#
+# branch-summary — Summarize non-main branches as a markdown table.
+
+# List all local branches except main.
+branches=$($HOME/.guix-home/profile/bin/git \
+ -C "$CLAUDE_PROJECT_DIR" for-each-ref \
+ --format='%(refname:short)' refs/heads/ \
+ | $GUIX_BIN/grep -v '^main$')
+
+if [ -z "$branches" ]; then
+ $GUIX_BIN/echo "No other branches."
+ exit 0
+fi
+
+# Print up to 3 commit subjects from branch $1 as a comma-separated
+# list, escaped for markdown table use.
+format_commits() {
+ $HOME/.guix-home/profile/bin/git -C "$CLAUDE_PROJECT_DIR" log --oneline \
+ --format='%s' main.."$1" 2>/dev/null \
+ | head -3 \
+ | $GUIX_BIN/sed 's/|/\\|/g' \
+ | $GUIX_BIN/tr '\n' ',' \
+ | $GUIX_BIN/sed 's/,$//;s/,/, /g'
+}
+
+# Table header.
+$GUIX_BIN/echo '| Branch | Commits | Summary |'
+$GUIX_BIN/echo '|--------|---------|---------|'
+
+# One row per branch.
+echo "$branches" | while read -r b; do
+ count=$($HOME/.guix-home/profile/bin/git \
+ -C "$CLAUDE_PROJECT_DIR" rev-list --count \
+ main.."$b" 2>/dev/null)
+ if [ -z "$count" ] || [ "$count" -eq 0 ]; then
+ summary="(even with main)"
+ else
+ summary=$(format_commits "$b")
+ fi
+ $GUIX_BIN/echo "| \`$b\` | $count | $summary |"
+done
+
+exit 0
--- /dev/null
+#!/bin/sh
+# Copyright (c) 2026 Jakub Czajka <jakub@ekhem.eu.org>
+# License: GPL-3.0 or later.
+#
+# merge-worktree — Merge a worktree branch into main.
+#
+# This script operates on the main worktree (CLAUDE_PROJECT_DIR) as a
+# subprocess, independent of the session's current directory. It rebases
+# the branch for linear history then fast-forwards main.
+#
+# Usage: merge-worktree <branch-name>
+
+set -eu
+
+branch="${1:-}"
+if [ -z "$branch" ]; then
+ echo "Usage: merge-worktree <branch-name>" >&2
+ exit 1
+fi
+
+PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
+
+# Validate branch.
+if ! $HOME/.guix-home/profile/bin/git -C "$PROJECT_DIR" rev-parse \
+ --verify "$branch" >/dev/null 2>&1; then
+ echo "Error: Branch '$branch' does not exist." >&2
+ exit 1
+fi
+
+if [ "$branch" = "main" ]; then
+ echo "Error: Cannot merge main into itself." >&2
+ exit 1
+fi
+
+# Check main worktree is clean.
+if [ -n "$($HOME/.guix-home/profile/bin/git -C "$PROJECT_DIR" \
+ status --porcelain --untracked-files=no)" ]; then
+ echo "Error: Main worktree has uncommitted changes." >&2
+ echo " Commit or stash them first." >&2
+ exit 1
+fi
+
+# Ensure main is restored on any exit (rebase failure, interruption).
+trap '$HOME/.guix-home/profile/bin/git -C "$PROJECT_DIR" checkout main \
+ 2>/dev/null || true' EXIT
+
+# Fetch from origin.
+echo "=== Fetching from origin ==="
+$HOME/.guix-home/profile/bin/git -C "$PROJECT_DIR" fetch origin
+
+# Rebase branch onto main.
+echo "=== Rebasing $branch onto main ==="
+if ! $HOME/.guix-home/profile/bin/git -C "$PROJECT_DIR" \
+ rebase main "$branch"; then
+ rc=$?
+ echo "Error: Rebase failed (conflicts)." >&2
+ echo " Resolve them in the main worktree:" >&2
+ echo " cd $PROJECT_DIR" >&2
+ echo " git status # see conflicted files" >&2
+ echo " # fix conflicts, then:" >&2
+ echo " git rebase --continue" >&2
+ echo " git checkout main" >&2
+ echo " git merge --ff-only $branch" >&2
+ exit $rc
+fi
+
+# Switch back to main.
+echo "=== Switching to main ==="
+$HOME/.guix-home/profile/bin/git -C "$PROJECT_DIR" checkout main
+
+# Fast-forward merge.
+echo "=== Merging $branch into main (fast-forward) ==="
+if ! $HOME/.guix-home/profile/bin/git -C "$PROJECT_DIR" \
+ merge --ff-only "$branch"; then
+ rc=$?
+ echo "Error: Fast-forward merge failed." >&2
+ echo " This should not happen after a successful rebase." >&2
+ echo " Try: git -C $PROJECT_DIR merge $branch" >&2
+ exit $rc
+fi
+
+echo "=== Branch '$branch' successfully merged into main ==="
--- /dev/null
+---
+name: merge-worktree
+description: Merge a workspace branch into main and optionally reconfigure.
+---
+
+# merge-worktree
+
+Merge a worktree branch into main, rebasing for linear history, then
+optionally run guix reconfigure.
+
+## Instructions
+
+When the user asks to merge a workspace, integrate branch changes, or
+"commit to main":
+
+1. **Show available workspaces**:
+ Run `.claude/scripts/branch-summary` to display a markdown table of
+ all non-main branches (workspaces) and their commits.
+
+2. **Ask which workspace to merge**:
+ Use AskUserQuestion (single-select). Present the workspace branch
+ names from step 1 as options. If only one non-main branch exists,
+ ask to confirm merging that one.
+
+3. **Execute the merge**:
+ Run `.claude/scripts/merge-worktree <selected-branch>`.
+ If the merge fails (rebase conflict), instruct the user to resolve
+ conflicts in the main worktree at `$CLAUDE_PROJECT_DIR`, then retry.
+
+4. **Ask about guix reconfigure**:
+ Use AskUserQuestion (single-select) with these options:
+ - "guix home reconfigure"
+ - "guix system reconfigure"
+ - "Skip reconfigure"
+
+5. **Run reconfigure if selected**:
+ Run the chosen command using Bash against the main worktree.
+
+6. **Offer branch cleanup**:
+ Use AskUserQuestion (single-select) asking whether to delete the
+ merged branch. Options: "Delete branch and worktree", "Keep branch".
+
+## Notes
+
+- The merge script `cd`s to `$CLAUDE_PROJECT_DIR` (main worktree) as a
+ subprocess, independent of the session's worktree directory.
+- After the merge, `ExitWorktree` becomes available since the branch is
+ now an ancestor of main.