
Resolve Merge Conflicts
Summarize unmerged Git conflict hunks and give your agent compact context to resolve merges without opening every file manually.
Install
npx skills add https://github.com/warpdotdev/common-skills --skill resolve-merge-conflictsWhat is this skill?
- Parses Git unmerged index entries via ls-files -u and conflict marker blocks
- Supports standard <<<<<<< / ||||||| / >>>>>>> and labeled conflict sections
- Emits JSON summaries with difflib-style context for agent-friendly review
- Runs from any path inside a repo using git rev-parse --show-toplevel
- Python CLI designed for subprocess/git failure messages agents can act on
Adoption & trust: 3.5k installs on skills.sh; 18 GitHub stars; 3/3 security scanners passed (skills.sh audits).
Recommended Skills
Triagemattpocock/skills
Caveman Commitjuliusbrussee/caveman
Using Git Worktreesobra/superpowers
Finishing A Development Branchobra/superpowers
Git Commitgithub/awesome-copilot
Git Guardrails Claude Codemattpocock/skills
Journey fit
Primary fit
Merge work usually surfaces while integrating branches and dependencies during active product development. Conflict resolution is tightly coupled to repo tooling and third-party branch merges, which Prism shelves under build integrations.
Common Questions / FAQ
Is Resolve Merge Conflicts safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.
SKILL.md
READMESKILL.md - Resolve Merge Conflicts
#!/usr/bin/env python3 """Summarize and extract compact merge-conflict context from a Git repository.""" from __future__ import annotations import argparse import difflib import json import re import subprocess import sys from pathlib import Path START_RE = re.compile(r"^<<<<<<<(?: (.*))?$") BASE_RE = re.compile(r"^\|\|\|\|\|\|\|(?: (.*))?$") END_RE = re.compile(r"^>>>>>>>(?: (.*))?$") def run_git(repo_root: Path, *args: str) -> str: result = subprocess.run( ["git", "-C", str(repo_root), *args], capture_output=True, text=True, check=False, ) if result.returncode != 0: message = result.stderr.strip() or result.stdout.strip() or "unknown git error" raise RuntimeError(f"git {' '.join(args)} failed: {message}") return result.stdout def find_repo_root(start: Path) -> Path: result = subprocess.run( ["git", "-C", str(start), "rev-parse", "--show-toplevel"], capture_output=True, text=True, check=False, ) if result.returncode != 0: message = result.stderr.strip() or result.stdout.strip() or "not a git repository" raise RuntimeError(message) return Path(result.stdout.strip()).resolve() def get_unmerged_entries(repo_root: Path) -> dict[str, dict[int, dict[str, str]]]: entries: dict[str, dict[int, dict[str, str]]] = {} output = run_git(repo_root, "ls-files", "-u", "-z") for record in output.split("\0"): if not record: continue metadata, path = record.split("\t", 1) mode, object_id, stage_text = metadata.split() file_entry = entries.setdefault(path, {}) file_entry[int(stage_text)] = {"mode": mode, "object_id": object_id} return entries def read_text_file(path: Path) -> list[str] | None: if not path.exists() or path.is_dir(): return None try: text = path.read_text(encoding="utf-8", errors="replace") except OSError: return None if "\x00" in text: return None return text.splitlines() def read_stage_text(repo_root: Path, path: str, stage: int) -> list[str] | None: result = subprocess.run( ["git", "-C", str(repo_root), "show", f":{stage}:{path}"], capture_output=True, text=True, check=False, ) if result.returncode != 0: return None if "\x00" in result.stdout: return None return result.stdout.splitlines() def truncate_lines(lines: list[str], max_lines: int) -> list[str]: if len(lines) <= max_lines: return lines omitted = len(lines) - max_lines return [*lines[:max_lines], f"... ({omitted} more lines omitted)"] def build_diff( left_lines: list[str], right_lines: list[str], left_label: str, right_label: str, max_lines: int, ) -> list[str]: diff = list( difflib.unified_diff( left_lines, right_lines, fromfile=left_label, tofile=right_label, lineterm="", ) ) if not diff: diff = ["(no textual diff)"] return truncate_lines(diff, max_lines) def classify_conflict(stages: list[int], marker_hunks: int) -> str: if marker_hunks: return "text" stage_set = set(stages) if stage_set == {2, 3}: return "add/add" if stage_set == {1, 2}: return "deleted-by-them" if stage_set == {1, 3}: return "deleted-by-us" if stage_set == {1, 2, 3}: return "index-only" return "unmerged" def normalize_requested_path(repo_root: Path, raw_path: str) -> str: path = Path(raw_path) candidate = path.resolve() if path.is_absolute() else (repo_root / path).resolve() try: return str(candidate.relative_to(repo_root)) except ValueError as error: raise RuntimeError(f"path is outside repository: {raw_path}") from error def parse_conflict_hunks(lines: list[str], context: int) -> tuple[list[dict[str, object]], str | None]: h