diff --git a/examples/workflow-dispatch-agent.yml b/examples/workflow-dispatch-agent.yml
new file mode 100644
index 00000000..af651025
--- /dev/null
+++ b/examples/workflow-dispatch-agent.yml
@@ -0,0 +1,49 @@
+name: Claude Automation Workflow
+
+on:
+ workflow_dispatch:
+ inputs:
+ task:
+ description: "Task for Claude to perform"
+ required: true
+ type: string
+ default: "Update dependencies and run tests"
+ branch:
+ description: "Branch to work on"
+ required: false
+ type: string
+ default: "main"
+
+jobs:
+ claude-automation:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ pull-requests: write
+ issues: write
+ id-token: write
+ actions: read
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.branch || 'main' }}
+
+ - name: Run Claude Code in Agent Mode
+ uses: anthropics/claude-code-action@beta
+ with:
+ mode: agent
+ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
+ override_prompt: |
+ You are running in an automated workflow context.
+
+ Task: ${{ github.event.inputs.task }}
+
+ Current branch: ${{ github.event.inputs.branch || 'main' }}
+
+ Please complete the requested task. Remember:
+ - You have full access to the repository
+ - You can make commits and push changes
+ - Focus on the specific task provided
+ - Provide clear feedback about what you're doing
diff --git a/src/create-prompt/index.ts b/src/create-prompt/index.ts
index 27b32816..d75cf4bd 100644
--- a/src/create-prompt/index.ts
+++ b/src/create-prompt/index.ts
@@ -34,6 +34,16 @@ const BASE_ALLOWED_TOOLS = [
];
const DISALLOWED_TOOLS = ["WebSearch", "WebFetch"];
+function getEntityNumberXml(eventData: EventData): string {
+ if (eventData.isPR && "prNumber" in eventData) {
+ return `${eventData.prNumber}`;
+ }
+ if ("issueNumber" in eventData) {
+ return `${eventData.issueNumber}`;
+ }
+ return "";
+}
+
export function buildAllowedToolsString(
customAllowedTools?: string[],
includeActionsTools: boolean = false,
@@ -125,8 +135,10 @@ export function prepareContext(
const isPR = context.isPR;
// Get PR/Issue number from entityNumber
- const prNumber = isPR ? context.entityNumber.toString() : undefined;
- const issueNumber = !isPR ? context.entityNumber.toString() : undefined;
+ const prNumber =
+ isPR && context.entityNumber ? context.entityNumber.toString() : undefined;
+ const issueNumber =
+ !isPR && context.entityNumber ? context.entityNumber.toString() : undefined;
// Extract trigger username and comment data based on event type
let triggerUsername: string | undefined;
@@ -338,6 +350,24 @@ export function prepareContext(
};
break;
+ case "workflow_dispatch":
+ eventData = {
+ eventName: "workflow_dispatch",
+ isPR: false,
+ ...(baseBranch && { baseBranch }),
+ ...(claudeBranch && { claudeBranch }),
+ };
+ break;
+
+ case "schedule":
+ eventData = {
+ eventName: "schedule",
+ isPR: false,
+ ...(baseBranch && { baseBranch }),
+ ...(claudeBranch && { claudeBranch }),
+ };
+ break;
+
default:
throw new Error(`Unsupported event type: ${eventName}`);
}
@@ -400,6 +430,18 @@ export function getEventTypeAndContext(envVars: PreparedContext): {
: `pull request event`,
};
+ case "workflow_dispatch":
+ return {
+ eventType: "WORKFLOW_DISPATCH",
+ triggerContext: `workflow dispatch event`,
+ };
+
+ case "schedule":
+ return {
+ eventType: "SCHEDULE",
+ triggerContext: `scheduled automation event`,
+ };
+
default:
throw new Error(`Unexpected event type`);
}
@@ -407,11 +449,12 @@ export function getEventTypeAndContext(envVars: PreparedContext): {
function getCommitInstructions(
eventData: EventData,
- githubData: FetchDataResult,
+ githubData: FetchDataResult | null,
context: PreparedContext,
useCommitSigning: boolean,
): string {
const coAuthorLine =
+ githubData &&
(githubData.triggerDisplayName ?? context.triggerUsername !== "Unknown")
? `Co-authored-by: ${githubData.triggerDisplayName ?? context.triggerUsername} <${context.triggerUsername}@users.noreply.github.com>`
: "";
@@ -466,8 +509,26 @@ function getCommitInstructions(
function substitutePromptVariables(
template: string,
context: PreparedContext,
- githubData: FetchDataResult,
+ githubData: FetchDataResult | null,
): string {
+ // Handle automation events without GitHub data
+ if (!githubData) {
+ const { eventData } = context;
+ const variables: Record = {
+ EVENT_TYPE: eventData.eventName,
+ REPOSITORY: context.repository,
+ TRIGGER_USERNAME: context.triggerUsername ?? "automation",
+ CURRENT_BRANCH: eventData.claudeBranch || eventData.baseBranch || "main",
+ BASE_BRANCH: eventData.baseBranch || "main",
+ };
+
+ return Object.entries(variables).reduce(
+ (prompt, [key, value]) =>
+ prompt.replace(new RegExp(`{{${key}}}`, "g"), value),
+ template,
+ );
+ }
+
const { contextData, comments, reviewData, changedFilesWithSHA } = githubData;
const { eventData } = context;
@@ -528,7 +589,7 @@ function substitutePromptVariables(
export function generatePrompt(
context: PreparedContext,
- githubData: FetchDataResult,
+ githubData: FetchDataResult | null,
useCommitSigning: boolean,
): string {
if (context.overridePrompt) {
@@ -539,6 +600,59 @@ export function generatePrompt(
);
}
+ const { eventData } = context;
+
+ // Handle automation events that don't have GitHub data
+ if (!githubData) {
+ // For automation events, we have minimal context
+ const { eventType, triggerContext } = getEventTypeAndContext(context);
+
+ let promptContent = `You are Claude, an AI assistant designed to help with GitHub ${eventData.eventName === "workflow_dispatch" ? "workflow dispatch" : "scheduled automation"} tasks. Think carefully as you analyze the context and respond appropriately. Here's the context for your current task:
+
+
+Repository: ${context.repository}
+Event Type: ${eventData.eventName}
+Current Branch: ${eventData.claudeBranch || eventData.baseBranch || "main"}
+Base Branch: ${eventData.baseBranch || "main"}
+Actor: ${context.triggerUsername ?? "automation"}
+
+
+${eventType}
+false
+${triggerContext}
+${context.repository}
+${context.triggerUsername ?? "automation"}
+${
+ context.directPrompt
+ ? `
+IMPORTANT: The following are direct instructions from the automation workflow:
+
+${sanitizeContent(context.directPrompt)}
+`
+ : ""
+}
+
+
+You have been triggered by an automated workflow. Follow these guidelines:
+
+1. **Context**: You are running in an automated context without a specific issue or PR.
+2. **Branch**: You are currently on the ${eventData.claudeBranch || eventData.baseBranch || "main"} branch.
+3. **Tools**: You have access to file system and Git tools to make changes.
+${
+ useCommitSigning
+ ? "4. **Commits**: Use the MCP file operations server for signed commits."
+ : "4. **Commits**: You can commit changes directly using Git."
+}
+5. **Scope**: Focus on the task described${context.directPrompt ? " in the direct_prompt above" : ""}.
+
+Please proceed with the automated task.
+
+
+${context.customInstructions || ""}`;
+
+ return promptContent;
+ }
+
const {
contextData,
comments,
@@ -546,11 +660,10 @@ export function generatePrompt(
reviewData,
imageUrlMap,
} = githubData;
- const { eventData } = context;
const { eventType, triggerContext } = getEventTypeAndContext(context);
- const formattedContext = formatContext(contextData, eventData.isPR);
+ const formattedContext = formatContext(contextData, eventData.isPR ?? false);
const formattedComments = formatComments(comments, imageUrlMap);
const formattedReviewComments = eventData.isPR
? formatReviewComments(reviewData, imageUrlMap)
@@ -596,17 +709,13 @@ ${eventData.isPR ? formattedChangedFiles || "No files changed" : ""}
${imagesInfo}
${eventType}
-${eventData.isPR ? "true" : "false"}
+${(eventData.isPR ?? false) ? "true" : "false"}
${triggerContext}
${context.repository}
-${
- eventData.isPR
- ? `${eventData.prNumber}`
- : `${eventData.issueNumber ?? ""}`
-}
+${getEntityNumberXml(eventData)}
${context.claudeCommentId}
${context.triggerUsername ?? "Unknown"}
-${githubData.triggerDisplayName ?? context.triggerUsername ?? "Unknown"}
+${githubData?.triggerDisplayName ?? context.triggerUsername ?? "Unknown"}
${context.triggerPhrase}
${
(eventData.eventName === "issue_comment" ||
@@ -797,7 +906,7 @@ f. If you are unable to complete certain steps, such as running a linter or test
export async function createPrompt(
mode: Mode,
modeContext: ModeContext,
- githubData: FetchDataResult,
+ githubData: FetchDataResult | null,
context: ParsedGitHubContext,
) {
try {
diff --git a/src/create-prompt/types.ts b/src/create-prompt/types.ts
index e7a7130b..25e90064 100644
--- a/src/create-prompt/types.ts
+++ b/src/create-prompt/types.ts
@@ -88,6 +88,20 @@ type PullRequestEvent = {
baseBranch?: string;
};
+type WorkflowDispatchEvent = {
+ eventName: "workflow_dispatch";
+ isPR?: false;
+ baseBranch?: string;
+ claudeBranch?: string;
+};
+
+type ScheduleEvent = {
+ eventName: "schedule";
+ isPR?: false;
+ baseBranch?: string;
+ claudeBranch?: string;
+};
+
// Union type for all possible event types
export type EventData =
| PullRequestReviewCommentEvent
@@ -97,7 +111,9 @@ export type EventData =
| IssueOpenedEvent
| IssueAssignedEvent
| IssueLabeledEvent
- | PullRequestEvent;
+ | PullRequestEvent
+ | WorkflowDispatchEvent
+ | ScheduleEvent;
// Combined type with separate eventData field
export type PreparedContext = CommonFields & {
diff --git a/src/entrypoints/prepare.ts b/src/entrypoints/prepare.ts
index 6653c06a..7b80c6b5 100644
--- a/src/entrypoints/prepare.ts
+++ b/src/entrypoints/prepare.ts
@@ -7,17 +7,11 @@
import * as core from "@actions/core";
import { setupGitHubToken } from "../github/token";
-import { checkHumanActor } from "../github/validation/actor";
import { checkWritePermissions } from "../github/validation/permissions";
-import { createInitialComment } from "../github/operations/comments/create-initial";
-import { setupBranch } from "../github/operations/branch";
-import { configureGitAuth } from "../github/operations/git-config";
-import { prepareMcpConfig } from "../mcp/install-mcp-server";
import { createOctokit } from "../github/api/client";
-import { fetchGitHubData } from "../github/data/fetcher";
import { parseGitHubContext } from "../github/context";
import { getMode } from "../modes/registry";
-import { createPrompt } from "../create-prompt";
+import { prepare } from "../prepare";
async function run() {
try {
@@ -51,65 +45,16 @@ async function run() {
return;
}
- // Step 5: Check if actor is human
- await checkHumanActor(octokit.rest, context);
-
- // Step 6: Create initial tracking comment (mode-aware)
- // Some modes (e.g., agent mode) may not need tracking comments
- let commentId: number | undefined;
- let commentData:
- | Awaited>
- | undefined;
- if (mode.shouldCreateTrackingComment()) {
- commentData = await createInitialComment(octokit.rest, context);
- commentId = commentData.id;
- }
-
- // Step 7: Fetch GitHub data (once for both branch setup and prompt creation)
- const githubData = await fetchGitHubData({
- octokits: octokit,
- repository: `${context.repository.owner}/${context.repository.repo}`,
- prNumber: context.entityNumber.toString(),
- isPR: context.isPR,
- triggerUsername: context.actor,
- });
-
- // Step 8: Setup branch
- const branchInfo = await setupBranch(octokit, githubData, context);
-
- // Step 9: Configure git authentication if not using commit signing
- if (!context.inputs.useCommitSigning) {
- try {
- await configureGitAuth(githubToken, context, commentData?.user || null);
- } catch (error) {
- console.error("Failed to configure git authentication:", error);
- throw error;
- }
- }
-
- // Step 10: Create prompt file
- const modeContext = mode.prepareContext(context, {
- commentId,
- baseBranch: branchInfo.baseBranch,
- claudeBranch: branchInfo.claudeBranch,
- });
-
- await createPrompt(mode, modeContext, githubData, context);
-
- // Step 11: Get MCP configuration
- const additionalMcpConfig = process.env.MCP_CONFIG || "";
- const mcpConfig = await prepareMcpConfig({
- githubToken,
- owner: context.repository.owner,
- repo: context.repository.repo,
- branch: branchInfo.claudeBranch || branchInfo.currentBranch,
- baseBranch: branchInfo.baseBranch,
- additionalMcpConfig,
- claudeCommentId: commentId?.toString() || "",
- allowedTools: context.inputs.allowedTools,
+ // Step 5: Use the new modular prepare function
+ const result = await prepare({
context,
+ octokit,
+ mode,
+ githubToken,
});
- core.setOutput("mcp_config", mcpConfig);
+
+ // Set the MCP config output
+ core.setOutput("mcp_config", result.mcpConfig);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
core.setFailed(`Prepare step failed with error: ${errorMessage}`);
diff --git a/src/entrypoints/update-comment-link.ts b/src/entrypoints/update-comment-link.ts
index 85b24552..0e7c87db 100644
--- a/src/entrypoints/update-comment-link.ts
+++ b/src/entrypoints/update-comment-link.ts
@@ -24,6 +24,13 @@ async function run() {
const context = parseGitHubContext();
const { owner, repo } = context.repository;
+
+ // This script is only called for entity-based events
+ if (!context.entityNumber) {
+ throw new Error("update-comment-link requires an entityNumber");
+ }
+ const entityNumber = context.entityNumber;
+
const octokit = createOctokit(githubToken);
const serverUrl = GITHUB_SERVER_URL;
@@ -73,7 +80,7 @@ async function run() {
const { data: pr } = await octokit.rest.pulls.get({
owner,
repo,
- pull_number: context.entityNumber,
+ pull_number: entityNumber,
});
console.log(`PR state: ${pr.state}`);
console.log(`PR comments count: ${pr.comments}`);
diff --git a/src/github/context.ts b/src/github/context.ts
index 4e0d866e..26e5eead 100644
--- a/src/github/context.ts
+++ b/src/github/context.ts
@@ -7,6 +7,34 @@ import type {
PullRequestReviewEvent,
PullRequestReviewCommentEvent,
} from "@octokit/webhooks-types";
+
+// Custom types for GitHub Actions events that aren't webhooks
+export type WorkflowDispatchEvent = {
+ action?: never;
+ inputs?: Record;
+ ref?: string;
+ repository: {
+ name: string;
+ owner: {
+ login: string;
+ };
+ };
+ sender: {
+ login: string;
+ };
+ workflow: string;
+};
+
+export type ScheduleEvent = {
+ action?: never;
+ schedule?: string;
+ repository: {
+ name: string;
+ owner: {
+ login: string;
+ };
+ };
+};
import type { ModeName } from "../modes/types";
import { DEFAULT_MODE, isValidMode } from "../modes/registry";
@@ -25,9 +53,11 @@ export type ParsedGitHubContext = {
| IssueCommentEvent
| PullRequestEvent
| PullRequestReviewEvent
- | PullRequestReviewCommentEvent;
- entityNumber: number;
- isPR: boolean;
+ | PullRequestReviewCommentEvent
+ | WorkflowDispatchEvent
+ | ScheduleEvent;
+ entityNumber?: number;
+ isPR?: boolean;
inputs: {
mode: ModeName;
triggerPhrase: string;
@@ -129,6 +159,20 @@ export function parseGitHubContext(): ParsedGitHubContext {
isPR: true,
};
}
+ case "workflow_dispatch": {
+ return {
+ ...commonFields,
+ payload: context.payload as unknown as WorkflowDispatchEvent,
+ // No entityNumber or isPR for workflow_dispatch
+ };
+ }
+ case "schedule": {
+ return {
+ ...commonFields,
+ payload: context.payload as unknown as ScheduleEvent,
+ // No entityNumber or isPR for schedule
+ };
+ }
default:
throw new Error(`Unsupported event type: ${context.eventName}`);
}
diff --git a/src/github/operations/comments/create-initial.ts b/src/github/operations/comments/create-initial.ts
index 1243035b..8de1fbc5 100644
--- a/src/github/operations/comments/create-initial.ts
+++ b/src/github/operations/comments/create-initial.ts
@@ -22,6 +22,12 @@ export async function createInitialComment(
) {
const { owner, repo } = context.repository;
+ // Entity events should always have entityNumber
+ if (!context.entityNumber) {
+ throw new Error("createInitialComment requires an entityNumber");
+ }
+ const entityNumber = context.entityNumber;
+
const jobRunLink = createJobRunLink(owner, repo, context.runId);
const initialBody = createCommentBody(jobRunLink);
@@ -36,7 +42,7 @@ export async function createInitialComment(
const comments = await octokit.rest.issues.listComments({
owner,
repo,
- issue_number: context.entityNumber,
+ issue_number: entityNumber,
});
const existingComment = comments.data.find((comment) => {
const idMatch = comment.user?.id === CLAUDE_APP_BOT_ID;
@@ -59,7 +65,7 @@ export async function createInitialComment(
response = await octokit.rest.issues.createComment({
owner,
repo,
- issue_number: context.entityNumber,
+ issue_number: entityNumber,
body: initialBody,
});
}
@@ -68,7 +74,7 @@ export async function createInitialComment(
response = await octokit.rest.pulls.createReplyForReviewComment({
owner,
repo,
- pull_number: context.entityNumber,
+ pull_number: entityNumber,
comment_id: context.payload.comment.id,
body: initialBody,
});
@@ -77,7 +83,7 @@ export async function createInitialComment(
response = await octokit.rest.issues.createComment({
owner,
repo,
- issue_number: context.entityNumber,
+ issue_number: entityNumber,
body: initialBody,
});
}
@@ -95,7 +101,7 @@ export async function createInitialComment(
const response = await octokit.rest.issues.createComment({
owner,
repo,
- issue_number: context.entityNumber,
+ issue_number: entityNumber,
body: initialBody,
});
diff --git a/src/github/operations/default-branch.ts b/src/github/operations/default-branch.ts
new file mode 100644
index 00000000..51e32513
--- /dev/null
+++ b/src/github/operations/default-branch.ts
@@ -0,0 +1,13 @@
+import type { Octokit } from "@octokit/rest";
+
+export async function getDefaultBranch(
+ rest: Octokit,
+ owner: string,
+ repo: string,
+): Promise {
+ const repoResponse = await rest.repos.get({
+ owner,
+ repo,
+ });
+ return repoResponse.data.default_branch;
+}
diff --git a/src/mcp/install-mcp-server.ts b/src/mcp/install-mcp-server.ts
index 31c57dd7..6f3f85c9 100644
--- a/src/mcp/install-mcp-server.ts
+++ b/src/mcp/install-mcp-server.ts
@@ -141,7 +141,7 @@ export async function prepareMcpConfig(
GITHUB_TOKEN: process.env.ACTIONS_TOKEN,
REPO_OWNER: owner,
REPO_NAME: repo,
- PR_NUMBER: context.entityNumber.toString(),
+ PR_NUMBER: context.entityNumber?.toString() || "",
RUNNER_TEMP: process.env.RUNNER_TEMP || "/tmp",
},
};
diff --git a/src/prepare/automation-events.ts b/src/prepare/automation-events.ts
new file mode 100644
index 00000000..08f6c51f
--- /dev/null
+++ b/src/prepare/automation-events.ts
@@ -0,0 +1,70 @@
+/**
+ * Prepare logic for automation events (workflow_dispatch, schedule)
+ * These events don't have associated GitHub entities and require minimal setup
+ */
+
+import * as core from "@actions/core";
+import { prepareMcpConfig } from "../mcp/install-mcp-server";
+import { createPrompt } from "../create-prompt";
+import { getDefaultBranch } from "../github/operations/default-branch";
+import type { PrepareOptions, PrepareResult } from "./types";
+
+export async function prepareAutomationEvent({
+ context,
+ octokit,
+ mode,
+ githubToken,
+}: PrepareOptions): Promise {
+ // For automation events, we skip:
+ // - Human actor check (it's automation)
+ // - Tracking comment (no issue/PR to comment on)
+ // - GitHub data fetching (no entity to fetch)
+ // - Branch setup (use default branch or current branch)
+
+ // Get the default branch or use the one specified in inputs
+ const baseBranch =
+ context.inputs.baseBranch ||
+ (await getDefaultBranch(
+ octokit.rest,
+ context.repository.owner,
+ context.repository.repo,
+ ));
+
+ // For automation events, we stay on the current branch (typically main/master)
+ const branchInfo = {
+ baseBranch,
+ currentBranch: baseBranch,
+ claudeBranch: undefined,
+ };
+
+ // Create prompt file with minimal context
+ const modeContext = mode.prepareContext(context, {
+ baseBranch: branchInfo.baseBranch,
+ claudeBranch: branchInfo.claudeBranch,
+ });
+
+ // Pass null for githubData since automation events don't have associated entities
+ await createPrompt(mode, modeContext, null, context);
+
+ // Get MCP configuration
+ const additionalMcpConfig = process.env.MCP_CONFIG || "";
+ const mcpConfig = await prepareMcpConfig({
+ githubToken,
+ owner: context.repository.owner,
+ repo: context.repository.repo,
+ branch: branchInfo.currentBranch,
+ baseBranch: branchInfo.baseBranch,
+ additionalMcpConfig,
+ claudeCommentId: "",
+ allowedTools: context.inputs.allowedTools,
+ context,
+ });
+
+ core.setOutput("mcp_config", mcpConfig);
+
+ return {
+ commentId: undefined,
+ branchInfo,
+ mcpConfig,
+ };
+}
diff --git a/src/prepare/entity-events.ts b/src/prepare/entity-events.ts
new file mode 100644
index 00000000..867ae0c5
--- /dev/null
+++ b/src/prepare/entity-events.ts
@@ -0,0 +1,89 @@
+/**
+ * Prepare logic for entity-based events (issues, PRs, comments)
+ * These events have associated GitHub entities that need to be fetched and managed
+ */
+
+import * as core from "@actions/core";
+import { checkHumanActor } from "../github/validation/actor";
+import { createInitialComment } from "../github/operations/comments/create-initial";
+import { setupBranch } from "../github/operations/branch";
+import { configureGitAuth } from "../github/operations/git-config";
+import { prepareMcpConfig } from "../mcp/install-mcp-server";
+import { fetchGitHubData } from "../github/data/fetcher";
+import { createPrompt } from "../create-prompt";
+import type { PrepareOptions, PrepareResult } from "./types";
+
+export async function prepareEntityEvent({
+ context,
+ octokit,
+ mode,
+ githubToken,
+}: PrepareOptions): Promise {
+ // Check if actor is human
+ await checkHumanActor(octokit.rest, context);
+
+ // Create initial tracking comment (mode-aware)
+ let commentId: number | undefined;
+ let commentData: Awaited> | undefined;
+ if (mode.shouldCreateTrackingComment()) {
+ commentData = await createInitialComment(octokit.rest, context);
+ commentId = commentData.id;
+ }
+
+ // Fetch GitHub data - entity events always have entityNumber and isPR
+ if (!context.entityNumber || context.isPR === undefined) {
+ throw new Error("Entity events must have entityNumber and isPR defined");
+ }
+
+ const githubData = await fetchGitHubData({
+ octokits: octokit,
+ repository: `${context.repository.owner}/${context.repository.repo}`,
+ prNumber: context.entityNumber.toString(),
+ isPR: context.isPR,
+ triggerUsername: context.actor,
+ });
+
+ // Setup branch
+ const branchInfo = await setupBranch(octokit, githubData, context);
+
+ // Configure git authentication if not using commit signing
+ if (!context.inputs.useCommitSigning) {
+ try {
+ await configureGitAuth(githubToken, context, commentData?.user || null);
+ } catch (error) {
+ console.error("Failed to configure git authentication:", error);
+ throw error;
+ }
+ }
+
+ // Create prompt file
+ const modeContext = mode.prepareContext(context, {
+ commentId,
+ baseBranch: branchInfo.baseBranch,
+ claudeBranch: branchInfo.claudeBranch,
+ });
+
+ await createPrompt(mode, modeContext, githubData, context);
+
+ // Get MCP configuration
+ const additionalMcpConfig = process.env.MCP_CONFIG || "";
+ const mcpConfig = await prepareMcpConfig({
+ githubToken,
+ owner: context.repository.owner,
+ repo: context.repository.repo,
+ branch: branchInfo.claudeBranch || branchInfo.currentBranch,
+ baseBranch: branchInfo.baseBranch,
+ additionalMcpConfig,
+ claudeCommentId: commentId?.toString() || "",
+ allowedTools: context.inputs.allowedTools,
+ context,
+ });
+
+ core.setOutput("mcp_config", mcpConfig);
+
+ return {
+ commentId,
+ branchInfo,
+ mcpConfig,
+ };
+}
diff --git a/src/prepare/index.ts b/src/prepare/index.ts
new file mode 100644
index 00000000..72071d04
--- /dev/null
+++ b/src/prepare/index.ts
@@ -0,0 +1,21 @@
+/**
+ * Main prepare module that routes to appropriate prepare logic based on event type
+ */
+
+import type { PrepareOptions, PrepareResult } from "./types";
+import { prepareEntityEvent } from "./entity-events";
+import { prepareAutomationEvent } from "./automation-events";
+
+const AUTOMATION_EVENTS = ["workflow_dispatch", "schedule"];
+
+export async function prepare(options: PrepareOptions): Promise {
+ const { context } = options;
+
+ if (AUTOMATION_EVENTS.includes(context.eventName)) {
+ console.log(`Preparing automation event: ${context.eventName}`);
+ return prepareAutomationEvent(options);
+ }
+
+ console.log(`Preparing entity-based event: ${context.eventName}`);
+ return prepareEntityEvent(options);
+}
diff --git a/src/prepare/types.ts b/src/prepare/types.ts
new file mode 100644
index 00000000..5fa8c192
--- /dev/null
+++ b/src/prepare/types.ts
@@ -0,0 +1,20 @@
+import type { ParsedGitHubContext } from "../github/context";
+import type { Octokits } from "../github/api/client";
+import type { Mode } from "../modes/types";
+
+export type PrepareResult = {
+ commentId?: number;
+ branchInfo: {
+ baseBranch: string;
+ claudeBranch?: string;
+ currentBranch: string;
+ };
+ mcpConfig: string;
+};
+
+export type PrepareOptions = {
+ context: ParsedGitHubContext;
+ octokit: Octokits;
+ mode: Mode;
+ githubToken: string;
+};