Skip to content

Commit 839bb58

Browse files
orioltfclaude
andcommitted
fix(pr-review): code review fixes — null-line guard, max iterationId, missing tests, MARKER_FOUND guard
- match-finding.mjs: skip threads where start/end is null instead of defaulting to line 0, which caused false positive matches near top of file - detect-prior-review.mjs: use max() over all parsed iteration IDs instead of last-seen; thread insertion order no longer affects priorIterationId - review-pr.md (Step 3.5): add exit-code guard and value validation for MARKER_FOUND to prevent silent wrong-direction fallback on node failure - tests: add 6 new cases (31 total) covering null-end classifyThread, deleted-file [0,0] hunks, numeric ADO status 2/5, null-start/end matchFinding, and priorIterationId max-ordering in detectPriorReview Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 39658be commit 839bb58

6 files changed

Lines changed: 99 additions & 5 deletions

File tree

apps/claude-code/pr-review/commands/review-pr.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,12 @@ const prefix = '✅ Review complete — Iteration ' + process.env.PID
193193
const found = threads.some(t => t.threadId === sid && (t.comments ?? []).some(c => (c.content ?? '').startsWith(prefix)))
194194
console.log(found ? 'true' : 'false')
195195
EOJS
196-
)
196+
) || { echo "ERROR: partial-run check script failed — falling back to first-review mode for safety."; MARKER_FOUND="false"; }
197+
198+
if [ "$MARKER_FOUND" != "true" ] && [ "$MARKER_FOUND" != "false" ]; then
199+
echo "ERROR: unexpected MARKER_FOUND value '${MARKER_FOUND}' — falling back to first-review mode for safety."
200+
MARKER_FOUND="false"
201+
fi
197202

198203
if [ "$MARKER_FOUND" = "false" ]; then
199204
echo "No completion marker for Iteration $PRIOR_ITERATION_ID — partial prior run. Falling back to first-review mode."

apps/claude-code/pr-review/scripts/re-review/detect-prior-review.mjs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,15 @@ export function detectPriorReview({ threads, signaturePrefix }) {
4141
isSummaryThread: isSummaryCandidate && t.threadId === maxSummaryId,
4242
}))
4343

44-
// Last "Iteration N" seen across all bot comments in thread order
44+
// Highest "Iteration N" seen across all bot comments — use max, not last-seen,
45+
// so thread insertion order does not affect the result.
4546
let priorIterationId = null
4647
for (const t of priorThreads) {
4748
for (const c of t.comments) {
4849
const parsed = parseSignature(c.content ?? '')
49-
if (parsed) priorIterationId = parsed.iterationId
50+
if (parsed && (priorIterationId === null || parsed.iterationId > priorIterationId)) {
51+
priorIterationId = parsed.iterationId
52+
}
5053
}
5154
}
5255

