Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c128b293f | |||
| c08008668b | |||
| 7ac2938570 | |||
| 363e4ecda2 | |||
| 13cc56944f | |||
| 2d91473f6e | |||
| 3937c3bdba | |||
| bac3f3e9c6 |
+1
-1
@@ -7,4 +7,4 @@ echo "🔨 Building action..."
|
|||||||
pnpm build
|
pnpm build
|
||||||
|
|
||||||
# Add the built files and lockfile to the commit
|
# Add the built files and lockfile to the commit
|
||||||
git add entry.js pnpm-lock.yaml
|
git add entry.js mcp-server.js pnpm-lock.yaml
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ GitHub Action for running Claude Code and other agents via Pullfrog.
|
|||||||
```bash
|
```bash
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
pnpm install
|
pnpm install
|
||||||
|
|
||||||
# Test with default prompt
|
|
||||||
npm run play # Run locally on your machine
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing with play.ts
|
## Testing with play.ts
|
||||||
|
|||||||
+1
-1
@@ -67,7 +67,7 @@ export const claude: Agent = {
|
|||||||
|
|
||||||
// Write tarball to file
|
// Write tarball to file
|
||||||
const fileStream = createWriteStream(tarballPath);
|
const fileStream = createWriteStream(tarballPath);
|
||||||
await pipeline(response.body as any, fileStream);
|
await pipeline(response.body!, fileStream);
|
||||||
log.info(`Downloaded tarball to ${tarballPath}`);
|
log.info(`Downloaded tarball to ${tarballPath}`);
|
||||||
|
|
||||||
// Extract tarball
|
// Extract tarball
|
||||||
|
|||||||
@@ -4,52 +4,11 @@
|
|||||||
* Entry point for GitHub Action
|
* Entry point for GitHub Action
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { readdir } from "node:fs/promises";
|
|
||||||
import { join } from "node:path";
|
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import { dirName } from "@ark/fs";
|
|
||||||
import { type Inputs, main } from "./main.ts";
|
import { type Inputs, main } from "./main.ts";
|
||||||
import { createMcpServer } from "./mcp/server.ts";
|
|
||||||
import { log } from "./utils/cli.ts";
|
import { log } from "./utils/cli.ts";
|
||||||
|
|
||||||
// Export createMcpServer so it can be called from the spawned MCP process
|
|
||||||
export { createMcpServer };
|
|
||||||
|
|
||||||
async function printDirectoryTree(dir: string, prefix = "", rootDir = dir): Promise<string> {
|
|
||||||
const entries = await readdir(dir, { withFileTypes: true });
|
|
||||||
const lines: string[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < entries.length; i++) {
|
|
||||||
const entry = entries[i];
|
|
||||||
const isLast = i === entries.length - 1;
|
|
||||||
const currentPrefix = isLast ? "└── " : "├── ";
|
|
||||||
const nextPrefix = isLast ? " " : "│ ";
|
|
||||||
|
|
||||||
const fullPath = join(dir, entry.name);
|
|
||||||
lines.push(`${prefix}${currentPrefix}${entry.name}`);
|
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
|
||||||
const subTree = await printDirectoryTree(fullPath, `${prefix}${nextPrefix}`, rootDir);
|
|
||||||
lines.push(subTree);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
async function run(): Promise<void> {
|
||||||
// Debug: Print current directory tree before changing directories
|
|
||||||
const actionDir = dirName();
|
|
||||||
log.info(`action dir: ${actionDir}`);
|
|
||||||
try {
|
|
||||||
const tree = await printDirectoryTree(actionDir);
|
|
||||||
log.info(`Directory tree:\n${tree}`);
|
|
||||||
} catch (error) {
|
|
||||||
log.warning(
|
|
||||||
`Failed to print directory tree: ${error instanceof Error ? error.message : String(error)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change to GITHUB_WORKSPACE if set (this is where actions/checkout puts the repo)
|
// Change to GITHUB_WORKSPACE if set (this is where actions/checkout puts the repo)
|
||||||
// JavaScript actions run from the action's directory, not the checked-out repo
|
// JavaScript actions run from the action's directory, not the checked-out repo
|
||||||
if (process.env.GITHUB_WORKSPACE && process.cwd() !== process.env.GITHUB_WORKSPACE) {
|
if (process.env.GITHUB_WORKSPACE && process.cwd() !== process.env.GITHUB_WORKSPACE) {
|
||||||
|
|||||||
+15
-5
@@ -1,11 +1,7 @@
|
|||||||
import { build } from "esbuild";
|
import { build } from "esbuild";
|
||||||
|
|
||||||
// Build the GitHub Action bundle only
|
const sharedConfig = {
|
||||||
// For npm package builds, use zshy (pnpm build:npm)
|
|
||||||
await build({
|
|
||||||
entryPoints: ["./entry.ts"],
|
|
||||||
bundle: true,
|
bundle: true,
|
||||||
outfile: "./entry.js",
|
|
||||||
format: "esm",
|
format: "esm",
|
||||||
platform: "node",
|
platform: "node",
|
||||||
target: "node20",
|
target: "node20",
|
||||||
@@ -27,6 +23,20 @@ await build({
|
|||||||
treeShaking: true,
|
treeShaking: true,
|
||||||
// Drop console statements in production (but keep for debugging)
|
// Drop console statements in production (but keep for debugging)
|
||||||
drop: [],
|
drop: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build the main entry bundle (without MCP)
|
||||||
|
await build({
|
||||||
|
...sharedConfig,
|
||||||
|
entryPoints: ["./entry.ts"],
|
||||||
|
outfile: "./entry.js",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build the MCP server bundle
|
||||||
|
await build({
|
||||||
|
...sharedConfig,
|
||||||
|
entryPoints: ["./mcp/server.ts"],
|
||||||
|
outfile: "./mcp-server.js",
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("✅ Build completed successfully!");
|
console.log("✅ Build completed successfully!");
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
run npx cowsay "don't eat me"
|
Review https://github.com/pullfrogai/scratch/pull/17
|
||||||
Executable
+102694
File diff suppressed because one or more lines are too long
+5
-9
@@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
||||||
|
import { fromHere } from "@ark/fs";
|
||||||
import { parseRepoContext } from "../utils/github.ts";
|
import { parseRepoContext } from "../utils/github.ts";
|
||||||
|
|
||||||
export const ghPullfrogMcpName = "gh-pullfrog";
|
export const ghPullfrogMcpName = "gh-pullfrog";
|
||||||
@@ -15,19 +16,14 @@ export function createMcpConfigs(githubInstallationToken: string): McpConfigs {
|
|||||||
const repoContext = parseRepoContext();
|
const repoContext = parseRepoContext();
|
||||||
const githubRepository = `${repoContext.owner}/${repoContext.name}`;
|
const githubRepository = `${repoContext.owner}/${repoContext.name}`;
|
||||||
|
|
||||||
// Get absolute path to entry.js - use GITHUB_ACTION_PATH if available, otherwise current directory
|
// In production (GitHub Actions), mcp-server.js is in same directory as entry.js (where this is bundled)
|
||||||
const entryPath = process.env.GITHUB_ACTION_PATH
|
// In development, server.ts is in the same directory as this file (config.ts)
|
||||||
? `${process.env.GITHUB_ACTION_PATH}/entry.js`
|
const serverPath = process.env.GITHUB_ACTIONS ? fromHere("mcp-server.js") : fromHere("server.ts");
|
||||||
: `${process.cwd()}/entry.js`;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[ghPullfrogMcpName]: {
|
[ghPullfrogMcpName]: {
|
||||||
command: "node",
|
command: "node",
|
||||||
args: [
|
args: [serverPath],
|
||||||
"--input-type=module",
|
|
||||||
"-e",
|
|
||||||
`import('${entryPath.replace(/'/g, "\\'")}').then(m => m.createMcpServer())`,
|
|
||||||
],
|
|
||||||
env: {
|
env: {
|
||||||
GITHUB_INSTALLATION_TOKEN: githubInstallationToken,
|
GITHUB_INSTALLATION_TOKEN: githubInstallationToken,
|
||||||
GITHUB_REPOSITORY: githubRepository,
|
GITHUB_REPOSITORY: githubRepository,
|
||||||
|
|||||||
+2
-5
@@ -4,9 +4,6 @@ import { contextualize, tool } from "./shared.ts";
|
|||||||
|
|
||||||
export const Review = type({
|
export const Review = type({
|
||||||
pull_number: type.number.describe("The pull request number to review"),
|
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
|
body: type.string
|
||||||
.describe(
|
.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."
|
"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. " +
|
"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.",
|
"Only use 'body' for a brief summary or feedback that doesn't apply to a specific location.",
|
||||||
parameters: Review,
|
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
|
// Get the PR to determine the head commit if commit_id not provided
|
||||||
const pr = await ctx.octokit.rest.pulls.get({
|
const pr = await ctx.octokit.rest.pulls.get({
|
||||||
owner: ctx.owner,
|
owner: ctx.owner,
|
||||||
@@ -58,7 +55,7 @@ export const ReviewTool = tool({
|
|||||||
owner: ctx.owner,
|
owner: ctx.owner,
|
||||||
repo: ctx.name,
|
repo: ctx.name,
|
||||||
pull_number,
|
pull_number,
|
||||||
event,
|
event: "COMMENT",
|
||||||
};
|
};
|
||||||
if (body) params.body = body;
|
if (body) params.body = body;
|
||||||
if (commit_id) {
|
if (commit_id) {
|
||||||
|
|||||||
+13
-15
@@ -8,20 +8,18 @@ import { PullRequestInfoTool } from "./prInfo.ts";
|
|||||||
import { ReviewTool } from "./review.ts";
|
import { ReviewTool } from "./review.ts";
|
||||||
import { addTools } from "./shared.ts";
|
import { addTools } from "./shared.ts";
|
||||||
|
|
||||||
export function createMcpServer(): void {
|
const server = new FastMCP({
|
||||||
const server = new FastMCP({
|
name: "gh-pullfrog",
|
||||||
name: "gh-pullfrog",
|
version: "0.0.1",
|
||||||
version: "0.0.1",
|
});
|
||||||
});
|
|
||||||
|
|
||||||
addTools(server, [
|
addTools(server, [
|
||||||
CreateCommentTool,
|
CreateCommentTool,
|
||||||
EditCommentTool,
|
EditCommentTool,
|
||||||
IssueTool,
|
IssueTool,
|
||||||
PullRequestTool,
|
PullRequestTool,
|
||||||
ReviewTool,
|
ReviewTool,
|
||||||
PullRequestInfoTool,
|
PullRequestInfoTool,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
server.start();
|
server.start();
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@pullfrog/action",
|
"name": "@pullfrog/action",
|
||||||
"version": "0.0.84",
|
"version": "0.0.88",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
[x] add modes to prompt
|
[x] add modes to prompt
|
||||||
[x] progressively update comment
|
[x] progressively update comment
|
||||||
[] don't allow rejecting prs
|
[x] don't allow rejecting prs
|
||||||
[] fix pnpm caching
|
[x] 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"
|
[] fix prompt to avoid narration like "I just read all tools from MCP server"
|
||||||
[] investigate including terminal output from bash commands as collapsed groups
|
[] investigate including terminal output from bash commands as collapsed groups
|
||||||
[] avoid exposing env
|
[] avoid exposing env
|
||||||
|
[] test initialization trade offs for pullfrog.yml
|
||||||
|
[] try to find heavy claude code user
|
||||||
|
[] investigate repo config file?
|
||||||
|
|||||||
Reference in New Issue
Block a user