53970308ee
* add incremental re-review on new PR commits When new commits are pushed to a PR that Pullfrog has previously reviewed, automatically perform a focused re-review on only the new changes. Includes a supersede mechanism to abort stale in-flight reviews on rapid pushes, a new IncrementalReview mode with incremental diff + prior-feedback awareness, and a PRReview tracking model so re-review fires for both auto-reviewed and manually-triggered PRs. Reviews now always submit (APPROVE when clean). Co-authored-by: Cursor <cursoragent@cursor.com> * simplify re-review eligibility and add summary to incremental reviews Remove the path1/path2 distinction for re-review eligibility — now simply requires prReReview=enabled and a prior Pullfrog review on the PR. Show the re-review toggle regardless of prCreated setting. Add a top-level summary body to incremental reviews for consistency with full reviews. Co-authored-by: Cursor <cursoragent@cursor.com> * replace superseded polling with server-side in-flight dedup and add prApproveEnabled setting Made-with: Cursor * add armstrong cursor command Made-with: Cursor * update armstrong * feat: Pullfrogger game v1 (#378) * Frogger game basis code (CC0 1.0 Unversal license). * Initial React port. * fix props for Sprite. * fixed context issue in FroggerGame. * Fixed format. * Fixed lint issue in FroggerGame. * Restoring LeapingLoader, using FroggerGame as a new fallback in Suspense. * feat: Display toast when URL ready. * Add props constraints on Sprite. * feat: frog sprite. * fix: zoom and alignment. * fix: extract const. * fix: mv types. * fix: mv game into index.tsx file. * fix: replacing deprecated event prop which with key. * feat: Log sprite. * feat: turtle sprite. * Adjusting game colors. * feat: sprites for the cars. * rm primitive sprites. * fix: bulldozer sprite position. * Adjusting colors. * Shape constraints. * rm original. * minor: naming, cleanup. * fix: renderers dict. * fix: adjusting and renaming racer. * fix renderer binding for scored frogs. * feat: responsive layout with gap below and maintained aspect ratio. * grammar fix. * feat: road lane dividers. * feat: using AbortController to cleanup events. * fix: cleanup and shortening. * feat: extracting drawGameBackground. * feat: initObstacleRows and updateAndDrawObstacles helpers. * feat: initFroggers helper. * feat: drawFroggers helper. * feat: checkForCollision helper. * feat: makeKeydownHandler helper. * feat: listenToKeyboardEvents helper. * mv cleanup into drawGameBackground. * fix: cleanup. * feat: better adjustBrightness helper. * Polling on the page.tsx side, restoring timeout and fallbacks, dynamic link msg. * FEAT: wait for workflow to complete and notify additionally with a big link (incl.db migration). * Fix: larger title, shorter link. * fix(hook): Writing completedAt from hook.workflow_run.updated_at according to suggestion. * fix(loader): rm unused props from WorkflowRunClientProps as suggested. * fix(loader): mv id=dev case into the page. * fix(DNRY): mv PollStartedResult and PollCompletedResult types. * fix(DNRY): reusing drawEllipse() helper in Sprite. * fix(style): reordering methods by priority in Sprite. * fix(frogger): rm empty rows from canvas, square game. * feat: game canvas rounded corners. * fix(DNRY): extracting SpriteShape type for faster reference. * fix: rm target from links, use same window. * add incremental re-review on new PR commits When new commits are pushed to a PR that Pullfrog has previously reviewed, automatically perform a focused re-review on only the new changes. Includes a supersede mechanism to abort stale in-flight reviews on rapid pushes, a new IncrementalReview mode with incremental diff + prior-feedback awareness, and a PRReview tracking model so re-review fires for both auto-reviewed and manually-triggered PRs. Reviews now always submit (APPROVE when clean). Co-authored-by: Cursor <cursoragent@cursor.com> * replace superseded polling with server-side in-flight dedup and add prApproveEnabled setting Made-with: Cursor * consolidate workflow_run completed handling and track completedAt Removes the duplicate exported handleWorkflowRunCompleted in favor of the private one, merges status + orphan resolution logic into a single path, and sets completedAt on both normal completion and orphan cancel. Made-with: Cursor * fix rebase conflict resolution: restore eligibility logic, incremental review summaries, and exhaustiveness check - replace deleted hasPullfrogReviewedPR call with WorkflowRun.findFirst (the utility file was removed by the dedup improvements commit) - restore IncrementalReview summary body in modes.ts and selectMode.ts (lost during ca0168b conflict resolution; origin had re-added them via f98f902) - use switch + satisfies never for workflow_run event dispatch - lowercase comments per project conventions Made-with: Cursor * fix workflow-run polling architecture and improve incremental review prompts move polling loops from server actions to client to avoid serverless timeouts (pollForCompleted ran up to 600s in a single invocation). each server action is now a single DB check; client drives retries. also fix misleading prompt text about incremental diff scope and remove dead code in handleWebhook. Made-with: Cursor * await reportReviewNodeId to eliminate race condition and webhook sleep - refactor reportReviewNodeId from fire-and-forget to async/awaited, guaranteeing the dedup signal lands before the tool returns - remove the 5-second grace period sleep in the synchronize webhook handler (no longer needed with the awaited PATCH) - update IncrementalReview guidance to use get_review_comments for detailed prior line-level feedback instead of just review summaries - remove dead fallbackUrl field from CheckStartedResult type Made-with: Cursor * fix rebase artifacts: broken triggeringIssue reference, review formatting, and prompt wording Made-with: Cursor --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Anna Bocharova <robin_tail@me.com>
147 lines
4.2 KiB
TypeScript
147 lines
4.2 KiB
TypeScript
import TurndownService from "turndown";
|
|
import type { PayloadEvent } from "../external.ts";
|
|
import { log } from "./cli.ts";
|
|
import type { OctokitWithPlugins } from "./github.ts";
|
|
import type { RunContextData } from "./runContextData.ts";
|
|
|
|
const turndown = new TurndownService();
|
|
|
|
function hasImages(body: string | null | undefined): boolean {
|
|
if (!body) return false;
|
|
return body.includes("<img") || body.includes("![");
|
|
}
|
|
|
|
interface ResolveBodyContext {
|
|
event: PayloadEvent;
|
|
octokit: OctokitWithPlugins;
|
|
repo: RunContextData["repo"];
|
|
}
|
|
|
|
/**
|
|
* resolves the body of an event by fetching body_html and converting to markdown.
|
|
* only fetches body_html if the body contains images (to avoid unnecessary API calls).
|
|
* this ensures agents receive markdown with working signed image URLs instead of
|
|
* broken user-attachments URLs.
|
|
*/
|
|
export async function resolveBody(ctx: ResolveBodyContext): Promise<string | null> {
|
|
const body = ctx.event.body;
|
|
|
|
// pass through if no images - no API call needed
|
|
if (!hasImages(body)) return body ?? null;
|
|
|
|
log.debug(`[resolveBody] fetching body_html for ${ctx.event.trigger}`);
|
|
const bodyHtml = await fetchBodyHtml(ctx);
|
|
log.debug(`[resolveBody] bodyHtml: ${bodyHtml?.substring(0, 300)}`);
|
|
if (!bodyHtml) return body ?? null;
|
|
|
|
const resolved = turndown.turndown(bodyHtml);
|
|
log.debug(`[resolveBody] resolved: ${resolved.substring(0, 300)}`);
|
|
return resolved;
|
|
}
|
|
|
|
async function fetchBodyHtml(ctx: ResolveBodyContext): Promise<string | undefined> {
|
|
const event = ctx.event;
|
|
const headers = { accept: "application/vnd.github.full+json" };
|
|
const owner = ctx.repo.owner;
|
|
const repo = ctx.repo.name;
|
|
|
|
switch (event.trigger) {
|
|
case "issue_comment_created":
|
|
if (!event.comment_id) return;
|
|
return (
|
|
await ctx.octokit.rest.issues.getComment({
|
|
owner,
|
|
repo,
|
|
comment_id: event.comment_id,
|
|
headers,
|
|
})
|
|
).data.body_html;
|
|
|
|
case "issues_opened":
|
|
case "issues_assigned":
|
|
case "issues_labeled":
|
|
if (!event.issue_number) return;
|
|
return (
|
|
await ctx.octokit.rest.issues.get({
|
|
owner,
|
|
repo,
|
|
issue_number: event.issue_number,
|
|
headers,
|
|
})
|
|
).data.body_html;
|
|
|
|
case "pull_request_opened":
|
|
case "pull_request_ready_for_review":
|
|
case "pull_request_synchronize":
|
|
case "pull_request_review_requested":
|
|
// PRs are also issues - use issues.get which returns body_html
|
|
if (!event.issue_number) return;
|
|
return (
|
|
await ctx.octokit.rest.issues.get({
|
|
owner,
|
|
repo,
|
|
issue_number: event.issue_number,
|
|
headers,
|
|
})
|
|
).data.body_html;
|
|
|
|
case "pull_request_review_submitted":
|
|
if (!event.issue_number || !event.review_id) return;
|
|
return (
|
|
await ctx.octokit.rest.pulls.getReview({
|
|
owner,
|
|
repo,
|
|
pull_number: event.issue_number,
|
|
review_id: event.review_id,
|
|
headers,
|
|
})
|
|
).data.body_html;
|
|
|
|
case "pull_request_review_comment_created":
|
|
if (!event.comment_id) return;
|
|
return (
|
|
await ctx.octokit.rest.pulls.getReviewComment({
|
|
owner,
|
|
repo,
|
|
comment_id: event.comment_id,
|
|
headers,
|
|
})
|
|
).data.body_html;
|
|
|
|
case "check_suite_completed":
|
|
// body is the PR body
|
|
if (!event.issue_number) return;
|
|
return (
|
|
await ctx.octokit.rest.issues.get({
|
|
owner,
|
|
repo,
|
|
issue_number: event.issue_number,
|
|
headers,
|
|
})
|
|
).data.body_html;
|
|
|
|
case "implement_plan":
|
|
// body is the plan content from an issue comment
|
|
if (!event.plan_comment_id) return;
|
|
return (
|
|
await ctx.octokit.rest.issues.getComment({
|
|
owner,
|
|
repo,
|
|
comment_id: event.plan_comment_id,
|
|
headers,
|
|
})
|
|
).data.body_html;
|
|
|
|
// triggers without a body field that needs resolution
|
|
case "workflow_dispatch":
|
|
case "fix_review":
|
|
case "unknown":
|
|
return undefined;
|
|
|
|
default:
|
|
// exhaustiveness check - TypeScript will error if a trigger is missing
|
|
event satisfies never;
|
|
return undefined;
|
|
}
|
|
}
|