apps/claude-code/pr-review/scripts/re-review/match-finding.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ export function matchFinding({ finding, priorThreads, driftLines = 3 }) {
2222
for (const t of priorThreads) {
2323
if (t.isSummaryThread) continue
2424
if (t.filePath !== filePath) continue
25-
const ts = (t.start?.line ?? 0) - driftLines
26-
const te = (t.end?.line ?? 0) + driftLines
25+
if (t.start == null || t.end == null) continue
26+
const ts = t.start.line - driftLines
27+
const te = t.end.line + driftLines
2728
if (Math.max(fs, ts) <= Math.min(fe, te)) return t
2829
}
2930
return null

apps/claude-code/pr-review/tests/classify-thread.test.mjs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,60 @@ describe('classifyThread', () => {
110110
const result = classifyThread({ thread, diffHunks: [], signaturePrefix: SIGNATURE_PREFIX })
111111
assert.equal(result, 'pending')
112112
})
113+
114+
it('thread with null end, file changed in diff → pending (intersection requires both start and end)', () => {
115+
/** @type {import('../scripts/re-review/classify-thread.mjs').PriorThread} */
116+
const thread = {
117+
threadId: 102,
118+
filePath: '/src/feature.ts',
119+
start: { line: 42 },
120+
end: null,
121+
comments: [{ content: `Finding.\n---\n${SIGNATURE_PREFIX} — Iteration 1` }],
122+
status: 'active',
123+
}
124+
const result = classifyThread({ thread, diffHunks: withChangesDiff, signaturePrefix: SIGNATURE_PREFIX })
125+
assert.equal(result, 'pending')
126+
})
127+
128+
it('file present in diff with all-zero hunks (deleted) → obsolete', () => {
129+
/** @type {import('../scripts/re-review/classify-thread.mjs').PriorThread} */
130+
const thread = {
131+
threadId: 103,
132+
filePath: '/src/deleted.ts',
133+
start: { line: 5 },
134+
end: { line: 5 },
135+
comments: [{ content: `Finding.\n---\n${SIGNATURE_PREFIX} — Iteration 1` }],
136+
status: 'active',
137+
}
138+
/** @type {import('../scripts/re-review/classify-thread.mjs').DiffHunk[]} */
139+
const hunks = [{ filePath: '/src/deleted.ts', startLine: 0, endLine: 0 }]
140+
const result = classifyThread({ thread, diffHunks: hunks, signaturePrefix: SIGNATURE_PREFIX })
141+
assert.equal(result, 'obsolete')
142+
})
143+
144+
it('numeric status 2 (wontFix) → addressed', () => {
145+
/** @type {import('../scripts/re-review/classify-thread.mjs').PriorThread} */
146+
const thread = {
147+
threadId: 104,
148+
filePath: '/src/api.ts',
149+
start: { line: 42 },
150+
end: { line: 42 },
151+
comments: [{ content: `Finding.\n---\n${SIGNATURE_PREFIX} — Iteration 1` }],
152+
status: 2,
153+
}
154+
assert.equal(classifyThread({ thread, diffHunks: noChangeDiff, signaturePrefix: SIGNATURE_PREFIX }), 'addressed')
155+
})
156+
157+
it('numeric status 5 (byDesign) → addressed', () => {
158+
/** @type {import('../scripts/re-review/classify-thread.mjs').PriorThread} */
159+
const thread = {
160+
threadId: 105,
161+
filePath: '/src/api.ts',
162+
start: { line: 42 },
163+
end: { line: 42 },
164+
comments: [{ content: `Finding.\n---\n${SIGNATURE_PREFIX} — Iteration 1` }],
165+
status: 5,
166+
}
167+
assert.equal(classifyThread({ thread, diffHunks: noChangeDiff, signaturePrefix: SIGNATURE_PREFIX }), 'addressed')
168+
})
113169
})

apps/claude-code/pr-review/tests/detect-prior-review.test.mjs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,28 @@ describe('detectPriorReview', () => {
4949
assert.notEqual(result.summaryThread, null)
5050
})
5151

52+
it('priorIterationId is highest iteration seen, regardless of thread order', () => {
53+
/** @type {import('../scripts/re-review/detect-prior-review.mjs').RawADOThread[]} */
54+
const threads = [
55+
{
56+
id: 1,
57+
threadContext: null,
58+
comments: [{ content: `## PR Review Summary\n\nSummary.\n---\n${SIGNATURE_PREFIX} — Iteration 2` }],
59+
status: 'active',
60+
},
61+
{
62+
id: 2,
63+
threadContext: { filePath: '/src/api.ts', rightFileStart: { line: 5 }, rightFileEnd: { line: 5 } },
64+
comments: [{ content: `Finding.\n---\n${SIGNATURE_PREFIX} — Iteration 1` }],
65+
status: 'active',
66+
},
67+
]
68+
const result = detectPriorReview({ threads, signaturePrefix: SIGNATURE_PREFIX })
69+
// Summary thread (Iteration 2) appears before inline thread (Iteration 1)
70+
// priorIterationId must be 2 (max), not 1 (last-seen in array order)
71+
assert.equal(result.priorIterationId, 2)
72+
})
73+
5274
it('highest threadId wins when multiple summary candidates present', () => {
5375
/** @type {import('../scripts/re-review/detect-prior-review.mjs').RawADOThread[]} */
5476
const threads = [

apps/claude-code/pr-review/tests/match-finding.test.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,11 @@ describe('matchFinding', () => {
6767
const result = matchFinding({ finding, priorThreads, driftLines: 0 })
6868
assert.equal(result, null)
6969
})
70+
71+
it('thread with null start/end does not match any line-based finding', () => {
72+
const finding = { filePath: '/src/api.ts', startLine: 1, endLine: 3 }
73+
const priorThreads = [makeThread(9, '/src/api.ts', null, null)]
74+
const result = matchFinding({ finding, priorThreads })
75+
assert.equal(result, null)
76+
})
7077
})

0 commit comments

Comments
 (0)