This commit is contained in:
Mark Wylde
2025-05-30 20:02:39 +01:00
parent 180a1b6680
commit fb6df649ed
33 changed files with 4709 additions and 901 deletions

View File

@@ -15,7 +15,7 @@ import { setupBranch } from "../github/operations/branch";
import { updateTrackingComment } from "../github/operations/comments/update-with-branch";
import { prepareMcpConfig } from "../mcp/install-mcp-server";
import { createPrompt } from "../create-prompt";
import { createOctokit } from "../github/api/client";
import { createClient } from "../github/api/client";
import { fetchGitHubData } from "../github/data/fetcher";
import { parseGitHubContext } from "../github/context";
@@ -23,14 +23,14 @@ async function run() {
try {
// Step 1: Setup GitHub token
const githubToken = await setupGitHubToken();
const octokit = createOctokit(githubToken);
const client = createClient(githubToken);
// Step 2: Parse GitHub context (once for all operations)
const context = parseGitHubContext();
// Step 3: Check write permissions
const hasWritePermissions = await checkWritePermissions(
octokit.rest,
client.api,
context,
);
if (!hasWritePermissions) {
@@ -42,32 +42,41 @@ async function run() {
// Step 4: Check trigger conditions
const containsTrigger = await checkTriggerAction(context);
// Set outputs that are always needed
core.setOutput("contains_trigger", containsTrigger.toString());
core.setOutput("GITHUB_TOKEN", githubToken);
if (!containsTrigger) {
console.log("No trigger found, skipping remaining steps");
return;
}
// Step 5: Check if actor is human
await checkHumanActor(octokit.rest, context);
await checkHumanActor(client.api, context);
// Step 6: Create initial tracking comment
const commentId = await createInitialComment(octokit.rest, context);
const commentId = await createInitialComment(client.api, context);
core.setOutput("claude_comment_id", commentId.toString());
// Step 7: Fetch GitHub data (once for both branch setup and prompt creation)
const githubData = await fetchGitHubData({
octokits: octokit,
client: client,
repository: `${context.repository.owner}/${context.repository.repo}`,
prNumber: context.entityNumber.toString(),
isPR: context.isPR,
});
// Step 8: Setup branch
const branchInfo = await setupBranch(octokit, githubData, context);
const branchInfo = await setupBranch(client, githubData, context);
core.setOutput("BASE_BRANCH", branchInfo.baseBranch);
if (branchInfo.claudeBranch) {
core.setOutput("CLAUDE_BRANCH", branchInfo.claudeBranch);
}
// Step 9: Update initial comment with branch link (only for issues that created a new branch)
if (branchInfo.claudeBranch) {
await updateTrackingComment(
octokit,
client,
context,
commentId,
branchInfo.claudeBranch,

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bun
import { createOctokit } from "../github/api/client";
import { createClient } from "../github/api/client";
import * as fs from "fs/promises";
import {
updateCommentBody,
@@ -10,8 +10,14 @@ import {
parseGitHubContext,
isPullRequestReviewCommentEvent,
} from "../github/context";
import { GITHUB_SERVER_URL } from "../github/api/config";
import { GITEA_SERVER_URL } from "../github/api/config";
import { checkAndDeleteEmptyBranch } from "../github/operations/branch-cleanup";
import {
branchHasChanges,
fetchBranch,
branchExists,
remoteBranchExists,
} from "../github/utils/local-git";
async function run() {
try {
@@ -23,9 +29,9 @@ async function run() {
const context = parseGitHubContext();
const { owner, repo } = context.repository;
const octokit = createOctokit(githubToken);
const client = createClient(githubToken);
const serverUrl = GITHUB_SERVER_URL;
const serverUrl = GITEA_SERVER_URL;
const jobUrl = `${serverUrl}/${owner}/${repo}/actions/runs/${process.env.GITHUB_RUN_ID}`;
let comment;
@@ -37,12 +43,11 @@ async function run() {
if (isPullRequestReviewCommentEvent(context)) {
// For PR review comments, use the pulls API
console.log(`Fetching PR review comment ${commentId}`);
const { data: prComment } = await octokit.rest.pulls.getReviewComment({
owner,
repo,
comment_id: commentId,
});
comment = prComment;
const response = await client.api.customRequest(
"GET",
`/api/v1/repos/${owner}/${repo}/pulls/comments/${commentId}`,
);
comment = response.data;
isPRReviewComment = true;
console.log("Successfully fetched as PR review comment");
}
@@ -50,12 +55,11 @@ async function run() {
// For all other event types, use the issues API
if (!comment) {
console.log(`Fetching issue comment ${commentId}`);
const { data: issueComment } = await octokit.rest.issues.getComment({
owner,
repo,
comment_id: commentId,
});
comment = issueComment;
const response = await client.api.customRequest(
"GET",
`/api/v1/repos/${owner}/${repo}/issues/comments/${commentId}`,
);
comment = response.data;
isPRReviewComment = false;
console.log("Successfully fetched as issue comment");
}
@@ -69,14 +73,14 @@ async function run() {
// Try to get the PR info to understand the comment structure
try {
const { data: pr } = await octokit.rest.pulls.get({
const pr = await client.api.getPullRequest(
owner,
repo,
pull_number: context.entityNumber,
});
console.log(`PR state: ${pr.state}`);
console.log(`PR comments count: ${pr.comments}`);
console.log(`PR review comments count: ${pr.review_comments}`);
context.entityNumber,
);
console.log(`PR state: ${pr.data.state}`);
console.log(`PR comments count: ${pr.data.comments}`);
console.log(`PR review comments count: ${pr.data.review_comments}`);
} catch {
console.error("Could not fetch PR info for debugging");
}
@@ -88,7 +92,7 @@ async function run() {
// Check if we need to add branch link for new branches
const { shouldDeleteBranch, branchLink } = await checkAndDeleteEmptyBranch(
octokit,
client,
owner,
repo,
claudeBranch,
@@ -107,20 +111,79 @@ async function run() {
const containsPRUrl = currentBody.match(prUrlPattern);
if (!containsPRUrl) {
// Check if there are changes to the branch compared to the default branch
try {
const { data: comparison } =
await octokit.rest.repos.compareCommitsWithBasehead({
owner,
repo,
basehead: `${baseBranch}...${claudeBranch}`,
});
// Check if we're using Gitea or GitHub
const giteaApiUrl = process.env.GITEA_API_URL?.trim();
const isGitea =
giteaApiUrl &&
giteaApiUrl !== "" &&
!giteaApiUrl.includes("api.github.com") &&
!giteaApiUrl.includes("github.com");
// If there are changes (commits or file changes), add the PR URL
if (
comparison.total_commits > 0 ||
(comparison.files && comparison.files.length > 0)
) {
if (isGitea) {
// Use local git commands for Gitea
console.log(
"Using local git commands for PR link check (Gitea mode)",
);
try {
// Fetch latest changes from remote
await fetchBranch(claudeBranch);
await fetchBranch(baseBranch);
// Check if branch exists and has changes
const { hasChanges, branchSha, baseSha } = await branchHasChanges(
claudeBranch,
baseBranch,
);
if (branchSha && baseSha) {
if (hasChanges) {
console.log(
`Branch ${claudeBranch} appears to have changes (different SHA from base)`,
);
const entityType = context.isPR ? "PR" : "Issue";
const prTitle = encodeURIComponent(
`${entityType} #${context.entityNumber}: Changes from Claude`,
);
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/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
prLink = `\n[Create a PR](${prUrl})`;
} else {
console.log(
`Branch ${claudeBranch} has same SHA as base, no PR link needed`,
);
}
} else {
// If we can't get SHAs, check if branch exists at all
const localExists = await branchExists(claudeBranch);
const remoteExists = await remoteBranchExists(claudeBranch);
if (localExists || remoteExists) {
console.log(
`Branch ${claudeBranch} exists but SHA comparison failed, adding PR link to be safe`,
);
const entityType = context.isPR ? "PR" : "Issue";
const prTitle = encodeURIComponent(
`${entityType} #${context.entityNumber}: Changes from Claude`,
);
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/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
prLink = `\n[Create a PR](${prUrl})`;
} else {
console.log(
`Branch ${claudeBranch} does not exist yet - no PR link needed`,
);
prLink = "";
}
}
} catch (error: any) {
console.error("Error checking branch with git commands:", error);
// For errors, add PR link to be safe
console.log("Adding PR link as fallback due to git command error");
const entityType = context.isPR ? "PR" : "Issue";
const prTitle = encodeURIComponent(
`${entityType} #${context.entityNumber}: Changes from Claude`,
@@ -131,9 +194,71 @@ async function run() {
const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
prLink = `\n[Create a PR](${prUrl})`;
}
} catch (error) {
console.error("Error checking for changes in branch:", error);
// Don't fail the entire update if we can't check for changes
} else {
// Use API calls for GitHub
console.log("Using API calls for PR link check (GitHub mode)");
try {
// Get the branch info to see if it exists and has commits
const branchResponse = await client.api.getBranch(
owner,
repo,
claudeBranch,
);
// Get base branch info for comparison
const baseResponse = await client.api.getBranch(
owner,
repo,
baseBranch,
);
const branchSha = branchResponse.data.commit.sha;
const baseSha = baseResponse.data.commit.sha;
// If SHAs are different, assume there are changes and add PR link
if (branchSha !== baseSha) {
console.log(
`Branch ${claudeBranch} appears to have changes (different SHA from base)`,
);
const entityType = context.isPR ? "PR" : "Issue";
const prTitle = encodeURIComponent(
`${entityType} #${context.entityNumber}: Changes from Claude`,
);
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/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
prLink = `\n[Create a PR](${prUrl})`;
} else {
console.log(
`Branch ${claudeBranch} has same SHA as base, no PR link needed`,
);
}
} catch (error: any) {
console.error("Error checking branch:", error);
// Handle 404 specifically - branch doesn't exist
if (error.status === 404) {
console.log(
`Branch ${claudeBranch} does not exist yet - no PR link needed`,
);
// Don't add PR link since branch doesn't exist
prLink = "";
} else {
// For other errors, add PR link to be safe
console.log("Adding PR link as fallback due to non-404 error");
const entityType = context.isPR ? "PR" : "Issue";
const prTitle = encodeURIComponent(
`${entityType} #${context.entityNumber}: Changes from Claude`,
);
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/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
prLink = `\n[Create a PR](${prUrl})`;
}
}
}
}
}
@@ -207,19 +332,20 @@ async function run() {
// Update the comment using the appropriate API
try {
if (isPRReviewComment) {
await octokit.rest.pulls.updateReviewComment({
owner,
repo,
comment_id: commentId,
body: updatedBody,
});
await client.api.customRequest(
"PATCH",
`/api/v1/repos/${owner}/${repo}/pulls/comments/${commentId}`,
{
body: updatedBody,
},
);
} else {
await octokit.rest.issues.updateComment({
await client.api.updateIssueComment(
owner,
repo,
comment_id: commentId,
body: updatedBody,
});
commentId,
updatedBody,
);
}
console.log(
`✅ Updated ${isPRReviewComment ? "PR review" : "issue"} comment ${commentId} with job link`,