diff --git a/src/create-prompt/index.ts b/src/create-prompt/index.ts index 93bca54..468666d 100644 --- a/src/create-prompt/index.ts +++ b/src/create-prompt/index.ts @@ -361,6 +361,7 @@ export function getEventTypeAndContext(envVars: PreparedContext): { export function generatePrompt( context: PreparedContext, githubData: FetchDataResult, + isReusedBranch?: boolean, ): string { const { contextData, @@ -534,7 +535,7 @@ ${context.directPrompt ? ` - DIRECT INSTRUCTION: A direct instruction was prov - Use mcp__github_file_ops__commit_files to commit files atomically in a single commit (supports single or multiple files). - When pushing changes with this tool and TRIGGER_USERNAME is not "Unknown", include a "Co-authored-by: ${context.triggerUsername} <${context.triggerUsername}@users.noreply.github.com>" line in the commit message.` : ` - - You are already on the correct branch (${eventData.claudeBranch || "the PR branch"}). Do not create a new branch. + - You are already on the correct branch (${eventData.claudeBranch || "the PR branch"}). Do not create a new branch.${isReusedBranch ? `\n - NOTE: This branch (${eventData.claudeBranch}) was reused from a previous Claude invocation on this issue. It may already contain some work.` : ''} - Push changes directly to the current branch using mcp__github_file_ops__commit_files (works for both new and existing files) - Use mcp__github_file_ops__commit_files to commit files atomically in a single commit (supports single or multiple files). - When pushing changes and TRIGGER_USERNAME is not "Unknown", include a "Co-authored-by: ${context.triggerUsername} <${context.triggerUsername}@users.noreply.github.com>" line in the commit message. @@ -641,6 +642,7 @@ export async function createPrompt( claudeBranch: string | undefined, githubData: FetchDataResult, context: ParsedGitHubContext, + isReusedBranch?: boolean, ) { try { const preparedContext = prepareContext( @@ -653,7 +655,7 @@ export async function createPrompt( await mkdir("/tmp/claude-prompts", { recursive: true }); // Generate the prompt - const promptContent = generatePrompt(preparedContext, githubData); + const promptContent = generatePrompt(preparedContext, githubData, isReusedBranch); // Log the final prompt to console console.log("===== FINAL PROMPT ====="); diff --git a/src/entrypoints/prepare.ts b/src/entrypoints/prepare.ts index c3e0b38..67f9f22 100644 --- a/src/entrypoints/prepare.ts +++ b/src/entrypoints/prepare.ts @@ -81,6 +81,7 @@ async function run() { branchInfo.claudeBranch, githubData, context, + branchInfo.isReusedBranch, ); // Step 11: Get MCP configuration diff --git a/src/entrypoints/update-comment-link.ts b/src/entrypoints/update-comment-link.ts index 40c20ad..266f739 100644 --- a/src/entrypoints/update-comment-link.ts +++ b/src/entrypoints/update-comment-link.ts @@ -87,13 +87,29 @@ async function run() { const currentBody = comment.body ?? ""; // Check if we need to add branch link for new branches - const { shouldDeleteBranch, branchLink } = await checkAndDeleteEmptyBranch( - octokit, - owner, - repo, - claudeBranch, - baseBranch, - ); + // For issues, we don't delete branches anymore to allow reuse + const skipBranchDeletion = !context.isPR && claudeBranch; + + let shouldDeleteBranch = false; + let branchLink = ""; + + if (skipBranchDeletion) { + // For issue branches, just add the branch link without checking for deletion + const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`; + branchLink = `\n[View branch](${branchUrl})`; + console.log(`Keeping issue branch ${claudeBranch} for potential reuse`); + } else { + // For PR branches, use the existing cleanup logic + const result = await checkAndDeleteEmptyBranch( + octokit, + owner, + repo, + claudeBranch, + baseBranch, + ); + shouldDeleteBranch = result.shouldDeleteBranch; + branchLink = result.branchLink; + } // Check if we need to add PR URL when we have a new branch let prLink = ""; diff --git a/src/github/operations/branch.ts b/src/github/operations/branch.ts index 3379648..a389410 100644 --- a/src/github/operations/branch.ts +++ b/src/github/operations/branch.ts @@ -17,6 +17,7 @@ export type BranchInfo = { baseBranch: string; claudeBranch?: string; currentBranch: string; + isReusedBranch?: boolean; }; export async function setupBranch( @@ -79,57 +80,110 @@ export async function setupBranch( // 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} from source branch: ${sourceBranch}...`, - ); - - const timestamp = new Date() - .toISOString() - .replace(/[:-]/g, "") - .replace(/\.\d{3}Z/, "") - .split("T") - .join("_"); - - const newBranch = `claude/${entityType}-${entityNumber}-${timestamp}`; - - try { - // Get the SHA of the source branch - const sourceBranchRef = await octokits.rest.git.getRef({ - owner, - repo, - ref: `heads/${sourceBranch}`, - }); - - const currentSHA = sourceBranchRef.data.object.sha; - - console.log(`Current SHA: ${currentSHA}`); - - // Create branch using GitHub API - await octokits.rest.git.createRef({ - owner, - repo, - ref: `refs/heads/${newBranch}`, - sha: currentSHA, - }); - - // Checkout the new branch (shallow fetch for performance) - await $`git fetch origin --depth=1 ${newBranch}`; - await $`git checkout ${newBranch}`; - + + // For issues, check if a Claude branch already exists + let branchToUse: string | null = null; + let isReusedBranch = false; + + if (!isPR) { + // Check for existing Claude branches for this issue + try { + const { data: branches } = await octokits.rest.repos.listBranches({ + owner, + repo, + per_page: 100, + }); + + // Look for existing branches with pattern claude/issue-{entityNumber}-* + const existingBranch = branches.find(branch => + branch.name.startsWith(`claude/issue-${entityNumber}-`) + ); + + if (existingBranch) { + branchToUse = existingBranch.name; + isReusedBranch = true; + console.log(`Found existing Claude branch for issue #${entityNumber}: ${branchToUse}`); + } + } catch (error) { + console.error("Error checking for existing branches:", error); + // Continue with new branch creation if check fails + } + } + + // If no existing branch found or this is a PR, create a new branch + if (!branchToUse) { console.log( - `Successfully created and checked out new branch: ${newBranch}`, + `Creating new branch for ${entityType} #${entityNumber} from source branch: ${sourceBranch}...`, ); + const timestamp = new Date() + .toISOString() + .replace(/[:-]/g, "") + .replace(/\.\d{3}Z/, "") + .split("T") + .join("_"); + + branchToUse = `claude/${entityType}-${entityNumber}-${timestamp}`; + } + + try { + if (isReusedBranch) { + // For existing branches, just checkout + console.log(`Checking out existing branch: ${branchToUse}`); + + // Fetch the branch with more depth to allow for context + await $`git fetch origin --depth=20 ${branchToUse}`; + await $`git checkout ${branchToUse}`; + + console.log( + `Successfully checked out existing branch: ${branchToUse}`, + ); + console.log( + `Note: This is a reused branch from a previous Claude invocation on issue #${entityNumber}`, + ); + } else { + // Get the SHA of the source branch + const sourceBranchRef = await octokits.rest.git.getRef({ + owner, + repo, + ref: `heads/${sourceBranch}`, + }); + + const currentSHA = sourceBranchRef.data.object.sha; + + console.log(`Current SHA: ${currentSHA}`); + + // Create branch using GitHub API + await octokits.rest.git.createRef({ + owner, + repo, + ref: `refs/heads/${branchToUse}`, + sha: currentSHA, + }); + + // Checkout the new branch (shallow fetch for performance) + await $`git fetch origin --depth=1 ${branchToUse}`; + await $`git checkout ${branchToUse}`; + + console.log( + `Successfully created and checked out new branch: ${branchToUse}`, + ); + } + // Set outputs for GitHub Actions - core.setOutput("CLAUDE_BRANCH", newBranch); + core.setOutput("CLAUDE_BRANCH", branchToUse); core.setOutput("BASE_BRANCH", sourceBranch); + if (isReusedBranch) { + core.setOutput("IS_REUSED_BRANCH", "true"); + } return { baseBranch: sourceBranch, - claudeBranch: newBranch, - currentBranch: newBranch, + claudeBranch: branchToUse, + currentBranch: branchToUse, + isReusedBranch, }; } catch (error) { - console.error("Error creating branch:", error); + console.error("Error setting up branch:", error); process.exit(1); } }