[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M73593BZ33TKTNBDX8JNKYN8":3,"skill-same:01M73593BZ33TKTNBDX8JNKYN8":51},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":30,"commitSha":31,"skillDirectory":32,"forksCount":33,"repositoryHtmlUrl":34,"repositoryHomepageUrl":35,"ownerHtmlUrl":36,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":37,"install":38,"resources":50},"01M73593BZ33TKTNBDX8JNKYN8","resolve-conflicts",null,"Resolve merge conflicts.","Morrison-Lab\u002Fai-config","Morrison-Lab","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F104235520?v=4","01M734G0451S2G35MN4HFQK7XP","Morrison Lab","01M734G059BYFDFQA8YTPDGPMH",4,"MIT","https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Fresolve-conflicts\u002FSKILL.md",[],[],"2026-11-11 15:11:14",false,"---\nname: resolve-conflicts\ndescription: \"Resolve merge conflicts.\"\nuser-invocable: true\nallowed-tools:\n  - Bash\n  - Read\n  - Edit\n  - Write\n---\n\n# resolve-conflicts — consolidate the best of both branches\n\nA merge conflict means **both** branches edited the same region — and each side\nchanged it for a reason. The right resolution almost always keeps the *intent*\nof **both** sides. The default failure mode is to grab one side wholesale\n(`--ours` \u002F `--theirs`) and silently throw the other branch's work on the\nfloor. Don't. **Consolidate, don't choose.**\n\n## When this fires\n\n- Any git operation stops on a conflict: `git merge`, `git rebase`,\n  `git cherry-pick`, `git stash pop`, `git pull` (merge *or* rebase),\n  `git revert`. The tell is\n  `CONFLICT (content): Merge conflict in …` and `Unmerged paths:` in\n  `git status`.\n- \"resolve the conflicts\", \"fix the merge conflicts\", \"I have a merge\n  conflict\", \"consolidate the best of both branches\", \"help me merge these\n  branches\", \"the branch won't merge cleanly\".\n- **Delegated from another skill**: `sync-pr-branch` step 5 (\"resolve any\n  conflicts fully\"), `clean-branches`' rebase-onto-main step, or a stacked\n  `gii` MR. When one of those stops on a conflict, run this.\n\n## Verify before you act — don't trust `mergeable` alone\n\nBefore claiming and fixing a PR flagged by a cascade conflict scan\n(`post-merge` step 1.5, `ardi`'s opportunistic sweeps, `wrap-up` step 1), get\nground truth locally instead of acting on the API field directly:\n\n```bash\ngit fetch origin main \u003Cbranch> -q   # FETCH\ngit merge-tree --write-tree origin\u002Fmain origin\u002F\u003Cbranch>   # git >=2.38; no worktree\u002Fcheckout needed\necho \"exit: $?\"   # 0 = clean merge (no action needed); 1 = real conflict exists\n```\n\nTwo failure modes this catches:\n\n- **`mergeable == \"UNKNOWN\"` is not \"no conflict.\"** GitHub returns `UNKNOWN`\n  right after a push or a sibling PR's merge, while it's still computing —\n  sometimes for minutes. A cascade scan that filters only\n  `mergeable == \"CONFLICTING\"` silently skips a PR stuck in `UNKNOWN` even\n  when it's genuinely conflicting. Treat `UNKNOWN` the same as\n  `CONFLICTING`: a candidate to verify, not a PR to skip.\n- **`mergeable == \"CONFLICTING\"` (or a `dirty` mergeable_state) can itself be\n  stale.** GitHub computes it against whatever `main` snapshot it last saw —\n  if a sibling PR merged since, the flag may still say conflicting even\n  though a fresh merge against current `main` is clean. Acting on a stale\n  flag risks wasted resolution effort. (Seen on ai-config#372: flagged\n  `dirty`, but `git merge-tree` against current `main` came back clean —\n  no manual resolution was needed, just a `git merge` + push.)\n\nIf `git merge-tree` exits clean, there's nothing to resolve — skip the PR (or,\nif you want the branch to reflect the now-current `main`, do a plain\n`git merge origin\u002Fmain && git push`, no conflict markers involved). Only\nproceed to the consolidation procedure below when it reports a real conflict.\n\n**Check `git --version` before trusting the exit code — on git \u003C2.38 this\ncommand fails a different way that a naive check misreads.** `--write-tree`\nmode doesn't exist before 2.38; on an older install the command errors with\n`fatal: unknown rev --write-tree` (a non-zero exit, same as a real conflict)\nor, if scripted with a text-based check instead of the exit code, an error\nmessage that a naive `grep -qi conflict` fails to match — silently\nmisclassifying every single PR in a batch scan as clean (a false negative\nthat defeats the whole scan; hit across a 5-PR cascade scan on ai-config,\n2026-07-03). Confirm `git --version` is 2.38+ before relying on this\ncommand's exit code at all. On an older git, use the legacy 3-arg form\ninstead (same read-only guarantee, no worktree\u002Fcheckout):\n```bash\nbase=$(git merge-base origin\u002Fmain origin\u002F\u003Cbranch>)\ngit merge-tree \"$base\" origin\u002Fmain origin\u002F\u003Cbranch>   # git \u003C2.38 (any version)\n# conflict markers (\"\u003C\u003C\u003C\u003C\u003C\u003C\u003C\") in the output = real conflict; empty\u002Fclean = no conflict\n```\n\n## Core principle: consolidate the best of both branches\n\nEach conflict hunk has three inputs — the common **base**, **our** side, and\n**their** side. Picking a winner is right only when one side's change is\ngenuinely the *same* edit, or strictly subsumes the other. Otherwise the merge\nshould contain a **synthesis** that honors both:\n\n- Two different features touching neighbouring lines → keep **both** edits.\n- A refactor on one side + a bugfix on the other → apply the bugfix *to the\n  refactored code*.\n- A rename on one side + an edit to the old name on the other → carry the edit\n  onto the new name.\n- **A conflict over a string your branch turned into one shared definition**\n  (several per-consumer copies of a text, now read from one variable or file)\n  is not a one-hunk decision.\n  Main's edit may have been meant for only some of the consumers.\n  Before folding main's wording into the shared string,\n  list every file or chunk that reads it (`grep` for the name),\n  and read what main did to each consumer.\n  If main treated them differently, split the string.\n  (Morrison-Lab\u002Flds#2208: main reworded the alt text of the interactive figures\n  and deliberately kept the old wording for the static matplotlib figure;\n  putting the new wording into the shared string changed both,\n  and the review blocked the merge head, ai-config#5270.)\n- **`CONFLICT (add\u002Fadd)` on the same newly-added file** (common with sibling PRs)\n  → compare `:2:` vs `:3:` first; keep the side that carries the newer\n  baseline (often `main` after a sibling PR merged), then manually re-apply any\n  newer deltas from the other side (review fixes, wording corrections, version\n  pins) before staging.\n\nIf you can't see why a side changed the hunk, you're not ready to resolve it —\ngo read the history first (step 2).\n\n## Procedure\n\n1. **Survey the battlefield.** Know which operation you're in (the semantics of\n   \"ours\"\u002F\"theirs\" depend on it — see the warning below) and list the\n   conflicted files:\n   ```bash\n   git status                                   # header names the operation\n   git diff --name-only --diff-filter=U         # just the unmerged paths\n   ```\n\n2. **Understand BOTH sides before editing a single hunk.** For each conflicted\n   file, look at the three-way picture and *why* each side diverged:\n   ```bash\n   git log --merge --oneline -- \u003Cfile>   # commits from both sides (during a MERGE)\n   git show :1:\u003Cfile>                    # BASE (common ancestor)\n   git show :2:\u003Cfile>                    # OURS  (HEAD \u002F current side)\n   git show :3:\u003Cfile>                    # THEIRS (incoming side)\n   ```\n   The `:1:`\u002F`:2:`\u002F`:3:` index stages work in any conflicted operation.\n   `git log --merge` is merge-only; for the others, inspect the incoming side\n   directly via the operation's ref — `git show MERGE_HEAD` (merge),\n   `REBASE_HEAD` (rebase), `CHERRY_PICK_HEAD` (cherry-pick), `REVERT_HEAD`\n   (revert), or the kept `stash@{0}` entry for a stash pop\n   (`git stash show -p stash@{0}`).\n   Ask: what problem was each side solving? Different features? One refactor +\n   one fix? A move\u002Frename vs an in-place edit? The answer dictates the merge.\n\n3. **Consolidate.** Edit the working file into a version that keeps the valuable\n   change from **both** sides, then delete every conflict marker\n   (`\u003C\u003C\u003C\u003C\u003C\u003C\u003C`, `=======`, `>>>>>>>`). Take one side wholesale only when you've\n   confirmed the other side has nothing worth keeping — and note that you did.\n\n   **Record a decision for every hunk.**\n   Draft the table while you resolve, and finish it before you finish the\n   operation.\n   For each conflict hunk, write one row: what you kept from each side, what\n   you dropped (from which side) and why, and what you modified and why.\n   Write each side's real name, its branch, commit or stash entry, in place\n   of `\u003Cside A>` and `\u003Cside B>`, never \"ours\" or \"theirs\", since those flip\n   in a rebase (see the warning below).\n   For a merge, put the table in the merge commit message.\n   When the work is on a PR, also post it in the PR comment that announces\n   the result, since a squash merge discards the merge message and a rebase,\n   cherry-pick, revert or stash pop leaves none.\n   With no PR, put it in your report to the user.\n   A reviewer can then check each choice against both sides without redoing\n   the operation.\n   In the table, `\u003Cn>` is the hunk's number within the file, counted from 1,\n   and `\u003Clines>` is a line range (`12-18`), never the text itself, which\n   would break the table row.\n   The range is in the resolved file, except under \"Dropped\", where it is in\n   the named side's version of the file.\n   Write one row per dropped range, so a hunk that drops lines from both\n   sides gets two rows; repeat `\u003Cpath>`:`\u003Cn>` in each row, and put the Kept\n   and Modified ranges on the first row only, with `none` in the others.\n   Write `none` in any empty cell.\n   For a conflict with no text hunks (delete\u002Fmodify, rename, binary), write\n   `whole file` for `\u003Cn>` and `\u003Clines>`:\n\n   | File:hunk | Kept from `\u003Cside A>` | Kept from `\u003Cside B>` | Dropped (side, why) | Modified (why) |\n   | --- | --- | --- | --- | --- |\n   | `\u003Cpath>`:`\u003Cn>` | `\u003Clines or \"none\">` | `\u003Clines or \"none\">` | `\u003Clines>` (`\u003Cside>`, `\u003Creason>`) | `\u003Clines>` (`\u003Creason>`) |\n\n   A row with something in \"Dropped\" and no reason is the defect this table\n   exists to expose: content that either side added may be dropped only by a\n   decision you can state (Ezra, 2026-10-23, ai-config#4716).\n\n   **Also check whether the merged result duplicates content that already\n   exists elsewhere in the file — a separate problem from the textual\n   conflict itself.** This can arise two ways: a single incoming PR may\n   internally copy a sentence into a new section (the merge is clean, but\n   the result now says the same thing twice), or both branches may\n   independently add equivalent sentences in different sections (a\n   cross-branch variant with the same symptom). In both cases `git\n   merge`\u002F`merge-tree` reports zero conflicts even though the result\n   contains duplication — sometimes with a pronoun or reference that only\n   made sense in its original location. Grep the merged file for the\n   distinctive phrase from each side's addition before finishing;\n   if it already appears elsewhere, keep one copy (usually the version with\n   the clearer standalone referent) and drop the redundant one. (ai-config#446:\n   the merge itself was clean, but the incoming PR's own diff had separately\n   duplicated a CLAUDE.md sentence verbatim into a different section, where\n   \"this policy\" lost its antecedent — the intra-PR variant, caught by the\n   `@claude` review bot as a distinct finding from the conflict resolution.)\n\n4. **Prove no markers (or whitespace damage) slipped through:**\n   ```bash\n   git diff --check                      # flags leftover markers + whitespace errors\n   grep -rnE '^(\u003C\u003C\u003C\u003C\u003C\u003C\u003C |======= *$|>>>>>>> )' . --exclude-dir=.git   # precise markers, portable -E\n   ```\n\n5. **Run the repo's pre-commit checks before committing the resolution.** A\n   merge where each side compiled *separately* can still break *combined*.\n   Build \u002F lint \u002F test \u002F spellcheck per the current repo (use its `render` \u002F\n   `lint` \u002F `spell` \u002F `test` skills if it ships them). Don't commit a\n   resolution that fails checks.\n\n6. **Stage the resolved files, then finish the operation** — never `--abort` or\n   `--skip` away a conflict you were asked to resolve. The finish command\n   matches the operation `git status` names, **not** the command you typed\n   (a `git pull` is a merge *or* a rebase underneath — check which):\n   ```bash\n   git add \u003Cfile> …\n   git commit                 # merge (message prefilled from MERGE_MSG)\n   git rebase --continue      # rebase (incl. `git pull --rebase`)\n   git cherry-pick --continue # cherry-pick\n   git revert --continue      # revert (message prefilled)\n   # stash pop: no commit\u002Fcontinue — `git add` the resolution, then `git stash drop`.\n   #   A conflicted pop does NOT auto-drop the stash, and dropping it is\n   #   independent of committing: the resolved changes stay as working-tree edits.\n   ```\n\n## ⚠️ \"ours\" and \"theirs\" flip between merge and rebase\n\n- In a **merge** (`git merge X` on branch `B`): `ours`\u002F`:2:` = `B` (where you\n  are), `theirs`\u002F`:3:` = `X` (being merged in).\n- In a **rebase** (`git rebase X` on branch `B`): rebase replays *your* commits\n  onto `X`, so `ours`\u002F`:2:` = **`X`, the branch you're landing on**, and\n  `theirs`\u002F`:3:` = **your own commit being replayed**. The opposite of a merge.\n\nConfirm which physical branch is \"ours\" before you reach for `--ours` \u002F\n`--theirs` — getting this backwards silently keeps the wrong side. (This is\n*why* the skill defaults to consolidating both rather than picking a side.)\n\n## Relationship to other skills\n\n- **`sync-pr-branch`** (aliases `sync` \u002F `merge-main` \u002F `resync-branch`) — its\n  step 5 says \"resolve any conflicts fully\"; **this** is that step's how-to.\n  Run it when that sync stops on a conflict, then return to finish the sync.\n- **`clean-branches`** — when rebasing a stale branch onto `main` hits a\n  non-trivial conflict, resolve it here instead of skipping the branch.\n- **`gii` \u002F `split-concerns`** — stacked or parallel MRs are the usual source\n  of conflicts; this is how you clear them once they collide.\n- **`check-history`** — run *before* implementing to avoid creating avoidable\n  conflicts\u002Fregressions in the first place; this skill cleans up the ones that\n  still happen.\n- **`session-lock`** — **not** this skill.\n  It is\n  about two AI *sessions* clobbering one local checkout (a locking concern),\n  not git *content* conflicts. Different sense of \"conflict.\"\n\n## Anti-patterns\n\n- ❌ Blind `git checkout --ours\u002F--theirs \u003Cfile>` (or `git merge -X ours\u002Ftheirs`)\n  without confirming the dropped side had nothing worth keeping — the classic\n  silent-data-loss move.\n  The hook `hooks\u002Fwarn-one-sided-conflict-resolution.py` warns on these commands (and on `restore`, `merge-file`, `rebase`, `cherry-pick`, `revert` and `pull` with the same options).\n  It also warns on `git merge -s ours`, which drops the other branch's changes entirely.\n- ❌ Resolving a hunk before understanding *why* each side changed it.\n- ❌ Finishing a merge, rebase, cherry-pick, revert or stash pop\n  with no per-hunk keep\u002Fdrop\u002Fmodify record (step 3),\n  so nobody can tell a deliberate drop from a lost line.\n- ❌ Carrying the wording from one conflicting hunk into a string that several\n  consumers share, without reading main's version of each consumer.\n- ❌ Mixing up \"ours\"\u002F\"theirs\" in a rebase (they're reversed vs a merge).\n- ❌ Committing with conflict markers still in a file — always `git diff --check`.\n- ❌ Committing the resolution without running the repo's build\u002Flint\u002Ftest checks.\n- ❌ `git rebase --skip` \u002F `git merge --abort` to dodge a conflict you were\n  asked to resolve.\n- ❌ Force-pushing a half-resolved merge\u002Frebase.\n- ❌ Restoring a whole file from a pre-merge commit\n  (`git show \u003Cold>:\u003Cpath> > \u003Cpath>`) to recover one entry --- that reverts every\n  other change the file has taken, including everything the merge just brought\n  in.\n  Rebuild the resolution from the conflict stages (`:1:`\u002F`:2:`\u002F`:3:`, above)\n  and swap in only the hunk you came for.\n  Note that `git show :\u003Cpath>` is not a safe baseline here.\n  While the path is conflicted it has no stage 0 and the command fails, and\n  once you have staged a resolution stage 0 holds *your* content --- so if the\n  bad restore is what you staged, that is what comes back.\n  See\n  [`batch-merge-and-resolve`](..\u002F..\u002Fshared\u002Fworkflow\u002Fbatch-merge-and-resolve.md)'s\n  \"The recovery has a silent failure mode of its own, and no merge produces\n  it\", which is why the batch pass's per-merge re-check never surfaces this\n  one.\n- ❌ Rewriting a file from scratch with no conflict in sight,\n  such as rebasing a repo's instruction files on another repo's.\n  That is a one-sided merge of the old file, and it drops content the same way,\n  with no markers to warn you.\n  Before pushing, list what the old version had\n  (`git diff \u003Cbase> -- \u003Cpath>`, removed lines),\n  and confirm each substantive line either moved somewhere or was deliberately cut.\n  On 2026-09-28 a rebuild of Morrison-Lab\u002Fmln's `CLAUDE.md` and `AUTHORING.md` on rme's layout\n  dropped repository roles, a CI deadlock's symptoms, conversion history and rule links\n  until that audit restored them.\n","# resolve-conflicts — consolidate the best of both branches\n\nA merge conflict means **both** branches edited the same region — and each side\nchanged it for a reason. The right resolution almost always keeps the *intent*\nof **both** sides. The default failure mode is to grab one side wholesale\n(`--ours` \u002F `--theirs`) and silently throw the other branch's work on the\nfloor. Don't. **Consolidate, don't choose.**\n\n## When this fires\n\n- Any git operation stops on a conflict: `git merge`, `git rebase`,\n  `git cherry-pick`, `git stash pop`, `git pull` (merge *or* rebase),\n  `git revert`. The tell is\n  `CONFLICT (content): Merge conflict in …` and `Unmerged paths:` in\n  `git status`.\n- \"resolve the conflicts\", \"fix the merge conflicts\", \"I have a merge\n  conflict\", \"consolidate the best of both branches\", \"help me merge these\n  branches\", \"the branch won't merge cleanly\".\n- **Delegated from another skill**: `sync-pr-branch` step 5 (\"resolve any\n  conflicts fully\"), `clean-branches`' rebase-onto-main step, or a stacked\n  `gii` MR. When one of those stops on a conflict, run this.\n\n## Verify before you act — don't trust `mergeable` alone\n\nBefore claiming and fixing a PR flagged by a cascade conflict scan\n(`post-merge` step 1.5, `ardi`'s opportunistic sweeps, `wrap-up` step 1), get\nground truth locally instead of acting on the API field directly:\n\n```bash\ngit fetch origin main \u003Cbranch> -q   # FETCH\ngit merge-tree --write-tree origin\u002Fmain origin\u002F\u003Cbranch>   # git >=2.38; no worktree\u002Fcheckout needed\necho \"exit: $?\"   # 0 = clean merge (no action needed); 1 = real conflict exists\n```\n\nTwo failure modes this catches:\n\n- **`mergeable == \"UNKNOWN\"` is not \"no conflict.\"** GitHub returns `UNKNOWN`\n  right after a push or a sibling PR's merge, while it's still computing —\n  sometimes for minutes. A cascade scan that filters only\n  `mergeable == \"CONFLICTING\"` silently skips a PR stuck in `UNKNOWN` even\n  when it's genuinely conflicting. Treat `UNKNOWN` the same as\n  `CONFLICTING`: a candidate to verify, not a PR to skip.\n- **`mergeable == \"CONFLICTING\"` (or a `dirty` mergeable_state) can itself be\n  stale.** GitHub computes it against whatever `main` snapshot it last saw —\n  if a sibling PR merged since, the flag may still say conflicting even\n  though a fresh merge against current `main` is clean. Acting on a stale\n  flag risks wasted resolution effort. (Seen on ai-config#372: flagged\n  `dirty`, but `git merge-tree` against current `main` came back clean —\n  no manual resolution was needed, just a `git merge` + push.)\n\nIf `git merge-tree` exits clean, there's nothing to resolve — skip the PR (or,\nif you want the branch to reflect the now-current `main`, do a plain\n`git merge origin\u002Fmain && git push`, no conflict markers involved). Only\nproceed to the consolidation procedure below when it reports a real conflict.\n\n**Check `git --version` before trusting the exit code — on git \u003C2.38 this\ncommand fails a different way that a naive check misreads.** `--write-tree`\nmode doesn't exist before 2.38; on an older install the command errors with\n`fatal: unknown rev --write-tree` (a non-zero exit, same as a real conflict)\nor, if scripted with a text-based check instead of the exit code, an error\nmessage that a naive `grep -qi conflict` fails to match — silently\nmisclassifying every single PR in a batch scan as clean (a false negative\nthat defeats the whole scan; hit across a 5-PR cascade scan on ai-config,\n2026-07-03). Confirm `git --version` is 2.38+ before relying on this\ncommand's exit code at all. On an older git, use the legacy 3-arg form\ninstead (same read-only guarantee, no worktree\u002Fcheckout):\n```bash\nbase=$(git merge-base origin\u002Fmain origin\u002F\u003Cbranch>)\ngit merge-tree \"$base\" origin\u002Fmain origin\u002F\u003Cbranch>   # git \u003C2.38 (any version)\n# conflict markers (\"\u003C\u003C\u003C\u003C\u003C\u003C\u003C\") in the output = real conflict; empty\u002Fclean = no conflict\n```\n\n## Core principle: consolidate the best of both branches\n\nEach conflict hunk has three inputs — the common **base**, **our** side, and\n**their** side. Picking a winner is right only when one side's change is\ngenuinely the *same* edit, or strictly subsumes the other. Otherwise the merge\nshould contain a **synthesis** that honors both:\n\n- Two different features touching neighbouring lines → keep **both** edits.\n- A refactor on one side + a bugfix on the other → apply the bugfix *to the\n  refactored code*.\n- A rename on one side + an edit to the old name on the other → carry the edit\n  onto the new name.\n- **A conflict over a string your branch turned into one shared definition**\n  (several per-consumer copies of a text, now read from one variable or file)\n  is not a one-hunk decision.\n  Main's edit may have been meant for only some of the consumers.\n  Before folding main's wording into the shared string,\n  list every file or chunk that reads it (`grep` for the name),\n  and read what main did to each consumer.\n  If main treated them differently, split the string.\n  (Morrison-Lab\u002Flds#2208: main reworded the alt text of the interactive figures\n  and deliberately kept the old wording for the static matplotlib figure;\n  putting the new wording into the shared string changed both,\n  and the review blocked the merge head, ai-config#5270.)\n- **`CONFLICT (add\u002Fadd)` on the same newly-added file** (common with sibling PRs)\n  → compare `:2:` vs `:3:` first; keep the side that carries the newer\n  baseline (often `main` after a sibling PR merged), then manually re-apply any\n  newer deltas from the other side (review fixes, wording corrections, version\n  pins) before staging.\n\nIf you can't see why a side changed the hunk, you're not ready to resolve it —\ngo read the history first (step 2).\n\n## Procedure\n\n1. **Survey the battlefield.** Know which operation you're in (the semantics of\n   \"ours\"\u002F\"theirs\" depend on it — see the warning below) and list the\n   conflicted files:\n   ```bash\n   git status                                   # header names the operation\n   git diff --name-only --diff-filter=U         # just the unmerged paths\n   ```\n\n2. **Understand BOTH sides before editing a single hunk.** For each conflicted\n   file, look at the three-way picture and *why* each side diverged:\n   ```bash\n   git log --merge --oneline -- \u003Cfile>   # commits from both sides (during a MERGE)\n   git show :1:\u003Cfile>                    # BASE (common ancestor)\n   git show :2:\u003Cfile>                    # OURS  (HEAD \u002F current side)\n   git show :3:\u003Cfile>                    # THEIRS (incoming side)\n   ```\n   The `:1:`\u002F`:2:`\u002F`:3:` index stages work in any conflicted operation.\n   `git log --merge` is merge-only; for the others, inspect the incoming side\n   directly via the operation's ref — `git show MERGE_HEAD` (merge),\n   `REBASE_HEAD` (rebase), `CHERRY_PICK_HEAD` (cherry-pick), `REVERT_HEAD`\n   (revert), or the kept `stash@{0}` entry for a stash pop\n   (`git stash show -p stash@{0}`).\n   Ask: what problem was each side solving? Different features? One refactor +\n   one fix? A move\u002Frename vs an in-place edit? The answer dictates the merge.\n\n3. **Consolidate.** Edit the working file into a version that keeps the valuable\n   change from **both** sides, then delete every conflict marker\n   (`\u003C\u003C\u003C\u003C\u003C\u003C\u003C`, `=======`, `>>>>>>>`). Take one side wholesale only when you've\n   confirmed the other side has nothing worth keeping — and note that you did.\n\n   **Record a decision for every hunk.**\n   Draft the table while you resolve, and finish it before you finish the\n   operation.\n   For each conflict hunk, write one row: what you kept from each side, what\n   you dropped (from which side) and why, and what you modified and why.\n   Write each side's real name, its branch, commit or stash entry, in place\n   of `\u003Cside A>` and `\u003Cside B>`, never \"ours\" or \"theirs\", since those flip\n   in a rebase (see the warning below).\n   For a merge, put the table in the merge commit message.\n   When the work is on a PR, also post it in the PR comment that announces\n   the result, since a squash merge discards the merge message and a rebase,\n   cherry-pick, revert or stash pop leaves none.\n   With no PR, put it in your report to the user.\n   A reviewer can then check each choice against both sides without redoing\n   the operation.\n   In the table, `\u003Cn>` is the hunk's number within the file, counted from 1,\n   and `\u003Clines>` is a line range (`12-18`), never the text itself, which\n   would break the table row.\n   The range is in the resolved file, except under \"Dropped\", where it is in\n   the named side's version of the file.\n   Write one row per dropped range, so a hunk that drops lines from both\n   sides gets two rows; repeat `\u003Cpath>`:`\u003Cn>` in each row, and put the Kept\n   and Modified ranges on the first row only, with `none` in the others.\n   Write `none` in any empty cell.\n   For a conflict with no text hunks (delete\u002Fmodify, rename, binary), write\n   `whole file` for `\u003Cn>` and `\u003Clines>`:\n\n   | File:hunk | Kept from `\u003Cside A>` | Kept from `\u003Cside B>` | Dropped (side, why) | Modified (why) |\n   | --- | --- | --- | --- | --- |\n   | `\u003Cpath>`:`\u003Cn>` | `\u003Clines or \"none\">` | `\u003Clines or \"none\">` | `\u003Clines>` (`\u003Cside>`, `\u003Creason>`) | `\u003Clines>` (`\u003Creason>`) |\n\n   A row with something in \"Dropped\" and no reason is the defect this table\n   exists to expose: content that either side added may be dropped only by a\n   decision you can state (Ezra, 2026-10-23, ai-config#4716).\n\n   **Also check whether the merged result duplicates content that already\n   exists elsewhere in the file — a separate problem from the textual\n   conflict itself.** This can arise two ways: a single incoming PR may\n   internally copy a sentence into a new section (the merge is clean, but\n   the result now says the same thing twice), or both branches may\n   independently add equivalent sentences in different sections (a\n   cross-branch variant with the same symptom). In both cases `git\n   merge`\u002F`merge-tree` reports zero conflicts even though the result\n   contains duplication — sometimes with a pronoun or reference that only\n   made sense in its original location. Grep the merged file for the\n   distinctive phrase from each side's addition before finishing;\n   if it already appears elsewhere, keep one copy (usually the version with\n   the clearer standalone referent) and drop the redundant one. (ai-config#446:\n   the merge itself was clean, but the incoming PR's own diff had separately\n   duplicated a CLAUDE.md sentence verbatim into a different section, where\n   \"this policy\" lost its antecedent — the intra-PR variant, caught by the\n   `@claude` review bot as a distinct finding from the conflict resolution.)\n\n4. **Prove no markers (or whitespace damage) slipped through:**\n   ```bash\n   git diff --check                      # flags leftover markers + whitespace errors\n   grep -rnE '^(\u003C\u003C\u003C\u003C\u003C\u003C\u003C |======= *$|>>>>>>> )' . --exclude-dir=.git   # precise markers, portable -E\n   ```\n\n5. **Run the repo's pre-commit checks before committing the resolution.** A\n   merge where each side compiled *separately* can still break *combined*.\n   Build \u002F lint \u002F test \u002F spellcheck per the current repo (use its `render` \u002F\n   `lint` \u002F `spell` \u002F `test` skills if it ships them). Don't commit a\n   resolution that fails checks.\n\n6. **Stage the resolved files, then finish the operation** — never `--abort` or\n   `--skip` away a conflict you were asked to resolve. The finish command\n   matches the operation `git status` names, **not** the command you typed\n   (a `git pull` is a merge *or* a rebase underneath — check which):\n   ```bash\n   git add \u003Cfile> …\n   git commit                 # merge (message prefilled from MERGE_MSG)\n   git rebase --continue      # rebase (incl. `git pull --rebase`)\n   git cherry-pick --continue # cherry-pick\n   git revert --continue      # revert (message prefilled)\n   # stash pop: no commit\u002Fcontinue — `git add` the resolution, then `git stash drop`.\n   #   A conflicted pop does NOT auto-drop the stash, and dropping it is\n   #   independent of committing: the resolved changes stay as working-tree edits.\n   ```\n\n## ⚠️ \"ours\" and \"theirs\" flip between merge and rebase\n\n- In a **merge** (`git merge X` on branch `B`): `ours`\u002F`:2:` = `B` (where you\n  are), `theirs`\u002F`:3:` = `X` (being merged in).\n- In a **rebase** (`git rebase X` on branch `B`): rebase replays *your* commits\n  onto `X`, so `ours`\u002F`:2:` = **`X`, the branch you're landing on**, and\n  `theirs`\u002F`:3:` = **your own commit being replayed**. The opposite of a merge.\n\nConfirm which physical branch is \"ours\" before you reach for `--ours` \u002F\n`--theirs` — getting this backwards silently keeps the wrong side. (This is\n*why* the skill defaults to consolidating both rather than picking a side.)\n\n## Relationship to other skills\n\n- **`sync-pr-branch`** (aliases `sync` \u002F `merge-main` \u002F `resync-branch`) — its\n  step 5 says \"resolve any conflicts fully\"; **this** is that step's how-to.\n  Run it when that sync stops on a conflict, then return to finish the sync.\n- **`clean-branches`** — when rebasing a stale branch onto `main` hits a\n  non-trivial conflict, resolve it here instead of skipping the branch.\n- **`gii` \u002F `split-concerns`** — stacked or parallel MRs are the usual source\n  of conflicts; this is how you clear them once they collide.\n- **`check-history`** — run *before* implementing to avoid creating avoidable\n  conflicts\u002Fregressions in the first place; this skill cleans up the ones that\n  still happen.\n- **`session-lock`** — **not** this skill.\n  It is\n  about two AI *sessions* clobbering one local checkout (a locking concern),\n  not git *content* conflicts. Different sense of \"conflict.\"\n\n## Anti-patterns\n\n- ❌ Blind `git checkout --ours\u002F--theirs \u003Cfile>` (or `git merge -X ours\u002Ftheirs`)\n  without confirming the dropped side had nothing worth keeping — the classic\n  silent-data-loss move.\n  The hook `hooks\u002Fwarn-one-sided-conflict-resolution.py` warns on these commands (and on `restore`, `merge-file`, `rebase`, `cherry-pick`, `revert` and `pull` with the same options).\n  It also warns on `git merge -s ours`, which drops the other branch's changes entirely.\n- ❌ Resolving a hunk before understanding *why* each side changed it.\n- ❌ Finishing a merge, rebase, cherry-pick, revert or stash pop\n  with no per-hunk keep\u002Fdrop\u002Fmodify record (step 3),\n  so nobody can tell a deliberate drop from a lost line.\n- ❌ Carrying the wording from one conflicting hunk into a string that several\n  consumers share, without reading main's version of each consumer.\n- ❌ Mixing up \"ours\"\u002F\"theirs\" in a rebase (they're reversed vs a merge).\n- ❌ Committing with conflict markers still in a file — always `git diff --check`.\n- ❌ Committing the resolution without running the repo's build\u002Flint\u002Ftest checks.\n- ❌ `git rebase --skip` \u002F `git merge --abort` to dodge a conflict you were\n  asked to resolve.\n- ❌ Force-pushing a half-resolved merge\u002Frebase.\n- ❌ Restoring a whole file from a pre-merge commit\n  (`git show \u003Cold>:\u003Cpath> > \u003Cpath>`) to recover one entry --- that reverts every\n  other change the file has taken, including everything the merge just brought\n  in.\n  Rebuild the resolution from the conflict stages (`:1:`\u002F`:2:`\u002F`:3:`, above)\n  and swap in only the hunk you came for.\n  Note that `git show :\u003Cpath>` is not a safe baseline here.\n  While the path is conflicted it has no stage 0 and the command fails, and\n  once you have staged a resolution stage 0 holds *your* content --- so if the\n  bad restore is what you staged, that is what comes back.\n  See\n  [`batch-merge-and-resolve`](..\u002F..\u002Fshared\u002Fworkflow\u002Fbatch-merge-and-resolve.md)'s\n  \"The recovery has a silent failure mode of its own, and no merge produces\n  it\", which is why the batch pass's per-merge re-check never surfaces this\n  one.\n- ❌ Rewriting a file from scratch with no conflict in sight,\n  such as rebasing a repo's instruction files on another repo's.\n  That is a one-sided merge of the old file, and it drops content the same way,\n  with no markers to warn you.\n  Before pushing, list what the old version had\n  (`git diff \u003Cbase> -- \u003Cpath>`, removed lines),\n  and confirm each substantive line either moved somewhere or was deliberately cut.\n  On 2026-09-28 a rebuild of Morrison-Lab\u002Fmln's `CLAUDE.md` and `AUTHORING.md` on rme's layout\n  dropped repository roles, a CI deadlock's symptoms, conversion history and rule links\n  until that audit restored them.\n",{"name":5,"description":7,"allowed-tools":24,"user-invocable":29},[25,26,27,28],"Bash","Read","Edit","Write",true,"main","aed9be7a6beca85e267d19d9d354f50da6c37417","skills\u002Fresolve-conflicts",1,"https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config","https:\u002F\u002Fmorrison-lab.github.io\u002Fai-config\u002F","https:\u002F\u002Fgithub.com\u002FMorrison-Lab","2026-11-15 04:39:15",{"source":8,"skillName":5,"commands":39,"prompt":49,"zipAvailable":29},[40,43,46],{"tool":41,"command":42},"npx","npx skills add Morrison-Lab\u002Fai-config --skill resolve-conflicts",{"tool":44,"command":45},"bunx","bunx skills add Morrison-Lab\u002Fai-config --skill resolve-conflicts",{"tool":47,"command":48},"pnpm","pnpm dlx skills add Morrison-Lab\u002Fai-config --skill resolve-conflicts","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Fresolve-conflicts\u002FSKILL.md",[],[52,59,66,73,80,87],{"skillNo":53,"name":54,"nameZh":6,"description":55,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":56,"categories":57,"keywords":58,"publishedAt":19},"01M7359QPSB0GMXCG2RB0V34YY","send-notes","Send content to Morrison-Lab notes repos.","https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Fsend-notes\u002FSKILL.md",[],[],{"skillNo":60,"name":61,"nameZh":6,"description":62,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":63,"categories":64,"keywords":65,"publishedAt":19},"01M735ADD75T5N3CYSMV3PKZN7","stack-prs","Stack dependent PR on another.","https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Fstack-prs\u002FSKILL.md",[],[],{"skillNo":67,"name":68,"nameZh":6,"description":69,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":70,"categories":71,"keywords":72,"publishedAt":19},"01M7353JY4NDFPQEMYDBJSTYGC","finish-wave","Finish the current wave; start no new one.","https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Ffinish-wave\u002FSKILL.md",[],[],{"skillNo":74,"name":75,"nameZh":6,"description":76,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":77,"categories":78,"keywords":79,"publishedAt":19},"01M734Y0XAAEAWM3J2X7JFDZSM","advisor","Read-only second opinion from a different model family.","https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Fadvisor\u002FSKILL.md",[],[],{"skillNo":81,"name":82,"nameZh":6,"description":83,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":84,"categories":85,"keywords":86,"publishedAt":19},"01M73584TVDWNA1HMAQ8XXGSZV","rec","Provide recommendations for choices.","https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Frec\u002FSKILL.md",[],[],{"skillNo":88,"name":89,"nameZh":6,"description":90,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":91,"categories":92,"keywords":93,"publishedAt":19},"01M7357KC2MZNHTDDSY4DT9CGJ","push","Pre-push check for active sessions.","https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Fpush\u002FSKILL.md",[],[]]