diff --git a/action.yml b/action.yml index e96fc32..1716baf 100644 --- a/action.yml +++ b/action.yml @@ -12,6 +12,9 @@ inputs: assignee_trigger: description: "The assignee username that triggers the action (e.g. @claude)" required: false + base_branch: + description: "The branch to use as the base/source when creating new branches (defaults to repository default branch)" + required: false # Claude Code configuration model: @@ -85,6 +88,7 @@ runs: env: TRIGGER_PHRASE: ${{ inputs.trigger_phrase }} ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }} + BASE_BRANCH: ${{ inputs.base_branch }} ALLOWED_TOOLS: ${{ inputs.allowed_tools }} CUSTOM_INSTRUCTIONS: ${{ inputs.custom_instructions }} DIRECT_PROMPT: ${{ inputs.direct_prompt }} @@ -146,7 +150,7 @@ runs: TRIGGER_COMMENT_ID: ${{ github.event.comment.id }} CLAUDE_BRANCH: ${{ steps.prepare.outputs.CLAUDE_BRANCH }} IS_PR: ${{ github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' }} - DEFAULT_BRANCH: ${{ steps.prepare.outputs.DEFAULT_BRANCH }} + BASE_BRANCH: ${{ steps.prepare.outputs.BASE_BRANCH }} CLAUDE_SUCCESS: ${{ steps.claude-code.outputs.conclusion == 'success' }} OUTPUT_FILE: ${{ steps.claude-code.outputs.execution_file || '' }} TRIGGER_USERNAME: ${{ github.event.comment.user.login || github.event.issue.user.login || github.event.pull_request.user.login || github.event.sender.login || github.triggering_actor || github.actor || '' }} diff --git a/src/create-prompt/index.ts b/src/create-prompt/index.ts index 9a91110..2d8db0e 100644 --- a/src/create-prompt/index.ts +++ b/src/create-prompt/index.ts @@ -86,7 +86,7 @@ export function buildDisallowedToolsString( export function prepareContext( context: ParsedGitHubContext, claudeCommentId: string, - defaultBranch?: string, + baseBranch?: string, claudeBranch?: string, ): PreparedContext { const repository = context.repository.full_name; @@ -164,7 +164,7 @@ export function prepareContext( ...(commentId && { commentId }), commentBody, ...(claudeBranch && { claudeBranch }), - ...(defaultBranch && { defaultBranch }), + ...(baseBranch && { baseBranch }), }; break; @@ -186,7 +186,7 @@ export function prepareContext( prNumber, commentBody, ...(claudeBranch && { claudeBranch }), - ...(defaultBranch && { defaultBranch }), + ...(baseBranch && { baseBranch }), }; break; @@ -211,13 +211,13 @@ export function prepareContext( prNumber, commentBody, ...(claudeBranch && { claudeBranch }), - ...(defaultBranch && { defaultBranch }), + ...(baseBranch && { baseBranch }), }; break; } else if (!claudeBranch) { throw new Error("CLAUDE_BRANCH is required for issue_comment event"); - } else if (!defaultBranch) { - throw new Error("DEFAULT_BRANCH is required for issue_comment event"); + } else if (!baseBranch) { + throw new Error("BASE_BRANCH is required for issue_comment event"); } else if (!issueNumber) { throw new Error( "ISSUE_NUMBER is required for issue_comment event for issues", @@ -229,7 +229,7 @@ export function prepareContext( commentId, isPR: false, claudeBranch: claudeBranch, - defaultBranch, + baseBranch, issueNumber, commentBody, }; @@ -245,8 +245,8 @@ export function prepareContext( if (isPR) { throw new Error("IS_PR must be false for issues event"); } - if (!defaultBranch) { - throw new Error("DEFAULT_BRANCH is required for issues event"); + if (!baseBranch) { + throw new Error("BASE_BRANCH is required for issues event"); } if (!claudeBranch) { throw new Error("CLAUDE_BRANCH is required for issues event"); @@ -263,7 +263,7 @@ export function prepareContext( eventAction: "assigned", isPR: false, issueNumber, - defaultBranch, + baseBranch, claudeBranch, assigneeTrigger, }; @@ -273,7 +273,7 @@ export function prepareContext( eventAction: "opened", isPR: false, issueNumber, - defaultBranch, + baseBranch, claudeBranch, }; } else { @@ -294,7 +294,7 @@ export function prepareContext( isPR: true, prNumber, ...(claudeBranch && { claudeBranch }), - ...(defaultBranch && { defaultBranch }), + ...(baseBranch && { baseBranch }), }; break; @@ -541,13 +541,13 @@ ${context.directPrompt ? ` - DIRECT INSTRUCTION: A direct instruction was prov ${ eventData.claudeBranch ? `- Provide a URL to create a PR manually in this format: - [Create a PR](${GITHUB_SERVER_URL}/${context.repository}/compare/${eventData.defaultBranch}...?quick_pull=1&title=&body=) + [Create a PR](${GITHUB_SERVER_URL}/${context.repository}/compare/${eventData.baseBranch}...?quick_pull=1&title=&body=) - IMPORTANT: Use THREE dots (...) between branch names, not two (..) Example: ${GITHUB_SERVER_URL}/${context.repository}/compare/main...feature-branch (correct) NOT: ${GITHUB_SERVER_URL}/${context.repository}/compare/main..feature-branch (incorrect) - IMPORTANT: Ensure all URL parameters are properly encoded - spaces should be encoded as %20, not left as spaces Example: Instead of "fix: update welcome message", use "fix%3A%20update%20welcome%20message" - - The target-branch should be '${eventData.defaultBranch}'. + - The target-branch should be '${eventData.baseBranch}'. - The branch-name is the current branch: ${eventData.claudeBranch} - The body should include: - A clear description of the changes @@ -632,7 +632,7 @@ f. If you are unable to complete certain steps, such as running a linter or test export async function createPrompt( claudeCommentId: number, - defaultBranch: string | undefined, + baseBranch: string | undefined, claudeBranch: string | undefined, githubData: FetchDataResult, context: ParsedGitHubContext, @@ -641,7 +641,7 @@ export async function createPrompt( const preparedContext = prepareContext( context, claudeCommentId.toString(), - defaultBranch, + baseBranch, claudeBranch, ); diff --git a/src/create-prompt/types.ts b/src/create-prompt/types.ts index eb5b13a..00bba5e 100644 --- a/src/create-prompt/types.ts +++ b/src/create-prompt/types.ts @@ -16,7 +16,7 @@ type PullRequestReviewCommentEvent = { commentId?: string; // May be present for review comments commentBody: string; claudeBranch?: string; - defaultBranch?: string; + baseBranch?: string; }; type PullRequestReviewEvent = { @@ -25,7 +25,7 @@ type PullRequestReviewEvent = { prNumber: string; commentBody: string; claudeBranch?: string; - defaultBranch?: string; + baseBranch?: string; }; type IssueCommentEvent = { @@ -33,7 +33,7 @@ type IssueCommentEvent = { commentId: string; issueNumber: string; isPR: false; - defaultBranch: string; + baseBranch: string; claudeBranch: string; commentBody: string; }; @@ -46,7 +46,7 @@ type PullRequestCommentEvent = { isPR: true; commentBody: string; claudeBranch?: string; - defaultBranch?: string; + baseBranch?: string; }; type IssueOpenedEvent = { @@ -54,7 +54,7 @@ type IssueOpenedEvent = { eventAction: "opened"; isPR: false; issueNumber: string; - defaultBranch: string; + baseBranch: string; claudeBranch: string; }; @@ -63,7 +63,7 @@ type IssueAssignedEvent = { eventAction: "assigned"; isPR: false; issueNumber: string; - defaultBranch: string; + baseBranch: string; claudeBranch: string; assigneeTrigger: string; }; @@ -74,7 +74,7 @@ type PullRequestEvent = { isPR: true; prNumber: string; claudeBranch?: string; - defaultBranch?: string; + baseBranch?: string; }; // Union type for all possible event types diff --git a/src/entrypoints/prepare.ts b/src/entrypoints/prepare.ts index 1470798..c3e0b38 100644 --- a/src/entrypoints/prepare.ts +++ b/src/entrypoints/prepare.ts @@ -77,7 +77,7 @@ async function run() { // Step 10: Create prompt file await createPrompt( commentId, - branchInfo.defaultBranch, + branchInfo.baseBranch, branchInfo.claudeBranch, githubData, context, diff --git a/src/entrypoints/update-comment-link.ts b/src/entrypoints/update-comment-link.ts index d74bcd5..40c20ad 100644 --- a/src/entrypoints/update-comment-link.ts +++ b/src/entrypoints/update-comment-link.ts @@ -18,7 +18,7 @@ async function run() { const commentId = parseInt(process.env.CLAUDE_COMMENT_ID!); const githubToken = process.env.GITHUB_TOKEN!; const claudeBranch = process.env.CLAUDE_BRANCH; - const defaultBranch = process.env.DEFAULT_BRANCH || "main"; + const baseBranch = process.env.BASE_BRANCH || "main"; const triggerUsername = process.env.TRIGGER_USERNAME; const context = parseGitHubContext(); @@ -92,7 +92,7 @@ async function run() { owner, repo, claudeBranch, - defaultBranch, + baseBranch, ); // Check if we need to add PR URL when we have a new branch @@ -102,7 +102,7 @@ async function run() { // Check if comment already contains a PR URL const serverUrlPattern = serverUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const prUrlPattern = new RegExp( - `${serverUrlPattern}\\/.+\\/compare\\/${defaultBranch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.\\.\\.`, + `${serverUrlPattern}\\/.+\\/compare\\/${baseBranch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.\\.\\.`, ); const containsPRUrl = currentBody.match(prUrlPattern); @@ -113,7 +113,7 @@ async function run() { await octokit.rest.repos.compareCommitsWithBasehead({ owner, repo, - basehead: `${defaultBranch}...${claudeBranch}`, + basehead: `${baseBranch}...${claudeBranch}`, }); // If there are changes (commits or file changes), add the PR URL @@ -128,7 +128,7 @@ async function run() { const prBody = encodeURIComponent( `This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`, ); - const prUrl = `${serverUrl}/${owner}/${repo}/compare/${defaultBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`; + const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`; prLink = `\n[Create a PR](${prUrl})`; } } catch (error) { diff --git a/src/github/context.ts b/src/github/context.ts index b8abf71..0fb7f65 100644 --- a/src/github/context.ts +++ b/src/github/context.ts @@ -32,6 +32,7 @@ export type ParsedGitHubContext = { disallowedTools: string; customInstructions: string; directPrompt: string; + baseBranch?: string; }; }; @@ -55,6 +56,7 @@ export function parseGitHubContext(): ParsedGitHubContext { disallowedTools: process.env.DISALLOWED_TOOLS ?? "", customInstructions: process.env.CUSTOM_INSTRUCTIONS ?? "", directPrompt: process.env.DIRECT_PROMPT ?? "", + baseBranch: process.env.BASE_BRANCH, }, }; diff --git a/src/github/operations/branch-cleanup.ts b/src/github/operations/branch-cleanup.ts index 9c1334d..662a474 100644 --- a/src/github/operations/branch-cleanup.ts +++ b/src/github/operations/branch-cleanup.ts @@ -6,7 +6,7 @@ export async function checkAndDeleteEmptyBranch( owner: string, repo: string, claudeBranch: string | undefined, - defaultBranch: string, + baseBranch: string, ): Promise<{ shouldDeleteBranch: boolean; branchLink: string }> { let branchLink = ""; let shouldDeleteBranch = false; @@ -18,7 +18,7 @@ export async function checkAndDeleteEmptyBranch( await octokit.rest.repos.compareCommitsWithBasehead({ owner, repo, - basehead: `${defaultBranch}...${claudeBranch}`, + basehead: `${baseBranch}...${claudeBranch}`, }); // If there are no commits, mark branch for deletion diff --git a/src/github/operations/branch.ts b/src/github/operations/branch.ts index 0f405cb..3379648 100644 --- a/src/github/operations/branch.ts +++ b/src/github/operations/branch.ts @@ -14,7 +14,7 @@ import type { Octokits } from "../api/client"; import type { FetchDataResult } from "../data/fetcher"; export type BranchInfo = { - defaultBranch: string; + baseBranch: string; claudeBranch?: string; currentBranch: string; }; @@ -26,15 +26,9 @@ export async function setupBranch( ): Promise { const { owner, repo } = context.repository; const entityNumber = context.entityNumber; + const { baseBranch } = context.inputs; const isPR = context.isPR; - // Get the default branch first - const repoResponse = await octokits.rest.repos.get({ - owner, - repo, - }); - const defaultBranch = repoResponse.data.default_branch; - if (isPR) { const prData = githubData.contextData as GitHubPullRequest; const prState = prData.state; @@ -42,7 +36,7 @@ export async function setupBranch( // Check if PR is closed or merged if (prState === "CLOSED" || prState === "MERGED") { console.log( - `PR #${entityNumber} is ${prState}, creating new branch from default...`, + `PR #${entityNumber} is ${prState}, creating new branch from source...`, ); // Fall through to create a new branch like we do for issues } else { @@ -58,17 +52,36 @@ export async function setupBranch( console.log(`Successfully checked out PR branch for PR #${entityNumber}`); - // For open PRs, return branch info + // For open PRs, we need to get the base branch of the PR + const baseBranch = prData.baseRefName; + return { - defaultBranch, + baseBranch, currentBranch: branchName, }; } } + // Determine source branch - use baseBranch if provided, otherwise fetch default + let sourceBranch: string; + + if (baseBranch) { + // Use provided base branch for source + sourceBranch = baseBranch; + } else { + // No base branch provided, fetch the default branch to use as source + const repoResponse = await octokits.rest.repos.get({ + owner, + repo, + }); + sourceBranch = repoResponse.data.default_branch; + } + // Creating a new branch for either an issue or closed/merged PR const entityType = isPR ? "pr" : "issue"; - console.log(`Creating new branch for ${entityType} #${entityNumber}...`); + console.log( + `Creating new branch for ${entityType} #${entityNumber} from source branch: ${sourceBranch}...`, + ); const timestamp = new Date() .toISOString() @@ -80,14 +93,14 @@ export async function setupBranch( const newBranch = `claude/${entityType}-${entityNumber}-${timestamp}`; try { - // Get the SHA of the default branch - const defaultBranchRef = await octokits.rest.git.getRef({ + // Get the SHA of the source branch + const sourceBranchRef = await octokits.rest.git.getRef({ owner, repo, - ref: `heads/${defaultBranch}`, + ref: `heads/${sourceBranch}`, }); - const currentSHA = defaultBranchRef.data.object.sha; + const currentSHA = sourceBranchRef.data.object.sha; console.log(`Current SHA: ${currentSHA}`); @@ -109,9 +122,9 @@ export async function setupBranch( // Set outputs for GitHub Actions core.setOutput("CLAUDE_BRANCH", newBranch); - core.setOutput("DEFAULT_BRANCH", defaultBranch); + core.setOutput("BASE_BRANCH", sourceBranch); return { - defaultBranch, + baseBranch: sourceBranch, claudeBranch: newBranch, currentBranch: newBranch, }; diff --git a/test/create-prompt.test.ts b/test/create-prompt.test.ts index 3eb493d..94064b6 100644 --- a/test/create-prompt.test.ts +++ b/test/create-prompt.test.ts @@ -127,7 +127,7 @@ describe("generatePrompt", () => { eventName: "issue_comment", commentId: "67890", isPR: false, - defaultBranch: "main", + baseBranch: "main", claudeBranch: "claude/issue-67890-20240101_120000", issueNumber: "67890", commentBody: "@claude please fix this", @@ -183,7 +183,7 @@ describe("generatePrompt", () => { eventAction: "opened", isPR: false, issueNumber: "789", - defaultBranch: "main", + baseBranch: "main", claudeBranch: "claude/issue-789-20240101_120000", }, }; @@ -210,7 +210,7 @@ describe("generatePrompt", () => { eventAction: "assigned", isPR: false, issueNumber: "999", - defaultBranch: "develop", + baseBranch: "develop", claudeBranch: "claude/issue-999-20240101_120000", assigneeTrigger: "claude-bot", }, @@ -238,7 +238,7 @@ describe("generatePrompt", () => { eventAction: "opened", isPR: false, issueNumber: "789", - defaultBranch: "main", + baseBranch: "main", claudeBranch: "claude/issue-789-20240101_120000", }, }; @@ -285,7 +285,7 @@ describe("generatePrompt", () => { commentId: "67890", isPR: false, issueNumber: "123", - defaultBranch: "main", + baseBranch: "main", claudeBranch: "claude/issue-67890-20240101_120000", commentBody: "@claude please fix this", }, @@ -307,7 +307,7 @@ describe("generatePrompt", () => { commentId: "67890", isPR: false, issueNumber: "123", - defaultBranch: "main", + baseBranch: "main", claudeBranch: "claude/issue-67890-20240101_120000", commentBody: "@claude please fix this", }, @@ -362,7 +362,7 @@ describe("generatePrompt", () => { eventAction: "opened", isPR: false, issueNumber: "789", - defaultBranch: "main", + baseBranch: "main", claudeBranch: "claude/issue-789-20240101_120000", }, }; @@ -400,7 +400,7 @@ describe("generatePrompt", () => { commentId: "67890", isPR: false, issueNumber: "123", - defaultBranch: "main", + baseBranch: "main", claudeBranch: "claude/issue-123-20240101_120000", commentBody: "@claude please fix this", }, @@ -432,7 +432,7 @@ describe("generatePrompt", () => { prNumber: "456", commentBody: "@claude please fix this", claudeBranch: "claude/pr-456-20240101_120000", - defaultBranch: "main", + baseBranch: "main", }, }; @@ -470,7 +470,7 @@ describe("generatePrompt", () => { isPR: true, prNumber: "456", commentBody: "@claude please fix this", - // No claudeBranch or defaultBranch for open PRs + // No claudeBranch or baseBranch for open PRs }, }; @@ -503,7 +503,7 @@ describe("generatePrompt", () => { prNumber: "789", commentBody: "@claude please update this", claudeBranch: "claude/pr-789-20240101_123000", - defaultBranch: "develop", + baseBranch: "develop", }, }; @@ -531,7 +531,7 @@ describe("generatePrompt", () => { commentId: "review-comment-123", commentBody: "@claude fix this issue", claudeBranch: "claude/pr-999-20240101_140000", - defaultBranch: "main", + baseBranch: "main", }, }; @@ -559,7 +559,7 @@ describe("generatePrompt", () => { isPR: true, prNumber: "555", claudeBranch: "claude/pr-555-20240101_150000", - defaultBranch: "main", + baseBranch: "main", }, }; @@ -604,7 +604,7 @@ describe("getEventTypeAndContext", () => { eventAction: "assigned", isPR: false, issueNumber: "999", - defaultBranch: "main", + baseBranch: "main", claudeBranch: "claude/issue-999-20240101_120000", assigneeTrigger: "claude-bot", }, diff --git a/test/prepare-context.test.ts b/test/prepare-context.test.ts index 6bece55..5be89f0 100644 --- a/test/prepare-context.test.ts +++ b/test/prepare-context.test.ts @@ -34,7 +34,7 @@ describe("parseEnvVarsWithContext", () => { beforeEach(() => { process.env = { ...BASE_ENV, - DEFAULT_BRANCH: "main", + BASE_BRANCH: "main", CLAUDE_BRANCH: "claude/issue-67890-20240101_120000", }; }); @@ -62,7 +62,7 @@ describe("parseEnvVarsWithContext", () => { expect(result.eventData.claudeBranch).toBe( "claude/issue-67890-20240101_120000", ); - expect(result.eventData.defaultBranch).toBe("main"); + expect(result.eventData.baseBranch).toBe("main"); expect(result.eventData.commentBody).toBe( "@claude can you help explain how to configure the logging system?", ); @@ -75,7 +75,7 @@ describe("parseEnvVarsWithContext", () => { ).toThrow("CLAUDE_BRANCH is required for issue_comment event"); }); - test("should throw error when DEFAULT_BRANCH is missing", () => { + test("should throw error when BASE_BRANCH is missing", () => { expect(() => prepareContext( mockIssueCommentContext, @@ -83,7 +83,7 @@ describe("parseEnvVarsWithContext", () => { undefined, "claude/issue-67890-20240101_120000", ), - ).toThrow("DEFAULT_BRANCH is required for issue_comment event"); + ).toThrow("BASE_BRANCH is required for issue_comment event"); }); }); @@ -151,7 +151,7 @@ describe("parseEnvVarsWithContext", () => { beforeEach(() => { process.env = { ...BASE_ENV, - DEFAULT_BRANCH: "main", + BASE_BRANCH: "main", CLAUDE_BRANCH: "claude/issue-42-20240101_120000", }; }); @@ -172,7 +172,7 @@ describe("parseEnvVarsWithContext", () => { result.eventData.eventAction === "opened" ) { expect(result.eventData.issueNumber).toBe("42"); - expect(result.eventData.defaultBranch).toBe("main"); + expect(result.eventData.baseBranch).toBe("main"); expect(result.eventData.claudeBranch).toBe( "claude/issue-42-20240101_120000", ); @@ -195,7 +195,7 @@ describe("parseEnvVarsWithContext", () => { result.eventData.eventAction === "assigned" ) { expect(result.eventData.issueNumber).toBe("123"); - expect(result.eventData.defaultBranch).toBe("main"); + expect(result.eventData.baseBranch).toBe("main"); expect(result.eventData.claudeBranch).toBe( "claude/issue-123-20240101_120000", ); @@ -209,7 +209,7 @@ describe("parseEnvVarsWithContext", () => { ).toThrow("CLAUDE_BRANCH is required for issues event"); }); - test("should throw error when DEFAULT_BRANCH is missing for issues", () => { + test("should throw error when BASE_BRANCH is missing for issues", () => { expect(() => prepareContext( mockIssueOpenedContext, @@ -217,7 +217,7 @@ describe("parseEnvVarsWithContext", () => { undefined, "claude/issue-42-20240101_120000", ), - ).toThrow("DEFAULT_BRANCH is required for issues event"); + ).toThrow("BASE_BRANCH is required for issues event"); }); });