mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 14:24:13 +08:00
Add detection logic to recognize actionable suggestions in PR comments, even when they come from bot accounts like claude[bot]. This addresses the issue where the autofix workflow incorrectly classified clear bug fix suggestions as not actionable. New features: - detectActionableSuggestion(): Analyzes comment body for actionable patterns - Detects GitHub inline committable suggestions (```suggestion blocks) - Detects clear bug fix language patterns (e.g., "should be", "change to", "replace with") - Detects code alternatives and fix recommendations - Returns confidence level (high/medium/low) and reason for the determination - checkContainsActionableSuggestion(): Context-aware wrapper for GitHub events - checkContainsTriggerOrActionableSuggestion(): Combined trigger and suggestion check - checkIsActionableForAutofix(): Convenience function for autofix workflows - extractSuggestionCode(): Extracts code from GitHub suggestion blocks This enables workflows to automatically apply suggestions from code review comments, improving the developer experience for PR reviews.
240 lines
7.4 KiB
TypeScript
240 lines
7.4 KiB
TypeScript
#!/usr/bin/env bun
|
|
|
|
import * as core from "@actions/core";
|
|
import {
|
|
isIssuesEvent,
|
|
isIssuesAssignedEvent,
|
|
isIssueCommentEvent,
|
|
isPullRequestEvent,
|
|
isPullRequestReviewEvent,
|
|
isPullRequestReviewCommentEvent,
|
|
} from "../context";
|
|
import type { ParsedGitHubContext } from "../context";
|
|
import {
|
|
detectActionableSuggestion,
|
|
isCommentActionableForAutofix,
|
|
type ActionableSuggestionResult,
|
|
} from "../../utils/detect-actionable-suggestion";
|
|
|
|
export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
|
|
const {
|
|
inputs: { assigneeTrigger, labelTrigger, triggerPhrase, prompt },
|
|
} = context;
|
|
|
|
// If prompt is provided, always trigger
|
|
if (prompt) {
|
|
console.log(`Prompt provided, triggering action`);
|
|
return true;
|
|
}
|
|
|
|
// Check for assignee trigger
|
|
if (isIssuesAssignedEvent(context)) {
|
|
// Remove @ symbol from assignee_trigger if present
|
|
let triggerUser = assigneeTrigger.replace(/^@/, "");
|
|
const assigneeUsername = context.payload.assignee?.login || "";
|
|
|
|
if (triggerUser && assigneeUsername === triggerUser) {
|
|
console.log(`Issue assigned to trigger user '${triggerUser}'`);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check for label trigger
|
|
if (isIssuesEvent(context) && context.eventAction === "labeled") {
|
|
const labelName = (context.payload as any).label?.name || "";
|
|
|
|
if (labelTrigger && labelName === labelTrigger) {
|
|
console.log(`Issue labeled with trigger label '${labelTrigger}'`);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check for issue body and title trigger on issue creation
|
|
if (isIssuesEvent(context) && context.eventAction === "opened") {
|
|
const issueBody = context.payload.issue.body || "";
|
|
const issueTitle = context.payload.issue.title || "";
|
|
// Check for exact match with word boundaries or punctuation
|
|
const regex = new RegExp(
|
|
`(^|\\s)${escapeRegExp(triggerPhrase)}([\\s.,!?;:]|$)`,
|
|
);
|
|
|
|
// Check in body
|
|
if (regex.test(issueBody)) {
|
|
console.log(
|
|
`Issue body contains exact trigger phrase '${triggerPhrase}'`,
|
|
);
|
|
return true;
|
|
}
|
|
|
|
// Check in title
|
|
if (regex.test(issueTitle)) {
|
|
console.log(
|
|
`Issue title contains exact trigger phrase '${triggerPhrase}'`,
|
|
);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check for pull request body and title trigger
|
|
if (isPullRequestEvent(context)) {
|
|
const prBody = context.payload.pull_request.body || "";
|
|
const prTitle = context.payload.pull_request.title || "";
|
|
// Check for exact match with word boundaries or punctuation
|
|
const regex = new RegExp(
|
|
`(^|\\s)${escapeRegExp(triggerPhrase)}([\\s.,!?;:]|$)`,
|
|
);
|
|
|
|
// Check in body
|
|
if (regex.test(prBody)) {
|
|
console.log(
|
|
`Pull request body contains exact trigger phrase '${triggerPhrase}'`,
|
|
);
|
|
return true;
|
|
}
|
|
|
|
// Check in title
|
|
if (regex.test(prTitle)) {
|
|
console.log(
|
|
`Pull request title contains exact trigger phrase '${triggerPhrase}'`,
|
|
);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check for pull request review body trigger
|
|
if (
|
|
isPullRequestReviewEvent(context) &&
|
|
(context.eventAction === "submitted" || context.eventAction === "edited")
|
|
) {
|
|
const reviewBody = context.payload.review.body || "";
|
|
// Check for exact match with word boundaries or punctuation
|
|
const regex = new RegExp(
|
|
`(^|\\s)${escapeRegExp(triggerPhrase)}([\\s.,!?;:]|$)`,
|
|
);
|
|
if (regex.test(reviewBody)) {
|
|
console.log(
|
|
`Pull request review contains exact trigger phrase '${triggerPhrase}'`,
|
|
);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check for comment trigger
|
|
if (
|
|
isIssueCommentEvent(context) ||
|
|
isPullRequestReviewCommentEvent(context)
|
|
) {
|
|
const commentBody = isIssueCommentEvent(context)
|
|
? context.payload.comment.body
|
|
: context.payload.comment.body;
|
|
// Check for exact match with word boundaries or punctuation
|
|
const regex = new RegExp(
|
|
`(^|\\s)${escapeRegExp(triggerPhrase)}([\\s.,!?;:]|$)`,
|
|
);
|
|
if (regex.test(commentBody)) {
|
|
console.log(`Comment contains exact trigger phrase '${triggerPhrase}'`);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
console.log(`No trigger was met for ${triggerPhrase}`);
|
|
|
|
return false;
|
|
}
|
|
|
|
export function escapeRegExp(string: string) {
|
|
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
}
|
|
|
|
export async function checkTriggerAction(context: ParsedGitHubContext) {
|
|
const containsTrigger = checkContainsTrigger(context);
|
|
core.setOutput("contains_trigger", containsTrigger.toString());
|
|
return containsTrigger;
|
|
}
|
|
|
|
/**
|
|
* Checks if the context contains an actionable suggestion that can be automatically fixed.
|
|
* This is useful for autofix workflows that want to respond to code review suggestions,
|
|
* even when they come from bot accounts like claude[bot].
|
|
*
|
|
* @param context - The parsed GitHub context
|
|
* @returns Detection result with confidence level and reason
|
|
*/
|
|
export function checkContainsActionableSuggestion(
|
|
context: ParsedGitHubContext,
|
|
): ActionableSuggestionResult {
|
|
// Extract comment body based on event type
|
|
let commentBody: string | undefined;
|
|
|
|
if (isPullRequestReviewCommentEvent(context)) {
|
|
commentBody = context.payload.comment.body;
|
|
} else if (isIssueCommentEvent(context)) {
|
|
commentBody = context.payload.comment.body;
|
|
} else if (isPullRequestReviewEvent(context)) {
|
|
commentBody = context.payload.review.body ?? undefined;
|
|
}
|
|
|
|
return detectActionableSuggestion(commentBody);
|
|
}
|
|
|
|
/**
|
|
* Enhanced trigger check that also considers actionable suggestions.
|
|
* This function first checks for the standard trigger phrase, and if not found,
|
|
* optionally checks for actionable suggestions when `checkSuggestions` is true.
|
|
*
|
|
* @param context - The parsed GitHub context
|
|
* @param checkSuggestions - Whether to also check for actionable suggestions (default: false)
|
|
* @returns Whether the action should be triggered
|
|
*/
|
|
export function checkContainsTriggerOrActionableSuggestion(
|
|
context: ParsedGitHubContext,
|
|
checkSuggestions: boolean = false,
|
|
): boolean {
|
|
// First, check for standard trigger
|
|
if (checkContainsTrigger(context)) {
|
|
return true;
|
|
}
|
|
|
|
// If checkSuggestions is enabled, also check for actionable suggestions
|
|
if (checkSuggestions) {
|
|
const suggestionResult = checkContainsActionableSuggestion(context);
|
|
if (suggestionResult.isActionable) {
|
|
console.log(
|
|
`Comment contains actionable suggestion: ${suggestionResult.reason} (confidence: ${suggestionResult.confidence})`,
|
|
);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if a PR comment is actionable for autofix purposes.
|
|
* This is a convenience function for workflows that want to automatically
|
|
* apply suggestions from code review comments.
|
|
*
|
|
* @param context - The parsed GitHub context
|
|
* @returns Whether the comment should be treated as actionable for autofix
|
|
*/
|
|
export function checkIsActionableForAutofix(
|
|
context: ParsedGitHubContext,
|
|
): boolean {
|
|
// Only applicable to PR review comment events
|
|
if (!isPullRequestReviewCommentEvent(context)) {
|
|
return false;
|
|
}
|
|
|
|
const commentBody = context.payload.comment.body;
|
|
const authorUsername = context.payload.comment.user?.login;
|
|
|
|
return isCommentActionableForAutofix(commentBody, authorUsername);
|
|
}
|
|
|
|
// Re-export the types and functions from the utility module for convenience
|
|
export {
|
|
detectActionableSuggestion,
|
|
isCommentActionableForAutofix,
|
|
type ActionableSuggestionResult,
|
|
} from "../../utils/detect-actionable-suggestion";
|