Files
shockbot/utils/reviewCleanup.ts
T
David Blass a4c7c0fc15 feat: workflow run artifact chips + GraphQL url resolution (#447) (#527)
* 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>
2026-04-14 04:42:40 +00:00

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) },
});
}