* feat: enhance mode routing with track_progress and context preservation This PR implements enhanced mode routing to address two critical v1 migration issues: 1. Lost GitHub context when using custom prompts in tag mode 2. Missing tracking comments for automatic PR reviews Changes: - Add track_progress input to force tag mode with tracking comments for PR/issue events - Support custom prompt injection in tag mode via <custom_instructions> section - Inject GitHub context as environment variables in agent mode - Validate track_progress usage (only allowed for PR/issue events) - Comprehensive test coverage for new routing logic Event Routing: - Comment events: Default to tag mode, switch to agent with explicit prompt - PR/Issue events: Default to agent mode, switch to tag mode with track_progress - Custom prompts can now be used in tag mode without losing context This ensures backward compatibility while solving context preservation and tracking visibility issues reported in discussions #490 and #491. * formatting * fix: address review comments - Simplify track_progress description to be more general - Move import to top of types.ts file * revert: keep detailed track_progress description The original description provides clarity about which specific event actions are supported. * fix: add GitHub CI MCP tools to tag mode allowed list Claude was trying to use CI status tools but they weren't in the allowed list for tag mode, causing permission errors. This fix adds the CI tools so Claude can check workflow status when reviewing PRs. * fix: provide explicit git base branch reference to prevent PR review errors - Tell Claude to use 'origin/{baseBranch}' instead of assuming 'main' - Add explicit instructions for git diff/log commands with correct base branch - Fixes 'fatal: ambiguous argument main..HEAD' error in fork environments - Claude was autonomously running git diff main..HEAD when reviewing PRs * fix prompt generation * ci pass --------- Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
196 lines
6.0 KiB
TypeScript
196 lines
6.0 KiB
TypeScript
import * as core from "@actions/core";
|
|
import { mkdir, writeFile } from "fs/promises";
|
|
import type { Mode, ModeOptions, ModeResult } from "../types";
|
|
import type { PreparedContext } from "../../create-prompt/types";
|
|
import { prepareMcpConfig } from "../../mcp/install-mcp-server";
|
|
import { parseAllowedTools } from "./parse-tools";
|
|
import { configureGitAuth } from "../../github/operations/git-config";
|
|
import type { GitHubContext } from "../../github/context";
|
|
import { isEntityContext } from "../../github/context";
|
|
|
|
/**
|
|
* Extract GitHub context as environment variables for agent mode
|
|
*/
|
|
function extractGitHubContext(context: GitHubContext): Record<string, string> {
|
|
const envVars: Record<string, string> = {};
|
|
|
|
// Basic repository info
|
|
envVars.GITHUB_REPOSITORY = context.repository.full_name;
|
|
envVars.GITHUB_TRIGGER_ACTOR = context.actor;
|
|
envVars.GITHUB_EVENT_NAME = context.eventName;
|
|
|
|
// Entity-specific context (PR/issue numbers, branches, etc.)
|
|
if (isEntityContext(context)) {
|
|
if (context.isPR) {
|
|
envVars.GITHUB_PR_NUMBER = String(context.entityNumber);
|
|
|
|
// Extract branch info from payload if available
|
|
if (
|
|
context.payload &&
|
|
"pull_request" in context.payload &&
|
|
context.payload.pull_request
|
|
) {
|
|
envVars.GITHUB_BASE_REF = context.payload.pull_request.base?.ref || "";
|
|
envVars.GITHUB_HEAD_REF = context.payload.pull_request.head?.ref || "";
|
|
}
|
|
} else {
|
|
envVars.GITHUB_ISSUE_NUMBER = String(context.entityNumber);
|
|
}
|
|
}
|
|
|
|
return envVars;
|
|
}
|
|
|
|
/**
|
|
* Agent mode implementation.
|
|
*
|
|
* This mode runs whenever an explicit prompt is provided in the workflow configuration.
|
|
* It bypasses the standard @claude mention checking and comment tracking used by tag mode,
|
|
* providing direct access to Claude Code for automation workflows.
|
|
*/
|
|
export const agentMode: Mode = {
|
|
name: "agent",
|
|
description: "Direct automation mode for explicit prompts",
|
|
|
|
shouldTrigger(context) {
|
|
// Only trigger when an explicit prompt is provided
|
|
return !!context.inputs?.prompt;
|
|
},
|
|
|
|
prepareContext(context) {
|
|
// Agent mode doesn't use comment tracking or branch management
|
|
return {
|
|
mode: "agent",
|
|
githubContext: context,
|
|
};
|
|
},
|
|
|
|
getAllowedTools() {
|
|
return [];
|
|
},
|
|
|
|
getDisallowedTools() {
|
|
return [];
|
|
},
|
|
|
|
shouldCreateTrackingComment() {
|
|
return false;
|
|
},
|
|
|
|
async prepare({
|
|
context,
|
|
githubToken,
|
|
octokit,
|
|
}: ModeOptions): Promise<ModeResult> {
|
|
// Configure git authentication for agent mode (same as tag mode)
|
|
if (!context.inputs.useCommitSigning) {
|
|
try {
|
|
// Get the authenticated user (will be claude[bot] when using Claude App token)
|
|
const { data: authenticatedUser } =
|
|
await octokit.rest.users.getAuthenticated();
|
|
const user = {
|
|
login: authenticatedUser.login,
|
|
id: authenticatedUser.id,
|
|
};
|
|
|
|
// Use the shared git configuration function
|
|
await configureGitAuth(githubToken, context, user);
|
|
} catch (error) {
|
|
console.error("Failed to configure git authentication:", error);
|
|
// Continue anyway - git operations may still work with default config
|
|
}
|
|
}
|
|
|
|
// Create prompt directory
|
|
await mkdir(`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts`, {
|
|
recursive: true,
|
|
});
|
|
|
|
// Write the prompt file - use the user's prompt directly
|
|
const promptContent =
|
|
context.inputs.prompt ||
|
|
`Repository: ${context.repository.owner}/${context.repository.repo}`;
|
|
|
|
await writeFile(
|
|
`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts/claude-prompt.txt`,
|
|
promptContent,
|
|
);
|
|
|
|
// Parse allowed tools from user's claude_args
|
|
const userClaudeArgs = process.env.CLAUDE_ARGS || "";
|
|
const allowedTools = parseAllowedTools(userClaudeArgs);
|
|
|
|
// Check for branch info from environment variables (useful for auto-fix workflows)
|
|
const claudeBranch = process.env.CLAUDE_BRANCH || undefined;
|
|
const baseBranch =
|
|
process.env.BASE_BRANCH || context.inputs.baseBranch || "main";
|
|
|
|
// Detect current branch from GitHub environment
|
|
const currentBranch =
|
|
claudeBranch ||
|
|
process.env.GITHUB_HEAD_REF ||
|
|
process.env.GITHUB_REF_NAME ||
|
|
"main";
|
|
|
|
// Get our GitHub MCP servers config
|
|
const ourMcpConfig = await prepareMcpConfig({
|
|
githubToken,
|
|
owner: context.repository.owner,
|
|
repo: context.repository.repo,
|
|
branch: currentBranch,
|
|
baseBranch: baseBranch,
|
|
claudeCommentId: undefined, // No tracking comment in agent mode
|
|
allowedTools,
|
|
context,
|
|
});
|
|
|
|
// Build final claude_args with multiple --mcp-config flags
|
|
let claudeArgs = "";
|
|
|
|
// Add our GitHub servers config if we have any
|
|
const ourConfig = JSON.parse(ourMcpConfig);
|
|
if (ourConfig.mcpServers && Object.keys(ourConfig.mcpServers).length > 0) {
|
|
const escapedOurConfig = ourMcpConfig.replace(/'/g, "'\\''");
|
|
claudeArgs = `--mcp-config '${escapedOurConfig}'`;
|
|
}
|
|
|
|
// Append user's claude_args (which may have more --mcp-config flags)
|
|
claudeArgs = `${claudeArgs} ${userClaudeArgs}`.trim();
|
|
|
|
core.setOutput("claude_args", claudeArgs);
|
|
|
|
return {
|
|
commentId: undefined,
|
|
branchInfo: {
|
|
baseBranch: baseBranch,
|
|
currentBranch: baseBranch, // Use base branch as current when creating new branch
|
|
claudeBranch: claudeBranch,
|
|
},
|
|
mcpConfig: ourMcpConfig,
|
|
};
|
|
},
|
|
|
|
generatePrompt(context: PreparedContext): string {
|
|
// Inject GitHub context as environment variables
|
|
if (context.githubContext) {
|
|
const envVars = extractGitHubContext(context.githubContext);
|
|
for (const [key, value] of Object.entries(envVars)) {
|
|
core.exportVariable(key, value);
|
|
}
|
|
}
|
|
|
|
// Agent mode uses prompt field
|
|
if (context.prompt) {
|
|
return context.prompt;
|
|
}
|
|
|
|
// Minimal fallback - repository is a string in PreparedContext
|
|
return `Repository: ${context.repository}`;
|
|
},
|
|
|
|
getSystemPrompt() {
|
|
// Agent mode doesn't need additional system prompts
|
|
return undefined;
|
|
},
|
|
};
|