Skip to content
Back to skills

Fork Merge Plan

ASecurity

Plan a bidirectional merge between a personal fork and an upstream repo. Produces a written merge plan (commit classification, conflict map, strategy) without executing any git operations. Use when both branches have diverged and you need to preserve functionality from both sides.

  • 8 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 20, 2026
code-qualitygobashgit

Security analysis

A100/100

Scanned September 20, 2026

npx -y skills add tstapler/dotfiles --skill fork-merge-plan --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Fork Merge Plan?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Fork Merge Plan
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tstapler-fork-merge-plan/badge)](https://www.skillsdirectory.com/skills/tstapler-fork-merge-plan)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: fork-merge-plan
description: Plan a bidirectional merge between a personal fork and an upstream repo. Produces a written merge plan (commit classification, conflict map, strategy) without executing any git operations. Use when both branches have diverged and you need to preserve functionality from both sides.
---

# Fork ↔ Upstream Merge Plan

**Goal**: Produce a rigorous written plan for merging two diverged branches (fork + upstream). No merge is executed — only analysis and a plan.

See also:
- `/sync-remotes` — execute the plan when both repos are equally canonical (public, bidirectional)
- `/git-upstream-fork` — execute the plan when contributing from a private context to a different public project
- `/git-worktrees` — create the merge branch in isolation when you're ready to execute

---

> For executing the merge plan using isolated branches, apply the `git-worktrees` skill.

## Step 1: Establish Context

Identify which remote is the upstream and which is the personal fork. Infer from existing remotes if not specified:

```bash
git remote -v
```

Common pattern for this project:
- **Fork (personal)**: `origin` → `tstapler/stapler-squad`
- **Upstream (work)**: `upstream-fanatics` → `TylerStaplerAtFanatics/stapler-squad`

Set variables:
```bash
FORK_REMOTE=origin
UPSTREAM_REMOTE=upstream-fanatics
BRANCH=main
```

## Step 2: Fetch & Find Divergence

```bash
git fetch $FORK_REMOTE
git fetch $UPSTREAM_REMOTE

# Commits only on fork (not in upstream)
git log $UPSTREAM_REMOTE/$BRANCH..HEAD --oneline --no-merges

# Commits only on upstream (not in fork)
git log HEAD..$UPSTREAM_REMOTE/$BRANCH --oneline --no-merges

# Merge base (common ancestor)
git merge-base HEAD $UPSTREAM_REMOTE/$BRANCH
```

## Step 3: Categorize Every Commit

For each commit on **each side**, classify it into one of these buckets:

| Bucket | Description | Merge action |
|--------|-------------|--------------|
| `FEATURE` | New user-visible functionality | Merge — preserve |
| `FIX` | Bug fix | Merge — preserve |
| `REFACTOR` | Code restructure, no behavior change | Merge — assess conflicts |
| `INFRA` | CI, build, tooling | Merge selectively |
| `BASELINE` | Auto-generated benchmark baselines (`[skip ci]`) | Skip — regenerate after merge |
| `PRIVATE` | Personal config, secrets, work-only changes | Do NOT merge upstream |
| `DUPLICATE` | Already on the other side (cherry-pick or same change) | Skip |

```bash
# Get the full commit list with stats for each side
git log $UPSTREAM_REMOTE/$BRANCH..HEAD --oneline --no-merges --stat
git log HEAD..$UPSTREAM_REMOTE/$BRANCH --oneline --no-merges --stat
```

Read each commit's diff summary (`--stat`) and assign a bucket. Auto-assign `BASELINE` for commits matching `chore(bench): update`.

## Step 4: Map Conflicts

Find files changed on **both** sides since the merge base:

```bash
MERGE_BASE=$(git merge-base HEAD $UPSTREAM_REMOTE/$BRANCH)

# Files changed on fork side
git diff --name-only $MERGE_BASE HEAD

# Files changed on upstream side
git diff --name-only $MERGE_BASE $UPSTREAM_REMOTE/$BRANCH

# Files changed on BOTH sides (potential conflicts)
comm -12 \
  <(git diff --name-only $MERGE_BASE HEAD | sort) \
  <(git diff --name-only $MERGE_BASE $UPSTREAM_REMOTE/$BRANCH | sort)
```

For each conflict file, compare the changes:
```bash
# What fork changed in this file
git diff $MERGE_BASE HEAD -- <file>

# What upstream changed in this file
git diff $MERGE_BASE $UPSTREAM_REMOTE/$BRANCH -- <file>
```

Classify each conflict:
- **Textual only** — git can auto-resolve (different hunks, no overlap)
- **Logical conflict** — same section edited differently (requires manual resolution)
- **Intent conflict** — one side added, other deleted (requires decision)

## Step 5: Simulate the Merge (Dry Run)

```bash
# Set diff3 conflict style so conflict markers show the common ancestor —
# makes manual resolution in Step 6 much faster
git config merge.conflictstyle diff3

# Create a temporary branch from fork HEAD (do NOT commit to it)
# This is only for conflict detection
git checkout -b merge-simulation HEAD

git merge --no-commit --no-ff $UPSTREAM_REMOTE/$BRANCH

# Show what would conflict
git status

# Abort — we are not executing the merge
git merge --abort
git checkout -
git branch -D merge-simulation
```

Note all files listed as `CONFLICT` in the status output.

## Step 6: Write the Merge Plan

Produce a markdown document with these sections:

### Plan Structure

```markdown
# Merge Plan: fork ↔ upstream — <date>

## Summary
- Fork ahead by N commits (after excluding baselines)
- Upstream ahead by M commits
- Merge base: <sha> (<date>)
- Conflict files: N files need manual resolution

## Commit Classification

### Fork-only commits (origin/main → upstream)
| SHA | Message | Bucket | Action |
|-----|---------|--------|--------|
| ... | ...     | FEATURE | Merge |
| ... | ...     | BASELINE | Skip |

### Upstream-only commits (upstream-fanatics/main → fork)
| SHA | Message | Bucket | Action |
|-----|---------|--------|--------|
| ... | ...     | FIX | Merge |

## Conflict Map

### Files with overlapping changes
| File | Fork change summary | Upstream change summary | Conflict type | Resolution notes |
|------|--------------------|-----------------------|--------------|-----------------|
| ...  | ...                | ...                   | Logical      | Keep both, reconcile X |

## Merge Strategy

**Direction**: Merge upstream INTO fork on a new branch
**Branch name**: `merge/upstream-YYYYMMDD`

**Sequence**:
1. Create integration branch from fork HEAD
2. `git merge upstream-fanatics/main`
3. Resolve conflicts in order: [list files with resolution guidance]
4. Verify: build passes, tests pass, benchmark baselines excluded

## Post-Merge Checklist
- [ ] `make build` passes
- [ ] `make test` passes
- [ ] `make lint` passes
- [ ] No benchmark baseline commits included
- [ ] No private config files included
- [ ] PR created against upstream-fanatics/main (or origin/main, depending on direction)
```

## Rules

- **Plan only** — never execute `git merge`, `git am`, or `git apply` during this skill. The dry-run simulation is the only exception, and it MUST end with `git merge --abort`.
- **Classify before recommending** — read every commit diff before assigning a bucket. Do not guess from the message alone.
- **Baseline commits are noise** — commits matching `chore(bench): update * baseline [skip ci]` are auto-generated artifacts. Always bucket as `BASELINE` and skip.
- **Conflict count drives complexity** — if >5 logical conflicts, flag the merge as HIGH COMPLEXITY and recommend splitting into multiple steps.
- **Both sides matter** — the plan must preserve features from the fork AND from upstream. Do not default to "take upstream" — assess each change on its merits.
- **Private guard** — before including any fork commit in the upstream direction, check: does it contain personal config, credentials, private paths, or work-only references? If so, bucket as `PRIVATE`.

---

## Related Skills

| Skill | When to apply |
|-------|--------------|
| `git-worktrees` | Execute the merge plan in an isolated branch without disturbing the working tree |
| `github-pr` | Create the pull request after the merge branch is clean and tests pass |

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…