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>
309 lines
9.4 KiB
TypeScript
309 lines
9.4 KiB
TypeScript
/**
|
|
* ⚠️ LIMITED IMPORTS - this file is imported by Next.js and must avoid pulling in backend code.
|
|
* All shared constants, types, and data used by both the Next.js app and the action runtime live here.
|
|
* Other files in action/ re-export from this file for backward compatibility.
|
|
*/
|
|
|
|
import { type } from "arktype";
|
|
|
|
// mcp name constant
|
|
export const ghPullfrogMcpName = "gh_pullfrog";
|
|
|
|
export interface AgentManifest {
|
|
displayName: string;
|
|
/** empty array means accepts any *API_KEY* env var */
|
|
apiKeyNames: string[];
|
|
url: string;
|
|
}
|
|
|
|
// agent manifest - static metadata about available agents
|
|
export const agentsManifest = {
|
|
claude: {
|
|
displayName: "Claude Code",
|
|
apiKeyNames: ["ANTHROPIC_API_KEY"],
|
|
url: "https://claude.com/claude-code",
|
|
},
|
|
codex: {
|
|
displayName: "Codex CLI",
|
|
apiKeyNames: ["OPENAI_API_KEY"],
|
|
url: "https://platform.openai.com/docs/guides/codex",
|
|
},
|
|
cursor: {
|
|
displayName: "Cursor CLI",
|
|
apiKeyNames: ["CURSOR_API_KEY"],
|
|
url: "https://cursor.com/",
|
|
},
|
|
gemini: {
|
|
displayName: "Gemini CLI",
|
|
apiKeyNames: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
url: "https://ai.google.dev/gemini-api/docs",
|
|
},
|
|
opencode: {
|
|
displayName: "OpenCode",
|
|
apiKeyNames: [],
|
|
url: "https://opencode.ai",
|
|
},
|
|
} as const satisfies Record<string, AgentManifest>;
|
|
|
|
// agent name type - union of agent slugs
|
|
export type AgentName = keyof typeof agentsManifest;
|
|
export const AgentName = type.enumerated(...(Object.keys(agentsManifest) as AgentName[]));
|
|
|
|
export type AgentApiKeyName = (typeof agentsManifest)[AgentName]["apiKeyNames"][number];
|
|
|
|
// effort level type - controls model selection and thinking level
|
|
// mini = fast/minimal, auto = balanced/default, max = maximum capability
|
|
export const Effort = type.enumerated("mini", "auto", "max");
|
|
export type Effort = typeof Effort.infer;
|
|
|
|
// tool permission types shared with server dispatch
|
|
export type ToolPermission = "disabled" | "enabled";
|
|
export type ShellPermission = "disabled" | "restricted" | "enabled";
|
|
export type PushPermission = "disabled" | "restricted" | "enabled";
|
|
|
|
// workflow yml permissions for GITHUB_TOKEN
|
|
export type WorkflowPermissionValue = "read" | "write" | "none";
|
|
export type WorkflowIdTokenPermissionValue = "write" | "none";
|
|
|
|
export interface WorkflowPermissions {
|
|
actions?: WorkflowPermissionValue;
|
|
attestations?: WorkflowPermissionValue;
|
|
checks?: WorkflowPermissionValue;
|
|
contents?: WorkflowPermissionValue;
|
|
deployments?: WorkflowPermissionValue;
|
|
discussions?: WorkflowPermissionValue;
|
|
"id-token"?: WorkflowIdTokenPermissionValue;
|
|
issues?: WorkflowPermissionValue;
|
|
models?: WorkflowPermissionValue;
|
|
packages?: WorkflowPermissionValue;
|
|
pages?: WorkflowPermissionValue;
|
|
"pull-requests"?: WorkflowPermissionValue;
|
|
"repository-projects"?: WorkflowPermissionValue;
|
|
"security-events"?: WorkflowPermissionValue;
|
|
statuses?: WorkflowPermissionValue;
|
|
}
|
|
|
|
// permission level for the author who triggered the event
|
|
// matches GitHub's permission levels: admin > write > maintain > triage > read > none
|
|
export type AuthorPermission = "admin" | "maintain" | "write" | "triage" | "read" | "none";
|
|
|
|
// base interface for common payload event fields
|
|
interface BasePayloadEvent {
|
|
issue_number?: number;
|
|
is_pr?: boolean;
|
|
branch?: string;
|
|
/** title of the issue/PR (or contextual title for comments) */
|
|
title?: string;
|
|
/** primary content for this trigger (issue body, PR body, comment body, review body, etc.) */
|
|
body?: string | null;
|
|
comment_id?: number;
|
|
review_id?: number;
|
|
review_state?: string;
|
|
thread?: any;
|
|
pull_request?: any;
|
|
check_suite?: {
|
|
id: number;
|
|
head_sha: string;
|
|
head_branch: string | null;
|
|
status: string | null;
|
|
conclusion: string | null;
|
|
url: string;
|
|
};
|
|
comment_ids?: number[] | "all";
|
|
/** permission level of the user who triggered this event */
|
|
authorPermission?: AuthorPermission;
|
|
/** when true, runs silently without progress comments (e.g., auto-labeling) */
|
|
silent?: boolean;
|
|
[key: string]: any;
|
|
}
|
|
|
|
interface PullRequestOpenedEvent extends BasePayloadEvent {
|
|
trigger: "pull_request_opened";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
title: string;
|
|
body: string | null;
|
|
branch: string;
|
|
}
|
|
|
|
interface PullRequestReadyForReviewEvent extends BasePayloadEvent {
|
|
trigger: "pull_request_ready_for_review";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
title: string;
|
|
body: string | null;
|
|
branch: string;
|
|
}
|
|
|
|
interface PullRequestReviewRequestedEvent extends BasePayloadEvent {
|
|
trigger: "pull_request_review_requested";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
title: string;
|
|
body: string | null;
|
|
branch: string;
|
|
}
|
|
|
|
interface PullRequestReviewSubmittedEvent extends BasePayloadEvent {
|
|
trigger: "pull_request_review_submitted";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
review_id: number;
|
|
/** review body is the primary content */
|
|
body: string | null;
|
|
review_state: string;
|
|
branch: string;
|
|
}
|
|
|
|
interface PullRequestReviewCommentCreatedEvent extends BasePayloadEvent {
|
|
trigger: "pull_request_review_comment_created";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
title: string;
|
|
comment_id: number;
|
|
/** comment body is the primary content (null if already in prompt) */
|
|
body: string | null;
|
|
thread?: any;
|
|
branch: string;
|
|
}
|
|
|
|
interface IssuesOpenedEvent extends BasePayloadEvent {
|
|
trigger: "issues_opened";
|
|
issue_number: number;
|
|
title: string;
|
|
body: string | null;
|
|
}
|
|
|
|
interface IssuesAssignedEvent extends BasePayloadEvent {
|
|
trigger: "issues_assigned";
|
|
issue_number: number;
|
|
title: string;
|
|
body: string | null;
|
|
}
|
|
|
|
interface IssuesLabeledEvent extends BasePayloadEvent {
|
|
trigger: "issues_labeled";
|
|
issue_number: number;
|
|
title: string;
|
|
body: string | null;
|
|
}
|
|
|
|
interface IssueCommentCreatedEvent extends BasePayloadEvent {
|
|
trigger: "issue_comment_created";
|
|
comment_id: number;
|
|
/** distinguishes this from PR review comments (which use pull_request_review_comment_created) */
|
|
comment_type: "issue";
|
|
/** comment body is the primary content (null if already in prompt) */
|
|
body: string | null;
|
|
issue_number: number;
|
|
// PR-specific fields (only present when is_pr is true)
|
|
is_pr?: true;
|
|
branch?: string;
|
|
title?: string;
|
|
}
|
|
|
|
interface CheckSuiteCompletedEvent extends BasePayloadEvent {
|
|
trigger: "check_suite_completed";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
title: string;
|
|
body: string | null;
|
|
pull_request: any;
|
|
branch: string;
|
|
check_suite: {
|
|
id: number;
|
|
head_sha: string;
|
|
head_branch: string | null;
|
|
status: string | null;
|
|
conclusion: string | null;
|
|
url: string;
|
|
};
|
|
}
|
|
|
|
interface WorkflowDispatchEvent extends BasePayloadEvent {
|
|
trigger: "workflow_dispatch";
|
|
}
|
|
|
|
interface FixReviewEvent extends BasePayloadEvent {
|
|
trigger: "fix_review";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
review_id: number;
|
|
/** when true, only address comments the triggerer approved with 👍 (vs all comments) */
|
|
approved_only?: boolean | undefined;
|
|
}
|
|
|
|
interface ImplementPlanEvent extends BasePayloadEvent {
|
|
trigger: "implement_plan";
|
|
issue_number: number;
|
|
plan_comment_id: number;
|
|
/** plan content is the primary content (null if already in prompt) */
|
|
body: string | null;
|
|
}
|
|
|
|
interface PullRequestSynchronizeEvent extends BasePayloadEvent {
|
|
trigger: "pull_request_synchronize";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
title: string;
|
|
body: string | null;
|
|
branch: string;
|
|
/** SHA before the push -- used to compute incremental diff via `git diff before_sha...HEAD` */
|
|
before_sha: string;
|
|
}
|
|
|
|
interface UnknownEvent extends BasePayloadEvent {
|
|
trigger: "unknown";
|
|
}
|
|
|
|
// discriminated union for payload event based on trigger
|
|
// note: all events use issue_number for consistency (PRs are issues in GitHub's API)
|
|
export type PayloadEvent =
|
|
| PullRequestOpenedEvent
|
|
| PullRequestReadyForReviewEvent
|
|
| PullRequestSynchronizeEvent
|
|
| PullRequestReviewRequestedEvent
|
|
| PullRequestReviewSubmittedEvent
|
|
| PullRequestReviewCommentCreatedEvent
|
|
| IssuesOpenedEvent
|
|
| IssuesAssignedEvent
|
|
| IssuesLabeledEvent
|
|
| IssueCommentCreatedEvent
|
|
| CheckSuiteCompletedEvent
|
|
| WorkflowDispatchEvent
|
|
| FixReviewEvent
|
|
| ImplementPlanEvent
|
|
| UnknownEvent;
|
|
|
|
// writeable payload type for building payloads
|
|
export interface WriteablePayload {
|
|
"~pullfrog": true;
|
|
/** semantic version of the payload to ensure compatibility */
|
|
version: string;
|
|
/** agent slug identifier (e.g., "claude", "codex", "gemini") */
|
|
agent?: AgentName | undefined;
|
|
/** the user's actual request (body if @pullfrog tagged) */
|
|
prompt: string;
|
|
/** github username of the human who triggered this workflow run */
|
|
triggerer?: string | undefined;
|
|
/** event-level instructions for this trigger type (flag-expanded server-side) */
|
|
eventInstructions?: string | undefined;
|
|
/** repo-level instructions (flag-expanded server-side) */
|
|
repoInstructions?: string | undefined;
|
|
/** event data from webhook payload - discriminated union based on trigger field */
|
|
event: PayloadEvent;
|
|
/** effort level for model selection (mini, auto, max) - defaults to "auto" */
|
|
effort?: Effort | undefined;
|
|
/** timeout for agent run (e.g., "10m", "1h30m") - defaults to "1h" */
|
|
timeout?: string | undefined;
|
|
/** working directory for the agent */
|
|
cwd?: string | undefined;
|
|
/** pre-created progress comment ID for updating status */
|
|
progressCommentId?: string | undefined;
|
|
/** whether debug mode is enabled (LOG_LEVEL=debug) */
|
|
debug?: boolean | undefined;
|
|
}
|
|
|
|
// immutable payload type for agent execution
|
|
export type Payload = Readonly<WriteablePayload>;
|