mirror of
https://github.com/markwylde/claude-code-gitea-action.git
synced 2026-06-21 22:50:33 +08:00
This commit is contained in:
@@ -120,7 +120,9 @@ export function buildDisallowedToolsString(
|
|||||||
// If user has explicitly allowed some hardcoded disallowed tools, remove them from disallowed list
|
// If user has explicitly allowed some hardcoded disallowed tools, remove them from disallowed list
|
||||||
const allowedList = normalizeToolList(allowedTools);
|
const allowedList = normalizeToolList(allowedTools);
|
||||||
if (allowedList.length > 0) {
|
if (allowedList.length > 0) {
|
||||||
disallowedTools = disallowedTools.filter((tool) => !allowedList.includes(tool));
|
disallowedTools = disallowedTools.filter(
|
||||||
|
(tool) => !allowedList.includes(tool),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let allDisallowedTools = disallowedTools.join(",");
|
let allDisallowedTools = disallowedTools.join(",");
|
||||||
@@ -164,16 +166,20 @@ export function prepareContext(
|
|||||||
let commentBody: string | undefined;
|
let commentBody: string | undefined;
|
||||||
|
|
||||||
if (isIssueCommentEvent(context)) {
|
if (isIssueCommentEvent(context)) {
|
||||||
commentId = context.payload.comment.id.toString();
|
commentId = context.payload.comment?.id?.toString();
|
||||||
commentBody = context.payload.comment.body;
|
commentBody = context.payload.comment?.body;
|
||||||
triggerUsername = context.payload.comment.user.login;
|
triggerUsername = context.payload.comment?.user?.login;
|
||||||
} else if (isPullRequestReviewEvent(context)) {
|
} else if (isPullRequestReviewEvent(context)) {
|
||||||
commentBody = context.payload.review.body ?? "";
|
commentBody =
|
||||||
triggerUsername = context.payload.review.user.login;
|
context.payload.review?.body ?? context.payload.review?.content ?? "";
|
||||||
|
triggerUsername =
|
||||||
|
context.payload.review?.user?.login ?? context.payload.sender?.login;
|
||||||
} else if (isPullRequestReviewCommentEvent(context)) {
|
} else if (isPullRequestReviewCommentEvent(context)) {
|
||||||
commentId = context.payload.comment.id.toString();
|
commentId = context.payload.comment?.id?.toString();
|
||||||
commentBody = context.payload.comment.body;
|
commentBody =
|
||||||
triggerUsername = context.payload.comment.user.login;
|
context.payload.comment?.body ?? context.payload.review?.content;
|
||||||
|
triggerUsername =
|
||||||
|
context.payload.comment?.user?.login ?? context.payload.sender?.login;
|
||||||
} else if (isIssuesEvent(context)) {
|
} else if (isIssuesEvent(context)) {
|
||||||
triggerUsername = context.payload.issue.user.login;
|
triggerUsername = context.payload.issue.user.login;
|
||||||
}
|
}
|
||||||
@@ -595,18 +601,7 @@ ${sanitizeContent(context.directPrompt)}
|
|||||||
</direct_prompt>`
|
</direct_prompt>`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
${
|
${`<comment_tool_info>
|
||||||
eventData.eventName === "pull_request_review_comment"
|
|
||||||
? `<comment_tool_info>
|
|
||||||
IMPORTANT: For this inline PR review comment, you have been provided with ONLY the mcp__gitea__update_pull_request_comment tool to update this specific review comment.
|
|
||||||
|
|
||||||
Tool usage example for mcp__gitea__update_pull_request_comment:
|
|
||||||
{
|
|
||||||
"body": "Your comment text here"
|
|
||||||
}
|
|
||||||
All four parameters (owner, repo, commentId, body) are required.
|
|
||||||
</comment_tool_info>`
|
|
||||||
: `<comment_tool_info>
|
|
||||||
IMPORTANT: For this event type, you have been provided with ONLY the mcp__gitea__update_issue_comment tool to update comments.
|
IMPORTANT: For this event type, you have been provided with ONLY the mcp__gitea__update_issue_comment tool to update comments.
|
||||||
|
|
||||||
Tool usage example for mcp__gitea__update_issue_comment:
|
Tool usage example for mcp__gitea__update_issue_comment:
|
||||||
@@ -617,8 +612,7 @@ Tool usage example for mcp__gitea__update_issue_comment:
|
|||||||
"body": "Your comment text here"
|
"body": "Your comment text here"
|
||||||
}
|
}
|
||||||
All four parameters (owner, repo, commentId, body) are required.
|
All four parameters (owner, repo, commentId, body) are required.
|
||||||
</comment_tool_info>`
|
</comment_tool_info>`}
|
||||||
}
|
|
||||||
|
|
||||||
Your task is to analyze the context, understand the request, and provide helpful responses and/or implement code changes as needed.
|
Your task is to analyze the context, understand the request, and provide helpful responses and/or implement code changes as needed.
|
||||||
|
|
||||||
@@ -632,7 +626,7 @@ Follow these steps:
|
|||||||
1. Create a Todo List:
|
1. Create a Todo List:
|
||||||
- Use your Gitea comment to maintain a detailed task list based on the request.
|
- Use your Gitea comment to maintain a detailed task list based on the request.
|
||||||
- Format todos as a checklist (- [ ] for incomplete, - [x] for complete).
|
- Format todos as a checklist (- [ ] for incomplete, - [x] for complete).
|
||||||
- Update the comment using ${eventData.eventName === "pull_request_review_comment" ? "mcp__gitea__update_pull_request_comment" : "mcp__gitea__update_issue_comment"} with each task completion.
|
- Update the comment using mcp__gitea__update_issue_comment with each task completion.
|
||||||
|
|
||||||
2. Gather Context:
|
2. Gather Context:
|
||||||
- Analyze the pre-fetched data provided above.
|
- Analyze the pre-fetched data provided above.
|
||||||
@@ -738,8 +732,8 @@ ${!eventData.isPR || !eventData.claudeBranch ? `6. Final Update:` : `5. Final Up
|
|||||||
|
|
||||||
Important Notes:
|
Important Notes:
|
||||||
- All communication must happen through Gitea PR comments.
|
- All communication must happen through Gitea PR comments.
|
||||||
- Never create new comments. Only update the existing comment using ${eventData.eventName === "pull_request_review_comment" ? "mcp__gitea__update_pull_request_comment" : "mcp__gitea__update_issue_comment"} with comment_id: ${context.claudeCommentId}.
|
- Never create new comments. Only update the existing comment using mcp__gitea__update_issue_comment with comment_id: ${context.claudeCommentId}.
|
||||||
- This includes ALL responses: code reviews, answers to questions, progress updates, and final results.${eventData.isPR ? "\n- PR CRITICAL: After reading files and forming your response, you MUST post it by calling mcp__gitea__update_issue_comment. Do NOT just respond with a normal response, the user will not see it." : ""}
|
- This includes ALL responses: code reviews, answers to questions, progress updates, and final results.${eventData.isPR ? `\n- PR CRITICAL: After reading files and forming your response, you MUST post it by calling mcp__gitea__update_issue_comment. Do NOT just respond with a normal response, the user will not see it.` : ""}
|
||||||
- You communicate exclusively by editing your single comment - not through any other means.
|
- You communicate exclusively by editing your single comment - not through any other means.
|
||||||
- Use this spinner HTML when work is in progress: <img src="https://raw.githubusercontent.com/markwylde/claude-code-gitea-action/refs/heads/gitea/assets/spinner.gif" width="14px" height="14px" style="vertical-align: middle; margin-left: 4px;" />
|
- Use this spinner HTML when work is in progress: <img src="https://raw.githubusercontent.com/markwylde/claude-code-gitea-action/refs/heads/gitea/assets/spinner.gif" width="14px" height="14px" style="vertical-align: middle; margin-left: 4px;" />
|
||||||
${eventData.isPR && !eventData.claudeBranch ? `- Always push to the existing branch when triggered on a PR.` : eventData.claudeBranch ? `- IMPORTANT: You are already on the correct branch (${eventData.claudeBranch}). Do not create additional branches.` : `- IMPORTANT: You are currently on the base branch (${eventData.baseBranch}). First check for existing claude branches for this ${eventData.isPR ? "PR" : "issue"} and use them if found, otherwise create a new branch using mcp__local_git_ops__create_branch.`}
|
${eventData.isPR && !eventData.claudeBranch ? `- Always push to the existing branch when triggered on a PR.` : eventData.claudeBranch ? `- IMPORTANT: You are already on the correct branch (${eventData.claudeBranch}). Do not create additional branches.` : `- IMPORTANT: You are currently on the base branch (${eventData.baseBranch}). First check for existing claude branches for this ${eventData.isPR ? "PR" : "issue"} and use them if found, otherwise create a new branch using mcp__local_git_ops__create_branch.`}
|
||||||
@@ -750,8 +744,8 @@ ${eventData.isPR && !eventData.claudeBranch ? `- Always push to the existing bra
|
|||||||
- mcp__local_git_ops__delete_files: {"files": ["path/to/old.js"], "message": "chore: remove deprecated file"}
|
- mcp__local_git_ops__delete_files: {"files": ["path/to/old.js"], "message": "chore: remove deprecated file"}
|
||||||
- Display the todo list as a checklist in the Gitea comment and mark things off as you go.
|
- Display the todo list as a checklist in the Gitea comment and mark things off as you go.
|
||||||
- All communication must happen through Gitea PR comments.
|
- All communication must happen through Gitea PR comments.
|
||||||
- Never create new comments. Only update the existing comment using ${eventData.eventName === "pull_request_review_comment" ? "mcp__gitea__update_pull_request_comment" : "mcp__gitea__update_issue_comment"}.
|
- Never create new comments. Only update the existing comment using mcp__gitea__update_issue_comment.
|
||||||
- This includes ALL responses: code reviews, answers to questions, progress updates, and final results.${eventData.isPR ? "\n- PR CRITICAL: After reading files and forming your response, you MUST post it by calling mcp__gitea__update_issue_comment. Do NOT just respond with a normal response, the user will not see it." : ""}
|
- This includes ALL responses: code reviews, answers to questions, progress updates, and final results.${eventData.isPR ? `\n- PR CRITICAL: After reading files and forming your response, you MUST post it by calling mcp__gitea__update_issue_comment. Do NOT just respond with a normal response, the user will not see it.` : ""}
|
||||||
- You communicate exclusively by editing your single comment - not through any other means.
|
- You communicate exclusively by editing your single comment - not through any other means.
|
||||||
- Use this spinner HTML when work is in progress: <img src="https://github.com/user-attachments/assets/5ac382c7-e004-429b-8e35-7feb3e8f9c6f" width="14px" height="14px" style="vertical-align: middle; margin-left: 4px;" />
|
- Use this spinner HTML when work is in progress: <img src="https://github.com/user-attachments/assets/5ac382c7-e004-429b-8e35-7feb3e8f9c6f" width="14px" height="14px" style="vertical-align: middle; margin-left: 4px;" />
|
||||||
${eventData.isPR && !eventData.claudeBranch ? `- Always push to the existing branch when triggered on a PR.` : `- IMPORTANT: You are already on the correct branch (${eventData.claudeBranch || "the created branch"}). Never create new branches when triggered on issues or closed/merged PRs.`}
|
${eventData.isPR && !eventData.claudeBranch ? `- Always push to the existing branch when triggered on a PR.` : `- IMPORTANT: You are already on the correct branch (${eventData.claudeBranch || "the created branch"}). Never create new branches when triggered on issues or closed/merged PRs.`}
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ async function run() {
|
|||||||
// GitHub has separate ID namespaces for review comments and issue comments
|
// GitHub has separate ID namespaces for review comments and issue comments
|
||||||
// We need to use the correct API based on the event type
|
// We need to use the correct API based on the event type
|
||||||
if (isPullRequestReviewCommentEvent(context)) {
|
if (isPullRequestReviewCommentEvent(context)) {
|
||||||
// For PR review comments, use the pulls API
|
// Try the PR review comment endpoint first; Gitea may have created an
|
||||||
|
// issue comment instead (no comment.id in payload), so fall through on 404.
|
||||||
|
try {
|
||||||
console.log(`Fetching PR review comment ${commentId}`);
|
console.log(`Fetching PR review comment ${commentId}`);
|
||||||
const response = await client.api.customRequest(
|
const response = await client.api.customRequest(
|
||||||
"GET",
|
"GET",
|
||||||
@@ -50,6 +52,11 @@ async function run() {
|
|||||||
comment = response.data;
|
comment = response.data;
|
||||||
isPRReviewComment = true;
|
isPRReviewComment = true;
|
||||||
console.log("Successfully fetched as PR review comment");
|
console.log("Successfully fetched as PR review comment");
|
||||||
|
} catch {
|
||||||
|
console.log(
|
||||||
|
"PR review comment not found, falling back to issue comment",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For all other event types, use the issues API
|
// For all other event types, use the issues API
|
||||||
|
|||||||
@@ -10,6 +10,20 @@ import type {
|
|||||||
import type { ModeName } from "../modes/types";
|
import type { ModeName } from "../modes/types";
|
||||||
import { DEFAULT_MODE, isValidMode } from "../modes/registry";
|
import { DEFAULT_MODE, isValidMode } from "../modes/registry";
|
||||||
|
|
||||||
|
// Gitea review payloads use `review.content` instead of `review.body`, and
|
||||||
|
// `sender` instead of nested user objects. These types extend the GitHub base
|
||||||
|
// types to make both fields available without `as any` casts.
|
||||||
|
export type GiteaPullRequestReviewEvent = PullRequestReviewEvent & {
|
||||||
|
review?: { content?: string };
|
||||||
|
sender?: { login: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GiteaPullRequestReviewCommentEvent =
|
||||||
|
PullRequestReviewCommentEvent & {
|
||||||
|
review?: { type: string; content: string };
|
||||||
|
sender?: { login: string };
|
||||||
|
};
|
||||||
|
|
||||||
export type ParsedGitHubContext = {
|
export type ParsedGitHubContext = {
|
||||||
runId: string;
|
runId: string;
|
||||||
eventName: string;
|
eventName: string;
|
||||||
@@ -24,8 +38,8 @@ export type ParsedGitHubContext = {
|
|||||||
| IssuesEvent
|
| IssuesEvent
|
||||||
| IssueCommentEvent
|
| IssueCommentEvent
|
||||||
| PullRequestEvent
|
| PullRequestEvent
|
||||||
| PullRequestReviewEvent
|
| GiteaPullRequestReviewEvent
|
||||||
| PullRequestReviewCommentEvent;
|
| GiteaPullRequestReviewCommentEvent;
|
||||||
entityNumber: number;
|
entityNumber: number;
|
||||||
isPR: boolean;
|
isPR: boolean;
|
||||||
inputs: {
|
inputs: {
|
||||||
@@ -181,13 +195,15 @@ export function isPullRequestEvent(
|
|||||||
|
|
||||||
export function isPullRequestReviewEvent(
|
export function isPullRequestReviewEvent(
|
||||||
context: ParsedGitHubContext,
|
context: ParsedGitHubContext,
|
||||||
): context is ParsedGitHubContext & { payload: PullRequestReviewEvent } {
|
): context is ParsedGitHubContext & { payload: GiteaPullRequestReviewEvent } {
|
||||||
return context.eventName === "pull_request_review";
|
return context.eventName === "pull_request_review";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isPullRequestReviewCommentEvent(
|
export function isPullRequestReviewCommentEvent(
|
||||||
context: ParsedGitHubContext,
|
context: ParsedGitHubContext,
|
||||||
): context is ParsedGitHubContext & { payload: PullRequestReviewCommentEvent } {
|
): context is ParsedGitHubContext & {
|
||||||
|
payload: GiteaPullRequestReviewCommentEvent;
|
||||||
|
} {
|
||||||
return context.eventName === "pull_request_review_comment";
|
return context.eventName === "pull_request_review_comment";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { GITEA_SERVER_URL } from "../api/config";
|
|
||||||
|
|
||||||
export type ExecutionDetails = {
|
export type ExecutionDetails = {
|
||||||
cost_usd?: number;
|
cost_usd?: number;
|
||||||
duration_ms?: number;
|
duration_ms?: number;
|
||||||
@@ -166,7 +164,11 @@ export function updateCommentBody(input: CommentUpdateInput): string {
|
|||||||
.filter((segment) => segment);
|
.filter((segment) => segment);
|
||||||
const [owner, repo] = segments;
|
const [owner, repo] = segments;
|
||||||
if (owner && repo) {
|
if (owner && repo) {
|
||||||
branchUrl = `${GITEA_SERVER_URL}/${owner}/${repo}/src/branch/${finalBranchName}`;
|
const serverUrl =
|
||||||
|
process.env.GITEA_SERVER_URL ||
|
||||||
|
process.env.GITHUB_SERVER_URL ||
|
||||||
|
"https://github.com";
|
||||||
|
branchUrl = `${serverUrl}/${owner}/${repo}/src/branch/${finalBranchName}`;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`Failed to derive branch URL from job URL: ${error}`);
|
console.warn(`Failed to derive branch URL from job URL: ${error}`);
|
||||||
|
|||||||
@@ -31,7 +31,10 @@ export async function createInitialComment(
|
|||||||
console.log(`Repository: ${owner}/${repo}`);
|
console.log(`Repository: ${owner}/${repo}`);
|
||||||
|
|
||||||
// Only use createReplyForReviewComment if it's a PR review comment AND we have a comment_id
|
// Only use createReplyForReviewComment if it's a PR review comment AND we have a comment_id
|
||||||
if (isPullRequestReviewCommentEvent(context)) {
|
if (
|
||||||
|
isPullRequestReviewCommentEvent(context) &&
|
||||||
|
context.payload.comment?.id
|
||||||
|
) {
|
||||||
console.log(`Creating PR review comment reply`);
|
console.log(`Creating PR review comment reply`);
|
||||||
response = await api.customRequest(
|
response = await api.customRequest(
|
||||||
"POST",
|
"POST",
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
|
|||||||
// Check for issue label trigger
|
// Check for issue label trigger
|
||||||
if (isIssuesEvent(context) && context.eventAction === "labeled") {
|
if (isIssuesEvent(context) && context.eventAction === "labeled") {
|
||||||
const triggerLabel = context.inputs.labelTrigger?.trim();
|
const triggerLabel = context.inputs.labelTrigger?.trim();
|
||||||
const appliedLabel = (context.payload as IssuesLabeledEvent).label?.name
|
const appliedLabel = (
|
||||||
?.trim();
|
context.payload as IssuesLabeledEvent
|
||||||
|
).label?.name?.trim();
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`Checking label trigger: expected='${triggerLabel}', applied='${appliedLabel}'`,
|
`Checking label trigger: expected='${triggerLabel}', applied='${appliedLabel}'`,
|
||||||
@@ -55,7 +56,9 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
|
|||||||
if (
|
if (
|
||||||
triggerLabel &&
|
triggerLabel &&
|
||||||
appliedLabel &&
|
appliedLabel &&
|
||||||
triggerLabel.localeCompare(appliedLabel, undefined, { sensitivity: "accent" }) === 0
|
triggerLabel.localeCompare(appliedLabel, undefined, {
|
||||||
|
sensitivity: "accent",
|
||||||
|
}) === 0
|
||||||
) {
|
) {
|
||||||
console.log(`Issue labeled with trigger label '${triggerLabel}'`);
|
console.log(`Issue labeled with trigger label '${triggerLabel}'`);
|
||||||
return true;
|
return true;
|
||||||
@@ -115,9 +118,10 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
|
|||||||
|
|
||||||
// Check if trigger user is in requested reviewers (treat same as mention in text)
|
// Check if trigger user is in requested reviewers (treat same as mention in text)
|
||||||
const triggerUser = triggerPhrase.replace(/^@/, "");
|
const triggerUser = triggerPhrase.replace(/^@/, "");
|
||||||
const requestedReviewers = context.payload.pull_request.requested_reviewers || [];
|
const requestedReviewers =
|
||||||
const isReviewerRequested = requestedReviewers.some(reviewer =>
|
context.payload.pull_request.requested_reviewers || [];
|
||||||
'login' in reviewer && reviewer.login === triggerUser
|
const isReviewerRequested = requestedReviewers.some(
|
||||||
|
(reviewer) => "login" in reviewer && reviewer.login === triggerUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isReviewerRequested) {
|
if (isReviewerRequested) {
|
||||||
@@ -131,9 +135,12 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
|
|||||||
// Check for pull request review body trigger
|
// Check for pull request review body trigger
|
||||||
if (
|
if (
|
||||||
isPullRequestReviewEvent(context) &&
|
isPullRequestReviewEvent(context) &&
|
||||||
(context.eventAction === "submitted" || context.eventAction === "edited")
|
(context.eventAction === "submitted" ||
|
||||||
|
context.eventAction === "edited" ||
|
||||||
|
context.eventAction === "reviewed")
|
||||||
) {
|
) {
|
||||||
const reviewBody = context.payload.review.body || "";
|
const reviewBody =
|
||||||
|
context.payload.review?.body ?? context.payload.review?.content ?? "";
|
||||||
// Check for exact match with word boundaries or punctuation
|
// Check for exact match with word boundaries or punctuation
|
||||||
const regex = new RegExp(
|
const regex = new RegExp(
|
||||||
`(^|\\s)${escapeRegExp(triggerPhrase)}([\\s.,!?;:]|$)`,
|
`(^|\\s)${escapeRegExp(triggerPhrase)}([\\s.,!?;:]|$)`,
|
||||||
@@ -153,7 +160,7 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
|
|||||||
) {
|
) {
|
||||||
const commentBody = isIssueCommentEvent(context)
|
const commentBody = isIssueCommentEvent(context)
|
||||||
? context.payload.comment.body
|
? context.payload.comment.body
|
||||||
: context.payload.comment.body;
|
: (context.payload.comment?.body ?? context.payload.review?.content);
|
||||||
// Check for exact match with word boundaries or punctuation
|
// Check for exact match with word boundaries or punctuation
|
||||||
const regex = new RegExp(
|
const regex = new RegExp(
|
||||||
`(^|\\s)${escapeRegExp(triggerPhrase)}([\\s.,!?;:]|$)`,
|
`(^|\\s)${escapeRegExp(triggerPhrase)}([\\s.,!?;:]|$)`,
|
||||||
|
|||||||
Reference in New Issue
Block a user