import { type } from "arktype"; import { buildPullfrogFooter, stripExistingFooter } from "../utils/buildPullfrogFooter.ts"; import { log } from "../utils/cli.ts"; import { containsSecrets } from "../utils/secrets.ts"; import { $ } from "../utils/shell.ts"; import type { ToolContext } from "./server.ts"; import { execute, tool } from "./shared.ts"; export const PullRequest = type({ title: type.string.describe("the title of the pull request"), body: type.string.describe("the body content of the pull request"), base: type.string.describe("the base branch to merge into (e.g., 'main')"), }); function buildPrBodyWithFooter(ctx: ToolContext, body: string): string { const footer = buildPullfrogFooter({ triggeredBy: true, agent: { displayName: ctx.agent.displayName, url: ctx.agent.url }, workflowRun: ctx.runId ? { owner: ctx.repo.owner, repo: ctx.repo.name, runId: ctx.runId, jobId: ctx.jobId } : undefined, }); const bodyWithoutFooter = stripExistingFooter(body); return `${bodyWithoutFooter}${footer}`; } export function CreatePullRequestTool(ctx: ToolContext) { return tool({ name: "create_pull_request", description: "Create a pull request from the current branch", parameters: PullRequest, execute: execute(async ({ title, body, base }) => { const currentBranch = $("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false }); log.debug(`Current branch: ${currentBranch}`); // validate PR title and body for secrets if (containsSecrets(title) || containsSecrets(body)) { throw new Error( "PR creation blocked: secrets detected in PR title or body. " + "Please remove any sensitive information (API keys, tokens, passwords) before creating a PR." ); } // validate all changes that would be in the PR (from base to HEAD) // FORK PR NOTE: origin/ is fetched by setupGit, so this works for both fork and same-repo PRs // use two-dot (..) not three-dot (...) for reliable diffs with shallow clones const diff = $("git", ["diff", `origin/${base}..HEAD`], { log: false }); if (containsSecrets(diff)) { throw new Error( "PR creation blocked: secrets detected in changes. " + "Please remove any sensitive information (API keys, tokens, passwords) before creating a PR." ); } const bodyWithFooter = buildPrBodyWithFooter(ctx, body); const result = await ctx.octokit.rest.pulls.create({ owner: ctx.repo.owner, repo: ctx.repo.name, title: title, body: bodyWithFooter, head: currentBranch, base: base, }); return { success: true, pullRequestId: result.data.id, number: result.data.number, url: result.data.html_url, title: result.data.title, head: result.data.head.ref, base: result.data.base.ref, }; }), }); }