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/README.md +17 -1
- package/dist/cli.mjs +461 -203
- package/dist/index.js +460 -202
- package/dist/internal.js +15 -10
- package/dist/mcp/review.d.ts +41 -0
- package/dist/toolState.d.ts +4 -0
- package/dist/utils/agent.d.ts +3 -1
- package/dist/utils/isTransientNetworkError.d.ts +9 -0
- package/dist/utils/payload.d.ts +10 -1
- package/dist/utils/statusChecks.d.ts +25 -0
- package/dist/yes/index.d.ts +26 -0
- package/dist/yes.js +38 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -101618,15 +101618,20 @@ var providers = {
|
|
|
101618
101618
|
displayName: "Anthropic",
|
|
101619
101619
|
envVars: ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
|
|
101620
101620
|
models: {
|
|
101621
|
-
// claude-fable-5 is
|
|
101622
|
-
//
|
|
101623
|
-
//
|
|
101624
|
-
//
|
|
101625
|
-
//
|
|
101621
|
+
// claude-fable-5 is selectable but not recommended/auto-selected — it's
|
|
101622
|
+
// moving to usage-credits-only billing and isn't broadly available yet, so
|
|
101623
|
+
// opus stays the universally-available flagship, the AUTO_INTELLIGENT tier
|
|
101624
|
+
// target, and the recommended pick. an explicit fable pick hits the real
|
|
101625
|
+
// API and is access-gated: it errors for accounts without access today and
|
|
101626
|
+
// just works once usage credits are live — no silent opus fallback, the
|
|
101627
|
+
// API is the source of truth (#959).
|
|
101626
101628
|
"claude-fable": {
|
|
101627
101629
|
displayName: "Claude Fable",
|
|
101628
101630
|
resolve: "anthropic/claude-fable-5",
|
|
101629
|
-
|
|
101631
|
+
// rolling alias: models.dev's OpenRouter mirror lags brand-new pinned
|
|
101632
|
+
// versions (claude-fable-5 isn't indexed yet), so track ~…-latest to
|
|
101633
|
+
// stay catalog-valid and auto-follow version bumps.
|
|
101634
|
+
openRouterResolve: "openrouter/~anthropic/claude-fable-latest",
|
|
101630
101635
|
subagentModel: "claude-sonnet"
|
|
101631
101636
|
},
|
|
101632
101637
|
"claude-opus": {
|
|
@@ -101638,8 +101643,8 @@ var providers = {
|
|
|
101638
101643
|
},
|
|
101639
101644
|
"claude-sonnet": {
|
|
101640
101645
|
displayName: "Claude Sonnet",
|
|
101641
|
-
resolve: "anthropic/claude-sonnet-
|
|
101642
|
-
openRouterResolve: "openrouter/anthropic/claude-sonnet-
|
|
101646
|
+
resolve: "anthropic/claude-sonnet-5",
|
|
101647
|
+
openRouterResolve: "openrouter/anthropic/claude-sonnet-5"
|
|
101643
101648
|
},
|
|
101644
101649
|
"claude-haiku": {
|
|
101645
101650
|
displayName: "Claude Haiku",
|
|
@@ -101817,8 +101822,8 @@ var providers = {
|
|
|
101817
101822
|
},
|
|
101818
101823
|
"claude-sonnet": {
|
|
101819
101824
|
displayName: "Claude Sonnet",
|
|
101820
|
-
resolve: "opencode/claude-sonnet-
|
|
101821
|
-
openRouterResolve: "openrouter/anthropic/claude-sonnet-
|
|
101825
|
+
resolve: "opencode/claude-sonnet-5",
|
|
101826
|
+
openRouterResolve: "openrouter/anthropic/claude-sonnet-5"
|
|
101822
101827
|
},
|
|
101823
101828
|
"claude-haiku": {
|
|
101824
101829
|
displayName: "Claude Haiku",
|
|
@@ -102836,7 +102841,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
102836
102841
|
// package.json
|
|
102837
102842
|
var package_default = {
|
|
102838
102843
|
name: "pullfrog",
|
|
102839
|
-
version: "0.1.
|
|
102844
|
+
version: "0.1.32",
|
|
102840
102845
|
type: "module",
|
|
102841
102846
|
bin: {
|
|
102842
102847
|
pullfrog: "dist/cli.mjs",
|
|
@@ -104575,7 +104580,7 @@ function buildAgentsJson() {
|
|
|
104575
104580
|
[REVIEWER_AGENT_NAME]: {
|
|
104576
104581
|
description: "Read-only review subagent for lens-based code review (correctness, security, billing-subsystem, etc.). Reads only \u2014 no writes, no state-changing shell or MCP calls, no nested subagent dispatch.",
|
|
104577
104582
|
prompt: REVIEWER_SYSTEM_PROMPT,
|
|
104578
|
-
model: "claude-sonnet-
|
|
104583
|
+
model: "claude-sonnet-5"
|
|
104579
104584
|
}
|
|
104580
104585
|
};
|
|
104581
104586
|
return JSON.stringify(agents2);
|
|
@@ -152710,6 +152715,34 @@ function getCacheKeyString(value2) {
|
|
|
152710
152715
|
);
|
|
152711
152716
|
}
|
|
152712
152717
|
}
|
|
152718
|
+
var DEFAULT_RETRY_AFTER_CAP_MS = 2e4;
|
|
152719
|
+
function getErrorResponseHeaders(error49) {
|
|
152720
|
+
if (error49 === null || typeof error49 !== "object") return void 0;
|
|
152721
|
+
const response = error49.response;
|
|
152722
|
+
if (response === null || typeof response !== "object") return void 0;
|
|
152723
|
+
const headers = response.headers;
|
|
152724
|
+
if (headers === null || typeof headers !== "object") return void 0;
|
|
152725
|
+
return headers;
|
|
152726
|
+
}
|
|
152727
|
+
function retryAfterMs(error49) {
|
|
152728
|
+
const headers = getErrorResponseHeaders(error49);
|
|
152729
|
+
if (!headers) return void 0;
|
|
152730
|
+
const retryAfter = headers["retry-after"];
|
|
152731
|
+
if (typeof retryAfter === "string" && retryAfter.trim() !== "") {
|
|
152732
|
+
const seconds = Number(retryAfter);
|
|
152733
|
+
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
|
|
152734
|
+
const dateMs = Date.parse(retryAfter);
|
|
152735
|
+
if (!Number.isNaN(dateMs)) return Math.max(0, dateMs - Date.now());
|
|
152736
|
+
}
|
|
152737
|
+
if (headers["x-ratelimit-remaining"] === "0") {
|
|
152738
|
+
const reset = headers["x-ratelimit-reset"];
|
|
152739
|
+
if (typeof reset === "string" && reset.trim() !== "") {
|
|
152740
|
+
const resetEpoch = Number(reset);
|
|
152741
|
+
if (Number.isFinite(resetEpoch)) return Math.max(0, resetEpoch * 1e3 - Date.now());
|
|
152742
|
+
}
|
|
152743
|
+
}
|
|
152744
|
+
return void 0;
|
|
152745
|
+
}
|
|
152713
152746
|
function validateSchema(schema2, value2) {
|
|
152714
152747
|
const result = schema2["~standard"].validate(value2);
|
|
152715
152748
|
if (result instanceof Promise) {
|
|
@@ -152847,7 +152880,15 @@ function _op(fn2, options) {
|
|
|
152847
152880
|
log2.info({ key, error: error49, attempt });
|
|
152848
152881
|
}
|
|
152849
152882
|
} else {
|
|
152850
|
-
|
|
152883
|
+
let delay2 = retries[attempt];
|
|
152884
|
+
if (options.retryAfter) {
|
|
152885
|
+
const extract = options.retryAfter === true ? retryAfterMs : options.retryAfter;
|
|
152886
|
+
const hint = extract(error49);
|
|
152887
|
+
if (hint !== void 0) {
|
|
152888
|
+
const cap = options.retryAfterCap ?? DEFAULT_RETRY_AFTER_CAP_MS;
|
|
152889
|
+
delay2 = Math.max(delay2, Math.min(cap, hint));
|
|
152890
|
+
}
|
|
152891
|
+
}
|
|
152851
152892
|
if (namePrefix) {
|
|
152852
152893
|
log2.info(
|
|
152853
152894
|
`${namePrefix}attempt ${attempt + 1}/${retries.length + 1} failed, retrying in ${delay2}ms`
|
|
@@ -158576,6 +158617,12 @@ function isTransientNetworkError(error49, extraPatterns = []) {
|
|
|
158576
158617
|
const patterns = ["fetch failed", "ECONNRESET", "ETIMEDOUT", ...extraPatterns];
|
|
158577
158618
|
return patterns.some((p2) => error49.message.includes(p2));
|
|
158578
158619
|
}
|
|
158620
|
+
function isTransientOctokitError(error49) {
|
|
158621
|
+
if (error49 !== null && typeof error49 === "object" && "status" in error49 && typeof error49.status === "number") {
|
|
158622
|
+
return error49.status >= 500;
|
|
158623
|
+
}
|
|
158624
|
+
return isTransientNetworkError(error49);
|
|
158625
|
+
}
|
|
158579
158626
|
|
|
158580
158627
|
// utils/patchWorkflowRunFields.ts
|
|
158581
158628
|
var STRING_KEYS = [
|
|
@@ -158669,6 +158716,195 @@ function aggregateUsage(entries) {
|
|
|
158669
158716
|
return out;
|
|
158670
158717
|
}
|
|
158671
158718
|
|
|
158719
|
+
// utils/payload.ts
|
|
158720
|
+
var core4 = __toESM(require_core(), 1);
|
|
158721
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
158722
|
+
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
|
|
158723
|
+
|
|
158724
|
+
// utils/versioning.ts
|
|
158725
|
+
var import_semver2 = __toESM(require_semver2(), 1);
|
|
158726
|
+
var COMPATIBILITY_POLICY = "non-breaking";
|
|
158727
|
+
function validateCompatibility(payloadVersion, actionVersion) {
|
|
158728
|
+
const payloadSemVer = import_semver2.default.parse(payloadVersion);
|
|
158729
|
+
if (!payloadSemVer)
|
|
158730
|
+
throw new Error(`Payload version ${payloadVersion} is not a valid semantic version.`);
|
|
158731
|
+
const major = payloadSemVer.major;
|
|
158732
|
+
const minor = payloadSemVer.minor;
|
|
158733
|
+
const patch = payloadSemVer.patch;
|
|
158734
|
+
const compatibilityRange = COMPATIBILITY_POLICY === "same-features" ? `^${major}.${minor}.${major === 0 ? patch : 0}` : `^${major}.${major === 0 ? minor : 0}.${major === 0 ? "x" : 0}`;
|
|
158735
|
+
if (!import_semver2.default.satisfies(actionVersion, compatibilityRange)) {
|
|
158736
|
+
throw new Error(
|
|
158737
|
+
`Payload version ${payloadVersion} is incompatible with action version ${actionVersion}. Please update your workflow to use at least ${import_semver2.default.minVersion(compatibilityRange)} version of the action.`
|
|
158738
|
+
);
|
|
158739
|
+
}
|
|
158740
|
+
}
|
|
158741
|
+
|
|
158742
|
+
// utils/payload.ts
|
|
158743
|
+
var ShellPermissionInput = type.enumerated("disabled", "restricted", "enabled");
|
|
158744
|
+
var PushPermissionInput = type.enumerated("disabled", "restricted", "enabled");
|
|
158745
|
+
var StatusChecksInput = type.enumerated("disabled", "enabled");
|
|
158746
|
+
var JsonPayload = type({
|
|
158747
|
+
"~pullfrog": "true",
|
|
158748
|
+
version: "string",
|
|
158749
|
+
"model?": "string | undefined",
|
|
158750
|
+
"modelExplicit?": "boolean | undefined",
|
|
158751
|
+
prompt: "string",
|
|
158752
|
+
"triggerer?": "string | undefined",
|
|
158753
|
+
"baseInstructions?": "string | undefined",
|
|
158754
|
+
"eventInstructions?": "string",
|
|
158755
|
+
"previousRunsNote?": "string",
|
|
158756
|
+
"event?": "object",
|
|
158757
|
+
"xrepo?": type({
|
|
158758
|
+
mode: "'all' | 'explicit'",
|
|
158759
|
+
read: "string[]",
|
|
158760
|
+
write: "string[]",
|
|
158761
|
+
// optional so a payload from an older server build (pre-`unavailable`)
|
|
158762
|
+
// still parses against a newer action across a rolling deploy.
|
|
158763
|
+
"unavailable?": "string[]"
|
|
158764
|
+
}).or("undefined"),
|
|
158765
|
+
"timeout?": "string | undefined",
|
|
158766
|
+
"progressComment?": type({
|
|
158767
|
+
id: "string",
|
|
158768
|
+
type: "'issue' | 'review'"
|
|
158769
|
+
}).or("undefined"),
|
|
158770
|
+
"generateSummary?": "boolean | undefined"
|
|
158771
|
+
});
|
|
158772
|
+
var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
|
|
158773
|
+
function isCollaborator(event) {
|
|
158774
|
+
const perm = event.authorPermission;
|
|
158775
|
+
return perm !== void 0 && COLLABORATOR_PERMISSIONS.includes(perm);
|
|
158776
|
+
}
|
|
158777
|
+
var Inputs = type({
|
|
158778
|
+
"prompt?": type.string.or("undefined"),
|
|
158779
|
+
"prompt_file?": type.string.or("undefined"),
|
|
158780
|
+
"model?": type.string.or("undefined"),
|
|
158781
|
+
"timeout?": type.string.or("undefined"),
|
|
158782
|
+
"push?": PushPermissionInput.or("undefined"),
|
|
158783
|
+
"shell?": ShellPermissionInput.or("undefined"),
|
|
158784
|
+
"status_checks?": StatusChecksInput.or("undefined"),
|
|
158785
|
+
"cwd?": type.string.or("undefined"),
|
|
158786
|
+
"output_schema?": type.string.or("undefined")
|
|
158787
|
+
});
|
|
158788
|
+
function isPayloadEvent(value2) {
|
|
158789
|
+
return typeof value2 === "object" && value2 !== null && "trigger" in value2;
|
|
158790
|
+
}
|
|
158791
|
+
function resolveCwd(cwd) {
|
|
158792
|
+
const workspace = process.env.GITHUB_WORKSPACE;
|
|
158793
|
+
if (!cwd) return workspace;
|
|
158794
|
+
if (isAbsolute2(cwd)) return cwd;
|
|
158795
|
+
return workspace ? resolve2(workspace, cwd) : cwd;
|
|
158796
|
+
}
|
|
158797
|
+
function resolvePromptInput() {
|
|
158798
|
+
const promptInput = core4.getInput("prompt");
|
|
158799
|
+
const promptFile = core4.getInput("prompt_file");
|
|
158800
|
+
if (promptInput && promptFile) {
|
|
158801
|
+
throw new Error("set exactly one of 'prompt' or 'prompt_file' inputs, not both.");
|
|
158802
|
+
}
|
|
158803
|
+
if (promptFile) {
|
|
158804
|
+
return resolvePromptFile(promptFile);
|
|
158805
|
+
}
|
|
158806
|
+
if (!promptInput) {
|
|
158807
|
+
throw new Error("one of 'prompt' or 'prompt_file' inputs is required.");
|
|
158808
|
+
}
|
|
158809
|
+
let parsed2;
|
|
158810
|
+
try {
|
|
158811
|
+
parsed2 = JSON.parse(promptInput);
|
|
158812
|
+
} catch {
|
|
158813
|
+
return promptInput;
|
|
158814
|
+
}
|
|
158815
|
+
if (!parsed2 || typeof parsed2 !== "object" || !("~pullfrog" in parsed2)) {
|
|
158816
|
+
return promptInput;
|
|
158817
|
+
}
|
|
158818
|
+
const jsonPayload = JsonPayload.assert(parsed2);
|
|
158819
|
+
validateCompatibility(jsonPayload.version, package_default.version);
|
|
158820
|
+
return jsonPayload;
|
|
158821
|
+
}
|
|
158822
|
+
function resolvePromptFile(input) {
|
|
158823
|
+
const workspace = process.env.GITHUB_WORKSPACE;
|
|
158824
|
+
const path4 = isAbsolute2(input) ? input : workspace ? resolve2(workspace, input) : resolve2(input);
|
|
158825
|
+
const content = readFileSync4(path4, "utf-8");
|
|
158826
|
+
if (!content.trim()) {
|
|
158827
|
+
throw new Error(`prompt_file ${JSON.stringify(input)} is empty.`);
|
|
158828
|
+
}
|
|
158829
|
+
return content;
|
|
158830
|
+
}
|
|
158831
|
+
function resolveNonPromptInputs() {
|
|
158832
|
+
return Inputs.omit("prompt", "prompt_file").assert({
|
|
158833
|
+
model: core4.getInput("model") || void 0,
|
|
158834
|
+
timeout: core4.getInput("timeout") || void 0,
|
|
158835
|
+
cwd: core4.getInput("cwd") || void 0,
|
|
158836
|
+
push: core4.getInput("push") || void 0,
|
|
158837
|
+
shell: core4.getInput("shell") || void 0,
|
|
158838
|
+
status_checks: core4.getInput("status_checks") || void 0
|
|
158839
|
+
});
|
|
158840
|
+
}
|
|
158841
|
+
var isPullfrog = (actor) => {
|
|
158842
|
+
actor = actor?.replace("[bot]", "");
|
|
158843
|
+
return !!actor && (actor === "pullfrog" || actor === "pullfrogdev");
|
|
158844
|
+
};
|
|
158845
|
+
function resolvePayload(resolvedPromptInput, repoSettings) {
|
|
158846
|
+
const [prompt, jsonPayload] = typeof resolvedPromptInput !== "string" ? [resolvedPromptInput.prompt, resolvedPromptInput] : [resolvedPromptInput, void 0];
|
|
158847
|
+
const inputs = resolveNonPromptInputs();
|
|
158848
|
+
const rawEvent = jsonPayload?.event;
|
|
158849
|
+
const event = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
|
|
158850
|
+
const model = jsonPayload?.model ?? inputs.model ?? repoSettings.model ?? void 0;
|
|
158851
|
+
const isNonCollaborator = !isCollaborator(event);
|
|
158852
|
+
const repoShell = repoSettings.shell ?? "restricted";
|
|
158853
|
+
const inputShell = inputs.shell;
|
|
158854
|
+
let resolvedShell = repoShell;
|
|
158855
|
+
if (inputShell === "disabled") {
|
|
158856
|
+
resolvedShell = "disabled";
|
|
158857
|
+
} else if (inputShell === "restricted" && resolvedShell === "enabled") {
|
|
158858
|
+
resolvedShell = "restricted";
|
|
158859
|
+
}
|
|
158860
|
+
if (isNonCollaborator && resolvedShell === "enabled") {
|
|
158861
|
+
resolvedShell = "restricted";
|
|
158862
|
+
}
|
|
158863
|
+
return {
|
|
158864
|
+
"~pullfrog": true,
|
|
158865
|
+
version: jsonPayload?.version ?? package_default.version,
|
|
158866
|
+
model,
|
|
158867
|
+
// explicit only when the model came from a per-run override flag (carried on
|
|
158868
|
+
// the JSON payload). a GHA `model` input or the repo default is not explicit.
|
|
158869
|
+
modelExplicit: jsonPayload?.modelExplicit ?? false,
|
|
158870
|
+
prompt,
|
|
158871
|
+
triggerer: jsonPayload?.triggerer ?? // it's not a common use case but GITHUB_ACTOR can be a user when the workflow is manually triggered by a user through GitHub Actions UI
|
|
158872
|
+
(!isPullfrog(process.env.GITHUB_ACTOR) ? process.env.GITHUB_ACTOR : void 0),
|
|
158873
|
+
baseInstructions: jsonPayload?.baseInstructions,
|
|
158874
|
+
eventInstructions: jsonPayload?.eventInstructions,
|
|
158875
|
+
previousRunsNote: jsonPayload?.previousRunsNote,
|
|
158876
|
+
event,
|
|
158877
|
+
xrepo: jsonPayload?.xrepo,
|
|
158878
|
+
timeout: inputs.timeout ?? jsonPayload?.timeout,
|
|
158879
|
+
cwd: resolveCwd(inputs.cwd),
|
|
158880
|
+
progressComment: jsonPayload?.progressComment,
|
|
158881
|
+
generateSummary: jsonPayload?.generateSummary,
|
|
158882
|
+
// permissions: inputs > repoSettings > fallbacks
|
|
158883
|
+
push: inputs.push ?? repoSettings.push ?? "restricted",
|
|
158884
|
+
shell: resolvedShell,
|
|
158885
|
+
// opt-in commit-status check-runs (branch protection). workflow-level
|
|
158886
|
+
// static input, off unless the repo's pullfrog.yml sets status_checks: enabled.
|
|
158887
|
+
statusChecks: inputs.status_checks === "enabled",
|
|
158888
|
+
// set by proxy logic in main.ts when routing through OpenRouter
|
|
158889
|
+
proxyModel: void 0
|
|
158890
|
+
};
|
|
158891
|
+
}
|
|
158892
|
+
function resolveOutputSchema() {
|
|
158893
|
+
const raw2 = core4.getInput("output_schema");
|
|
158894
|
+
if (!raw2) return void 0;
|
|
158895
|
+
let parsed2;
|
|
158896
|
+
try {
|
|
158897
|
+
parsed2 = JSON.parse(raw2);
|
|
158898
|
+
} catch {
|
|
158899
|
+
throw new Error(`invalid output_schema: not valid JSON`);
|
|
158900
|
+
}
|
|
158901
|
+
if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
|
|
158902
|
+
throw new Error(`invalid output_schema: must be a JSON object`);
|
|
158903
|
+
}
|
|
158904
|
+
log.info("\xBB structured output schema provided \u2014 output will be required");
|
|
158905
|
+
return parsed2;
|
|
158906
|
+
}
|
|
158907
|
+
|
|
158672
158908
|
// mcp/comment.ts
|
|
158673
158909
|
function isNotFoundError(error49) {
|
|
158674
158910
|
return error49 instanceof Error && error49.message.includes("Not Found");
|
|
@@ -159090,6 +159326,80 @@ async function buildCommentableMap(ctx, pullNumber) {
|
|
|
159090
159326
|
}
|
|
159091
159327
|
return map2;
|
|
159092
159328
|
}
|
|
159329
|
+
var OUTSTANDING_THREADS_QUERY = `
|
|
159330
|
+
query ($owner: String!, $name: String!, $prNumber: Int!, $cursor: String) {
|
|
159331
|
+
repository(owner: $owner, name: $name) {
|
|
159332
|
+
pullRequest(number: $prNumber) {
|
|
159333
|
+
reviewThreads(first: 100, after: $cursor) {
|
|
159334
|
+
pageInfo { hasNextPage endCursor }
|
|
159335
|
+
nodes {
|
|
159336
|
+
isResolved
|
|
159337
|
+
comments(first: 1) { nodes { author { login } } }
|
|
159338
|
+
}
|
|
159339
|
+
}
|
|
159340
|
+
}
|
|
159341
|
+
}
|
|
159342
|
+
}
|
|
159343
|
+
`;
|
|
159344
|
+
async function countOutstandingPullfrogThreads(ctx, pullNumber) {
|
|
159345
|
+
let count = 0;
|
|
159346
|
+
let cursor = null;
|
|
159347
|
+
for (let page = 0; page < 50; page += 1) {
|
|
159348
|
+
const response = await ctx.octokit.graphql(
|
|
159349
|
+
OUTSTANDING_THREADS_QUERY,
|
|
159350
|
+
{ owner: ctx.repo.owner, name: ctx.repo.name, prNumber: pullNumber, cursor }
|
|
159351
|
+
);
|
|
159352
|
+
const conn = response.repository?.pullRequest?.reviewThreads;
|
|
159353
|
+
for (const thread of conn?.nodes ?? []) {
|
|
159354
|
+
if (!thread || thread.isResolved) continue;
|
|
159355
|
+
const root = thread.comments?.nodes?.find((c2) => c2 != null);
|
|
159356
|
+
if (root && isPullfrog(root.author?.login)) count += 1;
|
|
159357
|
+
}
|
|
159358
|
+
if (!conn?.pageInfo.hasNextPage) break;
|
|
159359
|
+
cursor = conn.pageInfo.endCursor;
|
|
159360
|
+
}
|
|
159361
|
+
return count;
|
|
159362
|
+
}
|
|
159363
|
+
async function approveAfterFix(ctx) {
|
|
159364
|
+
if (ctx.payload.event.trigger !== "fix_review") return;
|
|
159365
|
+
if (!ctx.prApproveEnabled) return;
|
|
159366
|
+
if (ctx.toolState.approval) return;
|
|
159367
|
+
const pullNumber = ctx.payload.event.issue_number;
|
|
159368
|
+
if (typeof pullNumber !== "number") return;
|
|
159369
|
+
const outstanding = await countOutstandingPullfrogThreads(ctx, pullNumber);
|
|
159370
|
+
if (outstanding > 0) {
|
|
159371
|
+
log.info(
|
|
159372
|
+
`skipping fix auto-approval: ${outstanding} unresolved Pullfrog review thread(s) still open on #${pullNumber}`
|
|
159373
|
+
);
|
|
159374
|
+
return;
|
|
159375
|
+
}
|
|
159376
|
+
const pr = await ctx.octokit.rest.pulls.get({
|
|
159377
|
+
owner: ctx.repo.owner,
|
|
159378
|
+
repo: ctx.repo.name,
|
|
159379
|
+
pull_number: pullNumber
|
|
159380
|
+
});
|
|
159381
|
+
if (pr.data.state !== "open") return;
|
|
159382
|
+
const headSha = pr.data.head.sha;
|
|
159383
|
+
const summary2 = ctx.toolState.lastProgressBody?.trim();
|
|
159384
|
+
const body = "> \u2705 Pullfrog addressed all of its review feedback on this PR.\n\n" + (summary2 || "all Pullfrog-raised review threads are resolved \u2014 no outstanding findings remain.");
|
|
159385
|
+
const params = {
|
|
159386
|
+
owner: ctx.repo.owner,
|
|
159387
|
+
repo: ctx.repo.name,
|
|
159388
|
+
pull_number: pullNumber,
|
|
159389
|
+
event: "APPROVE",
|
|
159390
|
+
commit_id: headSha
|
|
159391
|
+
};
|
|
159392
|
+
const result = await createAndSubmitWithFooter(ctx, params, {
|
|
159393
|
+
body,
|
|
159394
|
+
approved: true,
|
|
159395
|
+
hasComments: false
|
|
159396
|
+
});
|
|
159397
|
+
ctx.toolState.approval = { wouldApprove: true, sha: headSha };
|
|
159398
|
+
log.info(`\xBB auto-approved #${pullNumber} after fix run (review ${result.data.id})`);
|
|
159399
|
+
await deleteProgressComment(ctx).catch((err) => {
|
|
159400
|
+
log.debug(`progress comment cleanup after fix auto-approval failed: ${err}`);
|
|
159401
|
+
});
|
|
159402
|
+
}
|
|
159093
159403
|
function validateInlineComments(comments, map2) {
|
|
159094
159404
|
const valid = [];
|
|
159095
159405
|
const dropped = [];
|
|
@@ -159180,7 +159490,7 @@ var CreatePullRequestReview = type({
|
|
|
159180
159490
|
"1-2 sentence high-level summary with urgency level, critical callouts, and feedback about code outside the diff. Specific feedback on diff lines goes in 'comments' array."
|
|
159181
159491
|
).optional(),
|
|
159182
159492
|
approved: type.boolean.describe(
|
|
159183
|
-
"Set to true to submit as an approval. Use for `> \u2705 No new issues found.` reviews where the PR is mergeable as-is and nothing in the body warrants code changes \u2014 approving also suppresses the Fix-button footer affordance so users don't dispatch a fix run on non-actionable feedback. Reserve approved: false for `> \u2139\uFE0F ...` (minor suggestions inline), `> [!IMPORTANT]` (recommended changes), and `> [!CAUTION]` (critical) reviews. Defaults to false (comment-only review). Mutually exclusive with request_changes."
|
|
159493
|
+
"Set to true to submit as an approval. Use for `> \u2705 No new issues found.` reviews where the PR is mergeable as-is and nothing in the body warrants code changes \u2014 approving also suppresses the Fix-button footer affordance so users don't dispatch a fix run on non-actionable feedback. Reserve approved: false for `> \u2139\uFE0F ...` (minor suggestions inline), `> [!IMPORTANT]` (recommended changes), and `> [!CAUTION]` (critical) reviews. Defaults to false (comment-only review). Mutually exclusive with request_changes. Approval is REJECTED while any unresolved Pullfrog review thread remains open on the PR (not just the latest commit's diff): resolve the threads the current code addresses (reply + resolve_review_thread) first, or submit a non-approving review if a real issue remains."
|
|
159184
159494
|
).optional(),
|
|
159185
159495
|
request_changes: type.boolean.describe(
|
|
159186
159496
|
"Set to true to submit a blocking REQUEST_CHANGES review \u2014 the PR cannot merge until the requested changes are made and the review is dismissed or re-reviewed. Reserve for changes you consider required, not optional suggestions. Mutually exclusive with approved; a contentless request (no body and no comments) is skipped."
|
|
@@ -159238,6 +159548,17 @@ function CreatePullRequestReviewTool(ctx) {
|
|
|
159238
159548
|
reviewId: dup.reviewId
|
|
159239
159549
|
};
|
|
159240
159550
|
}
|
|
159551
|
+
if (approved) {
|
|
159552
|
+
const outstanding = await countOutstandingPullfrogThreads(ctx, pull_number);
|
|
159553
|
+
if (outstanding > 0) {
|
|
159554
|
+
const listRef = formatMcpToolRef(ctx.agentId, "get_review_comments");
|
|
159555
|
+
const resolveRef2 = formatMcpToolRef(ctx.agentId, "resolve_review_thread");
|
|
159556
|
+
throw new Error(
|
|
159557
|
+
`cannot approve: ${outstanding} unresolved Pullfrog review thread(s) still open on this PR. approval requires every prior Pullfrog finding to be resolved, not just the latest commits to be clean. inspect them with \`${listRef}\`; for each thread the current code genuinely addresses, reply then call \`${resolveRef2}\`, and retry this approval. if any thread is a real outstanding issue, do NOT approve \u2014 submit a non-approving review (omit \`approved\`) that covers it instead.`
|
|
159558
|
+
);
|
|
159559
|
+
}
|
|
159560
|
+
}
|
|
159561
|
+
ctx.toolState.approval = { wouldApprove: approved === true, sha: primary.checkoutSha };
|
|
159241
159562
|
const skip = reviewSkipDecision({
|
|
159242
159563
|
approved: approved ?? false,
|
|
159243
159564
|
requestChanges: request_changes ?? false,
|
|
@@ -159371,6 +159692,7 @@ function CreatePullRequestReviewTool(ctx) {
|
|
|
159371
159692
|
nodeId: reviewNodeId,
|
|
159372
159693
|
reviewedSha: actuallyReviewedSha
|
|
159373
159694
|
};
|
|
159695
|
+
if (ctx.toolState.approval) ctx.toolState.approval.sha = actuallyReviewedSha;
|
|
159374
159696
|
ctx.toolState.wasUpdated = true;
|
|
159375
159697
|
await deleteProgressComment(ctx).catch((err) => {
|
|
159376
159698
|
log.debug(`progress comment cleanup after review failed: ${err}`);
|
|
@@ -160792,7 +161114,7 @@ function isMetadataYarnClassic(metadataPath) {
|
|
|
160792
161114
|
}
|
|
160793
161115
|
|
|
160794
161116
|
// utils/packageManager.ts
|
|
160795
|
-
var
|
|
161117
|
+
var import_semver3 = __toESM(require_semver2(), 1);
|
|
160796
161118
|
import { existsSync as existsSync5 } from "node:fs";
|
|
160797
161119
|
import { mkdir, readFile as readFile2 } from "node:fs/promises";
|
|
160798
161120
|
import { delimiter, join as join16 } from "node:path";
|
|
@@ -160814,7 +161136,7 @@ function parsePackageManagerField(value2) {
|
|
|
160814
161136
|
return {
|
|
160815
161137
|
name,
|
|
160816
161138
|
version: version3,
|
|
160817
|
-
concrete:
|
|
161139
|
+
concrete: import_semver3.default.valid(version3) !== null,
|
|
160818
161140
|
source: "packageManager"
|
|
160819
161141
|
};
|
|
160820
161142
|
}
|
|
@@ -160828,7 +161150,7 @@ function parseDevEnginesField(field) {
|
|
|
160828
161150
|
return {
|
|
160829
161151
|
name: field.name,
|
|
160830
161152
|
version: version3,
|
|
160831
|
-
concrete:
|
|
161153
|
+
concrete: import_semver3.default.valid(version3) !== null,
|
|
160832
161154
|
source: "devEngines"
|
|
160833
161155
|
};
|
|
160834
161156
|
}
|
|
@@ -160862,7 +161184,7 @@ async function resolvePackageManagerSpec(cwd) {
|
|
|
160862
161184
|
}
|
|
160863
161185
|
return devSpec;
|
|
160864
161186
|
}
|
|
160865
|
-
if (pmSpec.concrete &&
|
|
161187
|
+
if (pmSpec.concrete && import_semver3.default.satisfies(pmSpec.version, devSpec.version)) {
|
|
160866
161188
|
return pmSpec;
|
|
160867
161189
|
}
|
|
160868
161190
|
if (pmSpec.concrete) {
|
|
@@ -161074,8 +161396,15 @@ ${errorMessage}`]
|
|
|
161074
161396
|
};
|
|
161075
161397
|
|
|
161076
161398
|
// prep/installPythonDependencies.ts
|
|
161077
|
-
import { existsSync as existsSync7 } from "node:fs";
|
|
161399
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
161078
161400
|
import { join as join18 } from "node:path";
|
|
161401
|
+
function declaresBuildSystem(path4) {
|
|
161402
|
+
return /^\s*\[\s*build-system\s*\]/m.test(readFileSync5(path4, "utf8"));
|
|
161403
|
+
}
|
|
161404
|
+
function configApplies(config3, cwd) {
|
|
161405
|
+
const path4 = join18(cwd, config3.file);
|
|
161406
|
+
return existsSync7(path4) && (config3.applies?.(path4) ?? true);
|
|
161407
|
+
}
|
|
161079
161408
|
var PYTHON_CONFIGS = [
|
|
161080
161409
|
{
|
|
161081
161410
|
file: "requirements.txt",
|
|
@@ -161085,7 +161414,8 @@ var PYTHON_CONFIGS = [
|
|
|
161085
161414
|
{
|
|
161086
161415
|
file: "pyproject.toml",
|
|
161087
161416
|
tool: "pip",
|
|
161088
|
-
installCmd: ["pip", "install", "."]
|
|
161417
|
+
installCmd: ["pip", "install", "."],
|
|
161418
|
+
applies: declaresBuildSystem
|
|
161089
161419
|
},
|
|
161090
161420
|
{
|
|
161091
161421
|
file: "Pipfile",
|
|
@@ -161147,11 +161477,11 @@ var installPythonDependencies = {
|
|
|
161147
161477
|
return false;
|
|
161148
161478
|
}
|
|
161149
161479
|
const cwd = process.cwd();
|
|
161150
|
-
return PYTHON_CONFIGS.some((config3) =>
|
|
161480
|
+
return PYTHON_CONFIGS.some((config3) => configApplies(config3, cwd));
|
|
161151
161481
|
},
|
|
161152
161482
|
run: async (options) => {
|
|
161153
161483
|
const cwd = process.cwd();
|
|
161154
|
-
const config3 = PYTHON_CONFIGS.find((c2) =>
|
|
161484
|
+
const config3 = PYTHON_CONFIGS.find((c2) => configApplies(c2, cwd));
|
|
161155
161485
|
if (!config3) {
|
|
161156
161486
|
return {
|
|
161157
161487
|
language: "python",
|
|
@@ -163602,12 +163932,17 @@ function resolveModel(ctx) {
|
|
|
163602
163932
|
if (envModel) {
|
|
163603
163933
|
return resolveSlug(envModel) ?? envModel;
|
|
163604
163934
|
}
|
|
163605
|
-
|
|
163606
|
-
|
|
163935
|
+
const slug2 = ctx.slug?.trim();
|
|
163936
|
+
if (slug2) {
|
|
163937
|
+
const resolved = resolveSlug(slug2);
|
|
163607
163938
|
if (resolved) {
|
|
163608
163939
|
return resolved;
|
|
163609
163940
|
}
|
|
163610
|
-
|
|
163941
|
+
if (slug2.includes("/")) {
|
|
163942
|
+
log.info(`\xBB "${slug2}" is not a curated alias \u2014 passing through as a raw model specifier`);
|
|
163943
|
+
return slug2;
|
|
163944
|
+
}
|
|
163945
|
+
log.warning(`\xBB unknown model slug "${slug2}" \u2014 agent will auto-select`);
|
|
163611
163946
|
}
|
|
163612
163947
|
return void 0;
|
|
163613
163948
|
}
|
|
@@ -164170,6 +164505,8 @@ Never use \`sleep\` to wait for commands to complete. Commands run synchronously
|
|
|
164170
164505
|
|
|
164171
164506
|
When posting comments via ${pullfrogMcpName}, write as a professional team member would. Your final comments should be polished and actionable \u2014 do not include intermediate reasoning like "I'll now look at the code" or "Let me respond to the question."
|
|
164172
164507
|
|
|
164508
|
+
Never \`@\`-mention a GitHub username unless that exact handle appears in the user's request or the event context. GitHub already notifies the author and thread participants, so write "the author" or omit it.
|
|
164509
|
+
|
|
164173
164510
|
When embedding images (e.g. uploaded screenshots) in comments or PR bodies, always use markdown image syntax: \`\`. Never paste a naked URL \u2014 it will not render as an image.
|
|
164174
164511
|
|
|
164175
164512
|
### Progress reporting
|
|
@@ -164479,7 +164816,7 @@ function buildModelAccessError(input) {
|
|
|
164479
164816
|
}
|
|
164480
164817
|
|
|
164481
164818
|
// utils/normalizeEnv.ts
|
|
164482
|
-
var
|
|
164819
|
+
var core5 = __toESM(require_core(), 1);
|
|
164483
164820
|
function sanitizeSecret(key, value2) {
|
|
164484
164821
|
const trimmed = value2.trim();
|
|
164485
164822
|
if (trimmed.length === 0) {
|
|
@@ -164493,7 +164830,7 @@ function sanitizeSecret(key, value2) {
|
|
|
164493
164830
|
`\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
|
|
164494
164831
|
);
|
|
164495
164832
|
}
|
|
164496
|
-
|
|
164833
|
+
core5.setSecret(trimmed);
|
|
164497
164834
|
return trimmed;
|
|
164498
164835
|
}
|
|
164499
164836
|
function normalizeEnv() {
|
|
@@ -164537,7 +164874,7 @@ function normalizeEnv() {
|
|
|
164537
164874
|
}
|
|
164538
164875
|
|
|
164539
164876
|
// utils/overrides.ts
|
|
164540
|
-
var
|
|
164877
|
+
var core6 = __toESM(require_core(), 1);
|
|
164541
164878
|
var DENIED_OVERRIDE_NAMES = /* @__PURE__ */ new Set([
|
|
164542
164879
|
"GITHUB_TOKEN",
|
|
164543
164880
|
"GH_TOKEN",
|
|
@@ -164583,7 +164920,7 @@ function applyOverrides(params) {
|
|
|
164583
164920
|
denied.push(key);
|
|
164584
164921
|
continue;
|
|
164585
164922
|
}
|
|
164586
|
-
if (value2.length > 0)
|
|
164923
|
+
if (value2.length > 0) core6.setSecret(value2);
|
|
164587
164924
|
params.env[key] = value2;
|
|
164588
164925
|
applied.push(key);
|
|
164589
164926
|
}
|
|
@@ -164591,168 +164928,6 @@ function applyOverrides(params) {
|
|
|
164591
164928
|
return { applied, denied };
|
|
164592
164929
|
}
|
|
164593
164930
|
|
|
164594
|
-
// utils/payload.ts
|
|
164595
|
-
var core6 = __toESM(require_core(), 1);
|
|
164596
|
-
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
|
|
164597
|
-
|
|
164598
|
-
// utils/versioning.ts
|
|
164599
|
-
var import_semver3 = __toESM(require_semver2(), 1);
|
|
164600
|
-
var COMPATIBILITY_POLICY = "non-breaking";
|
|
164601
|
-
function validateCompatibility(payloadVersion, actionVersion) {
|
|
164602
|
-
const payloadSemVer = import_semver3.default.parse(payloadVersion);
|
|
164603
|
-
if (!payloadSemVer)
|
|
164604
|
-
throw new Error(`Payload version ${payloadVersion} is not a valid semantic version.`);
|
|
164605
|
-
const major = payloadSemVer.major;
|
|
164606
|
-
const minor = payloadSemVer.minor;
|
|
164607
|
-
const patch = payloadSemVer.patch;
|
|
164608
|
-
const compatibilityRange = COMPATIBILITY_POLICY === "same-features" ? `^${major}.${minor}.${major === 0 ? patch : 0}` : `^${major}.${major === 0 ? minor : 0}.${major === 0 ? "x" : 0}`;
|
|
164609
|
-
if (!import_semver3.default.satisfies(actionVersion, compatibilityRange)) {
|
|
164610
|
-
throw new Error(
|
|
164611
|
-
`Payload version ${payloadVersion} is incompatible with action version ${actionVersion}. Please update your workflow to use at least ${import_semver3.default.minVersion(compatibilityRange)} version of the action.`
|
|
164612
|
-
);
|
|
164613
|
-
}
|
|
164614
|
-
}
|
|
164615
|
-
|
|
164616
|
-
// utils/payload.ts
|
|
164617
|
-
var ShellPermissionInput = type.enumerated("disabled", "restricted", "enabled");
|
|
164618
|
-
var PushPermissionInput = type.enumerated("disabled", "restricted", "enabled");
|
|
164619
|
-
var JsonPayload = type({
|
|
164620
|
-
"~pullfrog": "true",
|
|
164621
|
-
version: "string",
|
|
164622
|
-
"model?": "string | undefined",
|
|
164623
|
-
"modelExplicit?": "boolean | undefined",
|
|
164624
|
-
prompt: "string",
|
|
164625
|
-
"triggerer?": "string | undefined",
|
|
164626
|
-
"baseInstructions?": "string | undefined",
|
|
164627
|
-
"eventInstructions?": "string",
|
|
164628
|
-
"previousRunsNote?": "string",
|
|
164629
|
-
"event?": "object",
|
|
164630
|
-
"xrepo?": type({
|
|
164631
|
-
mode: "'all' | 'explicit'",
|
|
164632
|
-
read: "string[]",
|
|
164633
|
-
write: "string[]",
|
|
164634
|
-
// optional so a payload from an older server build (pre-`unavailable`)
|
|
164635
|
-
// still parses against a newer action across a rolling deploy.
|
|
164636
|
-
"unavailable?": "string[]"
|
|
164637
|
-
}).or("undefined"),
|
|
164638
|
-
"timeout?": "string | undefined",
|
|
164639
|
-
"progressComment?": type({
|
|
164640
|
-
id: "string",
|
|
164641
|
-
type: "'issue' | 'review'"
|
|
164642
|
-
}).or("undefined"),
|
|
164643
|
-
"generateSummary?": "boolean | undefined"
|
|
164644
|
-
});
|
|
164645
|
-
var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
|
|
164646
|
-
function isCollaborator(event) {
|
|
164647
|
-
const perm = event.authorPermission;
|
|
164648
|
-
return perm !== void 0 && COLLABORATOR_PERMISSIONS.includes(perm);
|
|
164649
|
-
}
|
|
164650
|
-
var Inputs = type({
|
|
164651
|
-
prompt: "string",
|
|
164652
|
-
"model?": type.string.or("undefined"),
|
|
164653
|
-
"timeout?": type.string.or("undefined"),
|
|
164654
|
-
"push?": PushPermissionInput.or("undefined"),
|
|
164655
|
-
"shell?": ShellPermissionInput.or("undefined"),
|
|
164656
|
-
"cwd?": type.string.or("undefined"),
|
|
164657
|
-
"output_schema?": type.string.or("undefined")
|
|
164658
|
-
});
|
|
164659
|
-
function isPayloadEvent(value2) {
|
|
164660
|
-
return typeof value2 === "object" && value2 !== null && "trigger" in value2;
|
|
164661
|
-
}
|
|
164662
|
-
function resolveCwd(cwd) {
|
|
164663
|
-
const workspace = process.env.GITHUB_WORKSPACE;
|
|
164664
|
-
if (!cwd) return workspace;
|
|
164665
|
-
if (isAbsolute2(cwd)) return cwd;
|
|
164666
|
-
return workspace ? resolve2(workspace, cwd) : cwd;
|
|
164667
|
-
}
|
|
164668
|
-
function resolvePromptInput() {
|
|
164669
|
-
const prompt = core6.getInput("prompt", { required: true });
|
|
164670
|
-
let parsed2;
|
|
164671
|
-
try {
|
|
164672
|
-
parsed2 = JSON.parse(prompt);
|
|
164673
|
-
} catch {
|
|
164674
|
-
return prompt;
|
|
164675
|
-
}
|
|
164676
|
-
if (!parsed2 || typeof parsed2 !== "object" || !("~pullfrog" in parsed2)) {
|
|
164677
|
-
return prompt;
|
|
164678
|
-
}
|
|
164679
|
-
const jsonPayload = JsonPayload.assert(parsed2);
|
|
164680
|
-
validateCompatibility(jsonPayload.version, package_default.version);
|
|
164681
|
-
return jsonPayload;
|
|
164682
|
-
}
|
|
164683
|
-
function resolveNonPromptInputs() {
|
|
164684
|
-
return Inputs.omit("prompt").assert({
|
|
164685
|
-
model: core6.getInput("model") || void 0,
|
|
164686
|
-
timeout: core6.getInput("timeout") || void 0,
|
|
164687
|
-
cwd: core6.getInput("cwd") || void 0,
|
|
164688
|
-
push: core6.getInput("push") || void 0,
|
|
164689
|
-
shell: core6.getInput("shell") || void 0
|
|
164690
|
-
});
|
|
164691
|
-
}
|
|
164692
|
-
var isPullfrog = (actor) => {
|
|
164693
|
-
actor = actor?.replace("[bot]", "");
|
|
164694
|
-
return !!actor && (actor === "pullfrog" || actor === "pullfrogdev");
|
|
164695
|
-
};
|
|
164696
|
-
function resolvePayload(resolvedPromptInput, repoSettings) {
|
|
164697
|
-
const [prompt, jsonPayload] = typeof resolvedPromptInput !== "string" ? [resolvedPromptInput.prompt, resolvedPromptInput] : [resolvedPromptInput, void 0];
|
|
164698
|
-
const inputs = resolveNonPromptInputs();
|
|
164699
|
-
const rawEvent = jsonPayload?.event;
|
|
164700
|
-
const event = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
|
|
164701
|
-
const model = jsonPayload?.model ?? inputs.model ?? repoSettings.model ?? void 0;
|
|
164702
|
-
const isNonCollaborator = !isCollaborator(event);
|
|
164703
|
-
const repoShell = repoSettings.shell ?? "restricted";
|
|
164704
|
-
const inputShell = inputs.shell;
|
|
164705
|
-
let resolvedShell = repoShell;
|
|
164706
|
-
if (inputShell === "disabled") {
|
|
164707
|
-
resolvedShell = "disabled";
|
|
164708
|
-
} else if (inputShell === "restricted" && resolvedShell === "enabled") {
|
|
164709
|
-
resolvedShell = "restricted";
|
|
164710
|
-
}
|
|
164711
|
-
if (isNonCollaborator && resolvedShell === "enabled") {
|
|
164712
|
-
resolvedShell = "restricted";
|
|
164713
|
-
}
|
|
164714
|
-
return {
|
|
164715
|
-
"~pullfrog": true,
|
|
164716
|
-
version: jsonPayload?.version ?? package_default.version,
|
|
164717
|
-
model,
|
|
164718
|
-
// explicit only when the model came from a per-run override flag (carried on
|
|
164719
|
-
// the JSON payload). a GHA `model` input or the repo default is not explicit.
|
|
164720
|
-
modelExplicit: jsonPayload?.modelExplicit ?? false,
|
|
164721
|
-
prompt,
|
|
164722
|
-
triggerer: jsonPayload?.triggerer ?? // it's not a common use case but GITHUB_ACTOR can be a user when the workflow is manually triggered by a user through GitHub Actions UI
|
|
164723
|
-
(!isPullfrog(process.env.GITHUB_ACTOR) ? process.env.GITHUB_ACTOR : void 0),
|
|
164724
|
-
baseInstructions: jsonPayload?.baseInstructions,
|
|
164725
|
-
eventInstructions: jsonPayload?.eventInstructions,
|
|
164726
|
-
previousRunsNote: jsonPayload?.previousRunsNote,
|
|
164727
|
-
event,
|
|
164728
|
-
xrepo: jsonPayload?.xrepo,
|
|
164729
|
-
timeout: inputs.timeout ?? jsonPayload?.timeout,
|
|
164730
|
-
cwd: resolveCwd(inputs.cwd),
|
|
164731
|
-
progressComment: jsonPayload?.progressComment,
|
|
164732
|
-
generateSummary: jsonPayload?.generateSummary,
|
|
164733
|
-
// permissions: inputs > repoSettings > fallbacks
|
|
164734
|
-
push: inputs.push ?? repoSettings.push ?? "restricted",
|
|
164735
|
-
shell: resolvedShell,
|
|
164736
|
-
// set by proxy logic in main.ts when routing through OpenRouter
|
|
164737
|
-
proxyModel: void 0
|
|
164738
|
-
};
|
|
164739
|
-
}
|
|
164740
|
-
function resolveOutputSchema() {
|
|
164741
|
-
const raw2 = core6.getInput("output_schema");
|
|
164742
|
-
if (!raw2) return void 0;
|
|
164743
|
-
let parsed2;
|
|
164744
|
-
try {
|
|
164745
|
-
parsed2 = JSON.parse(raw2);
|
|
164746
|
-
} catch {
|
|
164747
|
-
throw new Error(`invalid output_schema: not valid JSON`);
|
|
164748
|
-
}
|
|
164749
|
-
if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
|
|
164750
|
-
throw new Error(`invalid output_schema: must be a JSON object`);
|
|
164751
|
-
}
|
|
164752
|
-
log.info("\xBB structured output schema provided \u2014 output will be required");
|
|
164753
|
-
return parsed2;
|
|
164754
|
-
}
|
|
164755
|
-
|
|
164756
164931
|
// utils/proxy.ts
|
|
164757
164932
|
var core8 = __toESM(require_core(), 1);
|
|
164758
164933
|
|
|
@@ -164903,7 +165078,10 @@ async function resolveTokens(params) {
|
|
|
164903
165078
|
contents: "write",
|
|
164904
165079
|
pull_requests: "write",
|
|
164905
165080
|
issues: "write",
|
|
164906
|
-
|
|
165081
|
+
// write (not read) so the run can post `pullfrog` / `pullfrog-approval`
|
|
165082
|
+
// commit-status check-runs for branch protection. the app already grants
|
|
165083
|
+
// checks:write; this scopes the MCP token up to use it.
|
|
165084
|
+
checks: "write",
|
|
164907
165085
|
actions: "read"
|
|
164908
165086
|
};
|
|
164909
165087
|
const mcpToken = await acquireNewToken({ repos: writeRepos, permissions: mcpPermissions });
|
|
@@ -165012,7 +165190,9 @@ async function resolveTokens(params) {
|
|
|
165012
165190
|
};
|
|
165013
165191
|
const removeSignalHandler = onExitSignal(dispose);
|
|
165014
165192
|
return {
|
|
165015
|
-
gitToken
|
|
165193
|
+
get gitToken() {
|
|
165194
|
+
return currentGitToken;
|
|
165195
|
+
},
|
|
165016
165196
|
mcpToken,
|
|
165017
165197
|
readToken,
|
|
165018
165198
|
refreshGitToken,
|
|
@@ -165452,7 +165632,11 @@ async function resolveRunContextData(params) {
|
|
|
165452
165632
|
} catch {
|
|
165453
165633
|
}
|
|
165454
165634
|
const [repoResponse, runContext] = await Promise.all([
|
|
165455
|
-
params.octokit.repos.get({ owner: repoContext.owner, repo: repoContext.name }),
|
|
165635
|
+
op(() => params.octokit.repos.get({ owner: repoContext.owner, repo: repoContext.name }), {
|
|
165636
|
+
name: "repos.get",
|
|
165637
|
+
retries: [100, 500],
|
|
165638
|
+
bail: (error49) => !isTransientOctokitError(error49)
|
|
165639
|
+
})(),
|
|
165456
165640
|
fetchRunContext({ token: params.token, repoContext, oidcToken })
|
|
165457
165641
|
]);
|
|
165458
165642
|
return {
|
|
@@ -165737,13 +165921,20 @@ async function dispatchFollowUpReReview(ctx, reviewedSha) {
|
|
|
165737
165921
|
eventInstructions: RE_REVIEW_PREAMBLE,
|
|
165738
165922
|
event
|
|
165739
165923
|
};
|
|
165740
|
-
await
|
|
165741
|
-
|
|
165742
|
-
|
|
165743
|
-
|
|
165744
|
-
|
|
165745
|
-
|
|
165746
|
-
|
|
165924
|
+
await op(
|
|
165925
|
+
() => ctx.octokit.rest.actions.createWorkflowDispatch({
|
|
165926
|
+
owner: ctx.repo.owner,
|
|
165927
|
+
repo: ctx.repo.name,
|
|
165928
|
+
workflow_id: getCurrentWorkflowFilename(),
|
|
165929
|
+
ref: pr.data.base.repo.default_branch,
|
|
165930
|
+
inputs: { prompt: JSON.stringify(payload) }
|
|
165931
|
+
}),
|
|
165932
|
+
{
|
|
165933
|
+
name: "reReviewDispatch",
|
|
165934
|
+
retries: [500, 2e3],
|
|
165935
|
+
bail: (error49) => !isTransientOctokitError(error49)
|
|
165936
|
+
}
|
|
165937
|
+
)();
|
|
165747
165938
|
}
|
|
165748
165939
|
function getCurrentWorkflowFilename() {
|
|
165749
165940
|
const ref = process.env.GITHUB_WORKFLOW_REF ?? "";
|
|
@@ -165751,6 +165942,62 @@ function getCurrentWorkflowFilename() {
|
|
|
165751
165942
|
return match3?.[1] ?? "pullfrog.yml";
|
|
165752
165943
|
}
|
|
165753
165944
|
|
|
165945
|
+
// utils/statusChecks.ts
|
|
165946
|
+
var COMPLETION_CHECK = "pullfrog";
|
|
165947
|
+
var APPROVAL_CHECK = "pullfrog-approval";
|
|
165948
|
+
async function createCheckRun(ctx, params) {
|
|
165949
|
+
const createParams = {
|
|
165950
|
+
owner: ctx.repo.owner,
|
|
165951
|
+
repo: ctx.repo.name,
|
|
165952
|
+
name: params.name,
|
|
165953
|
+
head_sha: params.headSha,
|
|
165954
|
+
status: "completed",
|
|
165955
|
+
conclusion: params.conclusion,
|
|
165956
|
+
output: { title: params.title, summary: params.summary }
|
|
165957
|
+
};
|
|
165958
|
+
if (ctx.runId) {
|
|
165959
|
+
createParams.details_url = `https://github.com/${ctx.repo.owner}/${ctx.repo.name}/actions/runs/${ctx.runId}`;
|
|
165960
|
+
}
|
|
165961
|
+
await ctx.octokit.rest.checks.create(createParams);
|
|
165962
|
+
log.info(`\xBB posted ${params.name} check (${params.conclusion}) on ${params.headSha.slice(0, 7)}`);
|
|
165963
|
+
}
|
|
165964
|
+
async function reportStatusChecks(ctx, params) {
|
|
165965
|
+
if (!ctx.payload.statusChecks) return;
|
|
165966
|
+
const event = ctx.payload.event;
|
|
165967
|
+
const pullNumber = event.issue_number;
|
|
165968
|
+
if (event.is_pr !== true || typeof pullNumber !== "number") return;
|
|
165969
|
+
let headSha;
|
|
165970
|
+
try {
|
|
165971
|
+
const pr = await ctx.octokit.rest.pulls.get({
|
|
165972
|
+
owner: ctx.repo.owner,
|
|
165973
|
+
repo: ctx.repo.name,
|
|
165974
|
+
pull_number: pullNumber
|
|
165975
|
+
});
|
|
165976
|
+
headSha = pr.data.head.sha;
|
|
165977
|
+
} catch (err) {
|
|
165978
|
+
log.debug(`status checks: failed to resolve PR #${pullNumber} head sha: ${err}`);
|
|
165979
|
+
return;
|
|
165980
|
+
}
|
|
165981
|
+
const completionSha = primaryRepoState(ctx.toolState).checkoutSha ?? headSha;
|
|
165982
|
+
await createCheckRun(ctx, {
|
|
165983
|
+
name: COMPLETION_CHECK,
|
|
165984
|
+
headSha: completionSha,
|
|
165985
|
+
conclusion: params.runSucceeded ? "success" : "failure",
|
|
165986
|
+
title: params.runSucceeded ? "Pullfrog run completed" : "Pullfrog run failed",
|
|
165987
|
+
summary: params.runSucceeded ? "The Pullfrog run finished successfully." : "The Pullfrog run failed or timed out. See the run logs for details."
|
|
165988
|
+
}).catch((err) => log.debug(`status checks: ${COMPLETION_CHECK} post failed: ${err}`));
|
|
165989
|
+
const approval = ctx.toolState.approval;
|
|
165990
|
+
if (params.runSucceeded && approval) {
|
|
165991
|
+
await createCheckRun(ctx, {
|
|
165992
|
+
name: APPROVAL_CHECK,
|
|
165993
|
+
headSha: approval.sha ?? headSha,
|
|
165994
|
+
conclusion: approval.wouldApprove ? "success" : "failure",
|
|
165995
|
+
title: approval.wouldApprove ? "Pullfrog would approve" : "Pullfrog would not approve",
|
|
165996
|
+
summary: approval.wouldApprove ? "Pullfrog has no outstanding review feedback on this PR." : "Pullfrog has outstanding review feedback or requested changes on this PR."
|
|
165997
|
+
}).catch((err) => log.debug(`status checks: ${APPROVAL_CHECK} post failed: ${err}`));
|
|
165998
|
+
}
|
|
165999
|
+
}
|
|
166000
|
+
|
|
165754
166001
|
// utils/runLifecycle.ts
|
|
165755
166002
|
async function persistRunArtifacts(toolContext) {
|
|
165756
166003
|
await postReviewCleanup(toolContext).catch((error49) => {
|
|
@@ -165794,6 +166041,12 @@ async function finalizeSuccessRun(input) {
|
|
|
165794
166041
|
log.info(`::pullfrog-output::${Buffer.from(input.toolState.output).toString("base64")}`);
|
|
165795
166042
|
core10.setOutput("result", input.toolState.output);
|
|
165796
166043
|
}
|
|
166044
|
+
if (input.result.success) {
|
|
166045
|
+
await approveAfterFix(input.toolContext).catch((error49) => {
|
|
166046
|
+
log.debug(`fix auto-approval failed: ${error49}`);
|
|
166047
|
+
});
|
|
166048
|
+
}
|
|
166049
|
+
await reportStatusChecks(input.toolContext, { runSucceeded: input.result.success });
|
|
165797
166050
|
}
|
|
165798
166051
|
async function writeRunErrorOutputs(input) {
|
|
165799
166052
|
try {
|
|
@@ -166220,7 +166473,11 @@ async function main() {
|
|
|
166220
166473
|
get githubInstallationToken() {
|
|
166221
166474
|
return getGitHubInstallationToken();
|
|
166222
166475
|
},
|
|
166223
|
-
|
|
166476
|
+
// live getter, same reason as #891 above — reads the current git token
|
|
166477
|
+
// (canonical rationale on TokenRef.gitToken). see #964.
|
|
166478
|
+
get gitToken() {
|
|
166479
|
+
return tokenRef.gitToken;
|
|
166480
|
+
},
|
|
166224
166481
|
refreshGitToken: tokenRef.refreshGitToken,
|
|
166225
166482
|
readToken: tokenRef.readToken,
|
|
166226
166483
|
xrepo: payload.xrepo,
|
|
@@ -166470,6 +166727,7 @@ ${instructions.user}` : null,
|
|
|
166470
166727
|
await writeRunErrorOutputs({ rendered, toolState });
|
|
166471
166728
|
if (toolContext) {
|
|
166472
166729
|
await persistRunArtifacts(toolContext);
|
|
166730
|
+
await reportStatusChecks(toolContext, { runSucceeded: false });
|
|
166473
166731
|
}
|
|
166474
166732
|
return {
|
|
166475
166733
|
success: false,
|
|
@@ -167384,7 +167642,7 @@ async function run2() {
|
|
|
167384
167642
|
}
|
|
167385
167643
|
|
|
167386
167644
|
// cli.ts
|
|
167387
|
-
var VERSION10 = "0.1.
|
|
167645
|
+
var VERSION10 = "0.1.32";
|
|
167388
167646
|
var bin = basename2(process.argv[1] || "");
|
|
167389
167647
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
167390
167648
|
var rawArgs = process.argv.slice(2);
|