pullfrog 0.1.46 → 0.1.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -101,7 +101,7 @@ jobs:
101
101
 
102
102
  ```
103
103
 
104
- To gate merges on Pullfrog with branch protection, add `status_checks: enabled` under `with:`. Each PR run then posts a `pullfrog` check (run completion success when the run finishes, failure on error/timeout) and a `pullfrog-approval` check (whether Pullfrog would approve the PR), both requireable as status checks. See [PR reviews → Required status checks](https://docs.pullfrog.dev/pr-reviews#required-status-checks-branch-protection).
104
+ Every PR run posts a `Pullfrog` commit-status check: `in_progress` from the moment the run is dispatched, then success when it finishes or failure on error/timeout. It is on by default (turn it off per-repo in the console). To also gate merges on Pullfrog's review verdict, add `status_checks: enabled`, which additionally posts a `Pullfrog approval` check (whether Pullfrog would approve the PR). Both are requireable as branch-protection status checks. See [PR reviews → Status checks](https://docs.pullfrog.dev/pr-reviews#status-checks).
105
105
 
106
106
  #### 2. Create `triggers.yml`
107
107
 
package/dist/cli.mjs CHANGED
@@ -101356,6 +101356,10 @@ async function setPullfrogSecret(ctx) {
101356
101356
  }
101357
101357
  return { saved: false, error: result.data.error || `api returned ${result.status}` };
101358
101358
  }
101359
+ function describeSecretTarget(ctx) {
101360
+ if (ctx.scope === "account") return `account ${import_picocolors.default.cyan(`@${ctx.owner}`)}`;
101361
+ return `repo ${import_picocolors.default.cyan(`${ctx.owner}/${ctx.repo}`)}`;
101362
+ }
101359
101363
  async function promptScope(ctx) {
101360
101364
  const scope2 = await _t({
101361
101365
  message: "secret scope",
@@ -101579,7 +101583,8 @@ async function runCodexAuth() {
101579
101583
  O2.warn(err instanceof Error ? err.message : String(err));
101580
101584
  savable = auth2;
101581
101585
  }
101582
- spin.start(`saving ${import_picocolors2.default.cyan(CODEX_AUTH_SECRET)} to Pullfrog`);
101586
+ const target = describeSecretTarget({ owner: remote.owner, repo: remote.repo, scope: scope2 });
101587
+ spin.start(`saving ${import_picocolors2.default.cyan(CODEX_AUTH_SECRET)} to ${target}`);
101583
101588
  const result = await setPullfrogSecret({
101584
101589
  token,
101585
101590
  owner: remote.owner,
@@ -101596,7 +101601,7 @@ async function runCodexAuth() {
101596
101601
  );
101597
101602
  process.exit(1);
101598
101603
  }
101599
- spin.stop(`saved ${import_picocolors2.default.cyan(CODEX_AUTH_SECRET)} to Pullfrog (${scope2})`);
101604
+ spin.stop(`saved ${import_picocolors2.default.cyan(CODEX_AUTH_SECRET)} to ${target}`);
101600
101605
  setActiveSpin(null);
101601
101606
  gt("done.");
101602
101607
  } catch (error49) {
@@ -101693,7 +101698,8 @@ async function runClaudeAuth() {
101693
101698
  `that doesn't look like a ${import_picocolors2.default.cyan("claude setup-token")} token (expected ${import_picocolors2.default.cyan(`${CLAUDE_OAUTH_TOKEN_PREFIX}\u2026`)}). saving it anyway.`
101694
101699
  );
101695
101700
  }
101696
- spin.start(`saving ${import_picocolors2.default.cyan(CLAUDE_OAUTH_SECRET)} to Pullfrog`);
101701
+ const target = describeSecretTarget({ owner: remote.owner, repo: remote.repo, scope: scope2 });
101702
+ spin.start(`saving ${import_picocolors2.default.cyan(CLAUDE_OAUTH_SECRET)} to ${target}`);
101697
101703
  const result = await setPullfrogSecret({
101698
101704
  token,
101699
101705
  owner: remote.owner,
@@ -101710,7 +101716,7 @@ async function runClaudeAuth() {
101710
101716
  );
101711
101717
  process.exit(1);
101712
101718
  }
101713
- spin.stop(`saved ${import_picocolors2.default.cyan(CLAUDE_OAUTH_SECRET)} to Pullfrog (${scope2})`);
101719
+ spin.stop(`saved ${import_picocolors2.default.cyan(CLAUDE_OAUTH_SECRET)} to ${target}`);
101714
101720
  setActiveSpin(null);
101715
101721
  gt("done.");
101716
101722
  } catch (error49) {
@@ -103050,7 +103056,7 @@ var import_semver = __toESM(require_semver2(), 1);
103050
103056
  // package.json
103051
103057
  var package_default = {
103052
103058
  name: "pullfrog",
103053
- version: "0.1.46",
103059
+ version: "0.1.47",
103054
103060
  type: "module",
103055
103061
  bin: {
103056
103062
  pullfrog: "dist/cli.mjs",
@@ -159967,6 +159973,9 @@ var JsonPayload = type({
159967
159973
  id: "string",
159968
159974
  type: "'issue' | 'review'"
159969
159975
  }).or("undefined"),
159976
+ // optional so a payload from an older server build (pre-`checkRun`) still parses
159977
+ // against a newer action across a rolling deploy.
159978
+ "checkRun?": type({ id: "string" }).or("undefined"),
159970
159979
  "generateSummary?": "boolean | undefined"
159971
159980
  });
159972
159981
  var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
@@ -160080,13 +160089,19 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
160080
160089
  timeout: inputs.timeout ?? jsonPayload?.timeout,
160081
160090
  cwd: resolveCwd(inputs.cwd),
160082
160091
  progressComment: jsonPayload?.progressComment,
160092
+ checkRun: jsonPayload?.checkRun,
160083
160093
  generateSummary: jsonPayload?.generateSummary,
160084
160094
  // permissions: inputs > repoSettings > fallbacks
160085
160095
  push: inputs.push ?? repoSettings.push ?? "restricted",
160086
160096
  shell: resolvedShell,
160087
- // opt-in commit-status check-runs (branch protection). workflow-level
160088
- // static input, off unless the repo's pullfrog.yml sets status_checks: enabled.
160089
- statusChecks: inputs.status_checks === "enabled",
160097
+ // the `pullfrog` run-lifecycle check. ON by default — the whole point is that a PR
160098
+ // shows whether Pullfrog is running without anyone having to opt in. the workflow
160099
+ // input is the source of truth when set (mirrors `push`); otherwise the repo
160100
+ // setting decides.
160101
+ runStatusCheck: inputs.status_checks === void 0 ? repoSettings.statusChecks : inputs.status_checks === "enabled",
160102
+ // the `pullfrog-approval` verdict check stays opt-in and workflow-only. it exists to
160103
+ // be *required* by branch protection, so it must never turn itself on.
160104
+ approvalCheck: inputs.status_checks === "enabled",
160090
160105
  // temporary progress chrome. the workflow input is the source of truth when
160091
160106
  // set (mirrors `push`); otherwise the repo setting decides. defaults to true.
160092
160107
  progressComments: inputs.progress_comments === void 0 ? repoSettings.progressComments : inputs.progress_comments === "enabled",
@@ -160614,7 +160629,11 @@ async function approveAfterFix(ctx) {
160614
160629
  approved: true,
160615
160630
  hasComments: false
160616
160631
  });
