a4c7c0fc15
* plan: issue 447 run artifact tracking and UI (supersedes stale pill notes) Made-with: Cursor * feat: workflow run artifact urls, chips, and safe PATCH validation Made-with: Cursor * chore(action): refresh latest-by-provider model snapshot Made-with: Cursor * refactor: resolve artifact urls via GraphQL nodes(ids), drop stored url columns Made-with: Cursor * docs: finalize issue 447 run-artifacts plan; remove demo backfill script Made-with: Cursor * refactor: DRY node-id constraint, replace margin with padding wrapper Made-with: Cursor * refactor: DRY audit — shared row info, derived types, unified Prisma select - extract WorkflowRunRowInfo component (description + issue link + time + pills) shared by ActiveWorkflowRunsSection and WorkflowRunHistory - derive API payload types via Omit + & instead of manual field lists; serialize with spread + override for bigint/date fields - extract workflowRunListSelect shared Prisma select base; history extends with completedAt - inline updateCommentNodeId → direct patchWorkflowRunFields calls - derive WorkflowRunArtifactSlice from canonical exported types - delete cancelling-out URL column migrations (no schema change vs main) Made-with: Cursor * refactor: artifact chips as inline CTAs with proper vertical alignment - chips now render as action links: "Open PR #N", "View summary", etc. - only render chips with resolved URLs; remove inert span fallback - inline chips in the row (right-justified) instead of a separate line - fix vertical alignment: remove ul/li wrappers that caused line-height mismatch, render chips as direct row siblings via flat flex layout - change row to items-center, remove compensating self-start/pt nudges - cancelled run X icon uses red-600 Made-with: Cursor * chore(action): refresh latest-by-provider model snapshot Made-with: Cursor --------- Co-authored-by: Colin McDonnell <colinmcd94@gmail.com>
96 lines
3.0 KiB
TypeScript
96 lines
3.0 KiB
TypeScript
import type { WriteablePayload } from "../external.ts";
|
|
import { reportReviewNodeId } from "../mcp/review.ts";
|
|
import type { ToolContext } from "../mcp/server.ts";
|
|
import { log } from "./cli.ts";
|
|
|
|
const RE_REVIEW_PREAMBLE =
|
|
"Incrementally re-review the new commits on this pull request. Use the IncrementalReview mode.";
|
|
|
|
/**
|
|
* post-agent review lifecycle: runs after the agent exits (success or timeout).
|
|
*
|
|
* normally the agent handles new commits inline: create_pull_request_review
|
|
* detects HEAD movement and tells the agent to pull and review the delta.
|
|
* this dispatch is a safety net for cases where the agent couldn't handle
|
|
* it (timeout, error, etc).
|
|
*
|
|
* ordering matters: reportReviewNodeId marks this run "done" FIRST so push
|
|
* webhooks stop being suppressed by dedup. the HEAD check runs SECOND to
|
|
* catch any pushes that were suppressed while this run was in-flight.
|
|
*/
|
|
export async function postReviewCleanup(ctx: ToolContext): Promise<void> {
|
|
const review = ctx.toolState.review;
|
|
if (!review) return;
|
|
delete ctx.toolState.review;
|
|
|
|
// mark review as submitted — unlocks webhook dedup for new pushes
|
|
await bestEffort(() => reportReviewNodeId(ctx, { nodeId: review.nodeId }), "reportReviewNodeId");
|
|
|
|
// dispatch follow-up if PR HEAD moved past the reviewed commit
|
|
if (review.reviewedSha) {
|
|
await bestEffort(
|
|
() => dispatchFollowUpReReview(ctx, review.reviewedSha!),
|
|
"follow-up re-review dispatch"
|
|
);
|
|
}
|
|
}
|
|
|
|
async function bestEffort(fn: () => Promise<unknown>, label: string): Promise<void> {
|
|
try {
|
|
await fn();
|
|
} catch (error) {
|
|
log.debug(`${label} failed: ${error}`);
|
|
}
|
|
}
|
|
|
|
async function dispatchFollowUpReReview(ctx: ToolContext, reviewedSha: string): Promise<void> {
|
|
const issueNumber = ctx.payload.event.issue_number;
|
|
if (!issueNumber) return;
|
|
|
|
const pr = await ctx.octokit.rest.pulls.get({
|
|
owner: ctx.repo.owner,
|
|
repo: ctx.repo.name,
|
|
pull_number: issueNumber,
|
|
});
|
|
|
|
if (pr.data.head.sha === reviewedSha) return;
|
|
if (pr.data.state !== "open") return;
|
|
if (pr.data.draft) return;
|
|
|
|
log.info(
|
|
`safety net: pr HEAD moved from ${reviewedSha.slice(0, 7)} to ${pr.data.head.sha.slice(0, 7)} ` +
|
|
`and agent did not review inline — dispatching follow-up re-review`
|
|
);
|
|
|
|
const event: WriteablePayload["event"] = {
|
|
trigger: "pull_request_synchronize",
|
|
issue_number: issueNumber,
|
|
is_pr: true,
|
|
title: pr.data.title,
|
|
body: null,
|
|
branch: pr.data.head.ref,
|
|
before_sha: reviewedSha,
|
|
silent: true,
|
|
};
|
|
if (ctx.payload.event.authorPermission) {
|
|
event.authorPermission = ctx.payload.event.authorPermission;
|
|
}
|
|
|
|
const payload: WriteablePayload = {
|
|
"~pullfrog": true,
|
|
version: ctx.payload.version,
|
|
model: ctx.payload.model,
|
|
prompt: "",
|
|
eventInstructions: RE_REVIEW_PREAMBLE,
|
|
event,
|
|
};
|
|
|
|
await ctx.octokit.rest.actions.createWorkflowDispatch({
|
|
owner: ctx.repo.owner,
|
|
repo: ctx.repo.name,
|
|
workflow_id: "pullfrog.yml",
|
|
ref: pr.data.base.repo.default_branch,
|
|
inputs: { prompt: JSON.stringify(payload) },
|
|
});
|
|
}
|