Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 22 additions & 28 deletions src/create-prompt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ export function buildDisallowedToolsString(
// If user has explicitly allowed some hardcoded disallowed tools, remove them from disallowed list
const allowedList = normalizeToolList(allowedTools);
if (allowedList.length > 0) {
disallowedTools = disallowedTools.filter((tool) => !allowedList.includes(tool));
disallowedTools = disallowedTools.filter(
(tool) => !allowedList.includes(tool),
);
}

let allDisallowedTools = disallowedTools.join(",");
Expand Down Expand Up @@ -164,16 +166,20 @@ export function prepareContext(
let commentBody: string | undefined;

if (isIssueCommentEvent(context)) {
commentId = context.payload.comment.id.toString();
commentBody = context.payload.comment.body;
triggerUsername = context.payload.comment.user.login;
commentId = context.payload.comment?.id?.toString();
commentBody = context.payload.comment?.body;
triggerUsername = context.payload.comment?.user?.login;
} else if (isPullRequestReviewEvent(context)) {
commentBody = context.payload.review.body ?? "";
triggerUsername = context.payload.review.user.login;
commentBody =
context.payload.review?.body ?? context.payload.review?.content ?? "";
triggerUsername =
context.payload.review?.user?.login ?? context.payload.sender?.login;
} else if (isPullRequestReviewCommentEvent(context)) {
commentId = context.payload.comment.id.toString();
commentBody = context.payload.comment.body;
triggerUsername = context.payload.comment.user.login;
commentId = context.payload.comment?.id?.toString();
commentBody =
context.payload.comment?.body ?? context.payload.review?.content;
triggerUsername =
context.payload.comment?.user?.login ?? context.payload.sender?.login;
} else if (isIssuesEvent(context)) {
triggerUsername = context.payload.issue.user.login;
}
Expand Down Expand Up @@ -595,18 +601,7 @@ ${sanitizeContent(context.directPrompt)}
</direct_prompt>`
: ""
}
${
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>
${`<comment_tool_info>
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:
Expand All @@ -617,8 +612,7 @@ Tool usage example for mcp__gitea__update_issue_comment:
"body": "Your comment text here"
}
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.

Expand All @@ -632,7 +626,7 @@ Follow these steps:
1. Create a Todo List:
- Use your Gitea comment to maintain a detailed task list based on the request.
- 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:
- Analyze the pre-fetched data provided above.
Expand Down Expand Up @@ -738,8 +732,8 @@ ${!eventData.isPR || !eventData.claudeBranch ? `6. Final Update:` : `5. Final Up

Important Notes:
- 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}.
- 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." : ""}
- 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.` : ""}
- 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;" />
${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.`}
Expand All @@ -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"}
- 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.
- 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"}.
- 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." : ""}
- 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.` : ""}
- 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;" />
${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.`}
Expand Down
25 changes: 16 additions & 9 deletions src/entrypoints/update-comment-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,22 @@ async function run() {
// GitHub has separate ID namespaces for review comments and issue comments
// We need to use the correct API based on the event type
if (isPullRequestReviewCommentEvent(context)) {
// For PR review comments, use the pulls API
console.log(`Fetching PR review comment ${commentId}`);
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");
// 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}`);
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");
} catch {
console.log(
"PR review comment not found, falling back to issue comment",
);
}
}

// For all other event types, use the issues API
Expand Down
24 changes: 20 additions & 4 deletions src/github/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ import type {
import type { ModeName } from "../modes/types";
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 = {
runId: string;
eventName: string;
Expand All @@ -24,8 +38,8 @@ export type ParsedGitHubContext = {
| IssuesEvent
| IssueCommentEvent
| PullRequestEvent
| PullRequestReviewEvent
| PullRequestReviewCommentEvent;
| GiteaPullRequestReviewEvent
| GiteaPullRequestReviewCommentEvent;
entityNumber: number;
isPR: boolean;
inputs: {
Expand Down Expand Up @@ -181,13 +195,15 @@ export function isPullRequestEvent(

export function isPullRequestReviewEvent(
context: ParsedGitHubContext,
): context is ParsedGitHubContext & { payload: PullRequestReviewEvent } {
): context is ParsedGitHubContext & { payload: GiteaPullRequestReviewEvent } {
return context.eventName === "pull_request_review";
}

export function isPullRequestReviewCommentEvent(
context: ParsedGitHubContext,
): context is ParsedGitHubContext & { payload: PullRequestReviewCommentEvent } {
): context is ParsedGitHubContext & {
payload: GiteaPullRequestReviewCommentEvent;
} {
return context.eventName === "pull_request_review_comment";
}

Expand Down
8 changes: 5 additions & 3 deletions src/github/operations/comment-logic.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { GITEA_SERVER_URL } from "../api/config";

export type ExecutionDetails = {
cost_usd?: number;
duration_ms?: number;
Expand Down Expand Up @@ -166,7 +164,11 @@ export function updateCommentBody(input: CommentUpdateInput): string {
.filter((segment) => segment);
const [owner, repo] = segments;
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) {
console.warn(`Failed to derive branch URL from job URL: ${error}`);
Expand Down
5 changes: 4 additions & 1 deletion src/github/operations/comments/create-initial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ export async function createInitialComment(
console.log(`Repository: ${owner}/${repo}`);

// 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`);
response = await api.customRequest(
"POST",
Expand Down
25 changes: 16 additions & 9 deletions src/github/validation/trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
// Check for issue label trigger
if (isIssuesEvent(context) && context.eventAction === "labeled") {
const triggerLabel = context.inputs.labelTrigger?.trim();
const appliedLabel = (context.payload as IssuesLabeledEvent).label?.name
?.trim();
const appliedLabel = (
context.payload as IssuesLabeledEvent
).label?.name?.trim();

console.log(
`Checking label trigger: expected='${triggerLabel}', applied='${appliedLabel}'`,
Expand All @@ -55,7 +56,9 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
if (
triggerLabel &&
appliedLabel &&
triggerLabel.localeCompare(appliedLabel, undefined, { sensitivity: "accent" }) === 0
triggerLabel.localeCompare(appliedLabel, undefined, {
sensitivity: "accent",
}) === 0
) {
console.log(`Issue labeled with trigger label '${triggerLabel}'`);
return true;
Expand Down Expand Up @@ -115,9 +118,10 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean {

// Check if trigger user is in requested reviewers (treat same as mention in text)
const triggerUser = triggerPhrase.replace(/^@/, "");
const requestedReviewers = context.payload.pull_request.requested_reviewers || [];
const isReviewerRequested = requestedReviewers.some(reviewer =>
'login' in reviewer && reviewer.login === triggerUser
const requestedReviewers =
context.payload.pull_request.requested_reviewers || [];
const isReviewerRequested = requestedReviewers.some(
(reviewer) => "login" in reviewer && reviewer.login === triggerUser,
);

if (isReviewerRequested) {
Expand All @@ -131,9 +135,12 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
// Check for pull request review body trigger
if (
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
const regex = new RegExp(
`(^|\\s)${escapeRegExp(triggerPhrase)}([\\s.,!?;:]|$)`,
Expand All @@ -153,7 +160,7 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
) {
const commentBody = isIssueCommentEvent(context)
? 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
const regex = new RegExp(
`(^|\\s)${escapeRegExp(triggerPhrase)}([\\s.,!?;:]|$)`,
Expand Down