Commit501d01eunknown_key
fix(pr-pipeline): re-review on new commits, draft ready trigger, ai-approved gate fix
2 files changed+36−13501d01efabffd5dc870e73eea29110adc618a735
2 changed files+36−13
Modifiedsrc/lib/ai-review.ts+24−10View fileUnifiedSplit
@@ -236,15 +236,18 @@ async function resolveHeadSha(
236236}
237237
238238/**
239 * Has this PR already been reviewed by the AI? Detected by an existing
240 * PR comment carrying our summary marker. Cheap LIKE query — if it
241 * fails (DB hiccup) we fall back to "not yet" and re-review, which is
242 * idempotent at worst (a duplicate summary), never destructive.
239 * Has this PR already been reviewed by the AI for the given head SHA?
240 * Detected by an existing PR comment carrying our summary marker that
241 * also embeds a SHA marker matching the current head. If the stored SHA
242 * differs (new commits pushed) we allow a re-review. If no review
243 * exists yet we also allow it. Cheap LIKE query — if it fails (DB
244 * hiccup) we fall back to "not yet" and re-review, which is idempotent
245 * at worst (a duplicate summary), never destructive.
243246 */
244async function alreadyReviewed(prId: string): Promise<boolean> {
247async function alreadyReviewed(prId: string, headSha: string): Promise<boolean> {
245248 try {
246249 const [row] = await db
247 .select({ id: prComments.id })
250 .select({ body: prComments.body })
248251 .from(prComments)
249252 .where(
250253 and(
@@ -254,7 +257,13 @@ async function alreadyReviewed(prId: string): Promise<boolean> {
254257 )
255258 )
256259 .limit(1);
257 return !!row;
260 if (!row) return false;
261 // Extract the SHA embedded in the comment. If it matches the current
262 // head we skip (same commit reviewed already). If it differs or is
263 // missing we allow re-review (new commits pushed).
264 const shaMatch = row.body.match(/<!-- gluecron-ai-review:sha:([0-9a-f]+) -->/);
265 if (!shaMatch) return false; // Legacy comment without SHA — re-review.
266 return shaMatch[1] === headSha;
258267 } catch {
259268 return false;
260269 }
@@ -290,12 +299,17 @@ export async function triggerAiReview(
290299): Promise<void> {
291300 try {
292301 if (!isAiReviewEnabled()) return;
302
303 // Resolve the current head SHA early — needed for SHA-based idempotency
304 // on both the single-Claude and trio paths.
305 const headSha = await resolveHeadSha(ownerName, repoName, headBranch);
306
293307 const useTrio = isTrioReviewEnabled();
294308 if (
295309 !options.force &&
296310 (useTrio
297311 ? await alreadyTrioReviewed(prId)
298 : await alreadyReviewed(prId))
312 : await alreadyReviewed(prId, headSha))
299313 )
300314 return;
301315
@@ -356,7 +370,6 @@ export async function triggerAiReview(
356370 // trio helper owns its own persistence + audit; we bail after it.
357371 if (useTrio) {
358372 try {
359 const headSha = await resolveHeadSha(ownerName, repoName, headBranch);
360373 await runTrioReview({
361374 pullRequestId: prId,
362375 headSha,
@@ -412,7 +425,8 @@ export async function triggerAiReview(
412425 const verdict = result.approved
413426 ? "**AI review:** no blocking issues found."
414427 : `**AI review:** flagged ${result.comments.length} item(s) for human attention.`;
415 const summaryBody = `${AI_REVIEW_MARKER}\n## AI Code Review\n\n${verdict}\n\n${result.summary}`;
428 const shaMarker = headSha ? `<!-- gluecron-ai-review:sha:${headSha} -->` : "";
429 const summaryBody = `${AI_REVIEW_MARKER}${shaMarker ? `\n${shaMarker}` : ""}\n## AI Code Review\n\n${verdict}\n\n${result.summary}`;
416430 await db
417431 .insert(prComments)
418432 .values({
Modifiedsrc/routes/pulls.tsx+12−3View fileUnifiedSplit
@@ -5931,7 +5931,12 @@ pulls.post(
59315931 );
59325932 }
59335933
5934 // Check if AI review approved this PR
5934 // Check if AI review approved this PR.
5935 // The AI summary comment body uses:
5936 // "**AI review:** no blocking issues found." → approved
5937 // "**AI review:** flagged N item(s)..." → not approved
5938 // "severity: blocking" → explicit blocking (future)
5939 // If no AI comments exist yet, treat as approved (gate hasn't run).
59355940 const aiComments = await db
59365941 .select()
59375942 .from(prComments)
@@ -5941,9 +5946,13 @@ pulls.post(
59415946 eq(prComments.isAiReview, true)
59425947 )
59435948 );
5944 const aiApproved = aiComments.length === 0 || aiComments.some(
5945 (c) => c.body.includes("**Approved**") || c.body.includes("approved: true") || c.body.toLowerCase().includes("lgtm")
5949 const aiSummaryComment = aiComments.find((c) =>
5950 c.body.includes("<!-- gluecron-ai-review:summary -->")
59465951 );
5952 const aiApproved =
5953 !aiSummaryComment ||
5954 (aiSummaryComment.body.includes("no blocking issues found") &&
5955 !aiSummaryComment.body.includes("severity: blocking"));
59475956
59485957 // Run all green gate checks (GateTest + mergeability + AI review)
59495958 const gateResult = await runAllGateChecks(
59505959