mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 15:04:13 +08:00
Compare commits
7 Commits
v0.0.17
...
ashwin/mul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9e73dc5d4 | ||
|
|
41dd0aa695 | ||
|
|
55966a1dc0 | ||
|
|
b10f287695 | ||
|
|
56d8eac7ce | ||
|
|
25f9b8ef9e | ||
|
|
3bcfbe7385 |
11
README.md
11
README.md
@@ -347,8 +347,15 @@ Claude does **not** have access to execute arbitrary Bash commands by default. I
|
||||
```yaml
|
||||
- uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
allowed_tools: "Bash(npm install),Bash(npm run test),Edit,Replace,NotebookEditCell"
|
||||
disallowed_tools: "TaskOutput,KillTask"
|
||||
allowed_tools: |
|
||||
Bash(npm install)
|
||||
Bash(npm run test)
|
||||
Edit
|
||||
Replace
|
||||
NotebookEditCell
|
||||
disallowed_tools: |
|
||||
TaskOutput
|
||||
KillTask
|
||||
# ... other inputs
|
||||
```
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ runs:
|
||||
- name: Run Claude Code
|
||||
id: claude-code
|
||||
if: steps.prepare.outputs.contains_trigger == 'true'
|
||||
uses: anthropics/claude-code-base-action@a0d79f9c1798b06292dbc80f8f95cf742ce7a213 # v0.0.14
|
||||
uses: anthropics/claude-code-base-action@ebd8558e902b3db132e89863de49565fcb9aec46 # v0.0.19
|
||||
with:
|
||||
prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
|
||||
allowed_tools: ${{ env.ALLOWED_TOOLS }}
|
||||
|
||||
@@ -24,6 +24,7 @@ export type { CommonFields, PreparedContext } from "./types";
|
||||
|
||||
const BASE_ALLOWED_TOOLS = [
|
||||
"Edit",
|
||||
"MultiEdit",
|
||||
"Glob",
|
||||
"Grep",
|
||||
"LS",
|
||||
@@ -417,6 +418,7 @@ ${
|
||||
}
|
||||
<claude_comment_id>${context.claudeCommentId}</claude_comment_id>
|
||||
<trigger_username>${context.triggerUsername ?? "Unknown"}</trigger_username>
|
||||
<trigger_display_name>${githubData.triggerDisplayName ?? context.triggerUsername ?? "Unknown"}</trigger_display_name>
|
||||
<trigger_phrase>${context.triggerPhrase}</trigger_phrase>
|
||||
${
|
||||
(eventData.eventName === "issue_comment" ||
|
||||
@@ -502,12 +504,14 @@ ${context.directPrompt ? ` - DIRECT INSTRUCTION: A direct instruction was prov
|
||||
? `
|
||||
- Push directly using mcp__github_file_ops__commit_files to the existing branch (works for both new and existing files).
|
||||
- Use mcp__github_file_ops__commit_files to commit files atomically in a single commit (supports single or multiple files).
|
||||
- When pushing changes with this tool and TRIGGER_USERNAME is not "Unknown", include a "Co-authored-by: ${context.triggerUsername} <${context.triggerUsername}@users.noreply.github.com>" line in the commit message.`
|
||||
- When pushing changes with this tool and the trigger user is not "Unknown", include a Co-authored-by trailer in the commit message.
|
||||
- Use: "Co-authored-by: ${githubData.triggerDisplayName ?? context.triggerUsername} <${context.triggerUsername}@users.noreply.github.com>"`
|
||||
: `
|
||||
- You are already on the correct branch (${eventData.claudeBranch || "the PR branch"}). Do not create a new branch.
|
||||
- Push changes directly to the current branch using mcp__github_file_ops__commit_files (works for both new and existing files)
|
||||
- Use mcp__github_file_ops__commit_files to commit files atomically in a single commit (supports single or multiple files).
|
||||
- When pushing changes and TRIGGER_USERNAME is not "Unknown", include a "Co-authored-by: ${context.triggerUsername} <${context.triggerUsername}@users.noreply.github.com>" line in the commit message.
|
||||
- When pushing changes and the trigger user is not "Unknown", include a Co-authored-by trailer in the commit message.
|
||||
- Use: "Co-authored-by: ${githubData.triggerDisplayName ?? context.triggerUsername} <${context.triggerUsername}@users.noreply.github.com>"
|
||||
${
|
||||
eventData.claudeBranch
|
||||
? `- Provide a URL to create a PR manually in this format:
|
||||
|
||||
@@ -59,6 +59,7 @@ async function run() {
|
||||
repository: `${context.repository.owner}/${context.repository.repo}`,
|
||||
prNumber: context.entityNumber.toString(),
|
||||
isPR: context.isPR,
|
||||
triggerUsername: context.actor,
|
||||
});
|
||||
|
||||
// Step 8: Setup branch
|
||||
|
||||
@@ -104,3 +104,11 @@ export const ISSUE_QUERY = `
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const USER_QUERY = `
|
||||
query($login: String!) {
|
||||
user(login: $login) {
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -52,14 +52,8 @@ export function parseGitHubContext(): ParsedGitHubContext {
|
||||
inputs: {
|
||||
triggerPhrase: process.env.TRIGGER_PHRASE ?? "@claude",
|
||||
assigneeTrigger: process.env.ASSIGNEE_TRIGGER ?? "",
|
||||
allowedTools: (process.env.ALLOWED_TOOLS ?? "")
|
||||
.split(",")
|
||||
.map((tool) => tool.trim())
|
||||
.filter((tool) => tool.length > 0),
|
||||
disallowedTools: (process.env.DISALLOWED_TOOLS ?? "")
|
||||
.split(",")
|
||||
.map((tool) => tool.trim())
|
||||
.filter((tool) => tool.length > 0),
|
||||
allowedTools: parseMultilineInput(process.env.ALLOWED_TOOLS ?? ""),
|
||||
disallowedTools: parseMultilineInput(process.env.DISALLOWED_TOOLS ?? ""),
|
||||
customInstructions: process.env.CUSTOM_INSTRUCTIONS ?? "",
|
||||
directPrompt: process.env.DIRECT_PROMPT ?? "",
|
||||
baseBranch: process.env.BASE_BRANCH,
|
||||
@@ -116,6 +110,14 @@ export function parseGitHubContext(): ParsedGitHubContext {
|
||||
}
|
||||
}
|
||||
|
||||
export function parseMultilineInput(s: string): string[] {
|
||||
return s
|
||||
.split(/,|[\n\r]+/)
|
||||
.map((tool) => tool.replace(/#.+$/, ""))
|
||||
.map((tool) => tool.trim())
|
||||
.filter((tool) => tool.length > 0);
|
||||
}
|
||||
|
||||
export function isIssuesEvent(
|
||||
context: ParsedGitHubContext,
|
||||
): context is ParsedGitHubContext & { payload: IssuesEvent } {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { execSync } from "child_process";
|
||||
import type { Octokits } from "../api/client";
|
||||
import { ISSUE_QUERY, PR_QUERY } from "../api/queries/github";
|
||||
import { ISSUE_QUERY, PR_QUERY, USER_QUERY } from "../api/queries/github";
|
||||
import type {
|
||||
GitHubComment,
|
||||
GitHubFile,
|
||||
@@ -18,6 +18,7 @@ type FetchDataParams = {
|
||||
repository: string;
|
||||
prNumber: string;
|
||||
isPR: boolean;
|
||||
triggerUsername?: string;
|
||||
};
|
||||
|
||||
export type GitHubFileWithSHA = GitHubFile & {
|
||||
@@ -31,6 +32,7 @@ export type FetchDataResult = {
|
||||
changedFilesWithSHA: GitHubFileWithSHA[];
|
||||
reviewData: { nodes: GitHubReview[] } | null;
|
||||
imageUrlMap: Map<string, string>;
|
||||
triggerDisplayName?: string | null;
|
||||
};
|
||||
|
||||
export async function fetchGitHubData({
|
||||
@@ -38,6 +40,7 @@ export async function fetchGitHubData({
|
||||
repository,
|
||||
prNumber,
|
||||
isPR,
|
||||
triggerUsername,
|
||||
}: FetchDataParams): Promise<FetchDataResult> {
|
||||
const [owner, repo] = repository.split("/");
|
||||
if (!owner || !repo) {
|
||||
@@ -191,6 +194,12 @@ export async function fetchGitHubData({
|
||||
allComments,
|
||||
);
|
||||
|
||||
// Fetch trigger user display name if username is provided
|
||||
let triggerDisplayName: string | null | undefined;
|
||||
if (triggerUsername) {
|
||||
triggerDisplayName = await fetchUserDisplayName(octokits, triggerUsername);
|
||||
}
|
||||
|
||||
return {
|
||||
contextData,
|
||||
comments,
|
||||
@@ -198,5 +207,27 @@ export async function fetchGitHubData({
|
||||
changedFilesWithSHA,
|
||||
reviewData,
|
||||
imageUrlMap,
|
||||
triggerDisplayName,
|
||||
};
|
||||
}
|
||||
|
||||
export type UserQueryResponse = {
|
||||
user: {
|
||||
name: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
export async function fetchUserDisplayName(
|
||||
octokits: Octokits,
|
||||
login: string,
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const result = await octokits.graphql<UserQueryResponse>(USER_QUERY, {
|
||||
login,
|
||||
});
|
||||
return result.user.name;
|
||||
} catch (error) {
|
||||
console.warn(`Failed to fetch user display name for ${login}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Types for GitHub GraphQL query responses
|
||||
export type GitHubAuthor = {
|
||||
login: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export type GitHubComment = {
|
||||
|
||||
@@ -466,6 +466,7 @@ server.tool(
|
||||
|
||||
const octokit = new Octokit({
|
||||
auth: githubToken,
|
||||
baseUrl: GITHUB_API_URL,
|
||||
});
|
||||
|
||||
const isPullRequestReviewComment =
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as core from "@actions/core";
|
||||
import { GITHUB_API_URL } from "../github/api/config";
|
||||
|
||||
type PrepareConfigParams = {
|
||||
githubToken: string;
|
||||
@@ -46,6 +47,7 @@ export async function prepareMcpConfig(
|
||||
...(claudeCommentId && { CLAUDE_COMMENT_ID: claudeCommentId }),
|
||||
GITHUB_EVENT_NAME: process.env.GITHUB_EVENT_NAME || "",
|
||||
IS_PR: process.env.IS_PR || "false",
|
||||
GITHUB_API_URL: GITHUB_API_URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -316,7 +316,7 @@ describe("generatePrompt", () => {
|
||||
|
||||
expect(prompt).toContain("<trigger_username>johndoe</trigger_username>");
|
||||
expect(prompt).toContain(
|
||||
"Co-authored-by: johndoe <johndoe@users.noreply.github.com>",
|
||||
'Use: "Co-authored-by: johndoe <johndoe@users.noreply.github.com>"',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
57
test/github/context.test.ts
Normal file
57
test/github/context.test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { describe, it, expect } from "bun:test";
|
||||
import { parseMultilineInput } from "../../src/github/context";
|
||||
|
||||
describe("parseMultilineInput", () => {
|
||||
it("should parse a comma-separated string", () => {
|
||||
const input = `Bash(bun install),Bash(bun test:*),Bash(bun typecheck)`;
|
||||
const result = parseMultilineInput(input);
|
||||
expect(result).toEqual([
|
||||
"Bash(bun install)",
|
||||
"Bash(bun test:*)",
|
||||
"Bash(bun typecheck)",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should parse multiline string", () => {
|
||||
const input = `Bash(bun install)
|
||||
Bash(bun test:*)
|
||||
Bash(bun typecheck)`;
|
||||
const result = parseMultilineInput(input);
|
||||
expect(result).toEqual([
|
||||
"Bash(bun install)",
|
||||
"Bash(bun test:*)",
|
||||
"Bash(bun typecheck)",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should parse comma-separated multiline line", () => {
|
||||
const input = `Bash(bun install),Bash(bun test:*)
|
||||
Bash(bun typecheck)`;
|
||||
const result = parseMultilineInput(input);
|
||||
expect(result).toEqual([
|
||||
"Bash(bun install)",
|
||||
"Bash(bun test:*)",
|
||||
"Bash(bun typecheck)",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should ignore comments", () => {
|
||||
const input = `Bash(bun install),
|
||||
Bash(bun test:*) # For testing
|
||||
# For type checking
|
||||
Bash(bun typecheck)
|
||||
`;
|
||||
const result = parseMultilineInput(input);
|
||||
expect(result).toEqual([
|
||||
"Bash(bun install)",
|
||||
"Bash(bun test:*)",
|
||||
"Bash(bun typecheck)",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should parse an empty string", () => {
|
||||
const input = "";
|
||||
const result = parseMultilineInput(input);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user