@@ -48,8 +48,27 @@ if ! echo "$PR_NUMBER" | grep -qE '^[0-9]+$'; then
4848 exit 1
4949fi
5050
51+ # Guard against a previously aborted merge left in place — if MERGE_HEAD exists but
52+ # .codegraph/resolve/ was already cleaned, the next `git merge` call would fail immediately.
53+ if git rev-parse --verify MERGE_HEAD > /dev/null 2>&1 ; then
54+ echo " ERROR: An in-progress merge (MERGE_HEAD) already exists."
55+ echo " Run: git merge --abort && rm -rf .codegraph/resolve"
56+ echo " Then re-run /resolve $PR_NUMBER "
57+ exit 1
58+ fi
59+
60+ # Detect repo slug dynamically so the skill works in any fork or renamed org
61+ REPO=$( gh repo view --json nameWithOwner -q .nameWithOwner 2> /dev/null \
62+ || git remote get-url origin | sed -E ' s|.*github\.com[:/](.+)(\.git)?$|\1|' )
63+ if [ -z " $REPO " ]; then
64+ echo " ERROR: could not detect GitHub repo slug — ensure 'gh' is authenticated or 'origin' points to GitHub"
65+ exit 1
66+ fi
67+ echo " Detected repo: $REPO "
68+
5169# Verify PR exists and is open
52- gh pr view " $PR_NUMBER " --repo optave/codegraph --json number,state,headRefName,baseRefName \
70+ gh pr view " $PR_NUMBER " --repo " $REPO " --json number,state,headRefName,baseRefName \
71+
5372 --jq ' "PR #\(.number) [\(.state)] \(.headRefName) → \(.baseRefName)"' \
5473 || { echo " ERROR: PR #$PR_NUMBER not found or inaccessible" ; exit 1; }
5574```
@@ -58,18 +77,36 @@ Persist PR metadata for use in later phases (shell variables don't survive acros
5877
5978``` bash
6079PR_NUMBER=$( echo " ${ARGUMENTS:- } " | tr -d ' [:space:]#' )
80+ REPO=$( gh repo view --json nameWithOwner -q .nameWithOwner 2> /dev/null \
81+ || git remote get-url origin | sed -E ' s|.*github\.com[:/](.+)(\.git)?$|\1|' )
6182mkdir -p .codegraph/resolve
62- gh pr view " $PR_NUMBER " --repo optave/codegraph \
83+ # Trap ensures .codegraph/resolve/ is always cleaned up on non-zero exit, even after a crash.
84+ # This prevents stale state from corrupting a future re-run (Phase 1 reads head-branch / base-branch
85+ # from here, and an aborted merge would see stale values).
86+ trap ' rm -rf .codegraph/resolve' ERR
87+ echo " $REPO " > .codegraph/resolve/repo
88+ gh pr view " $PR_NUMBER " --repo " $REPO " \
89+
6390 --json number,headRefName,baseRefName,title,body \
6491 > .codegraph/resolve/pr-meta.json \
6592 || { echo " ERROR: failed to fetch PR #$PR_NUMBER metadata" ; exit 1; }
6693jq -r ' .headRefName' .codegraph/resolve/pr-meta.json > .codegraph/resolve/head-branch
6794jq -r ' .baseRefName' .codegraph/resolve/pr-meta.json > .codegraph/resolve/base-branch
6895echo " $PR_NUMBER " > .codegraph/resolve/pr-number
96+
97+ HEAD_BRANCH=$( cat .codegraph/resolve/head-branch)
98+ BASE_BRANCH=$( cat .codegraph/resolve/base-branch)
99+ if [ " $HEAD_BRANCH " = " $BASE_BRANCH " ]; then
100+ echo " ERROR: PR head and base are the same branch ($HEAD_BRANCH ) — nothing to merge"
101+ rm -rf .codegraph/resolve
102+ exit 1
103+ fi
104+
69105echo " Pre-flight passed. PR: $( jq -r ' .title' .codegraph/resolve/pr-meta.json) "
70106```
71107
72- ** Exit condition:** Git repo confirmed, ` gh ` and ` jq ` available, PR exists, metadata written to ` .codegraph/resolve/ ` .
108+ ** Exit condition:** Git repo confirmed, ` gh ` and ` jq ` available, repo slug detected, PR exists, metadata written to ` .codegraph/resolve/ ` .
109+
73110
74111---
75112
@@ -81,9 +118,11 @@ Check out the PR branch and run `git merge` to surface all conflicts.
81118HEAD_BRANCH=$( cat .codegraph/resolve/head-branch)
82119BASE_BRANCH=$( cat .codegraph/resolve/base-branch)
83120PR_NUMBER=$( cat .codegraph/resolve/pr-number)
121+ REPO=$( cat .codegraph/resolve/repo)
84122
85123echo " Checking out PR branch: $HEAD_BRANCH "
86- gh pr checkout " $PR_NUMBER " --repo optave/codegraph \
124+ gh pr checkout " $PR_NUMBER " --repo " $REPO " \
125+
87126 || { echo " ERROR: failed to check out PR #$PR_NUMBER " ; exit 1; }
88127
89128echo " Fetching latest origin/$BASE_BRANCH ..."
@@ -141,6 +180,13 @@ For each conflicting file, identify **which commits and PRs on the base branch**
141180BASE_BRANCH=$( cat .codegraph/resolve/base-branch)
142181ORIG_HEAD=$( cat .codegraph/resolve/orig-head)
143182MERGE_HEAD=$( cat .codegraph/resolve/merge-head)
183+ REPO=$( cat .codegraph/resolve/repo)
184+
185+ # file_key: collision-resistant short name derived from file path
186+ # Uses printf+sha1sum to handle paths with underscores (src/foo_bar.ts and src/foo/bar.ts
187+ # both become different keys, unlike tr '/' '_')
188+ file_key () { printf ' %s' " $1 " | sha1sum | cut -c1-16; }
189+
144190
145191# Find all commits that came in from the base branch and touched conflicting files
146192mkdir -p .codegraph/resolve/incoming-prs
@@ -151,28 +197,36 @@ i=0
151197while IFS= read -r FILE; do
152198 i=$(( i + 1 ))
153199 echo " [$i /$TOTAL ] Tracing incoming changes: $FILE "
200+ KEY=$( file_key " $FILE " )
201+
154202
155203 # Commits from origin/<base> that are not yet in the PR branch (what came in from main)
156204 # 2>/dev/null: suppress git's "unknown revision" if ORIG_HEAD or MERGE_HEAD are temporarily unavailable — the empty file case is handled below
157205 git log --oneline " $ORIG_HEAD ..$MERGE_HEAD " -- " $FILE " \
158- > " .codegraph/resolve/incoming-commits-$( echo " $FILE " | tr ' / ' ' _ ' ) .txt" 2> /dev/null || true
206+ > " .codegraph/resolve/incoming-commits-$KEY .txt" 2> /dev/null || true
159207
160- if [ ! -s " .codegraph/resolve/incoming-commits-$( echo " $FILE " | tr ' / ' ' _ ' ) .txt" ]; then
208+ if [ ! -s " .codegraph/resolve/incoming-commits-$KEY .txt" ]; then
161209 echo " (no direct commit history for $FILE on incoming side — likely an add/add conflict)"
162210 else
163211 echo " Incoming commits touching $FILE :"
164- cat " .codegraph/resolve/incoming-commits-$( echo " $FILE " | tr ' /' ' _' ) .txt"
212+ cat " .codegraph/resolve/incoming-commits-$KEY .txt"
213+
165214 fi
166215done < .codegraph/resolve/conflicting-files.txt
167216```
168217
169218For each incoming commit, find the PR that introduced it:
170219
171220``` bash
221+ REPO=$( cat .codegraph/resolve/repo)
172222mkdir -p .codegraph/resolve/source-prs
173223
224+ file_key () { printf ' %s' " $1 " | sha1sum | cut -c1-16; }
225+
174226while IFS= read -r FILE; do
175- COMMIT_FILE=" .codegraph/resolve/incoming-commits-$( echo " $FILE " | tr ' /' ' _' ) .txt"
227+ KEY=$( file_key " $FILE " )
228+ COMMIT_FILE=" .codegraph/resolve/incoming-commits-$KEY .txt"
229+
176230 [ -f " $COMMIT_FILE " ] || continue
177231
178232 while IFS= read -r COMMIT_LINE; do
@@ -188,7 +242,8 @@ while IFS= read -r FILE; do
188242 echo " Looking up PR for commit $SHA ..."
189243 # gh api returns empty array if commit is not associated with a PR
190244 # 2>/dev/null: suppress gh's "HTTP 422" or network error output — the || clause writes an empty array so later steps handle it gracefully
191- gh api " repos/optave/codegraph/commits/$SHA /pulls" \
245+ gh api " repos/$REPO /commits/$SHA /pulls" \
246+
192247 --jq ' [.[] | {number: .number, title: .title, body: .body, state: .state}]' \
193248 > " $PR_FILE " 2> /dev/null || echo ' []' > " $PR_FILE "
194249
@@ -205,6 +260,8 @@ done < .codegraph/resolve/conflicting-files.txt
205260Fetch full descriptions and diffs for each source PR:
206261
207262``` bash
263+ REPO=$( cat .codegraph/resolve/repo)
264+
208265mkdir -p .codegraph/resolve/source-pr-diffs
209266
210267for PR_FILE in .codegraph/resolve/source-prs/* .json; do
@@ -220,7 +277,8 @@ for PR_FILE in .codegraph/resolve/source-prs/*.json; do
220277 if [ ! -f " $META_FILE " ]; then
221278 echo " Fetching source PR #$SOURCE_PR description..."
222279 # 2>/dev/null: suppress gh's auth/network error text — the || clause writes a stub so the skill continues gracefully
223- gh pr view " $SOURCE_PR " --repo optave/codegraph \
280+ gh pr view " $SOURCE_PR " --repo " $REPO " \
281+
224282 --json number,title,body,baseRefName,headRefName \
225283 > " $META_FILE " 2> /dev/null \
226284 || { echo " WARN: could not fetch PR #$SOURCE_PR metadata — skipping" ; echo ' {}' > " $META_FILE " ; }
@@ -229,7 +287,8 @@ for PR_FILE in .codegraph/resolve/source-prs/*.json; do
229287 if [ ! -f " $DIFF_FILE " ]; then
230288 echo " Fetching source PR #$SOURCE_PR diff..."
231289 # 2>/dev/null: suppress gh's auth/network error text — the || clause creates an empty file so the skill continues gracefully
232- gh pr diff " $SOURCE_PR " --repo optave/codegraph \
290+ gh pr diff " $SOURCE_PR " --repo " $REPO " \
291+
233292 > " $DIFF_FILE " 2> /dev/null \
234293 || { echo " WARN: could not fetch PR #$SOURCE_PR diff — skipping" ; touch " $DIFF_FILE " ; }
235294 fi
@@ -309,6 +368,9 @@ MERGE_HEAD=$(cat .codegraph/resolve/merge-head)
309368TOTAL=$( wc -l < .codegraph/resolve/conflicting-files.txt | tr -d ' [:space:]' )
310369i=0
311370
371+ file_key () { printf ' %s' " $1 " | sha1sum | cut -c1-16; }
372+
373+
312374while IFS= read -r FILE; do
313375 i=$(( i + 1 ))
314376 echo " === [$i /$TOTAL ] Resolving: $FILE ==="
@@ -329,7 +391,9 @@ while IFS= read -r FILE; do
329391 echo " "
330392 echo " --- Source PR context for $FILE ---"
331393 # Show which source PRs touched this file (from Phase: Conflict Archaeology)
332- COMMIT_FILE=" .codegraph/resolve/incoming-commits-$( echo " $FILE " | tr ' /' ' _' ) .txt"
394+ KEY=$( file_key " $FILE " )
395+ COMMIT_FILE=" .codegraph/resolve/incoming-commits-$KEY .txt"
396+
333397 if [ -s " $COMMIT_FILE " ]; then
334398 while IFS= read -r COMMIT_LINE; do
335399 SHA=$( echo " $COMMIT_LINE " | awk ' {print $1}' )
@@ -435,31 +499,41 @@ Run tests and lint to confirm the resolution didn't break anything.
435499
436500``` bash
437501echo " === Detecting test runner ==="
438- if [ -f " pnpm-lock.yaml" ]; then TEST_CMD=" pnpm test"
502+ if [ -f " pnpm-lock.yaml" ] && command -v pnpm > /dev/null 2>&1 ; then TEST_CMD=" pnpm test"
503+ elif [ -f " pnpm-lock.yaml" ]; then TEST_CMD=" npx pnpm test"
504+
439505elif [ -f " yarn.lock" ] && command -v yarn > /dev/null 2>&1 ; then TEST_CMD=" yarn test"
440506elif [ -f " package.json" ]; then TEST_CMD=" npm test"
441507else
442508 echo " WARN: No recognised test runner found — skipping tests"
443- TEST_CMD=" true "
509+ TEST_CMD=" "
444510fi
445- echo " Running: $TEST_CMD "
446- $TEST_CMD || { echo " ERROR: tests failed after conflict resolution — fix before committing" ; exit 1; }
511+ if [ -n " $TEST_CMD " ]; then
512+ echo " Running: $TEST_CMD "
513+ eval " $TEST_CMD " || { echo " ERROR: tests failed after conflict resolution — fix before committing" ; exit 1; }
514+ fi
515+
447516```
448517
449518``` bash
450519echo " === Detecting lint runner ==="
451520if [ -f " biome.json" ] && command -v npx > /dev/null 2>&1 ; then
452- LINT_CMD=" npx biome check --reporter=summary src/ tests/"
453- elif find . -maxdepth 1 -name " eslint.config.*" | grep -q . ; then
521+ # Point biome at . and let biome.json's files.include/files.ignore govern scope
522+ LINT_CMD=" npx biome check --reporter=summary ."
523+ elif ls eslint.config.* > /dev/null 2>&1 ; then
524+
454525 LINT_CMD=" npx eslint ."
455526elif [ -f " package.json" ] && grep -q ' "lint"' package.json; then
456527 LINT_CMD=" npm run lint"
457528else
458529 echo " WARN: No recognised lint runner found — skipping lint"
459- LINT_CMD=" true"
530+ LINT_CMD=" "
531+ fi
532+ if [ -n " $LINT_CMD " ]; then
533+ echo " Running: $LINT_CMD "
534+ eval " $LINT_CMD " || { echo " ERROR: lint failed after conflict resolution — fix before committing" ; exit 1; }
460535fi
461- echo " Running: $LINT_CMD "
462- $LINT_CMD || { echo " ERROR: lint failed after conflict resolution — fix before committing" ; exit 1; }
536+
463537```
464538
465539** Exit condition:** Tests and lint pass (or were not applicable). No new failures introduced by the resolution.
@@ -524,4 +598,5 @@ echo "Conflict resolution complete."
524598- ** Run tests and lint before pushing.** A clean merge that breaks tests is not ready to push.
525599- ** No co-author lines** in commit messages.
526600- ** No Claude Code references** in commit messages or comments.
527- - ** Cleanup:** ` .codegraph/resolve/ ` is created during the skill and removed after a successful push. If the skill exits early (due to ambiguity or error), remove it with ` rm -rf .codegraph/resolve ` before re-running.
601+ - ** Cleanup:** ` .codegraph/resolve/ ` is created during the skill, removed automatically on any error exit (via ` trap ` ), and removed after a successful push. If a crash left both ` .codegraph/resolve/ ` stale ** and** an in-progress merge (MERGE_HEAD), run ` git merge --abort && rm -rf .codegraph/resolve ` before re-running — Phase 0 will detect the leftover MERGE_HEAD and remind you.
602+
0 commit comments