pullfrog 0.1.31 → 0.1.32

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/dist/internal.js CHANGED
@@ -9,15 +9,20 @@ var providers = {
9
9
  displayName: "Anthropic",
10
10
  envVars: ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
11
11
  models: {
12
- // claude-fable-5 is disabled globally access has been revoked, so it
13
- // can't run as-is anywhere (BYOK, Claude Code, or Router). the fallback
14
- // redirects all resolution to opus and hides it from pickers; opus is the
15
- // universally-available flagship, the AUTO_INTELLIGENT tier target, and
16
- // the recommended pick. clear the fallback if fable access returns (#959).
12
+ // claude-fable-5 is selectable but not recommended/auto-selected it's
13
+ // moving to usage-credits-only billing and isn't broadly available yet, so
14
+ // opus stays the universally-available flagship, the AUTO_INTELLIGENT tier
15
+ // target, and the recommended pick. an explicit fable pick hits the real
16
+ // API and is access-gated: it errors for accounts without access today and
17
+ // just works once usage credits are live — no silent opus fallback, the
18
+ // API is the source of truth (#959).
17
19
  "claude-fable": {
18
20
  displayName: "Claude Fable",
19
21
  resolve: "anthropic/claude-fable-5",
20
- fallback: "anthropic/claude-opus",
22
+ // rolling alias: models.dev's OpenRouter mirror lags brand-new pinned
23
+ // versions (claude-fable-5 isn't indexed yet), so track ~…-latest to
24
+ // stay catalog-valid and auto-follow version bumps.
25
+ openRouterResolve: "openrouter/~anthropic/claude-fable-latest",
21
26
  subagentModel: "claude-sonnet"
22
27
  },
23
28
  "claude-opus": {
@@ -29,8 +34,8 @@ var providers = {
29
34
  },
30
35
  "claude-sonnet": {
31
36
  displayName: "Claude Sonnet",
32
- resolve: "anthropic/claude-sonnet-4-6",
33
- openRouterResolve: "openrouter/anthropic/claude-sonnet-4.6"
37
+ resolve: "anthropic/claude-sonnet-5",
38
+ openRouterResolve: "openrouter/anthropic/claude-sonnet-5"
34
39
  },
35
40
  "claude-haiku": {
36
41
  displayName: "Claude Haiku",
@@ -208,8 +213,8 @@ var providers = {
208
213
  },
209
214
  "claude-sonnet": {
210
215
  displayName: "Claude Sonnet",
211
- resolve: "opencode/claude-sonnet-4-6",
212
- openRouterResolve: "openrouter/anthropic/claude-sonnet-4.6"
216
+ resolve: "opencode/claude-sonnet-5",
217
+ openRouterResolve: "openrouter/anthropic/claude-sonnet-5"
213
218
  },
214
219
  "claude-haiku": {
215
220
  displayName: "Claude Haiku",
@@ -23,6 +23,41 @@ export declare const TRANSIENT_REVIEW_RETRY_DELAYS_MS: number[];
23
23
  */
24
24
  export declare function commentableLinesForFile(patch: string | undefined): CommentableLines;
25
25
  export declare function buildCommentableMap(ctx: ToolContext, pullNumber: number): Promise<Map<string, CommentableLines>>;
26
+ /**
27
+ * count unresolved review threads on a PR whose root comment was authored by
28
+ * Pullfrog's bot. this is the full set of open Pullfrog findings on the PR —
29
+ * the basis for both the never-approve-with-outstanding-issues invariant (the
30
+ * approval gate below) and the `pullfrog-approval` status verdict. it is a
31
+ * function of all open findings, NOT just the latest commit's delta.
32
+ *
33
+ * outdated-but-unresolved threads still count — GitHub marks a thread
34
+ * `[OUTDATED]` when the anchor line moved (reformat, rename, force-push), which
35
+ * is not the same as the concern being addressed. human-reviewer threads are
36
+ * excluded: they belong to those reviewers to resolve. walks every page so a
37
+ * long-lived PR with >100 threads can't hide an outstanding finding.
38
+ */
39
+ export declare function countOutstandingPullfrogThreads(ctx: ToolContext, pullNumber: number): Promise<number>;
40
+ /**
41
+ * proactive approve-when-clean for the Fix-all / Fix-👍s flow (`fix_review`
42
+ * trigger). when such a run completes successfully and every Pullfrog-originated
43
+ * review thread it raised is resolved, post a NEW approving review summarizing
44
+ * the run's work.
45
+ *
46
+ * the verification step shares `countOutstandingPullfrogThreads` with the
47
+ * approval gate in create_pull_request_review: that gate REACTIVELY blocks a
48
+ * bad approval; this is the PROACTIVE approve side. approval is contingent on
49
+ * Pullfrog's own findings actually being addressed — never a blind post-fix
50
+ * approval. a Fix-👍s run that only resolved a subset naturally fails the
51
+ * outstanding-thread check and is left unapproved.
52
+ *
53
+ * respects `prApproveEnabled` (the repo's "Allow Pullfrog to approve PRs"
54
+ * opt-in): a repo that hasn't enabled binding bot approvals never gets one
55
+ * here. skips when the agent already rendered an explicit review verdict this
56
+ * run (`toolState.approval`), so an agent that found a real issue and submitted
57
+ * a non-approving review is never overridden. best-effort — a failure must not
58
+ * flip the run's outcome.
59
+ */
60
+ export declare function approveAfterFix(ctx: ToolContext): Promise<void>;
26
61
  export type ReviewCommentInput = NonNullable<RestEndpointMethodTypes["pulls"]["createReview"]["parameters"]["comments"]>[number];
27
62
  export interface DroppedComment {
28
63
  path: string;
@@ -155,6 +190,11 @@ export declare function CreatePullRequestReviewTool(ctx: ToolContext): import("f
155
190
  }>> & {
156
191
  mutates?: boolean;
157
192
  };
193
+ type FooterOpts = {
194
+ body: string;
195
+ approved: boolean;
196
+ hasComments: boolean;
197
+ };
158
198
  /**
159
199
  * clear a pending review draft stranded on the PR by a prior hard-killed run
160
200
  * (workflow timeout, OOM) so the next createReview can succeed.
@@ -196,6 +236,7 @@ export declare function clearStrandedPendingReview(ctx: ToolContext, params: {
196
236
  * comments-only) until a body-path run happened to clear the draft.
197
237
  */
198
238
  export declare function createReviewWithStrandedRecovery(ctx: ToolContext, params: RestEndpointMethodTypes["pulls"]["createReview"]["parameters"]): Promise<Awaited<ReturnType<typeof ctx.octokit.rest.pulls.createReview>>>;
239
+ export declare function createAndSubmitWithFooter(ctx: ToolContext, params: RestEndpointMethodTypes["pulls"]["createReview"]["parameters"], opts: FooterOpts): Promise<Awaited<ReturnType<typeof ctx.octokit.rest.pulls.submitReview>>>;
199
240
  /**
200
241
  * report the review node ID so the WorkflowRun is marked as "review submitted".
201
242
  * exported for use in main.ts post-agent cleanup.
@@ -100,6 +100,10 @@ export interface ToolState {
100
100
  nodeId: string;
101
101
  reviewedSha: string | undefined;
102
102
  };
103
+ approval?: {
104
+ wouldApprove: boolean;
105
+ sha: string | undefined;
106
+ };
103
107
  reviewReplies?: Map<number, {
104
108
  commentId: number;
105
109
  url: string | undefined;
@@ -10,7 +10,9 @@ import type { Agent } from "../agents/index.ts";
10
10
  * Bedrock model, change `BEDROCK_MODEL_ID`, not `PULLFROG_MODEL`.
11
11
  * 2. slug from repo config / payload → alias registry. routing slugs
12
12
  * (e.g. `bedrock/byok`) defer to a separate env var (`BEDROCK_MODEL_ID`).
13
- * 3. undefined agent will auto-select.
13
+ * a non-curated value with a slash passes through unchanged as a raw
14
+ * models.dev specifier (same as `PULLFROG_MODEL`), not auto-selected away.
15
+ * 3. undefined (no slug, or a bare-word non-specifier) — agent auto-selects.
14
16
  */
15
17
  export declare function resolveModel(ctx: {
16
18
  slug?: string | undefined;
@@ -8,3 +8,12 @@
8
8
  * substrings on `error.message` (e.g. provider-specific framings).
9
9
  */
10
10
  export declare function isTransientNetworkError(error: unknown, extraPatterns?: string[]): boolean;
11
+ /**
12
+ * broader transient predicate for octokit calls: an HTTP 5xx (GitHub upstream
13
+ * blip) OR a network-class error. distinct from `isTransientNetworkError` — an
14
+ * HTTP 4xx (404/401/403/422) is deterministic and fails fast. the action
15
+ * octokit carries only the throttling plugin + a 401 re-mint (no 5xx retry),
16
+ * so callers on critical paths retry idempotent GitHub reads/dispatches with
17
+ * this. mirrors the server's `isTransientUpstreamError`. see #999.
18
+ */
19
+ export declare function isTransientOctokitError(error: unknown): boolean;
@@ -25,17 +25,25 @@ export declare const JsonPayload: import("arktype/internal/variants/object.ts").
25
25
  generateSummary?: boolean | undefined;
26
26
  }, {}>;
27
27
  export declare const Inputs: import("arktype/internal/variants/object.ts").ObjectType<{
28
- prompt: string;
28
+ prompt?: string | undefined;
29
+ prompt_file?: string | undefined;
29
30
  model?: string | undefined;
30
31
  timeout?: string | undefined;
31
32
  push?: "disabled" | "enabled" | "restricted" | undefined;
32
33
  shell?: "disabled" | "enabled" | "restricted" | undefined;
34
+ status_checks?: "disabled" | "enabled" | undefined;
33
35
  cwd?: string | undefined;
34
36
  output_schema?: string | undefined;
35
37
  }, {}>;
36
38
  export type Inputs = typeof Inputs.infer;
37
39
  export type ResolvedPromptInput = string | typeof JsonPayload.infer;
38
40
  export declare function resolvePromptInput(): ResolvedPromptInput;
41
+ /**
42
+ * true when `actor` is Pullfrog's own GitHub identity — the bot login
43
+ * (`pullfrog[bot]` / `pullfrogdev[bot]`) or the bare account. used to skip
44
+ * self-triggered events and to recognize Pullfrog-originated review threads.
45
+ */
46
+ export declare const isPullfrog: (actor: string | null | undefined) => boolean;
39
47
  export declare function resolvePayload(resolvedPromptInput: ResolvedPromptInput, repoSettings: RepoSettings): {
40
48
  "~pullfrog": true;
41
49
  version: string;
@@ -62,6 +70,7 @@ export declare function resolvePayload(resolvedPromptInput: ResolvedPromptInput,
62
70
  generateSummary: boolean | undefined;
63
71
  push: import("../external.ts").PushPermission;
64
72
  shell: import("../external.ts").ShellPermission;
73
+ statusChecks: boolean;
65
74
  proxyModel: string | undefined;
66
75
  };
67
76
  export type ResolvedPayload = ReturnType<typeof resolvePayload>;
@@ -0,0 +1,25 @@
1
+ import type { ToolContext } from "../mcp/server.ts";
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.
7
+ *
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.
12
+ *
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.
22
+ */
23
+ export declare function reportStatusChecks(ctx: ToolContext, params: {
24
+ runSucceeded: boolean;
25
+ }): Promise<void>;
@@ -4,6 +4,21 @@ export declare function range(n: number): number[];
4
4
  export declare function range(start: number, end: number): number[];
5
5
  export declare function range(n: number, fn: (i: number) => number): number[];
6
6
  export declare function schedule(fn: (i: number) => number, count: number): number[];
7
+ /**
8
+ * read a retry delay (ms) from a thrown HTTP error's response headers, honoring
9
+ * GitHub's rate-limit backoff hints on octokit errors (`error.response.headers`):
10
+ * - `retry-after` (RFC 9110 delta-seconds or an HTTP-date) — an explicit
11
+ * backoff directive whenever present (covers secondary rate limits, which
12
+ * carry it with `x-ratelimit-remaining > 0`).
13
+ * - else `x-ratelimit-reset` (epoch seconds) but ONLY when
14
+ * `x-ratelimit-remaining` is `"0"` — GitHub sends the reset header on ~every
15
+ * response (~1h ahead), so it's a backoff hint only under actual primary
16
+ * rate-limit exhaustion (matching octokit's throttling plugin). without this
17
+ * gate a non-rate-limit retried error (e.g. a permissions 403) would be
18
+ * wrongly delayed to the reset time.
19
+ * returns `undefined` when no applicable hint is present/parseable.
20
+ */
21
+ export declare function retryAfterMs(error: unknown): number | undefined;
7
22
  type InferInput<S> = S extends StandardSchemaV1<infer I, any> ? I : never;
8
23
  type InferOutput<S> = S extends StandardSchemaV1<any, infer O> ? O : never;
9
24
  export interface OpOptions<TReturn = any> {
@@ -11,6 +26,17 @@ export interface OpOptions<TReturn = any> {
11
26
  ttl?: number;
12
27
  maxItems?: number;
13
28
  retries?: number[];
29
+ /**
30
+ * honor a server backoff hint on retries: when a retried error carries a
31
+ * `retry-after` / `x-ratelimit-reset` header, schedule the next retry from
32
+ * that value (clamped to `[fixed delay, retryAfterCap]`) instead of the fixed
33
+ * `retries[attempt]` backoff. `true` uses the built-in `retryAfterMs` reader;
34
+ * a function extracts a delay-ms from a custom error shape. errors without the
35
+ * header fall back to the fixed backoff.
36
+ */
37
+ retryAfter?: boolean | ((error: unknown) => number | undefined);
38
+ /** cap (ms) on a honored retry-after hint. default 20000. */
39
+ retryAfterCap?: number;
14
40
  cacheHit?: ((key: string) => void) | null;
15
41
  cacheMiss?: ((key: string) => void) | null;
16
42
  skipCache?: (result: TReturn) => boolean;
package/dist/yes.js CHANGED
@@ -1022,6 +1022,34 @@ function range(a, b) {
1022
1022
  function schedule(fn, count) {
1023
1023
  return Array.from({ length: count }, (_, i) => fn(i));
1024
1024
  }
1025
+ var DEFAULT_RETRY_AFTER_CAP_MS = 2e4;
1026
+ function getErrorResponseHeaders(error) {
1027
+ if (error === null || typeof error !== "object") return void 0;
1028
+ const response = error.response;
1029
+ if (response === null || typeof response !== "object") return void 0;
1030
+ const headers = response.headers;
1031
+ if (headers === null || typeof headers !== "object") return void 0;
1032
+ return headers;
1033
+ }
1034
+ function retryAfterMs(error) {
1035
+ const headers = getErrorResponseHeaders(error);
1036
+ if (!headers) return void 0;
1037
+ const retryAfter = headers["retry-after"];
1038
+ if (typeof retryAfter === "string" && retryAfter.trim() !== "") {
1039
+ const seconds = Number(retryAfter);
1040
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
1041
+ const dateMs = Date.parse(retryAfter);
1042
+ if (!Number.isNaN(dateMs)) return Math.max(0, dateMs - Date.now());
1043
+ }
1044
+ if (headers["x-ratelimit-remaining"] === "0") {
1045
+ const reset = headers["x-ratelimit-reset"];
1046
+ if (typeof reset === "string" && reset.trim() !== "") {
1047
+ const resetEpoch = Number(reset);
1048
+ if (Number.isFinite(resetEpoch)) return Math.max(0, resetEpoch * 1e3 - Date.now());
1049
+ }
1050
+ }
1051
+ return void 0;
1052
+ }
1025
1053
  function validateSchema(schema, value) {
1026
1054
  const result = schema["~standard"].validate(value);
1027
1055
  if (result instanceof Promise) {
@@ -1159,7 +1187,15 @@ function _op(fn, options) {
1159
1187
  log.info({ key, error, attempt });
1160
1188
  }
1161
1189
  } else {
1162
- const delay = retries[attempt];
1190
+ let delay = retries[attempt];
1191
+ if (options.retryAfter) {
1192
+ const extract = options.retryAfter === true ? retryAfterMs : options.retryAfter;
1193
+ const hint = extract(error);
1194
+ if (hint !== void 0) {
1195
+ const cap = options.retryAfterCap ?? DEFAULT_RETRY_AFTER_CAP_MS;
1196
+ delay = Math.max(delay, Math.min(cap, hint));
1197
+ }
1198
+ }
1163
1199
  if (namePrefix) {
1164
1200
  log.info(
1165
1201
  `${namePrefix}attempt ${attempt + 1}/${retries.length + 1} failed, retrying in ${delay}ms`
@@ -1240,5 +1276,6 @@ function _op(fn, options) {
1240
1276
  export {
1241
1277
  op,
1242
1278
  range,
1279
+ retryAfterMs,
1243
1280
  schedule
1244
1281
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "pullfrog": "dist/cli.mjs",