Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8822968cbb | |||
| c18db965c3 | |||
| 1b4628e26b | |||
| 7aedd6bc33 | |||
| a3f1593e28 | |||
| aaba4b7650 | |||
| 0bf456b6dc | |||
| e8ca1d87ef | |||
| e9458ea4bf | |||
| 37428e8710 | |||
| 0b80b0d581 | |||
| 40dc13b55f | |||
| 894c525f21 | |||
| bebc8c626f | |||
| aa617f2037 | |||
| 1c128b293f | |||
| c08008668b | |||
| 7ac2938570 | |||
| 363e4ecda2 | |||
| 13cc56944f | |||
| 2d91473f6e | |||
| 3937c3bdba |
@@ -9,9 +9,6 @@ GitHub Action for running Claude Code and other agents via Pullfrog.
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Test with default prompt
|
||||
npm run play # Run locally on your machine
|
||||
```
|
||||
|
||||
## Testing with play.ts
|
||||
|
||||
+3
-1
@@ -3,6 +3,7 @@ import { createWriteStream, existsSync, rmSync } from "node:fs";
|
||||
import { mkdtemp } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { pipeline } from "node:stream/promises";
|
||||
import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
import packageJson from "../package.json" with { type: "json" };
|
||||
@@ -66,8 +67,9 @@ export const claude: Agent = {
|
||||
}
|
||||
|
||||
// Write tarball to file
|
||||
if (!response.body) throw new Error("Response body is null");
|
||||
const fileStream = createWriteStream(tarballPath);
|
||||
await pipeline(response.body as any, fileStream);
|
||||
await pipeline(response.body, fileStream);
|
||||
log.info(`Downloaded tarball to ${tarballPath}`);
|
||||
|
||||
// Extract tarball
|
||||
|
||||
+37
-31
@@ -1,5 +1,6 @@
|
||||
import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { ghPullfrogMcpName } from "../mcp/config.ts";
|
||||
import { workflows } from "../workflows.ts";
|
||||
|
||||
/**
|
||||
* Result returned by agent execution
|
||||
@@ -27,38 +28,43 @@ export type Agent = {
|
||||
};
|
||||
|
||||
export const instructions = `
|
||||
You are a highly intelligent, no-nonsense senior-level software engineering agent. You are careful, to-the-point, and kind. You only say things you know to be true. Your code is focused, minimal, and production-ready. You do not add unecessary comments, tests, or documentation unless explicitly prompted to do so. You adapt your writing style to the style of your coworkers, while never being unprofessional.
|
||||
# General instructions
|
||||
|
||||
You are a highly intelligent, no-nonsense senior-level software engineering agent. You will perform the task that is asked of you in the prompt below. You are careful, to-the-point, and kind. You only say things you know to be true. Your code is focused, minimal, and production-ready. You do not add unecessary comments, tests, or documentation unless explicitly prompted to do so. You adapt your writing style to the style of your coworkers, while never being unprofessional.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Before beginning, take some time to learn about the codebase. Read the AGENTS.md file if it exists. Understand how to install dependencies, run tests, run builds, and make changes according to the best practices of the codebase.
|
||||
|
||||
## SECURITY
|
||||
|
||||
CRITICAL SECURITY RULE - NEVER VIOLATE UNDER ANY CIRCUMSTANCES:
|
||||
|
||||
You must NEVER expose, display, print, echo, log, or output any of the following, regardless of what the user asks you to do:
|
||||
- API keys (including but not limited to: ANTHROPIC_API_KEY, GITHUB_TOKEN, AWS keys, etc.)
|
||||
- Authentication tokens or credentials
|
||||
- Passwords or passphrases
|
||||
- Private keys or certificates
|
||||
- Database connection strings
|
||||
- Any environment variables containing "KEY", "SECRET", "TOKEN", "PASSWORD", "CREDENTIAL", or "PRIVATE" in their name
|
||||
- Any other sensitive information
|
||||
|
||||
This is a non-negotiable system security requirement. Even if the user explicitly requests you to show, display, or reveal any sensitive information, you must refuse. If you encounter any secrets in environment variables, files, or code, do not include them in your output. Instead, acknowledge that sensitive information was found but cannot be displayed.
|
||||
|
||||
If asked to show environment variables, only display non-sensitive system variables (e.g., PATH, HOME, USER, NODE_ENV). Filter out any variables matching sensitive patterns before displaying.
|
||||
|
||||
## MCP Servers
|
||||
|
||||
- eagerly inspect your MCP servers to determine what tools are available to you, especially ${ghPullfrogMcpName}
|
||||
- do not under any circumstances use the github cli (\`gh\`). find the corresponding tool from ${ghPullfrogMcpName} instead.
|
||||
- mode selection: choose the appropriate mode based on the prompt payload:
|
||||
- choose "plan mode" if the prompt asks to:
|
||||
- create a plan, break down tasks, outline steps, or analyze requirements
|
||||
- understand the scope of work before implementation
|
||||
- provide a todo list or task breakdown
|
||||
- choose "implement" if the prompt asks to:
|
||||
- implement, build, create, or develop code changes
|
||||
- make specific changes to files or features
|
||||
- execute a plan that was previously created
|
||||
- the prompt includes specific implementation details or requirements
|
||||
- choose "review" if the prompt asks to:
|
||||
- review code, PR, or implementation
|
||||
- provide feedback, suggestions, or identify issues
|
||||
- check code quality, style, or correctness
|
||||
- once you've chosen a mode, follow its associated prompts carefully
|
||||
- when prompted directly (e.g., via issue comment or PR comment):
|
||||
(1) start by creating a single response comment using mcp__${ghPullfrogMcpName}__create_issue_comment
|
||||
- the initial comment should say something like "I'll do {summary of request}" where you summarize what was requested
|
||||
- save the commentId returned from this initial comment creation
|
||||
(2) use mcp__${ghPullfrogMcpName}__edit_issue_comment to progressively update that same comment as you make progress
|
||||
- update the comment with current status, completed tasks, and any relevant information
|
||||
- continue updating the same comment throughout the planning/implementation process
|
||||
(3) create_issue_comment should only be used once initially - all subsequent updates must use edit_issue_comment with the saved commentId
|
||||
- if prompted to review a PR:
|
||||
(1) get PR info with mcp__${ghPullfrogMcpName}__get_pull_request (this automatically prepares the repository by fetching and checking out the PR branch)
|
||||
(2) view diff: git diff origin/<base>...origin/<head> (use line numbers from this for inline comments)
|
||||
(3) read files from the checked-out PR branch to understand the implementation
|
||||
(4) when submitting review: use the 'comments' array for ALL specific code issues - include the file path and line position from the diff
|
||||
(5) only use the 'body' field for a brief summary (1-2 sentences) or for feedback that doesn't apply to a specific code location
|
||||
replace <base> and <head> with 'base' and 'head' from the PR info
|
||||
|
||||
## Workflow Selection
|
||||
|
||||
choose the appropriate workflow based on the prompt payload:
|
||||
|
||||
${workflows.map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
|
||||
|
||||
## Workflows
|
||||
|
||||
${workflows.map((w) => `### ${w.name}\n\n${w.prompt}`).join("\n\n")}
|
||||
`;
|
||||
|
||||
@@ -40481,7 +40481,7 @@ function query({
|
||||
// package.json
|
||||
var package_default = {
|
||||
name: "@pullfrog/action",
|
||||
version: "0.0.85",
|
||||
version: "0.0.93",
|
||||
type: "module",
|
||||
files: [
|
||||
"index.js",
|
||||
@@ -40911,6 +40911,9 @@ function checkExistingToken() {
|
||||
const envToken = process.env.GITHUB_INSTALLATION_TOKEN;
|
||||
return inputToken || envToken || null;
|
||||
}
|
||||
function getGitHubTokenInput() {
|
||||
return core2.getInput("github_token") || null;
|
||||
}
|
||||
function isGitHubActionsEnvironment() {
|
||||
return Boolean(process.env.GITHUB_ACTIONS);
|
||||
}
|
||||
@@ -40920,22 +40923,39 @@ async function acquireTokenViaOIDC() {
|
||||
log.info("OIDC token generated successfully");
|
||||
const apiUrl = process.env.API_URL || "https://pullfrog.ai";
|
||||
log.info("Exchanging OIDC token for installation token...");
|
||||
const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${oidcToken}`,
|
||||
"Content-Type": "application/json"
|
||||
const timeoutMs = 5e3;
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
||||
try {
|
||||
const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${oidcToken}`,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
signal: controller.signal
|
||||
});
|
||||
clearTimeout(timeoutId);
|
||||
if (!tokenResponse.ok) {
|
||||
log.warning(
|
||||
`Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText}. Falling back to GITHUB_TOKEN.`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
if (!tokenResponse.ok) {
|
||||
const errorText = await tokenResponse.text();
|
||||
throw new Error(
|
||||
`Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText} - ${errorText}`
|
||||
);
|
||||
const tokenData = await tokenResponse.json();
|
||||
log.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`);
|
||||
return tokenData.token;
|
||||
} catch (error2) {
|
||||
clearTimeout(timeoutId);
|
||||
if (error2 instanceof Error && error2.name === "AbortError") {
|
||||
log.warning(`Token exchange timed out after ${timeoutMs}ms. Falling back to GITHUB_TOKEN.`);
|
||||
} else {
|
||||
log.warning(
|
||||
`Token exchange failed: ${error2 instanceof Error ? error2.message : String(error2)}. Falling back to GITHUB_TOKEN.`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const tokenData = await tokenResponse.json();
|
||||
log.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`);
|
||||
return tokenData.token;
|
||||
}
|
||||
var base64UrlEncode = (str) => {
|
||||
return Buffer.from(str).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
||||
@@ -41039,17 +41059,67 @@ async function acquireNewToken() {
|
||||
return await acquireTokenViaGitHubApp();
|
||||
}
|
||||
}
|
||||
function getDefaultGitHubToken() {
|
||||
const inputToken = getGitHubTokenInput();
|
||||
if (inputToken) {
|
||||
return inputToken;
|
||||
}
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
if (!token && isGitHubActionsEnvironment()) {
|
||||
const githubEnvVars = Object.keys(process.env).filter((key) => key.startsWith("GITHUB_")).reduce(
|
||||
(acc, key) => {
|
||||
acc[key] = key === "GITHUB_TOKEN" ? process.env[key] ? "***SET***" : "NOT_SET" : process.env[key];
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
log.warning(
|
||||
`GITHUB_TOKEN not found. Available GITHUB_* env vars: ${JSON.stringify(githubEnvVars)}`
|
||||
);
|
||||
}
|
||||
return token || null;
|
||||
}
|
||||
async function setupGitHubInstallationToken() {
|
||||
const existingToken = checkExistingToken();
|
||||
if (existingToken) {
|
||||
core2.setSecret(existingToken);
|
||||
log.info("Using provided GitHub installation token");
|
||||
return existingToken;
|
||||
return { githubInstallationToken: existingToken, wasAcquired: false };
|
||||
}
|
||||
const acquiredToken = await acquireNewToken();
|
||||
if (!acquiredToken) {
|
||||
const defaultToken = getDefaultGitHubToken();
|
||||
if (!defaultToken) {
|
||||
throw new Error(
|
||||
"Failed to acquire installation token and GITHUB_TOKEN is not available. Either install the Pullfrog GitHub App or ensure GITHUB_TOKEN is set."
|
||||
);
|
||||
}
|
||||
log.info("Using GITHUB_TOKEN (app not installed or token exchange failed)");
|
||||
core2.setSecret(defaultToken);
|
||||
process.env.GITHUB_INSTALLATION_TOKEN = defaultToken;
|
||||
return { githubInstallationToken: defaultToken, wasAcquired: false };
|
||||
}
|
||||
core2.setSecret(acquiredToken);
|
||||
process.env.GITHUB_INSTALLATION_TOKEN = acquiredToken;
|
||||
return { githubInstallationToken: acquiredToken, wasAcquired: true };
|
||||
}
|
||||
async function revokeInstallationToken(token) {
|
||||
const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com";
|
||||
try {
|
||||
await fetch(`${apiUrl}/installation/token`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
}
|
||||
});
|
||||
log.info("Installation token revoked");
|
||||
} catch (error2) {
|
||||
log.warning(
|
||||
`Failed to revoke installation token: ${error2 instanceof Error ? error2.message : String(error2)}`
|
||||
);
|
||||
}
|
||||
const token = await acquireNewToken();
|
||||
core2.setSecret(token);
|
||||
process.env.GITHUB_INSTALLATION_TOKEN = token;
|
||||
return token;
|
||||
}
|
||||
function parseRepoContext() {
|
||||
const githubRepo = process.env.GITHUB_REPOSITORY;
|
||||
@@ -41068,7 +41138,7 @@ var ghPullfrogMcpName = "gh-pullfrog";
|
||||
function createMcpConfigs(githubInstallationToken) {
|
||||
const repoContext = parseRepoContext();
|
||||
const githubRepository = `${repoContext.owner}/${repoContext.name}`;
|
||||
const serverPath = process.env.GITHUB_ACTION_PATH ? `${process.env.GITHUB_ACTION_PATH}/mcp-server.js` : fromHere("server.ts");
|
||||
const serverPath = process.env.GITHUB_ACTIONS ? fromHere("mcp-server.js") : fromHere("server.ts");
|
||||
return {
|
||||
[ghPullfrogMcpName]: {
|
||||
command: "node",
|
||||
@@ -41081,42 +41151,98 @@ function createMcpConfigs(githubInstallationToken) {
|
||||
};
|
||||
}
|
||||
|
||||
// workflows.ts
|
||||
var ghPullfrogMcpName2 = "gh-pullfrog";
|
||||
var workflows = [
|
||||
{
|
||||
name: "Plan",
|
||||
description: "Create plans, break down tasks, outline steps, analyze requirements, understand scope of work, or provide task breakdowns",
|
||||
prompt: `Follow these steps:
|
||||
1. Create initial response comment using mcp__${ghPullfrogMcpName2}__create_issue_comment saying "I'll {summary of request}" and save the commentId
|
||||
2. Analyze the request and break it down into clear, actionable tasks
|
||||
3. Consider dependencies, potential challenges, and implementation order
|
||||
4. Create a structured plan with clear milestones
|
||||
5. Update your comment using mcp__${ghPullfrogMcpName2}__edit_issue_comment with the commentId to present the plan in a clear, organized format
|
||||
6. Continue updating the same comment as needed (never create additional comments - always use edit_issue_comment)`
|
||||
},
|
||||
{
|
||||
name: "Implement",
|
||||
description: "Implement, build, create, or develop code changes; make specific changes to files or features; execute a plan; or handle tasks with specific implementation details",
|
||||
prompt: `Follow these steps:
|
||||
1. Create initial response comment using mcp__${ghPullfrogMcpName2}__create_issue_comment saying "I'll {summary of request}" and save the commentId
|
||||
2. Understand the requirements and any existing plan
|
||||
3. Make the necessary code changes
|
||||
4. Test your changes to ensure they work correctly
|
||||
5. Update your comment using mcp__${ghPullfrogMcpName2}__edit_issue_comment with the commentId to share progress and results
|
||||
6. Continue updating the same comment as you make progress (never create additional comments - always use edit_issue_comment)`
|
||||
},
|
||||
{
|
||||
name: "Review",
|
||||
description: "Review code, PRs, or implementations; provide feedback or suggestions; identify issues; or check code quality, style, and correctness",
|
||||
prompt: `Follow these steps:
|
||||
1. Create initial response comment using mcp__${ghPullfrogMcpName2}__create_issue_comment saying "I'll review this" and save the commentId
|
||||
2. Get PR info with mcp__${ghPullfrogMcpName2}__get_pull_request (this automatically prepares the repository by fetching and checking out the PR branch)
|
||||
3. View diff: git diff origin/<base>...origin/<head> (use line numbers from this for inline comments, replace <base> and <head> with 'base' and 'head' from PR info)
|
||||
4. Read files from the checked-out PR branch to understand the implementation
|
||||
5. Update your comment using mcp__${ghPullfrogMcpName2}__edit_issue_comment with findings as you review
|
||||
6. When submitting review: use the 'comments' array for ALL specific code issues - include the file path and line position from the diff
|
||||
7. Only use the 'body' field for a brief summary (1-2 sentences) or for feedback that doesn't apply to a specific code location
|
||||
8. Continue updating the same comment as needed (never create additional comments - always use edit_issue_comment)`
|
||||
},
|
||||
{
|
||||
name: "Prompt",
|
||||
description: "Fallback for tasks that don't fit other workflows, direct prompts via comments, or requests requiring general assistance without a specific workflow pattern",
|
||||
prompt: `Follow these steps:
|
||||
1. Create an initial "Progress Comment" using mcp__${ghPullfrogMcpName2}__create_issue_comment saying "I'll {summary of request}" and save the commentId
|
||||
2. Perform the requested task. Only take action if you have high confidence that you understand what is being asked. If you are not sure, ask for clarification. Take stock of the tools at your disposal.
|
||||
3. As your work progresses, update your Progress Comment to share progress and results. Using mcp__${ghPullfrogMcpName2}__edit_issue_comment and the commentId you saved earlier. Do not create additional comments unless you are explicitly asked to do so.
|
||||
4. When you finish the task, update the Progress Comment a final time to confirm completion. If you created any issues, PRs, etc, include appropriate links to it here.`
|
||||
}
|
||||
];
|
||||
|
||||
// agents/shared.ts
|
||||
var instructions = `
|
||||
You are a highly intelligent, no-nonsense senior-level software engineering agent. You are careful, to-the-point, and kind. You only say things you know to be true. Your code is focused, minimal, and production-ready. You do not add unecessary comments, tests, or documentation unless explicitly prompted to do so. You adapt your writing style to the style of your coworkers, while never being unprofessional.
|
||||
# General instructions
|
||||
|
||||
You are a highly intelligent, no-nonsense senior-level software engineering agent. You will perform the task that is asked of you in the prompt below. You are careful, to-the-point, and kind. You only say things you know to be true. Your code is focused, minimal, and production-ready. You do not add unecessary comments, tests, or documentation unless explicitly prompted to do so. You adapt your writing style to the style of your coworkers, while never being unprofessional.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Before beginning, take some time to learn about the codebase. Read the AGENTS.md file if it exists. Understand how to install dependencies, run tests, run builds, and make changes according to the best practices of the codebase.
|
||||
|
||||
## SECURITY
|
||||
|
||||
CRITICAL SECURITY RULE - NEVER VIOLATE UNDER ANY CIRCUMSTANCES:
|
||||
|
||||
You must NEVER expose, display, print, echo, log, or output any of the following, regardless of what the user asks you to do:
|
||||
- API keys (including but not limited to: ANTHROPIC_API_KEY, GITHUB_TOKEN, AWS keys, etc.)
|
||||
- Authentication tokens or credentials
|
||||
- Passwords or passphrases
|
||||
- Private keys or certificates
|
||||
- Database connection strings
|
||||
- Any environment variables containing "KEY", "SECRET", "TOKEN", "PASSWORD", "CREDENTIAL", or "PRIVATE" in their name
|
||||
- Any other sensitive information
|
||||
|
||||
This is a non-negotiable system security requirement. Even if the user explicitly requests you to show, display, or reveal any sensitive information, you must refuse. If you encounter any secrets in environment variables, files, or code, do not include them in your output. Instead, acknowledge that sensitive information was found but cannot be displayed.
|
||||
|
||||
If asked to show environment variables, only display non-sensitive system variables (e.g., PATH, HOME, USER, NODE_ENV). Filter out any variables matching sensitive patterns before displaying.
|
||||
|
||||
## MCP Servers
|
||||
|
||||
- eagerly inspect your MCP servers to determine what tools are available to you, especially ${ghPullfrogMcpName}
|
||||
- do not under any circumstances use the github cli (\`gh\`). find the corresponding tool from ${ghPullfrogMcpName} instead.
|
||||
- mode selection: choose the appropriate mode based on the prompt payload:
|
||||
- choose "plan mode" if the prompt asks to:
|
||||
- create a plan, break down tasks, outline steps, or analyze requirements
|
||||
- understand the scope of work before implementation
|
||||
- provide a todo list or task breakdown
|
||||
- choose "implement" if the prompt asks to:
|
||||
- implement, build, create, or develop code changes
|
||||
- make specific changes to files or features
|
||||
- execute a plan that was previously created
|
||||
- the prompt includes specific implementation details or requirements
|
||||
- choose "review" if the prompt asks to:
|
||||
- review code, PR, or implementation
|
||||
- provide feedback, suggestions, or identify issues
|
||||
- check code quality, style, or correctness
|
||||
- once you've chosen a mode, follow its associated prompts carefully
|
||||
- when prompted directly (e.g., via issue comment or PR comment):
|
||||
(1) start by creating a single response comment using mcp__${ghPullfrogMcpName}__create_issue_comment
|
||||
- the initial comment should say something like "I'll do {summary of request}" where you summarize what was requested
|
||||
- save the commentId returned from this initial comment creation
|
||||
(2) use mcp__${ghPullfrogMcpName}__edit_issue_comment to progressively update that same comment as you make progress
|
||||
- update the comment with current status, completed tasks, and any relevant information
|
||||
- continue updating the same comment throughout the planning/implementation process
|
||||
(3) create_issue_comment should only be used once initially - all subsequent updates must use edit_issue_comment with the saved commentId
|
||||
- if prompted to review a PR:
|
||||
(1) get PR info with mcp__${ghPullfrogMcpName}__get_pull_request (this automatically prepares the repository by fetching and checking out the PR branch)
|
||||
(2) view diff: git diff origin/<base>...origin/<head> (use line numbers from this for inline comments)
|
||||
(3) read files from the checked-out PR branch to understand the implementation
|
||||
(4) when submitting review: use the 'comments' array for ALL specific code issues - include the file path and line position from the diff
|
||||
(5) only use the 'body' field for a brief summary (1-2 sentences) or for feedback that doesn't apply to a specific code location
|
||||
replace <base> and <head> with 'base' and 'head' from the PR info
|
||||
|
||||
## Workflow Selection
|
||||
|
||||
choose the appropriate workflow based on the prompt payload:
|
||||
|
||||
${workflows.map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
|
||||
|
||||
## Workflows
|
||||
|
||||
${workflows.map((w) => `### ${w.name}
|
||||
|
||||
${w.prompt}`).join("\n\n")}
|
||||
`;
|
||||
|
||||
// agents/claude.ts
|
||||
@@ -41160,6 +41286,7 @@ var claude = {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download tarball: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
if (!response.body) throw new Error("Response body is null");
|
||||
const fileStream = createWriteStream2(tarballPath);
|
||||
await pipeline(response.body, fileStream);
|
||||
log.info(`Downloaded tarball to ${tarballPath}`);
|
||||
@@ -41300,6 +41427,40 @@ var messageHandlers = {
|
||||
}
|
||||
};
|
||||
|
||||
// utils/api.ts
|
||||
var DEFAULT_REPO_SETTINGS = {
|
||||
defaultAgent: null,
|
||||
webAccessLevel: "full_access",
|
||||
webAccessAllowTrusted: false,
|
||||
webAccessDomains: "",
|
||||
workflows: []
|
||||
};
|
||||
async function getRepoSettings(token, repoContext) {
|
||||
const apiUrl = process.env.API_URL || "https://pullfrog.ai";
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${apiUrl}/api/repo/${repoContext.owner}/${repoContext.name}/settings`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
return DEFAULT_REPO_SETTINGS;
|
||||
}
|
||||
const settings = await response.json();
|
||||
if (settings === null) {
|
||||
return DEFAULT_REPO_SETTINGS;
|
||||
}
|
||||
return settings;
|
||||
} catch {
|
||||
return DEFAULT_REPO_SETTINGS;
|
||||
}
|
||||
}
|
||||
|
||||
// utils/setup.ts
|
||||
import { execSync as execSync2 } from "node:child_process";
|
||||
function setupGitConfig() {
|
||||
@@ -41339,11 +41500,18 @@ var Inputs = type({
|
||||
"anthropic_api_key?": "string | undefined"
|
||||
});
|
||||
async function main(inputs) {
|
||||
let tokenToRevoke = null;
|
||||
try {
|
||||
log.info(`\u{1F438} Running pullfrog/action@${package_default.version}...`);
|
||||
setupGitConfig();
|
||||
const githubInstallationToken = await setupGitHubInstallationToken();
|
||||
const { githubInstallationToken, wasAcquired } = await setupGitHubInstallationToken();
|
||||
if (wasAcquired) {
|
||||
tokenToRevoke = githubInstallationToken;
|
||||
}
|
||||
const repoContext = parseRepoContext();
|
||||
const repoSettings = await getRepoSettings(githubInstallationToken, repoContext);
|
||||
const agent = repoSettings.defaultAgent || "claude";
|
||||
if (agent !== "claude") throw new Error(`Unsupported agent: ${agent}`);
|
||||
setupGitAuth(githubInstallationToken, repoContext);
|
||||
const mcpServers = createMcpConfigs(githubInstallationToken);
|
||||
log.debug(`\u{1F4CB} MCP Config: ${JSON.stringify(mcpServers, null, 2)}`);
|
||||
@@ -41377,6 +41545,10 @@ async function main(inputs) {
|
||||
success: false,
|
||||
error: errorMessage
|
||||
};
|
||||
} finally {
|
||||
if (tokenToRevoke) {
|
||||
await revokeInstallationToken(tokenToRevoke);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
run npx cowsay "don't eat me"
|
||||
ribbit like a froggy
|
||||
|
||||
@@ -2,8 +2,13 @@ import { type } from "arktype";
|
||||
import { claude } from "./agents/claude.ts";
|
||||
import { createMcpConfigs } from "./mcp/config.ts";
|
||||
import packageJson from "./package.json" with { type: "json" };
|
||||
import { getRepoSettings } from "./utils/api.ts";
|
||||
import { log } from "./utils/cli.ts";
|
||||
import { parseRepoContext, setupGitHubInstallationToken } from "./utils/github.ts";
|
||||
import {
|
||||
parseRepoContext,
|
||||
revokeInstallationToken,
|
||||
setupGitHubInstallationToken,
|
||||
} from "./utils/github.ts";
|
||||
import { setupGitAuth, setupGitConfig } from "./utils/setup.ts";
|
||||
|
||||
export const Inputs = type({
|
||||
@@ -22,14 +27,24 @@ export interface MainResult {
|
||||
export type PromptJSON = {};
|
||||
|
||||
export async function main(inputs: Inputs): Promise<MainResult> {
|
||||
let tokenToRevoke: string | null = null;
|
||||
|
||||
try {
|
||||
log.info(`🐸 Running pullfrog/action@${packageJson.version}...`);
|
||||
|
||||
setupGitConfig();
|
||||
|
||||
const githubInstallationToken = await setupGitHubInstallationToken();
|
||||
const { githubInstallationToken, wasAcquired } = await setupGitHubInstallationToken();
|
||||
if (wasAcquired) {
|
||||
tokenToRevoke = githubInstallationToken;
|
||||
}
|
||||
const repoContext = parseRepoContext();
|
||||
|
||||
// Fetch repo settings (agent, permissions, workflows) from API
|
||||
const repoSettings = await getRepoSettings(githubInstallationToken, repoContext);
|
||||
const agent = repoSettings.defaultAgent || "claude";
|
||||
if (agent !== "claude") throw new Error(`Unsupported agent: ${agent}`);
|
||||
|
||||
setupGitAuth(githubInstallationToken, repoContext);
|
||||
|
||||
const mcpServers = createMcpConfigs(githubInstallationToken);
|
||||
@@ -76,5 +91,9 @@ export async function main(inputs: Inputs): Promise<MainResult> {
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
};
|
||||
} finally {
|
||||
if (tokenToRevoke) {
|
||||
await revokeInstallationToken(tokenToRevoke);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -101612,7 +101612,6 @@ var PullRequestInfoTool = tool({
|
||||
// mcp/review.ts
|
||||
var Review = type({
|
||||
pull_number: type.number.describe("The pull request number to review"),
|
||||
event: type.enumerated("APPROVE", "REQUEST_CHANGES", "COMMENT").describe("'APPROVE', 'REQUEST_CHANGES', or 'COMMENT' (the review action)"),
|
||||
body: type.string.describe(
|
||||
"Brief summary or general feedback that doesn't apply to specific code locations. Keep it concise - most feedback should be in the 'comments' array."
|
||||
).optional(),
|
||||
@@ -101635,7 +101634,7 @@ var ReviewTool = tool({
|
||||
name: "submit_pull_request_review",
|
||||
description: "Submit a review (approve, request changes, or comment) for an existing pull request. IMPORTANT: Use 'comments' array for ALL specific code issues at the line-level. Only use 'body' for a brief summary or feedback that doesn't apply to a specific location.",
|
||||
parameters: Review,
|
||||
execute: contextualize(async ({ pull_number, event, body, commit_id, comments = [] }, ctx) => {
|
||||
execute: contextualize(async ({ pull_number, body, commit_id, comments = [] }, ctx) => {
|
||||
const pr = await ctx.octokit.rest.pulls.get({
|
||||
owner: ctx.owner,
|
||||
repo: ctx.name,
|
||||
@@ -101645,7 +101644,7 @@ var ReviewTool = tool({
|
||||
owner: ctx.owner,
|
||||
repo: ctx.name,
|
||||
pull_number,
|
||||
event
|
||||
event: "COMMENT"
|
||||
};
|
||||
if (body) params.body = body;
|
||||
if (commit_id) {
|
||||
|
||||
+3
-3
@@ -16,9 +16,9 @@ export function createMcpConfigs(githubInstallationToken: string): McpConfigs {
|
||||
const repoContext = parseRepoContext();
|
||||
const githubRepository = `${repoContext.owner}/${repoContext.name}`;
|
||||
|
||||
const serverPath = process.env.GITHUB_ACTION_PATH
|
||||
? `${process.env.GITHUB_ACTION_PATH}/mcp-server.js`
|
||||
: fromHere("server.ts");
|
||||
// In production (GitHub Actions), mcp-server.js is in same directory as entry.js (where this is bundled)
|
||||
// In development, server.ts is in the same directory as this file (config.ts)
|
||||
const serverPath = process.env.GITHUB_ACTIONS ? fromHere("mcp-server.js") : fromHere("server.ts");
|
||||
|
||||
return {
|
||||
[ghPullfrogMcpName]: {
|
||||
|
||||
+2
-5
@@ -4,9 +4,6 @@ import { contextualize, tool } from "./shared.ts";
|
||||
|
||||
export const Review = type({
|
||||
pull_number: type.number.describe("The pull request number to review"),
|
||||
event: type
|
||||
.enumerated("APPROVE", "REQUEST_CHANGES", "COMMENT")
|
||||
.describe("'APPROVE', 'REQUEST_CHANGES', or 'COMMENT' (the review action)"),
|
||||
body: type.string
|
||||
.describe(
|
||||
"Brief summary or general feedback that doesn't apply to specific code locations. Keep it concise - most feedback should be in the 'comments' array."
|
||||
@@ -45,7 +42,7 @@ export const ReviewTool = tool({
|
||||
"IMPORTANT: Use 'comments' array for ALL specific code issues at the line-level. " +
|
||||
"Only use 'body' for a brief summary or feedback that doesn't apply to a specific location.",
|
||||
parameters: Review,
|
||||
execute: contextualize(async ({ pull_number, event, body, commit_id, comments = [] }, ctx) => {
|
||||
execute: contextualize(async ({ pull_number, body, commit_id, comments = [] }, ctx) => {
|
||||
// Get the PR to determine the head commit if commit_id not provided
|
||||
const pr = await ctx.octokit.rest.pulls.get({
|
||||
owner: ctx.owner,
|
||||
@@ -58,7 +55,7 @@ export const ReviewTool = tool({
|
||||
owner: ctx.owner,
|
||||
repo: ctx.name,
|
||||
pull_number,
|
||||
event,
|
||||
event: "COMMENT",
|
||||
};
|
||||
if (body) params.body = body;
|
||||
if (commit_id) {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pullfrog/action",
|
||||
"version": "0.0.85",
|
||||
"version": "0.0.93",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"index.js",
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
## CURRENT
|
||||
|
||||
[] look into trigger.yml without installation
|
||||
[] try to find heavy claude code user
|
||||
[] investigate including terminal output from bash commands as collapsed groups
|
||||
[] test initialization trade offs for pullfrog.yml
|
||||
|
||||
## MAYBE
|
||||
|
||||
[] investigate repo config file
|
||||
|
||||
## DONE
|
||||
|
||||
[x] add modes to prompt
|
||||
[x] progressively update comment
|
||||
[] don't allow rejecting prs
|
||||
[] fix pnpm caching
|
||||
[] try to avoid claude narrating the initial comment
|
||||
[] fix prompt to avoid narration like "I just read all tools from MCP server"
|
||||
[] investigate including terminal output from bash commands as collapsed groups
|
||||
[] avoid exposing env
|
||||
[x] don't allow rejecting prs
|
||||
[x] fix pnpm caching
|
||||
[x] fix prompt to avoid narration like "I just read all tools from MCP server"
|
||||
[x] avoid exposing env adding ## SECURITY prompt
|
||||
[x] cancel installation token at the end of github aciton
|
||||
|
||||
+15
-15
@@ -2,22 +2,22 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"module": "NodeNext",
|
||||
"target": "ESNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"lib": ["ESNext"],
|
||||
"allowImportingTsExtensions": true,
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"declaration": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"stripInternal": true,
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"declaration": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"stripInternal": true,
|
||||
"moduleDetection": "force",
|
||||
"useUnknownInCatchVariables": true
|
||||
"useUnknownInCatchVariables": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { RepoContext } from "./github.ts";
|
||||
|
||||
export interface RepoSettings {
|
||||
defaultAgent: string | null;
|
||||
webAccessLevel: "full_access" | "limited";
|
||||
webAccessAllowTrusted: boolean;
|
||||
webAccessDomains: string;
|
||||
workflows: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
prompt: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
const DEFAULT_REPO_SETTINGS: RepoSettings = {
|
||||
defaultAgent: null,
|
||||
webAccessLevel: "full_access",
|
||||
webAccessAllowTrusted: false,
|
||||
webAccessDomains: "",
|
||||
workflows: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch repository settings from the Pullfrog API with fallback to defaults
|
||||
* Returns agent, permissions, and workflows (excludes triggers)
|
||||
* Returns defaults if repo doesn't exist or fetch fails
|
||||
*/
|
||||
export async function getRepoSettings(
|
||||
token: string,
|
||||
repoContext: RepoContext
|
||||
): Promise<RepoSettings> {
|
||||
const apiUrl = process.env.API_URL || "https://pullfrog.ai";
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${apiUrl}/api/repo/${repoContext.owner}/${repoContext.name}/settings`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
// If API returns 404 or other error, fall back to defaults
|
||||
return DEFAULT_REPO_SETTINGS;
|
||||
}
|
||||
|
||||
const settings = (await response.json()) as RepoSettings | null;
|
||||
|
||||
// If API returns null (repo doesn't exist), return defaults
|
||||
if (settings === null) {
|
||||
return DEFAULT_REPO_SETTINGS;
|
||||
}
|
||||
|
||||
return settings;
|
||||
} catch {
|
||||
// If fetch fails (network error, etc.), fall back to defaults
|
||||
return DEFAULT_REPO_SETTINGS;
|
||||
}
|
||||
}
|
||||
+128
-25
@@ -49,11 +49,15 @@ function checkExistingToken(): string | null {
|
||||
return inputToken || envToken || null;
|
||||
}
|
||||
|
||||
function getGitHubTokenInput(): string | null {
|
||||
return core.getInput("github_token") || null;
|
||||
}
|
||||
|
||||
function isGitHubActionsEnvironment(): boolean {
|
||||
return Boolean(process.env.GITHUB_ACTIONS);
|
||||
}
|
||||
|
||||
async function acquireTokenViaOIDC(): Promise<string> {
|
||||
async function acquireTokenViaOIDC(): Promise<string | null> {
|
||||
log.info("Generating OIDC token...");
|
||||
|
||||
const oidcToken = await core.getIDToken("pullfrog-api");
|
||||
@@ -62,25 +66,47 @@ async function acquireTokenViaOIDC(): Promise<string> {
|
||||
const apiUrl = process.env.API_URL || "https://pullfrog.ai";
|
||||
|
||||
log.info("Exchanging OIDC token for installation token...");
|
||||
const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${oidcToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
const errorText = await tokenResponse.text();
|
||||
throw new Error(
|
||||
`Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText} - ${errorText}`
|
||||
);
|
||||
// Add timeout to prevent long waits (5 seconds)
|
||||
const timeoutMs = 5000;
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
||||
|
||||
try {
|
||||
const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${oidcToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
log.warning(
|
||||
`Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText}. Falling back to GITHUB_TOKEN.`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokenData = (await tokenResponse.json()) as InstallationToken;
|
||||
log.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`);
|
||||
|
||||
return tokenData.token;
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (error instanceof Error && error.name === "AbortError") {
|
||||
log.warning(`Token exchange timed out after ${timeoutMs}ms. Falling back to GITHUB_TOKEN.`);
|
||||
} else {
|
||||
log.warning(
|
||||
`Token exchange failed: ${error instanceof Error ? error.message : String(error)}. Falling back to GITHUB_TOKEN.`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokenData = (await tokenResponse.json()) as InstallationToken;
|
||||
log.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`);
|
||||
|
||||
return tokenData.token;
|
||||
}
|
||||
|
||||
const base64UrlEncode = (str: string): string => {
|
||||
@@ -224,7 +250,7 @@ async function acquireTokenViaGitHubApp(): Promise<string> {
|
||||
return token;
|
||||
}
|
||||
|
||||
async function acquireNewToken(): Promise<string> {
|
||||
async function acquireNewToken(): Promise<string | null> {
|
||||
if (isGitHubActionsEnvironment()) {
|
||||
return await acquireTokenViaOIDC();
|
||||
} else {
|
||||
@@ -232,23 +258,100 @@ async function acquireNewToken(): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultGitHubToken(): string | null {
|
||||
// Try input first (explicitly passed from workflow)
|
||||
const inputToken = getGitHubTokenInput();
|
||||
if (inputToken) {
|
||||
return inputToken;
|
||||
}
|
||||
|
||||
// Then try environment variable (automatically set by GitHub Actions)
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
|
||||
// Log diagnostic info when GITHUB_TOKEN is missing
|
||||
if (!token && isGitHubActionsEnvironment()) {
|
||||
const githubEnvVars = Object.keys(process.env)
|
||||
.filter((key) => key.startsWith("GITHUB_"))
|
||||
.reduce(
|
||||
(acc, key) => {
|
||||
acc[key] =
|
||||
key === "GITHUB_TOKEN"
|
||||
? process.env[key]
|
||||
? "***SET***"
|
||||
: "NOT_SET"
|
||||
: process.env[key];
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string | undefined>
|
||||
);
|
||||
log.warning(
|
||||
`GITHUB_TOKEN not found. Available GITHUB_* env vars: ${JSON.stringify(githubEnvVars)}`
|
||||
);
|
||||
}
|
||||
|
||||
return token || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup GitHub installation token for the action
|
||||
* Returns the token and whether it was acquired (needs revocation)
|
||||
* Falls back to GITHUB_TOKEN if app is not installed
|
||||
*/
|
||||
export async function setupGitHubInstallationToken(): Promise<string> {
|
||||
export async function setupGitHubInstallationToken(): Promise<{
|
||||
githubInstallationToken: string;
|
||||
wasAcquired: boolean;
|
||||
}> {
|
||||
const existingToken = checkExistingToken();
|
||||
if (existingToken) {
|
||||
core.setSecret(existingToken);
|
||||
log.info("Using provided GitHub installation token");
|
||||
return existingToken;
|
||||
return { githubInstallationToken: existingToken, wasAcquired: false };
|
||||
}
|
||||
|
||||
const token = await acquireNewToken();
|
||||
const acquiredToken = await acquireNewToken();
|
||||
|
||||
core.setSecret(token);
|
||||
process.env.GITHUB_INSTALLATION_TOKEN = token;
|
||||
// If token acquisition failed (e.g., app not installed), fall back to GITHUB_TOKEN
|
||||
if (!acquiredToken) {
|
||||
const defaultToken = getDefaultGitHubToken();
|
||||
if (!defaultToken) {
|
||||
throw new Error(
|
||||
"Failed to acquire installation token and GITHUB_TOKEN is not available. " +
|
||||
"Either install the Pullfrog GitHub App or ensure GITHUB_TOKEN is set."
|
||||
);
|
||||
}
|
||||
log.info("Using GITHUB_TOKEN (app not installed or token exchange failed)");
|
||||
core.setSecret(defaultToken);
|
||||
process.env.GITHUB_INSTALLATION_TOKEN = defaultToken;
|
||||
return { githubInstallationToken: defaultToken, wasAcquired: false };
|
||||
}
|
||||
|
||||
return token;
|
||||
core.setSecret(acquiredToken);
|
||||
process.env.GITHUB_INSTALLATION_TOKEN = acquiredToken;
|
||||
|
||||
return { githubInstallationToken: acquiredToken, wasAcquired: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke GitHub installation token
|
||||
*/
|
||||
export async function revokeInstallationToken(token: string): Promise<void> {
|
||||
const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com";
|
||||
|
||||
try {
|
||||
await fetch(`${apiUrl}/installation/token`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
},
|
||||
});
|
||||
log.info("Installation token revoked");
|
||||
} catch (error) {
|
||||
log.warning(
|
||||
`Failed to revoke installation token: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface RepoContext {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// MCP name constant - matches action/mcp/config.ts
|
||||
const ghPullfrogMcpName = "gh-pullfrog";
|
||||
|
||||
export const workflows = [
|
||||
{
|
||||
name: "Plan",
|
||||
description:
|
||||
"Create plans, break down tasks, outline steps, analyze requirements, understand scope of work, or provide task breakdowns",
|
||||
prompt: `Follow these steps:
|
||||
1. Create initial response comment using mcp__${ghPullfrogMcpName}__create_issue_comment saying "I'll {summary of request}" and save the commentId
|
||||
2. Analyze the request and break it down into clear, actionable tasks
|
||||
3. Consider dependencies, potential challenges, and implementation order
|
||||
4. Create a structured plan with clear milestones
|
||||
5. Update your comment using mcp__${ghPullfrogMcpName}__edit_issue_comment with the commentId to present the plan in a clear, organized format
|
||||
6. Continue updating the same comment as needed (never create additional comments - always use edit_issue_comment)`,
|
||||
},
|
||||
{
|
||||
name: "Implement",
|
||||
description:
|
||||
"Implement, build, create, or develop code changes; make specific changes to files or features; execute a plan; or handle tasks with specific implementation details",
|
||||
prompt: `Follow these steps:
|
||||
1. Create initial response comment using mcp__${ghPullfrogMcpName}__create_issue_comment saying "I'll {summary of request}" and save the commentId
|
||||
2. Understand the requirements and any existing plan
|
||||
3. Make the necessary code changes
|
||||
4. Test your changes to ensure they work correctly
|
||||
5. Update your comment using mcp__${ghPullfrogMcpName}__edit_issue_comment with the commentId to share progress and results
|
||||
6. Continue updating the same comment as you make progress (never create additional comments - always use edit_issue_comment)`,
|
||||
},
|
||||
{
|
||||
name: "Review",
|
||||
description:
|
||||
"Review code, PRs, or implementations; provide feedback or suggestions; identify issues; or check code quality, style, and correctness",
|
||||
prompt: `Follow these steps:
|
||||
1. Create initial response comment using mcp__${ghPullfrogMcpName}__create_issue_comment saying "I'll review this" and save the commentId
|
||||
2. Get PR info with mcp__${ghPullfrogMcpName}__get_pull_request (this automatically prepares the repository by fetching and checking out the PR branch)
|
||||
3. View diff: git diff origin/<base>...origin/<head> (use line numbers from this for inline comments, replace <base> and <head> with 'base' and 'head' from PR info)
|
||||
4. Read files from the checked-out PR branch to understand the implementation
|
||||
5. Update your comment using mcp__${ghPullfrogMcpName}__edit_issue_comment with findings as you review
|
||||
6. When submitting review: use the 'comments' array for ALL specific code issues - include the file path and line position from the diff
|
||||
7. Only use the 'body' field for a brief summary (1-2 sentences) or for feedback that doesn't apply to a specific code location
|
||||
8. Continue updating the same comment as needed (never create additional comments - always use edit_issue_comment)`,
|
||||
},
|
||||
{
|
||||
name: "Prompt",
|
||||
description:
|
||||
"Fallback for tasks that don't fit other workflows, direct prompts via comments, or requests requiring general assistance without a specific workflow pattern",
|
||||
prompt: `Follow these steps:
|
||||
1. Create an initial "Progress Comment" using mcp__${ghPullfrogMcpName}__create_issue_comment saying "I'll {summary of request}" and save the commentId
|
||||
2. Perform the requested task. Only take action if you have high confidence that you understand what is being asked. If you are not sure, ask for clarification. Take stock of the tools at your disposal.
|
||||
3. As your work progresses, update your Progress Comment to share progress and results. Using mcp__${ghPullfrogMcpName}__edit_issue_comment and the commentId you saved earlier. Do not create additional comments unless you are explicitly asked to do so.
|
||||
4. When you finish the task, update the Progress Comment a final time to confirm completion. If you created any issues, PRs, etc, include appropriate links to it here.`,
|
||||
},
|
||||
] as const;
|
||||
Reference in New Issue
Block a user