Add get_commit_info

This commit is contained in:
Colin McDonnell
2026-01-21 03:22:29 +00:00
committed by pullfrog[bot]
parent 22704dda35
commit 2f3c48edb6
3 changed files with 156 additions and 44 deletions
+94 -44
View File
@@ -121083,6 +121083,55 @@ function CreatePullRequestTool(ctx) {
}); });
} }
// mcp/commitInfo.ts
import { writeFileSync as writeFileSync3 } from "node:fs";
import { join as join5 } from "node:path";
var CommitInfo = type({
sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
});
function CommitInfoTool(ctx) {
return tool({
name: "get_commit_info",
description: "Retrieve commit metadata and diff via GitHub API. Use this instead of git show for reviewing commits - it works with shallow clones and shows the actual changes in the commit. Returns diffPath pointing to formatted diff file.",
parameters: CommitInfo,
execute: execute(async ({ sha }) => {
const response = await ctx.octokit.rest.repos.getCommit({
owner: ctx.repo.owner,
repo: ctx.repo.name,
ref: sha
});
const data = response.data;
const files = data.files ?? [];
const diffContent = formatFilesWithLineNumbers(files);
const tempDir = process.env.PULLFROG_TEMP_DIR;
if (!tempDir) {
throw new Error(
"PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
);
}
const diffFile = join5(tempDir, `commit-${sha.slice(0, 7)}.diff`);
writeFileSync3(diffFile, diffContent);
log.debug(`wrote commit diff to ${diffFile} (${diffContent.length} bytes)`);
return {
sha: data.sha,
message: data.commit.message,
author: data.author?.login ?? null,
committer: data.committer?.login ?? null,
date: data.commit.author?.date ?? data.commit.committer?.date ?? "",
url: data.html_url,
parents: data.parents.map((p) => p.sha),
stats: {
additions: data.stats?.additions ?? 0,
deletions: data.stats?.deletions ?? 0,
total: data.stats?.total ?? 0
},
fileCount: files.length,
diffFile
};
})
});
}
// mcp/prInfo.ts // mcp/prInfo.ts
var CLOSING_ISSUES_QUERY = ` var CLOSING_ISSUES_QUERY = `
query($owner: String!, $repo: String!, $number: Int!) { query($owner: String!, $repo: String!, $number: Int!) {
@@ -121253,8 +121302,8 @@ function CreatePullRequestReviewTool(ctx) {
} }
// mcp/reviewComments.ts // mcp/reviewComments.ts
import { writeFileSync as writeFileSync3 } from "node:fs"; import { writeFileSync as writeFileSync4 } from "node:fs";
import { join as join5 } from "node:path"; import { join as join6 } from "node:path";
var REPLY_TO_FRAGMENT = ` var REPLY_TO_FRAGMENT = `
replyTo { replyTo {
databaseId databaseId
@@ -121421,8 +121470,8 @@ function GetReviewCommentsTool(ctx) {
throw new Error("PULLFROG_TEMP_DIR not set"); throw new Error("PULLFROG_TEMP_DIR not set");
} }
const filename = approved_by ? `review-${review_id}-approved-by-${approved_by}.xml` : `review-${review_id}-comments.xml`; const filename = approved_by ? `review-${review_id}-approved-by-${approved_by}.xml` : `review-${review_id}-comments.xml`;
const commentsPath = join5(tempDir, filename); const commentsPath = join6(tempDir, filename);
writeFileSync3(commentsPath, content); writeFileSync4(commentsPath, content);
log.info(`wrote ${comments.length} comments to ${commentsPath}`); log.info(`wrote ${comments.length} comments to ${commentsPath}`);
log.box(content); log.box(content);
return { return {
@@ -121568,6 +121617,7 @@ async function startMcpHttpServer(ctx) {
CreatePullRequestTool(ctx), CreatePullRequestTool(ctx),
CreatePullRequestReviewTool(ctx), CreatePullRequestReviewTool(ctx),
PullRequestInfoTool(ctx), PullRequestInfoTool(ctx),
CommitInfoTool(ctx),
CheckoutPrTool(ctx), CheckoutPrTool(ctx),
GetReviewCommentsTool(ctx), GetReviewCommentsTool(ctx),
ListPullRequestReviewsTool(ctx), ListPullRequestReviewsTool(ctx),
@@ -121766,7 +121816,7 @@ import { spawn as spawn3 } from "child_process";
import { createInterface } from "readline"; import { createInterface } from "readline";
import * as fs2 from "fs"; import * as fs2 from "fs";
import { stat as statPromise, open } from "fs/promises"; import { stat as statPromise, open } from "fs/promises";
import { join as join6 } from "path"; import { join as join7 } from "path";
import { homedir } from "os"; import { homedir } from "os";
import { dirname, join as join22 } from "path"; import { dirname, join as join22 } from "path";
import { cwd } from "process"; import { cwd } from "process";
@@ -128468,7 +128518,7 @@ function shouldShowDebugMessage(message, filter) {
return shouldShowDebugCategories(categories, filter); return shouldShowDebugCategories(categories, filter);
} }
function getClaudeConfigHomeDir() { function getClaudeConfigHomeDir() {
return process.env.CLAUDE_CONFIG_DIR ?? join6(homedir(), ".claude"); return process.env.CLAUDE_CONFIG_DIR ?? join7(homedir(), ".claude");
} }
function isEnvTruthy(envVar) { function isEnvTruthy(envVar) {
if (!envVar) if (!envVar)
@@ -138375,7 +138425,7 @@ import { spawnSync as spawnSync2 } from "node:child_process";
import { chmodSync, createWriteStream as createWriteStream2, existsSync as existsSync5 } from "node:fs"; import { chmodSync, createWriteStream as createWriteStream2, existsSync as existsSync5 } from "node:fs";
import { mkdtemp } from "node:fs/promises"; import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { join as join7 } from "node:path"; import { join as join8 } from "node:path";
import { pipeline } from "node:stream/promises"; import { pipeline } from "node:stream/promises";
async function installFromNpmTarball(params) { async function installFromNpmTarball(params) {
let resolvedVersion = params.version; let resolvedVersion = params.version;
@@ -138399,7 +138449,7 @@ async function installFromNpmTarball(params) {
} }
log.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`); log.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
const tempDir = process.env.PULLFROG_TEMP_DIR; const tempDir = process.env.PULLFROG_TEMP_DIR;
const tarballPath = join7(tempDir, "package.tgz"); const tarballPath = join8(tempDir, "package.tgz");
const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org"; const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
let tarballUrl; let tarballUrl;
if (params.packageName.startsWith("@")) { if (params.packageName.startsWith("@")) {
@@ -138428,8 +138478,8 @@ async function installFromNpmTarball(params) {
`Failed to extract tarball: ${extractResult.stderr || extractResult.stdout || "Unknown error"}` `Failed to extract tarball: ${extractResult.stderr || extractResult.stdout || "Unknown error"}`
); );
} }
const extractedDir = join7(tempDir, "package"); const extractedDir = join8(tempDir, "package");
const cliPath = join7(extractedDir, params.executablePath); const cliPath = join8(extractedDir, params.executablePath);
if (!existsSync5(cliPath)) { if (!existsSync5(cliPath)) {
throw new Error(`Executable not found in extracted package at ${cliPath}`); throw new Error(`Executable not found in extracted package at ${cliPath}`);
} }
@@ -138491,10 +138541,10 @@ async function installFromGithub(params) {
const assetUrl = asset.browser_download_url; const assetUrl = asset.browser_download_url;
log.debug(`\xBB downloading asset from ${assetUrl}...`); log.debug(`\xBB downloading asset from ${assetUrl}...`);
const tempDirPrefix = `${params.owner}-${params.repo}-github-`; const tempDirPrefix = `${params.owner}-${params.repo}-github-`;
const tempDirPath = await mkdtemp(join7(tmpdir(), tempDirPrefix)); const tempDirPath = await mkdtemp(join8(tmpdir(), tempDirPrefix));
const urlPath = new URL(assetUrl).pathname; const urlPath = new URL(assetUrl).pathname;
const fileName3 = urlPath.split("/").pop() || "asset"; const fileName3 = urlPath.split("/").pop() || "asset";
const downloadPath = join7(tempDirPath, fileName3); const downloadPath = join8(tempDirPath, fileName3);
const assetResponse = await fetchWithRetry(assetUrl, headers, "Failed to download asset"); const assetResponse = await fetchWithRetry(assetUrl, headers, "Failed to download asset");
if (!assetResponse.body) throw new Error("Response body is null"); if (!assetResponse.body) throw new Error("Response body is null");
const fileStream = createWriteStream2(downloadPath); const fileStream = createWriteStream2(downloadPath);
@@ -138502,7 +138552,7 @@ async function installFromGithub(params) {
log.debug(`\xBB downloaded asset to ${downloadPath}`); log.debug(`\xBB downloaded asset to ${downloadPath}`);
let cliPath; let cliPath;
if (params.executablePath) { if (params.executablePath) {
cliPath = join7(tempDirPath, params.executablePath); cliPath = join8(tempDirPath, params.executablePath);
} else { } else {
cliPath = downloadPath; cliPath = downloadPath;
} }
@@ -138516,7 +138566,7 @@ async function installFromGithub(params) {
async function installFromCurl(params) { async function installFromCurl(params) {
log.info(`\xBB installing ${params.executableName}...`); log.info(`\xBB installing ${params.executableName}...`);
const tempDir = process.env.PULLFROG_TEMP_DIR; const tempDir = process.env.PULLFROG_TEMP_DIR;
const installScriptPath = join7(tempDir, "install.sh"); const installScriptPath = join8(tempDir, "install.sh");
log.debug(`\xBB downloading install script from ${params.installUrl}...`); log.debug(`\xBB downloading install script from ${params.installUrl}...`);
const installScriptResponse = await fetch(params.installUrl); const installScriptResponse = await fetch(params.installUrl);
if (!installScriptResponse.ok) { if (!installScriptResponse.ok) {
@@ -138535,7 +138585,7 @@ async function installFromCurl(params) {
// ensuring a fresh install for each run // ensuring a fresh install for each run
HOME: tempDir, HOME: tempDir,
// XDG_CONFIG_HOME must match HOME so CLI tools find config in the right place // XDG_CONFIG_HOME must match HOME so CLI tools find config in the right place
XDG_CONFIG_HOME: join7(tempDir, ".config"), XDG_CONFIG_HOME: join8(tempDir, ".config"),
SHELL: process.env.SHELL, SHELL: process.env.SHELL,
USER: process.env.USER USER: process.env.USER
}, },
@@ -138548,7 +138598,7 @@ async function installFromCurl(params) {
`Failed to install ${params.executableName}. Install script exited with code ${installResult.status}. Output: ${errorOutput}` `Failed to install ${params.executableName}. Install script exited with code ${installResult.status}. Output: ${errorOutput}`
); );
} }
const cliPath = join7(tempDir, ".local", "bin", params.executableName); const cliPath = join8(tempDir, ".local", "bin", params.executableName);
if (!existsSync5(cliPath)) { if (!existsSync5(cliPath)) {
throw new Error(`Executable not found at ${cliPath}`); throw new Error(`Executable not found at ${cliPath}`);
} }
@@ -138722,8 +138772,8 @@ var messageHandlers = {
}; };
// agents/codex.ts // agents/codex.ts
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "node:fs"; import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "node:fs";
import { join as join8 } from "node:path"; import { join as join9 } from "node:path";
// node_modules/.pnpm/@openai+codex-sdk@0.80.0/node_modules/@openai/codex-sdk/dist/index.js // node_modules/.pnpm/@openai+codex-sdk@0.80.0/node_modules/@openai/codex-sdk/dist/index.js
import { promises as fs3 } from "fs"; import { promises as fs3 } from "fs";
@@ -139083,9 +139133,9 @@ var codexReasoningEffort = {
max: "high" max: "high"
}; };
function writeCodexConfig(ctx) { function writeCodexConfig(ctx) {
const codexDir = join8(ctx.tmpdir, ".codex"); const codexDir = join9(ctx.tmpdir, ".codex");
mkdirSync3(codexDir, { recursive: true }); mkdirSync3(codexDir, { recursive: true });
const configPath = join8(codexDir, "config.toml"); const configPath = join9(codexDir, "config.toml");
log.info(`\xBB adding MCP server '${ghPullfrogMcpName}' at ${ctx.mcpServerUrl}`); log.info(`\xBB adding MCP server '${ghPullfrogMcpName}' at ${ctx.mcpServerUrl}`);
const mcpServerSections = [`[mcp_servers.${ghPullfrogMcpName}] const mcpServerSections = [`[mcp_servers.${ghPullfrogMcpName}]
url = "${ctx.mcpServerUrl}"`]; url = "${ctx.mcpServerUrl}"`];
@@ -139097,7 +139147,7 @@ url = "${ctx.mcpServerUrl}"`];
} }
const featuresSection = features.length > 0 ? `[features] const featuresSection = features.length > 0 ? `[features]
${features.join("\n")}` : ""; ${features.join("\n")}` : "";
writeFileSync4( writeFileSync5(
configPath, configPath,
`# written by pullfrog `# written by pullfrog
${featuresSection} ${featuresSection}
@@ -139120,7 +139170,7 @@ var codex = agent({
install: installCodex, install: installCodex,
run: async (ctx) => { run: async (ctx) => {
const cliPath = await installCodex(); const cliPath = await installCodex();
const configDir = join8(ctx.tmpdir, ".config", "codex"); const configDir = join9(ctx.tmpdir, ".config", "codex");
mkdirSync3(configDir, { recursive: true }); mkdirSync3(configDir, { recursive: true });
const codexDir = writeCodexConfig(ctx); const codexDir = writeCodexConfig(ctx);
process.env.HOME = ctx.tmpdir; process.env.HOME = ctx.tmpdir;
@@ -139265,9 +139315,9 @@ var messageHandlers2 = {
// agents/cursor.ts // agents/cursor.ts
import { spawn as spawn5 } from "node:child_process"; import { spawn as spawn5 } from "node:child_process";
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "node:fs"; import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "node:fs";
import { homedir as homedir2 } from "node:os"; import { homedir as homedir2 } from "node:os";
import { join as join9 } from "node:path"; import { join as join10 } from "node:path";
var cursorEffortModels = { var cursorEffortModels = {
mini: null, mini: null,
// use default (auto) // use default (auto)
@@ -139292,7 +139342,7 @@ var cursor = agent({
const cliPath = await installCursor(); const cliPath = await installCursor();
configureCursorMcpServers(ctx); configureCursorMcpServers(ctx);
configureCursorTools(ctx); configureCursorTools(ctx);
const projectCliConfigPath = join9(process.cwd(), ".cursor", "cli.json"); const projectCliConfigPath = join10(process.cwd(), ".cursor", "cli.json");
let modelOverride = null; let modelOverride = null;
if (existsSync6(projectCliConfigPath)) { if (existsSync6(projectCliConfigPath)) {
try { try {
@@ -139457,21 +139507,21 @@ var cursor = agent({
} }
}); });
function getCursorConfigDir() { function getCursorConfigDir() {
return join9(homedir2(), ".cursor"); return join10(homedir2(), ".cursor");
} }
function configureCursorMcpServers(ctx) { function configureCursorMcpServers(ctx) {
const cursorConfigDir = getCursorConfigDir(); const cursorConfigDir = getCursorConfigDir();
const mcpConfigPath = join9(cursorConfigDir, "mcp.json"); const mcpConfigPath = join10(cursorConfigDir, "mcp.json");
mkdirSync4(cursorConfigDir, { recursive: true }); mkdirSync4(cursorConfigDir, { recursive: true });
const mcpServers = { const mcpServers = {
[ghPullfrogMcpName]: { type: "http", url: ctx.mcpServerUrl } [ghPullfrogMcpName]: { type: "http", url: ctx.mcpServerUrl }
}; };
writeFileSync5(mcpConfigPath, JSON.stringify({ mcpServers }, null, 2), "utf-8"); writeFileSync6(mcpConfigPath, JSON.stringify({ mcpServers }, null, 2), "utf-8");
log.info(`\xBB MCP config written to ${mcpConfigPath}`); log.info(`\xBB MCP config written to ${mcpConfigPath}`);
} }
function configureCursorTools(ctx) { function configureCursorTools(ctx) {
const cursorConfigDir = getCursorConfigDir(); const cursorConfigDir = getCursorConfigDir();
const cliConfigPath = join9(cursorConfigDir, "cli-config.json"); const cliConfigPath = join10(cursorConfigDir, "cli-config.json");
mkdirSync4(cursorConfigDir, { recursive: true }); mkdirSync4(cursorConfigDir, { recursive: true });
const bash = ctx.payload.bash; const bash = ctx.payload.bash;
const deny = []; const deny = [];
@@ -139490,14 +139540,14 @@ function configureCursorTools(ctx) {
networkAccess: "allowlist" networkAccess: "allowlist"
}; };
} }
writeFileSync5(cliConfigPath, JSON.stringify(config4, null, 2), "utf-8"); writeFileSync6(cliConfigPath, JSON.stringify(config4, null, 2), "utf-8");
log.info(`\xBB CLI config written to ${cliConfigPath}`, JSON.stringify(config4, null, 2)); log.info(`\xBB CLI config written to ${cliConfigPath}`, JSON.stringify(config4, null, 2));
} }
// agents/gemini.ts // agents/gemini.ts
import { mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "node:fs"; import { mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync7 } from "node:fs";
import { homedir as homedir3 } from "node:os"; import { homedir as homedir3 } from "node:os";
import { join as join10 } from "node:path"; import { join as join11 } from "node:path";
var geminiEffortConfig = { var geminiEffortConfig = {
// https://ai.google.dev/gemini-api/docs/models // https://ai.google.dev/gemini-api/docs/models
// the docs mention needing to enable preview features for these models but if you // the docs mention needing to enable preview features for these models but if you
@@ -139668,8 +139718,8 @@ function configureGeminiSettings(ctx) {
const { model, thinkingLevel } = geminiEffortConfig[ctx.payload.effort]; const { model, thinkingLevel } = geminiEffortConfig[ctx.payload.effort];
log.info(`\xBB using model: ${model}, thinkingLevel: ${thinkingLevel}`); log.info(`\xBB using model: ${model}, thinkingLevel: ${thinkingLevel}`);
const realHome = homedir3(); const realHome = homedir3();
const geminiConfigDir = join10(realHome, ".gemini"); const geminiConfigDir = join11(realHome, ".gemini");
const settingsPath = join10(geminiConfigDir, "settings.json"); const settingsPath = join11(geminiConfigDir, "settings.json");
mkdirSync5(geminiConfigDir, { recursive: true }); mkdirSync5(geminiConfigDir, { recursive: true });
let existingSettings = {}; let existingSettings = {};
try { try {
@@ -139706,7 +139756,7 @@ function configureGeminiSettings(ctx) {
// v0.3.0+ nested format // v0.3.0+ nested format
...exclude.length > 0 && { tools: { exclude } } ...exclude.length > 0 && { tools: { exclude } }
}; };
writeFileSync6(settingsPath, JSON.stringify(newSettings, null, 2), "utf-8"); writeFileSync7(settingsPath, JSON.stringify(newSettings, null, 2), "utf-8");
log.info(`\xBB Gemini settings written to ${settingsPath}`); log.info(`\xBB Gemini settings written to ${settingsPath}`);
if (exclude.length > 0) { if (exclude.length > 0) {
log.info(`\xBB excluded tools: ${exclude.join(", ")}`); log.info(`\xBB excluded tools: ${exclude.join(", ")}`);
@@ -139715,8 +139765,8 @@ function configureGeminiSettings(ctx) {
} }
// agents/opencode.ts // agents/opencode.ts
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "node:fs"; import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync8 } from "node:fs";
import { join as join11 } from "node:path"; import { join as join12 } from "node:path";
async function installOpencode() { async function installOpencode() {
return await installFromNpmTarball({ return await installFromNpmTarball({
packageName: "opencode-ai", packageName: "opencode-ai",
@@ -139731,7 +139781,7 @@ var opencode = agent({
run: async (ctx) => { run: async (ctx) => {
const cliPath = await installOpencode(); const cliPath = await installOpencode();
const tempHome = ctx.tmpdir; const tempHome = ctx.tmpdir;
const configDir = join11(tempHome, ".config", "opencode"); const configDir = join12(tempHome, ".config", "opencode");
mkdirSync6(configDir, { recursive: true }); mkdirSync6(configDir, { recursive: true });
configureOpenCode(ctx); configureOpenCode(ctx);
const args3 = ["run", ctx.instructions.full, "--format", "json"]; const args3 = ["run", ctx.instructions.full, "--format", "json"];
@@ -139739,7 +139789,7 @@ var opencode = agent({
const env3 = { const env3 = {
...process.env, ...process.env,
HOME: tempHome, HOME: tempHome,
XDG_CONFIG_HOME: join11(tempHome, ".config"), XDG_CONFIG_HOME: join12(tempHome, ".config"),
// set GOOGLE_GENERATIVE_AI_API_KEY alias for Google provider compatibility (if not already set) // set GOOGLE_GENERATIVE_AI_API_KEY alias for Google provider compatibility (if not already set)
GOOGLE_GENERATIVE_AI_API_KEY: process.env.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GEMINI_API_KEY GOOGLE_GENERATIVE_AI_API_KEY: process.env.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GEMINI_API_KEY
}; };
@@ -139842,9 +139892,9 @@ var opencode = agent({
} }
}); });
function configureOpenCode(ctx) { function configureOpenCode(ctx) {
const configDir = join11(ctx.tmpdir, ".config", "opencode"); const configDir = join12(ctx.tmpdir, ".config", "opencode");
mkdirSync6(configDir, { recursive: true }); mkdirSync6(configDir, { recursive: true });
const configPath = join11(configDir, "opencode.json"); const configPath = join12(configDir, "opencode.json");
const opencodeMcpServers = { const opencodeMcpServers = {
[ghPullfrogMcpName]: { type: "remote", url: ctx.mcpServerUrl } [ghPullfrogMcpName]: { type: "remote", url: ctx.mcpServerUrl }
}; };
@@ -139862,7 +139912,7 @@ function configureOpenCode(ctx) {
}; };
const configJson = JSON.stringify(config4, null, 2); const configJson = JSON.stringify(config4, null, 2);
try { try {
writeFileSync7(configPath, configJson, "utf-8"); writeFileSync8(configPath, configJson, "utf-8");
} catch (error50) { } catch (error50) {
log.error( log.error(
`failed to write OpenCode config to ${configPath}: ${error50 instanceof Error ? error50.message : String(error50)}` `failed to write OpenCode config to ${configPath}: ${error50 instanceof Error ? error50.message : String(error50)}`
@@ -140573,9 +140623,9 @@ async function handleAgentResult(result) {
import { execSync as execSync2 } from "node:child_process"; import { execSync as execSync2 } from "node:child_process";
import { mkdtempSync } from "node:fs"; import { mkdtempSync } from "node:fs";
import { tmpdir as tmpdir2 } from "node:os"; import { tmpdir as tmpdir2 } from "node:os";
import { join as join12 } from "node:path"; import { join as join13 } from "node:path";
function createTempDirectory() { function createTempDirectory() {
const sharedTempDir = mkdtempSync(join12(tmpdir2(), "pullfrog-")); const sharedTempDir = mkdtempSync(join13(tmpdir2(), "pullfrog-"));
process.env.PULLFROG_TEMP_DIR = sharedTempDir; process.env.PULLFROG_TEMP_DIR = sharedTempDir;
log.info(`\xBB created temp dir at ${sharedTempDir}`); log.info(`\xBB created temp dir at ${sharedTempDir}`);
return sharedTempDir; return sharedTempDir;
+60
View File
@@ -0,0 +1,60 @@
import { writeFileSync } from "node:fs";
import { join } from "node:path";
import { type } from "arktype";
import { log } from "../utils/cli.ts";
import { formatFilesWithLineNumbers } from "./checkout.ts";
import type { ToolContext } from "./server.ts";
import { execute, tool } from "./shared.ts";
export const CommitInfo = type({
sha: type.string.describe("the commit SHA (full or abbreviated) to fetch"),
});
export function CommitInfoTool(ctx: ToolContext) {
return tool({
name: "get_commit_info",
description:
"Retrieve commit metadata and diff via GitHub API. Use this instead of git show for reviewing commits - " +
"it works with shallow clones and shows the actual changes in the commit. Returns diffPath pointing to formatted diff file.",
parameters: CommitInfo,
execute: execute(async ({ sha }) => {
const response = await ctx.octokit.rest.repos.getCommit({
owner: ctx.repo.owner,
repo: ctx.repo.name,
ref: sha,
});
const data = response.data;
const files = data.files ?? [];
// format diff with line numbers and write to file
const diffContent = formatFilesWithLineNumbers(files);
const tempDir = process.env.PULLFROG_TEMP_DIR;
if (!tempDir) {
throw new Error(
"PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
);
}
const diffFile = join(tempDir, `commit-${sha.slice(0, 7)}.diff`);
writeFileSync(diffFile, diffContent);
log.debug(`wrote commit diff to ${diffFile} (${diffContent.length} bytes)`);
return {
sha: data.sha,
message: data.commit.message,
author: data.author?.login ?? null,
committer: data.committer?.login ?? null,
date: data.commit.author?.date ?? data.commit.committer?.date ?? "",
url: data.html_url,
parents: data.parents.map((p) => p.sha),
stats: {
additions: data.stats?.additions ?? 0,
deletions: data.stats?.deletions ?? 0,
total: data.stats?.total ?? 0,
},
fileCount: files.length,
diffFile,
};
}),
});
}
+2
View File
@@ -89,6 +89,7 @@ import { GetIssueEventsTool } from "./issueEvents.ts";
import { IssueInfoTool } from "./issueInfo.ts"; import { IssueInfoTool } from "./issueInfo.ts";
import { AddLabelsTool } from "./labels.ts"; import { AddLabelsTool } from "./labels.ts";
import { CreatePullRequestTool } from "./pr.ts"; import { CreatePullRequestTool } from "./pr.ts";
import { CommitInfoTool } from "./commitInfo.ts";
import { PullRequestInfoTool } from "./prInfo.ts"; import { PullRequestInfoTool } from "./prInfo.ts";
import { CreatePullRequestReviewTool } from "./review.ts"; import { CreatePullRequestReviewTool } from "./review.ts";
import { GetReviewCommentsTool, ListPullRequestReviewsTool } from "./reviewComments.ts"; import { GetReviewCommentsTool, ListPullRequestReviewsTool } from "./reviewComments.ts";
@@ -171,6 +172,7 @@ export async function startMcpHttpServer(
CreatePullRequestTool(ctx), CreatePullRequestTool(ctx),
CreatePullRequestReviewTool(ctx), CreatePullRequestReviewTool(ctx),
PullRequestInfoTool(ctx), PullRequestInfoTool(ctx),
CommitInfoTool(ctx),
CheckoutPrTool(ctx), CheckoutPrTool(ctx),
GetReviewCommentsTool(ctx), GetReviewCommentsTool(ctx),
ListPullRequestReviewsTool(ctx), ListPullRequestReviewsTool(ctx),