mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 22:44:13 +08:00
* 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>
266 lines
8.1 KiB
TypeScript
266 lines
8.1 KiB
TypeScript
import { describe, test, expect, beforeEach, afterEach, spyOn } from "bun:test";
|
|
import { prepareMcpConfig } from "../src/mcp/install-mcp-server";
|
|
import * as core from "@actions/core";
|
|
import type { ParsedGitHubContext } from "../src/github/context";
|
|
|
|
describe("prepareMcpConfig", () => {
|
|
let consoleInfoSpy: any;
|
|
let consoleWarningSpy: any;
|
|
let setFailedSpy: any;
|
|
let processExitSpy: any;
|
|
|
|
// Create a mock context for tests
|
|
const mockContext: ParsedGitHubContext = {
|
|
runId: "test-run-id",
|
|
eventName: "issue_comment",
|
|
eventAction: "created",
|
|
repository: {
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
full_name: "test-owner/test-repo",
|
|
},
|
|
actor: "test-actor",
|
|
payload: {} as any,
|
|
entityNumber: 123,
|
|
isPR: false,
|
|
inputs: {
|
|
prompt: "",
|
|
triggerPhrase: "@claude",
|
|
assigneeTrigger: "",
|
|
labelTrigger: "",
|
|
branchPrefix: "",
|
|
useStickyComment: false,
|
|
useCommitSigning: false,
|
|
allowedBots: "",
|
|
trackProgress: false,
|
|
},
|
|
};
|
|
|
|
const mockPRContext: ParsedGitHubContext = {
|
|
...mockContext,
|
|
eventName: "pull_request",
|
|
isPR: true,
|
|
entityNumber: 456,
|
|
};
|
|
|
|
const mockContextWithSigning: ParsedGitHubContext = {
|
|
...mockContext,
|
|
inputs: {
|
|
...mockContext.inputs,
|
|
useCommitSigning: true,
|
|
},
|
|
};
|
|
|
|
beforeEach(() => {
|
|
consoleInfoSpy = spyOn(core, "info").mockImplementation(() => {});
|
|
consoleWarningSpy = spyOn(core, "warning").mockImplementation(() => {});
|
|
setFailedSpy = spyOn(core, "setFailed").mockImplementation(() => {});
|
|
processExitSpy = spyOn(process, "exit").mockImplementation(() => {
|
|
throw new Error("Process exit");
|
|
});
|
|
|
|
// Set up required environment variables
|
|
if (!process.env.GITHUB_ACTION_PATH) {
|
|
process.env.GITHUB_ACTION_PATH = "/test/action/path";
|
|
}
|
|
});
|
|
|
|
afterEach(() => {
|
|
consoleInfoSpy.mockRestore();
|
|
consoleWarningSpy.mockRestore();
|
|
setFailedSpy.mockRestore();
|
|
processExitSpy.mockRestore();
|
|
});
|
|
|
|
test("should return comment server when commit signing is disabled", async () => {
|
|
const result = await prepareMcpConfig({
|
|
githubToken: "test-token",
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
branch: "test-branch",
|
|
baseBranch: "main",
|
|
allowedTools: [],
|
|
context: mockContext,
|
|
});
|
|
|
|
const parsed = JSON.parse(result);
|
|
expect(parsed.mcpServers).toBeDefined();
|
|
expect(parsed.mcpServers.github).not.toBeDefined();
|
|
expect(parsed.mcpServers.github_file_ops).not.toBeDefined();
|
|
expect(parsed.mcpServers.github_comment).toBeDefined();
|
|
expect(parsed.mcpServers.github_comment.env.GITHUB_TOKEN).toBe(
|
|
"test-token",
|
|
);
|
|
});
|
|
|
|
test("should include file ops server when commit signing is enabled", async () => {
|
|
const result = await prepareMcpConfig({
|
|
githubToken: "test-token",
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
branch: "test-branch",
|
|
baseBranch: "main",
|
|
allowedTools: [],
|
|
context: mockContextWithSigning,
|
|
});
|
|
|
|
const parsed = JSON.parse(result);
|
|
expect(parsed.mcpServers).toBeDefined();
|
|
expect(parsed.mcpServers.github).not.toBeDefined();
|
|
expect(parsed.mcpServers.github_file_ops).toBeDefined();
|
|
expect(parsed.mcpServers.github_file_ops.env.GITHUB_TOKEN).toBe(
|
|
"test-token",
|
|
);
|
|
expect(parsed.mcpServers.github_file_ops.env.BRANCH_NAME).toBe(
|
|
"test-branch",
|
|
);
|
|
});
|
|
|
|
test("should include github MCP server when mcp__github__ tools are allowed", async () => {
|
|
const result = await prepareMcpConfig({
|
|
githubToken: "test-token",
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
branch: "test-branch",
|
|
baseBranch: "main",
|
|
allowedTools: ["mcp__github__create_issue", "mcp__github__create_pr"],
|
|
context: mockContext,
|
|
});
|
|
|
|
const parsed = JSON.parse(result);
|
|
expect(parsed.mcpServers).toBeDefined();
|
|
expect(parsed.mcpServers.github).toBeDefined();
|
|
expect(parsed.mcpServers.github.command).toBe("docker");
|
|
expect(parsed.mcpServers.github.env.GITHUB_PERSONAL_ACCESS_TOKEN).toBe(
|
|
"test-token",
|
|
);
|
|
});
|
|
|
|
test("should include inline comment server for PRs when tools are allowed", async () => {
|
|
const result = await prepareMcpConfig({
|
|
githubToken: "test-token",
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
branch: "test-branch",
|
|
baseBranch: "main",
|
|
allowedTools: ["mcp__github_inline_comment__create_inline_comment"],
|
|
context: mockPRContext,
|
|
});
|
|
|
|
const parsed = JSON.parse(result);
|
|
expect(parsed.mcpServers).toBeDefined();
|
|
expect(parsed.mcpServers.github_inline_comment).toBeDefined();
|
|
expect(parsed.mcpServers.github_inline_comment.env.GITHUB_TOKEN).toBe(
|
|
"test-token",
|
|
);
|
|
expect(parsed.mcpServers.github_inline_comment.env.PR_NUMBER).toBe("456");
|
|
});
|
|
|
|
test("should include comment server when no GitHub tools are allowed and signing disabled", async () => {
|
|
const result = await prepareMcpConfig({
|
|
githubToken: "test-token",
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
branch: "test-branch",
|
|
baseBranch: "main",
|
|
allowedTools: [],
|
|
context: mockContext,
|
|
});
|
|
|
|
const parsed = JSON.parse(result);
|
|
expect(parsed.mcpServers).toBeDefined();
|
|
expect(parsed.mcpServers.github).not.toBeDefined();
|
|
expect(parsed.mcpServers.github_file_ops).not.toBeDefined();
|
|
expect(parsed.mcpServers.github_comment).toBeDefined();
|
|
});
|
|
|
|
test("should set GITHUB_ACTION_PATH correctly", async () => {
|
|
process.env.GITHUB_ACTION_PATH = "/test/action/path";
|
|
|
|
const result = await prepareMcpConfig({
|
|
githubToken: "test-token",
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
branch: "test-branch",
|
|
baseBranch: "main",
|
|
allowedTools: [],
|
|
context: mockContextWithSigning,
|
|
});
|
|
|
|
const parsed = JSON.parse(result);
|
|
expect(parsed.mcpServers.github_file_ops.args).toContain(
|
|
"/test/action/path/src/mcp/github-file-ops-server.ts",
|
|
);
|
|
});
|
|
|
|
test("should use current working directory when GITHUB_WORKSPACE is not set", async () => {
|
|
delete process.env.GITHUB_WORKSPACE;
|
|
|
|
const result = await prepareMcpConfig({
|
|
githubToken: "test-token",
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
branch: "test-branch",
|
|
baseBranch: "main",
|
|
allowedTools: [],
|
|
context: mockContextWithSigning,
|
|
});
|
|
|
|
const parsed = JSON.parse(result);
|
|
expect(parsed.mcpServers.github_file_ops.env.REPO_DIR).toBe(process.cwd());
|
|
});
|
|
|
|
test("should include CI server when context.isPR is true and DEFAULT_WORKFLOW_TOKEN exists", async () => {
|
|
process.env.DEFAULT_WORKFLOW_TOKEN = "workflow-token";
|
|
|
|
const result = await prepareMcpConfig({
|
|
githubToken: "test-token",
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
branch: "test-branch",
|
|
baseBranch: "main",
|
|
allowedTools: [],
|
|
context: mockPRContext,
|
|
});
|
|
|
|
const parsed = JSON.parse(result);
|
|
expect(parsed.mcpServers.github_ci).toBeDefined();
|
|
expect(parsed.mcpServers.github_ci.env.GITHUB_TOKEN).toBe("workflow-token");
|
|
expect(parsed.mcpServers.github_ci.env.PR_NUMBER).toBe("456");
|
|
|
|
delete process.env.DEFAULT_WORKFLOW_TOKEN;
|
|
});
|
|
|
|
test("should not include github_ci server when context.isPR is false", async () => {
|
|
const result = await prepareMcpConfig({
|
|
githubToken: "test-token",
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
branch: "test-branch",
|
|
baseBranch: "main",
|
|
allowedTools: [],
|
|
context: mockContext,
|
|
});
|
|
|
|
const parsed = JSON.parse(result);
|
|
expect(parsed.mcpServers.github_ci).not.toBeDefined();
|
|
});
|
|
|
|
test("should not include github_ci server when DEFAULT_WORKFLOW_TOKEN is missing", async () => {
|
|
delete process.env.DEFAULT_WORKFLOW_TOKEN;
|
|
|
|
const result = await prepareMcpConfig({
|
|
githubToken: "test-token",
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
branch: "test-branch",
|
|
baseBranch: "main",
|
|
allowedTools: [],
|
|
context: mockPRContext,
|
|
});
|
|
|
|
const parsed = JSON.parse(result);
|
|
expect(parsed.mcpServers.github_ci).not.toBeDefined();
|
|
});
|
|
});
|