Skip to content

Commit e3a0be7

Browse files
rjmurillo-botrjmurilloclaudeCopilot
authored
feat: Implement PR merge state verification to prevent wasted review effort (#322)
* feat: add implementation plan for PR review merge state verification Session 85 lessons learned implementation plan: - Add PR merge state verification to pr-review command (Skill-PR-Review-006) - Create Test-PRMerged.ps1 script for GraphQL merge state checking - Document Thread Resolution Protocol (Skills PR-Review-004, -005) - Update Completion Criteria with merge verification Prevents wasted effort on already-merged PRs where gh pr view returns stale data. Related: Session 85, PR #315, PR #320 * feat: implement PR merge state verification (Issue #321) Implements Session 85 lessons learned to prevent wasted effort on merged PRs. Changes: - Create Test-PRMerged.ps1 script to check PR merge state via GraphQL * Exit code 0 = not merged (safe to proceed) * Exit code 1 = merged (skip review work) * GraphQL API is source of truth (gh pr view may return stale data) - Update pr-review command (.claude/commands/pr-review.md): * Add PR merge state verification to Step 1 * Add Thread Resolution Protocol section (Skills PR-Review-004, -005) * Update Completion Criteria with PR merge check - Thread Resolution Protocol documentation: * Single thread resolution (Skill-PR-Review-004) * Batch thread resolution using GraphQL mutation aliases (Skill-PR-Review-005) * Verification commands Testing: - ✅ Test-PRMerged.ps1 with merged PR #315 (exit code 1) - ✅ Test-PRMerged.ps1 with open PR #320 (exit code 0) Benefits: - Prevents wasted effort when gh pr view returns stale state - Reduces API calls via batch thread resolution (N calls → 1 call) - Documents 2-step process: reply + resolve thread Related: Session 85, PR #315, PR #320 Fixes #321 * docs: add Session 86 implementation log Comprehensive documentation of PR review improvements implementation. Deliverables: - Issue #321 created - Test-PRMerged.ps1 PowerShell script - pr-review.md updates (merge verification + thread resolution protocol) - Implementation plan document - PR #322 created Benefits: - Prevents wasted effort on merged PRs - Reduces API calls via batch thread resolution - Documents critical 2-step process (reply + resolve thread) Session metrics: - 45 minutes implementation time - 3 skills implemented (PR-Review-004, -005, -006) - 2 tests executed (merged PR #315, open PR #320) - 182 lines of code Related: Session 85, Issue #321, PR #322 * docs: Session 87 - Update out-of-date PR branches Updated 6 out of 16 PRs that were behind main: ✅ PR #313 (copilot/investigate-workflow-failure): 4 commits behind → up to date ✅ PR #310 (docs/adr-017): 5 commits behind → up to date ✅ PR #269 (copilot/add-pre-pr-validation-workflow): 17 commits behind → up to date ✅ PR #246 (docs/ai-misses): 10 commits behind → up to date ✅ PR #245 (refactor/issue-239-memory-decomposition-analysis): 22 commits behind → up to date ✅ PR #199 (feat/pr-comment-responder-memory-protocol): 10 commits behind → up to date 10 PRs require manual conflict resolution: ⚠️ PR #301, #300, #299, #285, #255, #247, #235, #202, #194, #143 Used gh pr update-branch to merge main into PR branches. Success rate: 37.5% (6/16 PRs updated without conflicts). Session log: .agents/sessions/2025-12-23-session-87-pr-branch-updates.md * fix: address PR #322 review comments Security fixes (gemini-code-assist[bot]): - Add $ErrorActionPreference = 'Stop' to Test-PRMerged.ps1 - Use parameterized GraphQL query to prevent injection vulnerability - Add try/catch for JSON parsing error handling Code quality fixes (Copilot): - Fix null reference on mergedBy (handle automated merges) - Fix string interpolation bug (use ${PullRequest} syntax) - Fix GraphQL mutation to use variables correctly Documentation fixes (Copilot): - Fix 'Skills' → 'Skill' capitalization consistency - Fix GraphQL variable inconsistency in mutation example - Add Owner/Repo fields to output object in planning doc - Fix 'gemini' → 'Gemini' capitalization - Clarify 182 lines = 96 (script) + 86 (docs) Addresses all review comments except #2644893439 (Pester tests). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR #322 Copilot review comments - Update planning document to match secure implementation: - Use parameterized GraphQL queries instead of string interpolation - Remove duplicate Owner/Repo property definitions - Fix unreachable code and consistent null handling - Add proper try/catch error handling - Fix pr-review.md issues: - Replace `continue` with `return` (valid outside loop context) - Complete GraphQL mutation example with threadId parameter 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(naming): add numeric IDs to skill references per ADR-017 Update skill reference names to comply with ADR-017 format: - pr-review-merge-state-verification → pr-review-006-merge-state-verification - pr-review-thread-resolution-single → pr-review-004-thread-resolution-single - pr-review-thread-resolution-batch → pr-review-005-thread-resolution-batch 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs(memory): extract session learnings to Serena memories Recursive learning extraction from session - 5 rounds total: - 6 new skills created - 3 existing skills updated - 6 rejected as duplicates New skills: - agent-workflow-post-implementation-critic-validation - orchestration-recursive-learning-extraction - pr-review-007-merge-state-verification - pr-review-008-session-state-continuity - pr-review-bot-mention-side-effects - validation-domain-index-format Updated skills: - graphql-pr-operations (thread resolution anti-pattern) - pattern-agent-generation-three-platforms (Claude variant maintenance) - pr-template-requirement (REST API remediation) - skill-index-selection-decision-tree (orphan prevention) All skills validated with atomicity >75% and indexed in domain files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: add Pester tests for Test-PRMerged.ps1 with 100% coverage (#383) * Initial plan * test: add comprehensive Pester tests for Test-PRMerged.ps1 with 100% coverage Co-authored-by: rjmurillo <6811113+rjmurillo@users.noreply.github.com> * fix: move Test-PRMerged.Tests.ps1 to correct location per governance standards Co-authored-by: rjmurillo <6811113+rjmurillo@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rjmurillo <6811113+rjmurillo@users.noreply.github.com> * fix: address PR review comments - skill ID consistency and documentation - Fix unclosed code block in pr-review.md (cursor[bot] critical bug) - Update all Skill-PR-Review-006 references to 007 for consistency - Correct evidence PR number from #345 to #315 - Update planning document status from [PLANNING] to [IMPLEMENTED] - Update implementation checklist to reflect completed tasks Addresses review comments from cursor[bot] and Copilot on PR #322 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: update test assertions to use Skill-PR-Review-007 Tests were checking for Skill-PR-Review-006 but script references 007. Addresses cursor[bot] comment on PR #322 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Signed-off-by: Richard Murillo <6811113+rjmurillo@users.noreply.github.com> Co-authored-by: rjmurillo-bot <rjmurillo-bot@users.noreply.github.com> Co-authored-by: Richard Murillo <6811113+rjmurillo@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
1 parent af97fdc commit e3a0be7

20 files changed

+1736
-11
lines changed
Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
# Session 85: PR Review Workflow Improvements
2+
3+
**Date**: 2025-12-23
4+
**Status**: [IMPLEMENTED]
5+
**Branch**: feat/pr-review-merge-state-verification
6+
**Related Session**: [Session 85](.agents/sessions/2025-12-23-session-85-pr-315-post-merge-analysis.md)
7+
8+
## Objective
9+
10+
Implement lessons learned from Session 85 to prevent wasted effort on already-merged PRs and improve PR review workflow efficiency.
11+
12+
## Background
13+
14+
Session 85 discovered that PR #315 was already merged, but `gh pr view` returned stale "OPEN" status. This led to 15 minutes of unnecessary thread resolution work. Three new skills were identified:
15+
16+
- **Skill-PR-Review-004**: Thread Resolution Protocol
17+
- **Skill-PR-Review-005**: Batch Thread Resolution Efficiency
18+
- **Skill-PR-Review-007**: PR Merge State Verification (prevents this issue)
19+
20+
## Changes Required
21+
22+
### 1. Add PR Merge State Verification to pr-review Command
23+
24+
**File**: `.claude/commands/pr-review.md`
25+
26+
**Location**: Step 1: Parse and Validate PRs
27+
28+
**Current**:
29+
```powershell
30+
pwsh .claude/skills/github/scripts/pr/Get-PRContext.ps1 -PullRequest {number}
31+
```
32+
33+
Verify: PR exists, is open (state != MERGED, CLOSED), targets current repo.
34+
35+
**Proposed Addition**:
36+
37+
```markdown
38+
### Step 1: Parse and Validate PRs
39+
40+
For `all-open`, query: `gh pr list --state open --json number,reviewDecision`
41+
42+
For each PR number, validate using:
43+
44+
```powershell
45+
pwsh .claude/skills/github/scripts/pr/Get-PRContext.ps1 -PullRequest {number}
46+
```
47+
48+
**CRITICAL - Verify PR Merge State (Skill-PR-Review-007)**:
49+
50+
Before proceeding with review work, verify PR has not been merged via GraphQL (source of truth):
51+
52+
```bash
53+
# Check merge state via GraphQL
54+
merge_state=$(gh api graphql -f query='
55+
query {
56+
repository(owner: "OWNER", name: "REPO") {
57+
pullRequest(number: PR_NUMBER) {
58+
state
59+
merged
60+
mergedAt
61+
}
62+
}
63+
}' --jq '.data.repository.pullRequest.merged')
64+
65+
if [[ "$merge_state" == "true" ]]; then
66+
echo "PR #PR_NUMBER is already merged. Skipping review work."
67+
continue # Skip to next PR
68+
fi
69+
```
70+
71+
Verify: PR exists, is open (state != MERGED, CLOSED), targets current repo, **NOT already merged**.
72+
```
73+
74+
**Rationale**: `gh pr view --json state` may return stale data for recently merged PRs. GraphQL API is source of truth.
75+
76+
### 2. Update pr-comment-responder Skill Instructions
77+
78+
**File**: User skill at `~/.claude/skills/pr-comment-responder/SKILL.md` (user-managed)
79+
80+
**Location**: Phase 1: Context Gathering, Step 1.2
81+
82+
**Proposed Addition**:
83+
84+
```markdown
85+
### Step 1.2: Verify PR Merge State (BLOCKING)
86+
87+
**CRITICAL - Skill-PR-Review-007**: Before fetching PR metadata, verify PR has not been merged.
88+
89+
```bash
90+
# Check merge state via GraphQL (source of truth)
91+
merge_state=$(gh api graphql -f query='
92+
query {
93+
repository(owner: "OWNER", name: "REPO") {
94+
pullRequest(number: PR_NUMBER) {
95+
state
96+
merged
97+
mergedAt
98+
}
99+
}
100+
}' --jq '.data.repository.pullRequest')
101+
102+
if [[ "$(echo $merge_state | jq -r '.merged')" == "true" ]]; then
103+
echo "[SKIP] PR #PR_NUMBER merged at $(echo $merge_state | jq -r '.mergedAt'). No review work needed."
104+
exit 0
105+
fi
106+
```
107+
108+
**If merged=true**: Skip all remaining phases. Create session log documenting skip reason, then exit.
109+
110+
**Why this matters**: `gh pr view` may return stale "OPEN" for recently merged PRs → wasted effort
111+
```
112+
113+
**Note**: This is documentation guidance for the user skill. User must update their own skill file.
114+
115+
### 3. Document Thread Resolution Protocol
116+
117+
**File**: `.claude/commands/pr-review.md`
118+
119+
**Location**: New section after "Completion Criteria"
120+
121+
**Proposed Addition**:
122+
123+
```markdown
124+
## Thread Resolution Protocol (Skill-PR-Review-004, -005)
125+
126+
### Single Thread Resolution
127+
128+
After replying to a review comment, resolve the thread via GraphQL:
129+
130+
```bash
131+
# Step 1: Reply to comment (via pr-comment-responder skill)
132+
# Step 2: Resolve thread (REQUIRED separate step)
133+
gh api graphql -f query='
134+
mutation($id: ID!) {
135+
resolveReviewThread(input: {threadId: $id}) {
136+
thread { isResolved }
137+
}
138+
}' -f id="PRRT_xxx"
139+
```
140+
141+
**CRITICAL**: Replying to a comment does NOT automatically resolve the thread.
142+
143+
### Batch Thread Resolution (Skill-PR-Review-005)
144+
145+
For 2+ threads, use GraphQL mutation aliases for efficiency:
146+
147+
```bash
148+
gh api graphql -f query='
149+
mutation {
150+
t1: resolveReviewThread(input: {threadId: "PRRT_xxx"}) { thread { id isResolved } }
151+
t2: resolveReviewThread(input: {threadId: "PRRT_yyy"}) { thread { id isResolved } }
152+
t3: resolveReviewThread(input: {threadId: "PRRT_zzz"}) { thread { id isResolved } }
153+
}
154+
'
155+
```
156+
157+
**Benefits**:
158+
- 1 API call instead of N calls
159+
- Reduced network latency (1 round trip vs N)
160+
- Atomic operation (all succeed or all fail)
161+
162+
### Verification
163+
164+
```bash
165+
# Check for unresolved threads
166+
unresolved_count=$(gh api graphql -f query='
167+
query {
168+
repository(owner: "OWNER", name: "REPO") {
169+
pullRequest(number: PR_NUMBER) {
170+
reviewThreads(first: 100) {
171+
nodes { id isResolved }
172+
}
173+
}
174+
}
175+
}' --jq '.data.repository.pullRequest.reviewThreads.nodes | map(select(.isResolved == false)) | length')
176+
177+
echo "Unresolved threads: $unresolved_count"
178+
```
179+
```
180+
181+
### 4. Create PowerShell Script for PR Merge Verification
182+
183+
**File**: `.claude/skills/github/scripts/pr/Test-PRMerged.ps1` (NEW)
184+
185+
**Purpose**: Reusable script to check PR merge state via GraphQL
186+
187+
**Content**:
188+
189+
```powershell
190+
<#
191+
.SYNOPSIS
192+
Checks if a GitHub Pull Request has been merged.
193+
194+
.DESCRIPTION
195+
Queries GitHub GraphQL API to determine PR merge state.
196+
Use this before starting PR review work to prevent wasted effort on merged PRs.
197+
Per Skill-PR-Review-007: gh pr view may return stale data.
198+
199+
.PARAMETER Owner
200+
Repository owner. Inferred from git remote if not provided.
201+
202+
.PARAMETER Repo
203+
Repository name. Inferred from git remote if not provided.
204+
205+
.PARAMETER PullRequest
206+
PR number (required).
207+
208+
.EXAMPLE
209+
.\Test-PRMerged.ps1 -PullRequest 315
210+
211+
.NOTES
212+
Exit Codes: 0=Not merged, 1=Merged, 2=Error
213+
Source: Skill-PR-Review-007 (Session 85)
214+
#>
215+
216+
[CmdletBinding()]
217+
param(
218+
[string]$Owner,
219+
[string]$Repo,
220+
[Parameter(Mandatory)] [int]$PullRequest
221+
)
222+
223+
Import-Module (Join-Path $PSScriptRoot ".." ".." "modules" "GitHubHelpers.psm1") -Force
224+
225+
Assert-GhAuthenticated
226+
$resolved = Resolve-RepoParams -Owner $Owner -Repo $Repo
227+
$Owner = $resolved.Owner
228+
$Repo = $resolved.Repo
229+
230+
# Use parameterized GraphQL query to prevent injection
231+
$query = @'
232+
query($owner: String!, $repo: String!, $prNumber: Int!) {
233+
repository(owner: $owner, name: $repo) {
234+
pullRequest(number: $prNumber) {
235+
state
236+
merged
237+
mergedAt
238+
mergedBy {
239+
login
240+
}
241+
}
242+
}
243+
}
244+
'@
245+
246+
Write-Verbose "Querying GraphQL for PR #${PullRequest} merge state"
247+
248+
$result = gh api graphql -f query="$query" -f owner="$Owner" -f repo="$Repo" -F prNumber=$PullRequest 2>&1
249+
if ($LASTEXITCODE -ne 0) {
250+
Write-ErrorAndExit "GraphQL query failed: $result" 2
251+
}
252+
253+
try {
254+
$pr = ($result | ConvertFrom-Json).data.repository.pullRequest
255+
256+
if (-not $pr) {
257+
Write-ErrorAndExit "PR #$PullRequest not found in $Owner/$Repo. The query might have failed or the PR does not exist." 2
258+
}
259+
}
260+
catch {
261+
Write-ErrorAndExit "Failed to parse GraphQL response for PR #$PullRequest. Response: $result. Error: $_" 2
262+
}
263+
264+
$output = [PSCustomObject]@{
265+
Success = $true
266+
PullRequest = $PullRequest
267+
Owner = $Owner
268+
Repo = $Repo
269+
State = $pr.state
270+
Merged = $pr.merged
271+
MergedAt = $pr.mergedAt
272+
MergedBy = if ($pr.mergedBy) { $pr.mergedBy.login } else { $null }
273+
}
274+
275+
Write-Output $output
276+
277+
if ($pr.merged) {
278+
$mergedByText = if ($pr.mergedBy) { "@$($pr.mergedBy.login)" } else { "automated process" }
279+
Write-Host "[MERGED] PR #$PullRequest merged at $($pr.mergedAt) by $mergedByText" -ForegroundColor Yellow
280+
exit 1 # Exit code 1 = merged (skip review work)
281+
} else {
282+
Write-Host "[NOT MERGED] PR #$PullRequest is not merged (state: $($pr.state))" -ForegroundColor Green
283+
exit 0 # Exit code 0 = not merged (safe to proceed)
284+
}
285+
```
286+
287+
### 5. Update Completion Criteria Documentation
288+
289+
**File**: `.claude/commands/pr-review.md`
290+
291+
**Location**: Completion Criteria section, line 171
292+
293+
**Current**:
294+
```markdown
295+
| No unresolved threads | GraphQL query for unresolved reviewThreads | Yes |
296+
```
297+
298+
**Proposed Change**:
299+
```markdown
300+
| No unresolved threads | GraphQL query for unresolved reviewThreads (see Thread Resolution Protocol) | Yes |
301+
| PR not merged | Test-PRMerged.ps1 exit code 0 | Yes |
302+
```
303+
304+
## Implementation Checklist
305+
306+
- [x] Update `.claude/commands/pr-review.md` with merge state verification (Step 1)
307+
- [x] Add Thread Resolution Protocol section to pr-review.md
308+
- [x] Create `.claude/skills/github/scripts/pr/Test-PRMerged.ps1`
309+
- [x] Test `Test-PRMerged.ps1` with merged PR (e.g., PR #315)
310+
- [x] Test `Test-PRMerged.ps1` with open PR
311+
- [ ] Document pr-comment-responder skill update guidance (user-managed skill)
312+
- [x] Update Completion Criteria with PR merge verification
313+
- [ ] Create example scenarios document
314+
- [x] Update session log with implementation results
315+
316+
## Testing Strategy
317+
318+
### Test Case 1: Merged PR Detection
319+
320+
**Scenario**: Run pr-review on already-merged PR #315
321+
322+
**Expected**:
323+
```bash
324+
$ pwsh .claude/skills/github/scripts/pr/Test-PRMerged.ps1 -PullRequest 315
325+
PR #315 merged at 2025-12-23T21:30:00Z by @rjmurillo
326+
Exit code: 1
327+
```
328+
329+
**Result**: pr-review workflow skips PR #315 with message "PR already merged"
330+
331+
### Test Case 2: Open PR Processing
332+
333+
**Scenario**: Run pr-review on open PR with comments
334+
335+
**Expected**:
336+
```bash
337+
$ pwsh .claude/skills/github/scripts/pr/Test-PRMerged.ps1 -PullRequest 320
338+
PR #320 is not merged (state: OPEN)
339+
Exit code: 0
340+
```
341+
342+
**Result**: pr-review workflow proceeds with normal review process
343+
344+
### Test Case 3: Batch Thread Resolution
345+
346+
**Scenario**: Resolve 5+ threads using GraphQL mutation aliases
347+
348+
**Expected**: Single API call resolves all threads atomically
349+
350+
**Verification**:
351+
```bash
352+
unresolved_count=$(gh api graphql ... | jq '... | length')
353+
# Should return 0
354+
```
355+
356+
## Success Criteria
357+
358+
- [ ] All test cases pass
359+
- [ ] Documentation updated in pr-review.md
360+
- [ ] PowerShell script created and tested
361+
- [ ] No breaking changes to existing workflow
362+
- [ ] Session log documents implementation
363+
- [ ] PR created for review
364+
365+
## Risks and Mitigations
366+
367+
| Risk | Impact | Mitigation |
368+
|------|--------|------------|
369+
| GraphQL query timeout | PR review blocked | Add timeout handling, fallback to gh pr view |
370+
| Script dependency on GitHubHelpers | Module not found error | Add import verification |
371+
| Breaking change to pr-comment-responder | User skill breaks | Document as opt-in guidance, not requirement |
372+
| False positive "merged" detection | Skip valid PRs | Verify with multiple fields (state, merged, mergedAt) |
373+
374+
## References
375+
376+
- [Session 85 Log](.agents/sessions/2025-12-23-session-85-pr-315-post-merge-analysis.md)
377+
- [Skill-PR-Review-004, -005, -006](.serena/memories/pr-review-core-workflow.md)
378+
- [PR #315](https://github.com/rjmurillo/ai-agents/pull/315) - Merged PR used for testing
379+
- [PR #320](https://github.com/rjmurillo/ai-agents/pull/320) - Session 85 documentation PR
380+
381+
---
382+
383+
**Status**: [PLANNING] → Ready for implementation

0 commit comments

Comments
 (0)