diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index 4eb7fd5..7d821a2 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -32,7 +32,7 @@ jobs: "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", - "ghcr.io/github/github-mcp-server:sha-7aced2b" + "ghcr.io/github/github-mcp-server:sha-6d69797" ], "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 96824d1..74e6140 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,20 +50,6 @@ Thank you for your interest in contributing to Claude Code Action! This document bun test ``` -2. **Integration Tests** (using GitHub Actions locally): - - ```bash - ./test-local.sh - ``` - - This script: - - - Installs `act` if not present (requires Homebrew on macOS) - - Runs the GitHub Action workflow locally using Docker - - Requires your `ANTHROPIC_API_KEY` to be set - - On Apple Silicon Macs, the script automatically adds the `--container-architecture linux/amd64` flag to avoid compatibility issues. - ## Pull Request Process 1. Create a new branch from `main`: @@ -103,13 +89,7 @@ Thank you for your interest in contributing to Claude Code Action! This document When modifying the action: -1. Test locally with the test script: - - ```bash - ./test-local.sh - ``` - -2. Test in a real GitHub Actions workflow by: +1. Test in a real GitHub Actions workflow by: - Creating a test repository - Using your branch as the action source: ```yaml diff --git a/action.yml b/action.yml index 8f489f7..081decc 100644 --- a/action.yml +++ b/action.yml @@ -114,7 +114,7 @@ runs: - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - uses: anthropics/claude-code-base-action@f481f924b73a7085d9efea0e50a3ba171ed1d74b # v0.0.20 + uses: anthropics/claude-code-base-action@ce5cfd683932f58cb459e749f20b06d2fb30c265 # v0.0.25 with: prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt allowed_tools: ${{ env.ALLOWED_TOOLS }} diff --git a/src/create-prompt/index.ts b/src/create-prompt/index.ts index 876c5d6..7e1c9d6 100644 --- a/src/create-prompt/index.ts +++ b/src/create-prompt/index.ts @@ -243,7 +243,7 @@ export function prepareContext( } if (eventAction === "assigned") { - if (!assigneeTrigger) { + if (!assigneeTrigger && !directPrompt) { throw new Error( "ASSIGNEE_TRIGGER is required for issue assigned event", ); @@ -255,7 +255,7 @@ export function prepareContext( issueNumber, baseBranch, claudeBranch, - assigneeTrigger, + ...(assigneeTrigger && { assigneeTrigger }), }; } else if (eventAction === "labeled") { if (!labelTrigger) { @@ -350,7 +350,9 @@ export function getEventTypeAndContext(envVars: PreparedContext): { } return { eventType: "ISSUE_ASSIGNED", - triggerContext: `issue assigned to '${eventData.assigneeTrigger}'`, + triggerContext: eventData.assigneeTrigger + ? `issue assigned to '${eventData.assigneeTrigger}'` + : `issue assigned event`, }; case "pull_request": diff --git a/src/create-prompt/types.ts b/src/create-prompt/types.ts index 618b33d..218eb65 100644 --- a/src/create-prompt/types.ts +++ b/src/create-prompt/types.ts @@ -65,7 +65,7 @@ type IssueAssignedEvent = { issueNumber: string; baseBranch: string; claudeBranch: string; - assigneeTrigger: string; + assigneeTrigger?: string; }; type IssueLabeledEvent = { diff --git a/src/github/context.ts b/src/github/context.ts index 214f2ca..2e92e89 100644 --- a/src/github/context.ts +++ b/src/github/context.ts @@ -1,6 +1,7 @@ import * as github from "@actions/github"; import type { IssuesEvent, + IssuesAssignedEvent, IssueCommentEvent, PullRequestEvent, PullRequestReviewEvent, @@ -149,3 +150,9 @@ export function isPullRequestReviewCommentEvent( ): context is ParsedGitHubContext & { payload: PullRequestReviewCommentEvent } { return context.eventName === "pull_request_review_comment"; } + +export function isIssuesAssignedEvent( + context: ParsedGitHubContext, +): context is ParsedGitHubContext & { payload: IssuesAssignedEvent } { + return isIssuesEvent(context) && context.eventAction === "assigned"; +} diff --git a/src/github/validation/trigger.ts b/src/github/validation/trigger.ts index 333f617..edb2c21 100644 --- a/src/github/validation/trigger.ts +++ b/src/github/validation/trigger.ts @@ -3,6 +3,7 @@ import * as core from "@actions/core"; import { isIssuesEvent, + isIssuesAssignedEvent, isIssueCommentEvent, isPullRequestEvent, isPullRequestReviewEvent, @@ -22,10 +23,10 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean { } // Check for assignee trigger - if (isIssuesEvent(context) && context.eventAction === "assigned") { + if (isIssuesAssignedEvent(context)) { // Remove @ symbol from assignee_trigger if present let triggerUser = assigneeTrigger.replace(/^@/, ""); - const assigneeUsername = context.payload.issue.assignee?.login || ""; + const assigneeUsername = context.payload.assignee?.login || ""; if (triggerUser && assigneeUsername === triggerUser) { console.log(`Issue assigned to trigger user '${triggerUser}'`); diff --git a/src/mcp/install-mcp-server.ts b/src/mcp/install-mcp-server.ts index 0fa5436..8748f67 100644 --- a/src/mcp/install-mcp-server.ts +++ b/src/mcp/install-mcp-server.ts @@ -62,7 +62,7 @@ export async function prepareMcpConfig( "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", - "ghcr.io/github/github-mcp-server:sha-e9f748f", // https://github.com/github/github-mcp-server/releases/tag/v0.4.0 + "ghcr.io/github/github-mcp-server:sha-6d69797", // https://github.com/github/github-mcp-server/releases/tag/v0.5.0 ], env: { GITHUB_PERSONAL_ACCESS_TOKEN: githubToken, diff --git a/test/create-prompt.test.ts b/test/create-prompt.test.ts index 7ae1ff7..df10668 100644 --- a/test/create-prompt.test.ts +++ b/test/create-prompt.test.ts @@ -663,6 +663,29 @@ describe("getEventTypeAndContext", () => { expect(result.eventType).toBe("ISSUE_LABELED"); expect(result.triggerContext).toBe("issue labeled with 'claude-task'"); }); + + test("should return correct type and context for issue assigned without assigneeTrigger", () => { + const envVars: PreparedContext = { + repository: "owner/repo", + claudeCommentId: "12345", + triggerPhrase: "@claude", + directPrompt: "Please assess this issue", + eventData: { + eventName: "issues", + eventAction: "assigned", + isPR: false, + issueNumber: "999", + baseBranch: "main", + claudeBranch: "claude/issue-999-20240101_120000", + // No assigneeTrigger when using directPrompt + }, + }; + + const result = getEventTypeAndContext(envVars); + + expect(result.eventType).toBe("ISSUE_ASSIGNED"); + expect(result.triggerContext).toBe("issue assigned event"); + }); }); describe("buildAllowedToolsString", () => { diff --git a/test/mockContext.ts b/test/mockContext.ts index a843ee9..c41146e 100644 --- a/test/mockContext.ts +++ b/test/mockContext.ts @@ -92,6 +92,12 @@ export const mockIssueAssignedContext: ParsedGitHubContext = { actor: "admin-user", payload: { action: "assigned", + assignee: { + login: "claude-bot", + id: 11111, + avatar_url: "https://avatars.githubusercontent.com/u/11111", + html_url: "https://github.com/claude-bot", + }, issue: { number: 123, title: "Feature: Add dark mode support", diff --git a/test/prepare-context.test.ts b/test/prepare-context.test.ts index 7811c5b..904dd37 100644 --- a/test/prepare-context.test.ts +++ b/test/prepare-context.test.ts @@ -219,6 +219,55 @@ describe("parseEnvVarsWithContext", () => { ), ).toThrow("BASE_BRANCH is required for issues event"); }); + + test("should allow issue assigned event with direct_prompt and no assigneeTrigger", () => { + const contextWithDirectPrompt = createMockContext({ + ...mockIssueAssignedContext, + inputs: { + ...mockIssueAssignedContext.inputs, + assigneeTrigger: "", // No assignee trigger + directPrompt: "Please assess this issue", // But direct prompt is provided + }, + }); + + const result = prepareContext( + contextWithDirectPrompt, + "12345", + "main", + "claude/issue-123-20240101_120000", + ); + + expect(result.eventData.eventName).toBe("issues"); + expect(result.eventData.isPR).toBe(false); + expect(result.directPrompt).toBe("Please assess this issue"); + if ( + result.eventData.eventName === "issues" && + result.eventData.eventAction === "assigned" + ) { + expect(result.eventData.issueNumber).toBe("123"); + expect(result.eventData.assigneeTrigger).toBeUndefined(); + } + }); + + test("should throw error when neither assigneeTrigger nor directPrompt provided for issue assigned event", () => { + const contextWithoutTriggers = createMockContext({ + ...mockIssueAssignedContext, + inputs: { + ...mockIssueAssignedContext.inputs, + assigneeTrigger: "", // No assignee trigger + directPrompt: "", // No direct prompt + }, + }); + + expect(() => + prepareContext( + contextWithoutTriggers, + "12345", + "main", + "claude/issue-123-20240101_120000", + ), + ).toThrow("ASSIGNEE_TRIGGER is required for issue assigned event"); + }); }); describe("optional fields", () => { diff --git a/test/trigger-validation.test.ts b/test/trigger-validation.test.ts index 8d8f23c..36708c0 100644 --- a/test/trigger-validation.test.ts +++ b/test/trigger-validation.test.ts @@ -90,6 +90,11 @@ describe("checkContainsTrigger", () => { ...mockIssueAssignedContext, payload: { ...mockIssueAssignedContext.payload, + assignee: { + ...(mockIssueAssignedContext.payload as IssuesAssignedEvent) + .assignee, + login: "otherUser", + }, issue: { ...(mockIssueAssignedContext.payload as IssuesAssignedEvent).issue, assignee: {