160617
- ctx.toolState.approval = { wouldApprove: true, sha: headSha };
160632
+ ctx.toolState.approval = {
160633
+ wouldApprove: true,
160634
+ sha: headSha,
160635
+ url: result.data.html_url
160636
+ };
160618
160637
  log.info(`\xBB auto-approved #${pullNumber} after fix run (review ${result.data.id})`);
160619
160638
  await deleteProgressComment(ctx).catch((err) => {
160620
160639
  log.debug(`progress comment cleanup after fix auto-approval failed: ${err}`);
@@ -160947,6 +160966,7 @@ function CreatePullRequestReviewTool(ctx) {
160947
160966
  }
160948
160967
  const reviewId = result.data.id;
160949
160968
  const reviewNodeId = result.data.node_id;
160969
+ if (ctx.toolState.approval) ctx.toolState.approval.url = result.data.html_url;
160950
160970
  log.info(`\xBB created review ${reviewId} on pull request #${pull_number}`);
160951
160971
  const actuallyReviewedSha = primary.checkoutSha ?? params.commit_id;
160952
160972
  ctx.toolState.review = {
@@ -166963,6 +166983,7 @@ var defaultSettings = {
166963
166983
  autoMergeEnabled: false,
166964
166984
  signedCommits: false,
166965
166985
  progressComments: true,
166986
+ statusChecks: true,
166966
166987
  modeInstructions: {},
166967
166988
  learnings: null,
166968
166989
  learningsHeadings: [],
@@ -167484,30 +167505,123 @@ function getCurrentWorkflowFilename() {
167484
167505
  return match3?.[1] ?? "pullfrog.yml";
167485
167506
  }
167486
167507
 
167487
- // utils/statusChecks.ts
167488
- var COMPLETION_CHECK = "pullfrog";
167489
- var APPROVAL_CHECK = "pullfrog-approval";
167490
- async function createCheckRun(ctx, params) {
167508
+ // utils/runStatusCheck.ts
167509
+ var RUN_STATUS_CHECK_NAME = "Pullfrog";
167510
+ var APPROVAL_CHECK_NAME = "Pullfrog approval";
167511
+ function parseCheckRunId(raw2) {
167512
+ if (!raw2?.id) return void 0;
167513
+ const id = parseInt(raw2.id, 10);
167514
+ if (Number.isNaN(id) || id <= 0) return void 0;
167515
+ return id;
167516
+ }
167517
+ function disableCheckLine(owner, repo) {
167518
+ const url4 = `https://pullfrog.com/console/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}#auto-review-prs`;
167519
+ return `
167520
+
167521
+ [Turn off this check \u2192](${url4}) \u2014 it reports run status only and gates nothing unless you required it in branch protection.`;
167522
+ }
167523
+ var TERMINAL_OUTPUT = {
167524
+ success: {
167525
+ title: "Pullfrog run completed",
167526
+ summary: "The Pullfrog run finished successfully."
167527
+ },
167528
+ failure: {
167529
+ title: "Pullfrog run failed",
167530
+ summary: "The Pullfrog run failed. See the run logs for details."
167531
+ },
167532
+ cancelled: {
167533
+ title: "Pullfrog run cancelled",
167534
+ summary: "The Pullfrog run was cancelled before it finished."
167535
+ },
167536
+ timed_out: {
167537
+ title: "Pullfrog run timed out",
167538
+ summary: "The Pullfrog run exceeded its timeout. See the run logs for details."
167539
+ },
167540
+ action_required: {
167541
+ title: "Pullfrog run needs attention",
167542
+ summary: "The Pullfrog run stopped and needs attention. See the run logs for details."
167543
+ },
167544
+ neutral: {
167545
+ title: "Pullfrog run finished",
167546
+ summary: "The Pullfrog run finished without a pass or fail outcome."
167547
+ },
167548
+ skipped: {
167549
+ title: "Pullfrog run skipped",
167550
+ summary: "This run was superseded by another Pullfrog run."
167551
+ },
167552
+ stale: {
167553
+ title: "Pullfrog run didn't finish",
167554
+ summary: "Pullfrog never received a completion signal for this run. See the run logs for details."
167555
+ }
167556
+ };
167557
+ function terminalOutput(params) {
167558
+ const base = TERMINAL_OUTPUT[params.conclusion];
167559
+ const review = params.reviewUrl ? `
167560
+
167561
+ [View the review Pullfrog posted \u2192](${params.reviewUrl})` : "";
167562
+ const disable = params.conclusion === "success" ? "" : disableCheckLine(params.owner, params.repo);
167563
+ return { title: base.title, summary: base.summary + review + disable };
167564
+ }
167565
+ async function finalizeRunStatusCheck(params) {
167566
+ const updateParams = {
167567
+ owner: params.owner,
167568
+ repo: params.repo,
167569
+ check_run_id: params.checkRunId,
167570
+ status: "completed",
167571
+ conclusion: params.conclusion,
167572
+ output: terminalOutput({
167573
+ conclusion: params.conclusion,
167574
+ owner: params.owner,
167575
+ repo: params.repo,
167576
+ reviewUrl: params.reviewUrl
167577
+ })
167578
+ };
167579
+ if (params.detailsUrl) updateParams.details_url = params.detailsUrl;
167580
+ await params.octokit.rest.checks.update(updateParams);
167581
+ }
167582
+ async function createTerminalRunStatusCheck(params) {
167491
167583
  const createParams = {
167492
- owner: ctx.repo.owner,
167493
- repo: ctx.repo.name,
167494
- name: params.name,
167584
+ owner: params.owner,
167585
+ repo: params.repo,
167586
+ name: RUN_STATUS_CHECK_NAME,
167495
167587
  head_sha: params.headSha,
167496
167588
  status: "completed",
167497
167589
  conclusion: params.conclusion,
167498
- output: { title: params.title, summary: params.summary }
167590
+ output: terminalOutput({
167591
+ conclusion: params.conclusion,
167592
+ owner: params.owner,
167593
+ repo: params.repo,
167594
+ reviewUrl: params.reviewUrl
167595
+ })
167499
167596
  };
167500
- if (ctx.runId) {
167501
- createParams.details_url = `https://github.com/${ctx.repo.owner}/${ctx.repo.name}/actions/runs/${ctx.runId}`;
167502
- }
167503
- await ctx.octokit.rest.checks.create(createParams);
167504
- log.info(`\xBB posted ${params.name} check (${params.conclusion}) on ${params.headSha.slice(0, 7)}`);
167597
+ if (params.detailsUrl) createParams.details_url = params.detailsUrl;
167598
+ await params.octokit.rest.checks.create(createParams);
167505
167599
  }
167600
+
167601
+ // utils/statusChecks.ts
167506
167602
  async function reportStatusChecks(ctx, params) {
167507
- if (!ctx.payload.statusChecks) return;
167508
167603
  const event = ctx.payload.event;
167509
167604
  const pullNumber = event.issue_number;
167510
167605
  if (event.is_pr !== true || typeof pullNumber !== "number") return;
167606
+ const checkRunId = parseCheckRunId(ctx.payload.checkRun);
167607
+ if (checkRunId === void 0 && !ctx.payload.runStatusCheck && !ctx.payload.approvalCheck) return;
167608
+ const conclusion = params.runSucceeded ? "success" : "failure";
167609
+ const detailsUrl = ctx.runId ? `https://github.com/${ctx.repo.owner}/${ctx.repo.name}/actions/runs/${ctx.runId}` : void 0;
167610
+ if (checkRunId !== void 0) {
167611
+ await finalizeRunStatusCheck({
167612
+ octokit: ctx.octokit,
167613
+ owner: ctx.repo.owner,
167614
+ repo: ctx.repo.name,
167615
+ checkRunId,
167616
+ conclusion,
167617
+ detailsUrl,
167618
+ reviewUrl: ctx.toolState.approval?.url
167619
+ }).then(() => log.info(`\xBB finalized ${RUN_STATUS_CHECK_NAME} check (${conclusion})`)).catch((err) => log.debug(`status checks: ${RUN_STATUS_CHECK_NAME} finalize failed: ${err}`));
167620
+ }
167621
+ const approval = ctx.toolState.approval;
167622
+ const needsApprovalCheck = ctx.payload.approvalCheck && params.runSucceeded && approval;
167623
+ const needsFallbackRunCheck = ctx.payload.runStatusCheck && checkRunId === void 0;
167624
+ if (!needsApprovalCheck && !needsFallbackRunCheck) return;
167511
167625
  let headSha;
167512
167626
  try {
167513
167627
  const pr = await ctx.octokit.rest.pulls.get({
@@ -167520,24 +167634,32 @@ async function reportStatusChecks(ctx, params) {
167520
167634
  log.debug(`status checks: failed to resolve PR #${pullNumber} head sha: ${err}`);
167521
167635
  return;
167522
167636
  }
167523
- const completionSha = primaryRepoState(ctx.toolState).checkoutSha ?? headSha;
167524
- await createCheckRun(ctx, {
167525
- name: COMPLETION_CHECK,
167526
- headSha: completionSha,
167527
- conclusion: params.runSucceeded ? "success" : "failure",
167528
- title: params.runSucceeded ? "Pullfrog run completed" : "Pullfrog run failed",
167529
- summary: params.runSucceeded ? "The Pullfrog run finished successfully." : "The Pullfrog run failed or timed out. See the run logs for details."
167530
- }).catch((err) => log.debug(`status checks: ${COMPLETION_CHECK} post failed: ${err}`));
167531
- const approval = ctx.toolState.approval;
167532
- if (params.runSucceeded && approval) {
167533
- await createCheckRun(ctx, {
167534
- name: APPROVAL_CHECK,
167535
- headSha: approval.sha ?? headSha,
167536
- conclusion: approval.wouldApprove ? "success" : "failure",
167637
+ if (needsFallbackRunCheck) {
167638
+ await createTerminalRunStatusCheck({
167639
+ octokit: ctx.octokit,
167640
+ owner: ctx.repo.owner,
167641
+ repo: ctx.repo.name,
167642
+ headSha: primaryRepoState(ctx.toolState).checkoutSha ?? headSha,
167643
+ conclusion,
167644
+ detailsUrl,
167645
+ reviewUrl: ctx.toolState.approval?.url
167646
+ }).then(() => log.info(`\xBB posted ${RUN_STATUS_CHECK_NAME} check (${conclusion})`)).catch((err) => log.debug(`status checks: ${RUN_STATUS_CHECK_NAME} post failed: ${err}`));
167647
+ }
167648
+ if (!needsApprovalCheck || !approval) return;
167649
+ const createParams = {
167650
+ owner: ctx.repo.owner,
167651
+ repo: ctx.repo.name,
167652
+ name: APPROVAL_CHECK_NAME,
167653
+ head_sha: approval.sha ?? headSha,
167654
+ status: "completed",
167655
+ conclusion: approval.wouldApprove ? "success" : "failure",
167656
+ output: {
167537
167657
  title: approval.wouldApprove ? "Pullfrog would approve" : "Pullfrog would not approve",
167538
167658
  summary: approval.wouldApprove ? "Pullfrog has no outstanding review feedback on this PR." : "Pullfrog has outstanding review feedback or requested changes on this PR."
167539
- }).catch((err) => log.debug(`status checks: ${APPROVAL_CHECK} post failed: ${err}`));
167540
- }
167659
+ }
167660
+ };
167661
+ if (detailsUrl) createParams.details_url = detailsUrl;
167662
+ await ctx.octokit.rest.checks.create(createParams).then(() => log.info(`\xBB posted ${APPROVAL_CHECK_NAME} check`)).catch((err) => log.debug(`status checks: ${APPROVAL_CHECK_NAME} post failed: ${err}`));
167541
167663
  }
167542
167664
 
167543
167665
  // utils/runLifecycle.ts
@@ -168938,7 +169060,8 @@ async function handleSecret(ctx) {
168938
169060
  }
168939
169061
  if (method === "pullfrog") {
168940
169062
  const scope2 = ctx.secrets.isOrg ? await promptScope2(ctx) : "account";
168941
- activeSpin2.start(`saving ${envVar}`);
169063
+ const target = describeSecretTarget({ owner: ctx.owner, repo: ctx.repo, scope: scope2 });
169064
+ activeSpin2.start(`saving ${import_picocolors3.default.cyan(envVar)} to ${target}`);
168942
169065
  let saveResult;
168943
169066
  try {
168944
169067
  saveResult = await setPullfrogSecret2({
@@ -168958,7 +169081,7 @@ async function handleSecret(ctx) {
168958
169081
  return;
168959
169082
  }
168960
169083
  if (saveResult.saved) {
168961
- activeSpin2.stop(`saved ${import_picocolors3.default.cyan(envVar)} to Pullfrog`);
169084
+ activeSpin2.stop(`saved ${import_picocolors3.default.cyan(envVar)} to ${target}`);
168962
169085
  } else {
168963
169086
  activeSpin2.stop(import_picocolors3.default.red("could not save secret"));
168964
169087
  O2.warn(
@@ -169308,7 +169431,7 @@ async function runCli4(input) {
169308
169431
  }
169309
169432
 
169310
169433
  // cli.ts
169311
- var VERSION10 = "0.1.46";
169434
+ var VERSION10 = "0.1.47";
169312
169435
  var bin = basename2(process.argv[1] || "");
169313
169436
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
169314
169437
  var rawArgs = process.argv.slice(2);
@@ -262,6 +262,14 @@ export interface WriteablePayload {
262
262
  id: string;
263
263
  type: "issue" | "review";
264
264
  } | undefined;
265
+ /**
266
+ * pre-created `pullfrog` check-run, seeded `in_progress` by the server at dispatch so
267
+ * the PR's checks list shows the run before the GHA runner boots. the action PATCHes
268
+ * it to a terminal conclusion at run end. see `action/utils/runStatusCheck.ts`.
269
+ */
270
+ checkRun?: {
271
+ id: string;
272
+ } | undefined;
265
273
  /** when true, seed the PR summary tmpfile + persist edits at run end */
266
274
  generateSummary?: boolean | undefined;
267
275
  }
package/dist/index.js CHANGED
@@ -101121,7 +101121,7 @@ var import_semver = __toESM(require_semver2(), 1);
101121
101121
  // package.json
101122
101122
  var package_default = {
101123
101123
  name: "pullfrog",
101124
- version: "0.1.46",
101124
+ version: "0.1.47",
101125
101125
  type: "module",
101126
101126
  bin: {
101127
101127
  pullfrog: "dist/cli.mjs",
@@ -158080,6 +158080,9 @@ var JsonPayload = type({
158080
158080
  id: "string",
158081
158081
  type: "'issue' | 'review'"
158082
158082
  }).or("undefined"),
158083
+ // optional so a payload from an older server build (pre-`checkRun`) still parses
158084
+ // against a newer action across a rolling deploy.
158085
+ "checkRun?": type({ id: "string" }).or("undefined"),
158083
158086
  "generateSummary?": "boolean | undefined"
158084
158087
  });
158085
158088
  var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
@@ -158193,13 +158196,19 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
158193
158196
  timeout: inputs.timeout ?? jsonPayload?.timeout,
158194
158197
  cwd: resolveCwd(inputs.cwd),
158195
158198
  progressComment: jsonPayload?.progressComment,
158199
+ checkRun: jsonPayload?.checkRun,
158196
158200
  generateSummary: jsonPayload?.generateSummary,
158197
158201
  // permissions: inputs > repoSettings > fallbacks
158198
158202
  push: inputs.push ?? repoSettings.push ?? "restricted",
158199
158203
  shell: resolvedShell,
158200
- // opt-in commit-status check-runs (branch protection). workflow-level
158201
- // static input, off unless the repo's pullfrog.yml sets status_checks: enabled.
158202
- statusChecks: inputs.status_checks === "enabled",
158204
+ // the `pullfrog` run-lifecycle check. ON by default — the whole point is that a PR
158205
+ // shows whether Pullfrog is running without anyone having to opt in. the workflow
158206
+ // input is the source of truth when set (mirrors `push`); otherwise the repo
158207
+ // setting decides.
158208
+ runStatusCheck: inputs.status_checks === void 0 ? repoSettings.statusChecks : inputs.status_checks === "enabled",
158209
+ // the `pullfrog-approval` verdict check stays opt-in and workflow-only. it exists to
158210
+ // be *required* by branch protection, so it must never turn itself on.
158211
+ approvalCheck: inputs.status_checks === "enabled",
158203
158212
  // temporary progress chrome. the workflow input is the source of truth when
158204
158213
  // set (mirrors `push`); otherwise the repo setting decides. defaults to true.
158205
158214
  progressComments: inputs.progress_comments === void 0 ? repoSettings.progressComments : inputs.progress_comments === "enabled",
@@ -158727,7 +158736,11 @@ async function approveAfterFix(ctx) {
158727
158736
  approved: true,
158728
158737
  hasComments: false
158729
158738
  });
158730
- ctx.toolState.approval = { wouldApprove: true, sha: headSha };
158739
+ ctx.toolState.approval = {
158740
+ wouldApprove: true,
158741
+ sha: headSha,
158742
+ url: result.data.html_url
158743
+ };
158731
158744
  log.info(`\xBB auto-approved #${pullNumber} after fix run (review ${result.data.id})`);
158732
158745
  await deleteProgressComment(ctx).catch((err) => {
158733
158746
  log.debug(`progress comment cleanup after fix auto-approval failed: ${err}`);
@@ -159060,6 +159073,7 @@ function CreatePullRequestReviewTool(ctx) {
159060
159073
  }
159061
159074
  const reviewId = result.data.id;
159062
159075
  const reviewNodeId = result.data.node_id;
159076
+ if (ctx.toolState.approval) ctx.toolState.approval.url = result.data.html_url;
159063
159077
  log.info(`\xBB created review ${reviewId} on pull request #${pull_number}`);
159064
159078
  const actuallyReviewedSha = primary.checkoutSha ?? params.commit_id;
159065
159079
  ctx.toolState.review = {
@@ -165076,6 +165090,7 @@ var defaultSettings = {
165076
165090
  autoMergeEnabled: false,
165077
165091
  signedCommits: false,
165078
165092
  progressComments: true,
165093
+ statusChecks: true,
165079
165094
  modeInstructions: {},
165080
165095
  learnings: null,
165081
165096
  learningsHeadings: [],
@@ -165597,30 +165612,123 @@ function getCurrentWorkflowFilename() {
165597
165612
  return match3?.[1] ?? "pullfrog.yml";
165598
165613
  }
165599
165614
 
165600
- // utils/statusChecks.ts
165601
- var COMPLETION_CHECK = "pullfrog";
165602
- var APPROVAL_CHECK = "pullfrog-approval";
165603
- async function createCheckRun(ctx, params) {
165615
+ // utils/runStatusCheck.ts
165616
+ var RUN_STATUS_CHECK_NAME = "Pullfrog";
165617
+ var APPROVAL_CHECK_NAME = "Pullfrog approval";
165618
+ function parseCheckRunId(raw2) {
165619
+ if (!raw2?.id) return void 0;
165620
+ const id = parseInt(raw2.id, 10);
165621
+ if (Number.isNaN(id) || id <= 0) return void 0;
165622
+ return id;
165623
+ }
165624
+ function disableCheckLine(owner, repo) {
165625
+ const url4 = `https://pullfrog.com/console/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}#auto-review-prs`;
165626
+ return `
165627
+
165628
+ [Turn off this check \u2192](${url4}) \u2014 it reports run status only and gates nothing unless you required it in branch protection.`;
165629
+ }
165630
+ var TERMINAL_OUTPUT = {
165631
+ success: {
165632
+ title: "Pullfrog run completed",
165633
+ summary: "The Pullfrog run finished successfully."
165634
+ },
165635
+ failure: {
165636
+ title: "Pullfrog run failed",
165637
+ summary: "The Pullfrog run failed. See the run logs for details."
165638
+ },
165639
+ cancelled: {
165640
+ title: "Pullfrog run cancelled",
165641
+ summary: "The Pullfrog run was cancelled before it finished."
165642
+ },
165643
+ timed_out: {
165644
+ title: "Pullfrog run timed out",
165645
+ summary: "The Pullfrog run exceeded its timeout. See the run logs for details."
165646
+ },
165647
+ action_required: {
165648
+ title: "Pullfrog run needs attention",
165649
+ summary: "The Pullfrog run stopped and needs attention. See the run logs for details."
165650
+ },
165651
+ neutral: {
165652
+ title: "Pullfrog run finished",
165653
+ summary: "The Pullfrog run finished without a pass or fail outcome."
165654
+ },
165655
+ skipped: {
165656
+ title: "Pullfrog run skipped",
165657
+ summary: "This run was superseded by another Pullfrog run."
165658
+ },
165659
+ stale: {
165660
+ title: "Pullfrog run didn't finish",
165661
+ summary: "Pullfrog never received a completion signal for this run. See the run logs for details."
165662
+ }
165663
+ };
165664
+ function terminalOutput(params) {
165665
+ const base = TERMINAL_OUTPUT[params.conclusion];
165666
+ const review = params.reviewUrl ? `
165667
+
165668
+ [View the review Pullfrog posted \u2192](${params.reviewUrl})` : "";
165669
+ const disable = params.conclusion === "success" ? "" : disableCheckLine(params.owner, params.repo);
165670
+ return { title: base.title, summary: base.summary + review + disable };
165671
+ }
165672
+ async function finalizeRunStatusCheck(params) {
165673
+ const updateParams = {
165674
+ owner: params.owner,
165675
+ repo: params.repo,
165676
+ check_run_id: params.checkRunId,
165677
+ status: "completed",
165678
+ conclusion: params.conclusion,
165679
+ output: terminalOutput({
165680
+ conclusion: params.conclusion,
165681
+ owner: params.owner,
165682
+ repo: params.repo,
165683
+ reviewUrl: params.reviewUrl
165684
+ })
165685
+ };
165686
+ if (params.detailsUrl) updateParams.details_url = params.detailsUrl;
165687
+ await params.octokit.rest.checks.update(updateParams);
165688
+ }
165689
+ async function createTerminalRunStatusCheck(params) {
165604
165690
  const createParams = {
165605
- owner: ctx.repo.owner,
165606
- repo: ctx.repo.name,
165607
- name: params.name,
165691
+ owner: params.owner,
165692
+ repo: params.repo,
165693
+ name: RUN_STATUS_CHECK_NAME,
165608
165694
  head_sha: params.headSha,
165609
165695
  status: "completed",
165610
165696
  conclusion: params.conclusion,
165611
- output: { title: params.title, summary: params.summary }
165697
+ output: terminalOutput({
165698
+ conclusion: params.conclusion,
165699
+ owner: params.owner,
165700
+ repo: params.repo,
165701
+ reviewUrl: params.reviewUrl
165702
+ })
165612
165703
  };
165613
- if (ctx.runId) {
165614
- createParams.details_url = `https://github.com/${ctx.repo.owner}/${ctx.repo.name}/actions/runs/${ctx.runId}`;
165615
- }
165616
- await ctx.octokit.rest.checks.create(createParams);
165617
- log.info(`\xBB posted ${params.name} check (${params.conclusion}) on ${params.headSha.slice(0, 7)}`);
165704
+ if (params.detailsUrl) createParams.details_url = params.detailsUrl;
165705
+ await params.octokit.rest.checks.create(createParams);
165618
165706
  }
165707
+
165708
+ // utils/statusChecks.ts
165619
165709
  async function reportStatusChecks(ctx, params) {
165620
- if (!ctx.payload.statusChecks) return;
165621
165710
  const event = ctx.payload.event;
165622
165711
  const pullNumber = event.issue_number;
165623
165712
  if (event.is_pr !== true || typeof pullNumber !== "number") return;
165713
+ const checkRunId = parseCheckRunId(ctx.payload.checkRun);
165714
+ if (checkRunId === void 0 && !ctx.payload.runStatusCheck && !ctx.payload.approvalCheck) return;
165715
+ const conclusion = params.runSucceeded ? "success" : "failure";
165716
+ const detailsUrl = ctx.runId ? `https://github.com/${ctx.repo.owner}/${ctx.repo.name}/actions/runs/${ctx.runId}` : void 0;
165717
+ if (checkRunId !== void 0) {
165718
+ await finalizeRunStatusCheck({
165719
+ octokit: ctx.octokit,
165720
+ owner: ctx.repo.owner,
165721
+ repo: ctx.repo.name,
165722
+ checkRunId,
165723
+ conclusion,
165724
+ detailsUrl,
165725
+ reviewUrl: ctx.toolState.approval?.url
165726
+ }).then(() => log.info(`\xBB finalized ${RUN_STATUS_CHECK_NAME} check (${conclusion})`)).catch((err) => log.debug(`status checks: ${RUN_STATUS_CHECK_NAME} finalize failed: ${err}`));
165727
+ }
165728
+ const approval = ctx.toolState.approval;
165729
+ const needsApprovalCheck = ctx.payload.approvalCheck && params.runSucceeded && approval;
165730
+ const needsFallbackRunCheck = ctx.payload.runStatusCheck && checkRunId === void 0;
165731
+ if (!needsApprovalCheck && !needsFallbackRunCheck) return;
165624
165732
  let headSha;
165625
165733
  try {
165626
165734
  const pr = await ctx.octokit.rest.pulls.get({
@@ -165633,24 +165741,32 @@ async function reportStatusChecks(ctx, params) {
165633
165741
  log.debug(`status checks: failed to resolve PR #${pullNumber} head sha: ${err}`);
165634
165742
  return;
165635
165743
  }
165636
- const completionSha = primaryRepoState(ctx.toolState).checkoutSha ?? headSha;
165637
- await createCheckRun(ctx, {
165638
- name: COMPLETION_CHECK,
165639
- headSha: completionSha,
165640
- conclusion: params.runSucceeded ? "success" : "failure",
165641
- title: params.runSucceeded ? "Pullfrog run completed" : "Pullfrog run failed",
165642
- summary: params.runSucceeded ? "The Pullfrog run finished successfully." : "The Pullfrog run failed or timed out. See the run logs for details."
165643
- }).catch((err) => log.debug(`status checks: ${COMPLETION_CHECK} post failed: ${err}`));
165644
- const approval = ctx.toolState.approval;
165645
- if (params.runSucceeded && approval) {
165646
- await createCheckRun(ctx, {
165647
- name: APPROVAL_CHECK,
165648
- headSha: approval.sha ?? headSha,
165649
- conclusion: approval.wouldApprove ? "success" : "failure",
165744
+ if (needsFallbackRunCheck) {
165745
+ await createTerminalRunStatusCheck({
165746
+ octokit: ctx.octokit,
165747
+ owner: ctx.repo.owner,
165748
+ repo: ctx.repo.name,
165749
+ headSha: primaryRepoState(ctx.toolState).checkoutSha ?? headSha,
165750
+ conclusion,
165751
+ detailsUrl,
165752
+ reviewUrl: ctx.toolState.approval?.url
165753
+ }).then(() => log.info(`\xBB posted ${RUN_STATUS_CHECK_NAME} check (${conclusion})`)).catch((err) => log.debug(`status checks: ${RUN_STATUS_CHECK_NAME} post failed: ${err}`));
165754
+ }
165755
+ if (!needsApprovalCheck || !approval) return;
165756
+ const createParams = {
165757
+ owner: ctx.repo.owner,
165758
+ repo: ctx.repo.name,
165759
+ name: APPROVAL_CHECK_NAME,
165760
+ head_sha: approval.sha ?? headSha,
165761
+ status: "completed",
165762
+ conclusion: approval.wouldApprove ? "success" : "failure",
165763
+ output: {
165650
165764
  title: approval.wouldApprove ? "Pullfrog would approve" : "Pullfrog would not approve",
165651
165765
  summary: approval.wouldApprove ? "Pullfrog has no outstanding review feedback on this PR." : "Pullfrog has outstanding review feedback or requested changes on this PR."
165652
- }).catch((err) => log.debug(`status checks: ${APPROVAL_CHECK} post failed: ${err}`));
165653
- }
165766
+ }
165767
+ };
165768
+ if (detailsUrl) createParams.details_url = detailsUrl;
165769
+ await ctx.octokit.rest.checks.create(createParams).then(() => log.info(`\xBB posted ${APPROVAL_CHECK_NAME} check`)).catch((err) => log.debug(`status checks: ${APPROVAL_CHECK_NAME} post failed: ${err}`));
165654
165770
  }
165655
165771
 
165656
165772
  // utils/runLifecycle.ts
@@ -15,4 +15,6 @@ export { isLeapingIntoActionCommentBody, LEAPING_INTO_ACTION_PREFIX, } from "../
15
15
  export { MAX_LEARNINGS_LENGTH, truncateAtLineBoundary } from "../utils/learningsTruncate.ts";
16
16
  export type { CreateProgressCommentTarget, ProgressComment, ProgressCommentType, } from "../utils/progressComment.ts";
17
17
  export { createLeapingProgressComment, deleteProgressCommentApi, getProgressComment, updateProgressComment, } from "../utils/progressComment.ts";
18
+ export type { RunStatusCheckConclusion, RunStatusCheckOctokit, } from "../utils/runStatusCheck.ts";
19
+ export { APPROVAL_CHECK_NAME, createRunStatusCheck, finalizeRunStatusCheck, RUN_STATUS_CHECK_NAME, runStatusCheckNeedsFinalizing, } from "../utils/runStatusCheck.ts";
18
20
  export { isValidTimeString, parseTimeString, TIMEOUT_DISABLED, } from "../utils/time.ts";
package/dist/internal.js CHANGED
@@ -1329,6 +1329,113 @@ async function createLeapingProgressComment(ctx, target, body) {
1329
1329
  };
1330
1330
  }
1331
1331
 
1332
+ // utils/runStatusCheck.ts
1333
+ var RUN_STATUS_CHECK_NAME = "Pullfrog";
1334
+ var APPROVAL_CHECK_NAME = "Pullfrog approval";
1335
+ var IN_PROGRESS_OUTPUT = {
1336
+ title: "Pullfrog is running",
1337
+ summary: "Pullfrog is working on this pull request. This check updates when the run finishes."
1338
+ };
1339
+ function disableCheckLine(owner, repo) {
1340
+ const url = `https://pullfrog.com/console/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}#auto-review-prs`;
1341
+ return `
1342
+
1343
+ [Turn off this check \u2192](${url}) \u2014 it reports run status only and gates nothing unless you required it in branch protection.`;
1344
+ }
1345
+ var TERMINAL_OUTPUT = {
1346
+ success: {
1347
+ title: "Pullfrog run completed",
1348
+ summary: "The Pullfrog run finished successfully."
1349
+ },
1350
+ failure: {
1351
+ title: "Pullfrog run failed",
1352
+ summary: "The Pullfrog run failed. See the run logs for details."
1353
+ },
1354
+ cancelled: {
1355
+ title: "Pullfrog run cancelled",
1356
+ summary: "The Pullfrog run was cancelled before it finished."
1357
+ },
1358
+ timed_out: {
1359
+ title: "Pullfrog run timed out",
1360
+ summary: "The Pullfrog run exceeded its timeout. See the run logs for details."
1361
+ },
1362
+ action_required: {
1363
+ title: "Pullfrog run needs attention",
1364
+ summary: "The Pullfrog run stopped and needs attention. See the run logs for details."
1365
+ },
1366
+ neutral: {
1367
+ title: "Pullfrog run finished",
1368
+ summary: "The Pullfrog run finished without a pass or fail outcome."
1369
+ },
1370
+ skipped: {
1371
+ title: "Pullfrog run skipped",
1372
+ summary: "This run was superseded by another Pullfrog run."
1373
+ },
1374
+ stale: {
1375
+ title: "Pullfrog run didn't finish",
1376
+ summary: "Pullfrog never received a completion signal for this run. See the run logs for details."
1377
+ }
1378
+ };
1379
+ function terminalOutput(params) {
1380
+ const base = TERMINAL_OUTPUT[params.conclusion];
1381
+ const review = params.reviewUrl ? `
1382
+
1383
+ [View the review Pullfrog posted \u2192](${params.reviewUrl})` : "";
1384
+ const disable = params.conclusion === "success" ? "" : disableCheckLine(params.owner, params.repo);
1385
+ return { title: base.title, summary: base.summary + review + disable };
1386
+ }
1387
+ async function createRunStatusCheck(params) {
1388
+ const existing = await params.octokit.rest.checks.listForRef({
1389
+ owner: params.owner,
1390
+ repo: params.repo,
1391
+ ref: params.headSha,
1392
+ check_name: RUN_STATUS_CHECK_NAME,
1393
+ status: "in_progress"
1394
+ }).catch(() => void 0);
1395
+ const reusable = existing?.data.check_runs[0];
1396
+ if (reusable) return reusable.id;
1397
+ const createParams = {
1398
+ owner: params.owner,
1399
+ repo: params.repo,
1400
+ name: RUN_STATUS_CHECK_NAME,
1401
+ head_sha: params.headSha,
1402
+ status: "in_progress",
1403
+ output: IN_PROGRESS_OUTPUT
1404
+ };
1405
+ if (params.detailsUrl) createParams.details_url = params.detailsUrl;
1406
+ const created = await params.octokit.rest.checks.create(createParams);
1407
+ return created.data.id;
1408
+ }
1409
+ async function finalizeRunStatusCheck(params) {
1410
+ const updateParams = {
1411
+ owner: params.owner,
1412
+ repo: params.repo,
1413
+ check_run_id: params.checkRunId,
1414
+ status: "completed",
1415
+ conclusion: params.conclusion,
1416
+ output: terminalOutput({
1417
+ conclusion: params.conclusion,
1418
+ owner: params.owner,
1419
+ repo: params.repo,
1420
+ reviewUrl: params.reviewUrl
1421
+ })
1422
+ };
1423
+ if (params.detailsUrl) updateParams.details_url = params.detailsUrl;
1424
+ await params.octokit.rest.checks.update(updateParams);
1425
+ }
1426
+ async function runStatusCheckNeedsFinalizing(params) {
1427
+ try {
1428
+ const existing = await params.octokit.rest.checks.get({
1429
+ owner: params.owner,
1430
+ repo: params.repo,
1431
+ check_run_id: params.checkRunId
1432
+ });
1433
+ return existing.data.status !== "completed";
1434
+ } catch {
1435
+ return false;
1436
+ }
1437
+ }
1438
+
1332
1439
  // utils/time.ts
1333
1440
  var TIMEOUT_DISABLED = "none";
1334
1441
  var TIME_STRING_REGEX = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/;
@@ -1344,6 +1451,7 @@ function isValidTimeString(input) {
1344
1451
  return parseTimeString(input) !== null;
1345
1452
  }
1346
1453
  export {
1454
+ APPROVAL_CHECK_NAME,
1347
1455
  AUTO_EFFICIENT,
1348
1456
  AUTO_INTELLIGENT,
1349
1457
  DEFAULT_PROXY_MODEL,
@@ -1351,12 +1459,15 @@ export {
1351
1459
  MAX_LEARNINGS_LENGTH,
1352
1460
  OAuthInvalidGrantError,
1353
1461
  PULLFROG_DIVIDER,
1462
+ RUN_STATUS_CHECK_NAME,
1354
1463
  TIMEOUT_DISABLED,
1355
1464
  buildPullfrogFooter,
1356
1465
  createLeapingProgressComment,
1466
+ createRunStatusCheck,
1357
1467
  decodeJwtExpMs,
1358
1468
  defaultAutoTier,
1359
1469
  deleteProgressCommentApi,
1470
+ finalizeRunStatusCheck,
1360
1471
  getAutoSelectHintModel,
1361
1472
  getModelEnvVars,
1362
1473
  getModelManagedCredentials,
@@ -1380,6 +1491,7 @@ export {
1380
1491
  resolveDisplayAlias,
1381
1492
  resolveModelSlug,
1382
1493
  resolveOpenRouterModel,
1494
+ runStatusCheckNeedsFinalizing,
1383
1495
  stringifyCodexAuthBody,
1384
1496
  stripExistingFooter,
1385
1497
  truncateAtLineBoundary,
@@ -104,6 +104,7 @@ export interface ToolState {
104
104
  approval?: {
105
105
  wouldApprove: boolean;
106
106
  sha: string | undefined;
107
+ url?: string | undefined;
107
108
  };
108
109
  reviewReplies?: Map<number, {
109
110
  commentId: number;
@@ -22,6 +22,9 @@ export declare const JsonPayload: import("arktype/internal/variants/object.ts").
22
22
  id: string;
23
23
  type: "issue" | "review";
24
24
  } | undefined;
25
+ checkRun?: {
26
+ id: string;
27
+ } | undefined;
25
28
  generateSummary?: boolean | undefined;
26
29
  }, {}>;
27
30
  export declare const Inputs: import("arktype/internal/variants/object.ts").ObjectType<{
@@ -68,10 +71,14 @@ export declare function resolvePayload(resolvedPromptInput: ResolvedPromptInput,
68
71
  id: string;
69
72
  type: "issue" | "review";
70
73
  } | undefined;
74
+ checkRun: {
75
+ id: string;
76
+ } | undefined;
71
77
  generateSummary: boolean | undefined;
72
78
  push: import("../external.ts").PushPermission;
73
79
  shell: import("../external.ts").ShellPermission;
74
- statusChecks: boolean;
80
+ runStatusCheck: boolean;
81
+ approvalCheck: boolean;
75
82
  progressComments: boolean;
76
83
  proxyModel: string | undefined;
77
84
  };
@@ -34,6 +34,7 @@ export interface RepoSettings {
34
34
  autoMergeEnabled: boolean;
35
35
  signedCommits: boolean;
36
36
  progressComments: boolean;
37
+ statusChecks: boolean;
37
38
  modeInstructions: Record<string, string>;
38
39
  learnings: string | null;
39
40
  learningsHeadings: LearningsHeading[];
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Single source of truth for the `pullfrog` commit-status check-run — the row a PR's
3
+ * checks list shows while a run is in flight and after it ends.
4
+ *
5
+ * Pullfrog dispatches its workflow with `ref: <default branch>`, so the GitHub Actions
6
+ * run attaches its check suite to the default branch head and never to the PR head.
7
+ * Nothing about a run is therefore visible from the PR itself unless we post this
8
+ * check-run against the PR head sha ourselves.
9
+ *
10
+ * Created `in_progress` by the server at dispatch — before the runner boots, and
11
+ * upstream of every action-side failure that would otherwise leave the PR silent
12
+ * (billing 402, token resolution, bad model slug, missing provider key). Moved to a
13
+ * terminal conclusion by whichever of three actors reaches it first, all calling
14
+ * `finalizeRunStatusCheck`:
15
+ *
16
+ * 1. the action at run end (`reportStatusChecks`)
17
+ * 2. the `workflow_run.completed` webhook (covers cancel / SIGKILL / early throw)
18
+ * 3. the hourly stuck-run reaper (covers a dropped webhook)
19
+ *
20
+ * That three-layer close-out is what makes an `in_progress` check safe here: a check
21
+ * stranded `in_progress` on a required branch-protection rule would block merges
22
+ * forever, which is why this file did not exist before.
23
+ */
24
+ /**
25
+ * the run-lifecycle check. verdict-agnostic — it reports that a run happened, not what it
26
+ * found. this string IS the branch-protection identifier, so changing it silently breaks
27
+ * any repo that required the old name.
28
+ */
29
+ export declare const RUN_STATUS_CHECK_NAME = "Pullfrog";
30
+ /** the review-verdict check. opt-in, terminal-only, and deliberately separate from the above. */
31
+ export declare const APPROVAL_CHECK_NAME = "Pullfrog approval";
32
+ /**
33
+ * The terminal states we report. Exactly GitHub's check-run `conclusion` enum, which
34
+ * happens to be `WorkflowRunStatus` minus `running` — so the server can map a finished
35
+ * run's status straight across (`checkConclusionFromStatus` in utils/workflowRunStatus.ts).
36
+ */
37
+ export type RunStatusCheckConclusion = "success" | "failure" | "cancelled" | "timed_out" | "action_required" | "neutral" | "skipped" | "stale";
38
+ /**
39
+ * Parse the on-the-wire `{ id: string }` shape (the form carried in `JsonPayload`) into
40
+ * a check-run id. Mirrors `parseProgressComment` — returns undefined when the id isn't a
41
+ * positive integer so callers can short-circuit cleanly.
42
+ */
43
+ export declare function parseCheckRunId(raw: {
44
+ id: string;
45
+ } | null | undefined): number | undefined;
46
+ interface CheckRunResponse {
47
+ data: {
48
+ id: number;
49
+ };
50
+ }
51
+ type CheckRunStatus = "queued" | "in_progress" | "completed";
52
+ type CheckRunOutput = {
53
+ title: string;
54
+ summary: string;
55
+ };
56
+ type CreateCheckRunParams = {
57
+ owner: string;
58
+ repo: string;
59
+ name: string;
60
+ head_sha: string;
61
+ status?: CheckRunStatus;
62
+ conclusion?: RunStatusCheckConclusion;
63
+ details_url?: string;
64
+ output?: CheckRunOutput;
65
+ };
66
+ type UpdateCheckRunParams = {
67
+ owner: string;
68
+ repo: string;
69
+ check_run_id: number;
70
+ status?: CheckRunStatus;
71
+ conclusion?: RunStatusCheckConclusion;
72
+ details_url?: string;
73
+ output?: CheckRunOutput;
74
+ };
75
+ export interface RunStatusCheckOctokit {
76
+ rest: {
77
+ checks: {
78
+ create: (params: CreateCheckRunParams) => Promise<CheckRunResponse>;
79
+ update: (params: UpdateCheckRunParams) => Promise<CheckRunResponse>;
80
+ get: (params: {
81
+ owner: string;
82
+ repo: string;
83
+ check_run_id: number;
84
+ }) => Promise<{
85
+ data: {
86
+ status: string;
87
+ };
88
+ }>;
89
+ listForRef: (params: {
90
+ owner: string;
91
+ repo: string;
92
+ ref: string;
93
+ check_name?: string;
94
+ status?: "queued" | "in_progress" | "completed";
95
+ }) => Promise<{
96
+ data: {
97
+ check_runs: {
98
+ id: number;
99
+ }[];
100
+ };
101
+ }>;
102
+ };
103
+ };
104
+ }
105
+ /**
106
+ * Post the `in_progress` check-run on `headSha` and return its id for the finalizers.
107
+ *
108
+ * Best-effort: a transient 5xx, a repo that revoked `checks: write`, or a PR whose head
109
+ * has already been deleted must never stop a run from dispatching. Returns undefined on
110
+ * failure, which every caller treats as "no check to finalize".
111
+ */
112
+ export declare function createRunStatusCheck(params: {
113
+ octokit: RunStatusCheckOctokit;
114
+ owner: string;
115
+ repo: string;
116
+ headSha: string;
117
+ detailsUrl: string | undefined;
118
+ }): Promise<number | undefined>;
119
+ /**
120
+ * Move an existing check-run to its terminal conclusion.
121
+ *
122
+ * A PATCH, not a second create: `POST /check-runs` with the same name + sha creates a
123
+ * SECOND row rather than replacing the first, so creating twice would show the PR two
124
+ * contradictory `pullfrog` rows. (That is a real bug today — `finalizeSuccessRun` posts
125
+ * success and a later throw posts failure, both as fresh rows.)
126
+ */
127
+ export declare function finalizeRunStatusCheck(params: {
128
+ octokit: RunStatusCheckOctokit;
129
+ owner: string;
130
+ repo: string;
131
+ checkRunId: number;
132
+ conclusion: RunStatusCheckConclusion;
133
+ detailsUrl: string | undefined;
134
+ reviewUrl?: string | undefined;
135
+ }): Promise<void>;
136
+ /**
137
+ * Post an already-terminal check-run in one call.
138
+ *
139
+ * The fallback for runs that never got a server-created check to update: a rolling
140
+ * deploy where the dispatch payload predates `checkRun`, or a workflow invoked outside
141
+ * Pullfrog's own dispatch path (`prompt_file`, a hand-written step).
142
+ */
143
+ export declare function createTerminalRunStatusCheck(params: {
144
+ octokit: RunStatusCheckOctokit;
145
+ owner: string;
146
+ repo: string;
147
+ headSha: string;
148
+ conclusion: RunStatusCheckConclusion;
149
+ detailsUrl: string | undefined;
150
+ reviewUrl?: string | undefined;
151
+ }): Promise<void>;
152
+ /**
153
+ * Whether this check still needs finalizing.
154
+ *
155
+ * The server-side close-outs are BACKSTOPS: they exist for runs the action could not
156
+ * finalize itself. Firing them unconditionally is actively harmful, because the action
157
+ * writes a richer summary than they can — it knows the review URL, they do not — so a
158
+ * blind re-PATCH silently strips that link seconds after it appears. It also resets
159
+ * `completed_at`, discarding the duration GitHub renders as "Successful in 2m".
160
+ *
161
+ * Returns false when GitHub cannot be reached: a backstop that cannot confirm the check
162
+ * is unfinished must not overwrite a good one.
163
+ */
164
+ export declare function runStatusCheckNeedsFinalizing(params: {
165
+ octokit: RunStatusCheckOctokit;
166
+ owner: string;
167
+ repo: string;
168
+ checkRunId: number;
169
+ }): Promise<boolean>;
170
+ export {};
@@ -1,24 +1,21 @@
1
1
  import type { ToolContext } from "../mcp/server.ts";
2
2
  /**
3
- * post the opt-in `pullfrog` (run completion) and `pullfrog-approval` (review
4
- * verdict) commit-status check-runs so they can be required by branch
5
- * protection. no-op unless the `status_checks` input is enabled and the run is
6
- * on a pull request.
3
+ * post the `Pullfrog` (run lifecycle) and `Pullfrog approval` (review verdict)
4
+ * commit-status check-runs.
7
5
  *
8
- * terminal-only by design: we never create an `in_progress` check. a
9
- * hard-cancelled run (SIGKILL) would strand a required check `in_progress` and
10
- * block merges forever; an absent required check already blocks merge the same
11
- * way while the run is in flight, with no stuck-check failure mode.
6
+ * - `Pullfrog` is on by default (`Repo.statusChecks`). the server already created it
7
+ * `in_progress` at dispatch, so the work here is a PATCH to its terminal conclusion —
8
+ * see `runStatusCheck.ts` for why a second create would leave two contradictory rows.
9
+ * the terminal-create fallback covers a payload with no `checkRun` (older server
10
+ * build mid-rolling-deploy, or a workflow driven outside Pullfrog's dispatch path).
11
+ * - `Pullfrog approval` stays opt-in (`status_checks: enabled`) and terminal-only:
12
+ * it asserts a review verdict, which only exists once a run produces one. anchored
13
+ * to the exact reviewed sha so a mid-run push leaves the new head unapproved until
14
+ * a follow-up re-review reports.
12
15
  *
13
- * - `pullfrog` is posted on every PR run: success iff the run finished
14
- * successfully, failure on error/timeout. review-verdict-agnostic.
15
- * - `pullfrog-approval` is posted only when this run produced an approval
16
- * verdict (`toolState.approval`, set by create_pull_request_review),
17
- * anchored to the reviewed sha so a mid-run push leaves the new head
18
- * unapproved until the follow-up re-review reports.
19
- *
20
- * best-effort throughout: a check-post failure (fork PR head not in the base
21
- * repo, transient 5xx, closed PR) must never flip the run's own outcome.
16
+ * best-effort throughout: a check-post failure (transient 5xx, closed PR, revoked
17
+ * permission) must never flip the run's own outcome. the `workflow_run.completed` webhook
18
+ * and both stuck-run reaper sweeps close out a check this function fails to finalize.
22
19
  */
23
20
  export declare function reportStatusChecks(ctx: ToolContext, params: {
24
21
  runSucceeded: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.1.46",
3
+ "version": "0.1.47",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "pullfrog": "dist/cli.mjs",