diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 35d9fe3..99407a3 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -36,4 +36,4 @@ jobs: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} allowed_tools: "Bash(bun install),Bash(bun test:*),Bash(bun run format),Bash(bun typecheck)" custom_instructions: "You have also been granted tools for editing files and running bun commands (install, run, test, typecheck) for testing your changes: bun install, bun test, bun run format, bun typecheck." - model: "claude-opus-4-20250514" + model: "claude-opus-4-1-20250805" diff --git a/action.yml b/action.yml index 1aa8b2e..6b7f283 100644 --- a/action.yml +++ b/action.yml @@ -160,7 +160,7 @@ runs: echo "Base-action dependencies installed" cd - # Install Claude Code globally - bun install -g @anthropic-ai/claude-code@1.0.69 + bun install -g @anthropic-ai/claude-code@1.0.70 - name: Setup Network Restrictions if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != '' diff --git a/base-action/README.md b/base-action/README.md index 2166511..2a9a863 100644 --- a/base-action/README.md +++ b/base-action/README.md @@ -69,7 +69,7 @@ Add the following to your workflow file: uses: anthropics/claude-code-base-action@beta with: prompt: "Review and fix TypeScript errors" - model: "claude-opus-4-20250514" + model: "claude-opus-4-1-20250805" fallback_model: "claude-sonnet-4-20250514" allowed_tools: "Bash(git:*),View,GlobTool,GrepTool,BatchTool" anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} @@ -217,7 +217,7 @@ Provide the settings configuration directly as a JSON string: prompt: "Your prompt here" settings: | { - "model": "claude-opus-4-20250514", + "model": "claude-opus-4-1-20250805", "env": { "DEBUG": "true", "API_URL": "https://api.example.com" diff --git a/base-action/action.yml b/base-action/action.yml index 250db3d..a9d626a 100644 --- a/base-action/action.yml +++ b/base-action/action.yml @@ -118,7 +118,7 @@ runs: - name: Install Claude Code shell: bash - run: bun install -g @anthropic-ai/claude-code@1.0.69 + run: bun install -g @anthropic-ai/claude-code@1.0.70 - name: Run Claude Code Action shell: bash diff --git a/base-action/test/setup-claude-code-settings.test.ts b/base-action/test/setup-claude-code-settings.test.ts index c5a103b..19cf0cd 100644 --- a/base-action/test/setup-claude-code-settings.test.ts +++ b/base-action/test/setup-claude-code-settings.test.ts @@ -134,7 +134,7 @@ describe("setupClaudeCodeSettings", () => { // Then, add new settings const newSettings = JSON.stringify({ newKey: "newValue", - model: "claude-opus-4-20250514", + model: "claude-opus-4-1-20250805", }); await setupClaudeCodeSettings(newSettings, testHomeDir); @@ -145,7 +145,7 @@ describe("setupClaudeCodeSettings", () => { expect(settings.enableAllProjectMcpServers).toBe(true); expect(settings.existingKey).toBe("existingValue"); expect(settings.newKey).toBe("newValue"); - expect(settings.model).toBe("claude-opus-4-20250514"); + expect(settings.model).toBe("claude-opus-4-1-20250805"); }); test("should copy slash commands to .claude directory when path provided", async () => { diff --git a/docs/configuration.md b/docs/configuration.md index 5d3d125..ec0f317 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -252,7 +252,7 @@ You can provide Claude Code settings to customize behavior such as model selecti with: settings: | { - "model": "claude-opus-4-20250514", + "model": "claude-opus-4-1-20250805", "env": { "DEBUG": "true", "API_URL": "https://api.example.com" diff --git a/src/mcp/github-inline-comment-server.ts b/src/mcp/github-inline-comment-server.ts new file mode 100644 index 0000000..28a8658 --- /dev/null +++ b/src/mcp/github-inline-comment-server.ts @@ -0,0 +1,178 @@ +#!/usr/bin/env node +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; +import { createOctokit } from "../github/api/client"; + +// Get repository and PR information from environment variables +const REPO_OWNER = process.env.REPO_OWNER; +const REPO_NAME = process.env.REPO_NAME; +const PR_NUMBER = process.env.PR_NUMBER; + +if (!REPO_OWNER || !REPO_NAME || !PR_NUMBER) { + console.error( + "Error: REPO_OWNER, REPO_NAME, and PR_NUMBER environment variables are required", + ); + process.exit(1); +} + +// GitHub Inline Comment MCP Server - Provides inline PR comment functionality +// Provides an inline comment tool without exposing full PR review capabilities, so that +// Claude can't accidentally approve a PR +const server = new McpServer({ + name: "GitHub Inline Comment Server", + version: "0.0.1", +}); + +server.tool( + "create_inline_comment", + "Create an inline comment on a specific line or lines in a PR file", + { + path: z + .string() + .describe("The file path to comment on (e.g., 'src/index.js')"), + body: z + .string() + .describe( + "The comment text (supports markdown and GitHub code suggestion blocks). " + + "For code suggestions, use: ```suggestion\\nreplacement code\\n```. " + + "IMPORTANT: The suggestion block will REPLACE the ENTIRE line range (single line or startLine to line). " + + "Ensure the replacement is syntactically complete and valid - it must work as a drop-in replacement for the selected lines.", + ), + line: z + .number() + .optional() + .describe( + "Line number for single-line comments (required if startLine is not provided)", + ), + startLine: z + .number() + .optional() + .describe( + "Start line for multi-line comments (use with line parameter for the end line)", + ), + side: z + .enum(["LEFT", "RIGHT"]) + .optional() + .default("RIGHT") + .describe( + "Side of the diff to comment on: LEFT (old code) or RIGHT (new code)", + ), + commit_id: z + .string() + .optional() + .describe( + "Specific commit SHA to comment on (defaults to latest commit)", + ), + }, + async ({ path, body, line, startLine, side, commit_id }) => { + try { + const githubToken = process.env.GITHUB_TOKEN; + + if (!githubToken) { + throw new Error("GITHUB_TOKEN environment variable is required"); + } + + const owner = REPO_OWNER; + const repo = REPO_NAME; + const pull_number = parseInt(PR_NUMBER, 10); + + const octokit = createOctokit(githubToken).rest; + + // Validate that either line or both startLine and line are provided + if (!line && !startLine) { + throw new Error( + "Either 'line' for single-line comments or both 'startLine' and 'line' for multi-line comments must be provided", + ); + } + + // If only line is provided, it's a single-line comment + // If both startLine and line are provided, it's a multi-line comment + const isSingleLine = !startLine; + + const pr = await octokit.pulls.get({ + owner, + repo, + pull_number, + }); + + const params: Parameters< + typeof octokit.rest.pulls.createReviewComment + >[0] = { + owner, + repo, + pull_number, + body, + path, + side: side || "RIGHT", + commit_id: commit_id || pr.data.head.sha, + }; + + if (isSingleLine) { + // Single-line comment + params.line = line; + } else { + // Multi-line comment + params.start_line = startLine; + params.start_side = side || "RIGHT"; + params.line = line; + } + + const result = await octokit.rest.pulls.createReviewComment(params); + + return { + content: [ + { + type: "text", + text: JSON.stringify( + { + success: true, + comment_id: result.data.id, + html_url: result.data.html_url, + path: result.data.path, + line: result.data.line || result.data.original_line, + message: `Inline comment created successfully on ${path}${isSingleLine ? ` at line ${line}` : ` from line ${startLine} to ${line}`}`, + }, + null, + 2, + ), + }, + ], + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + + // Provide more helpful error messages for common issues + let helpMessage = ""; + if (errorMessage.includes("Validation Failed")) { + helpMessage = + "\n\nThis usually means the line number doesn't exist in the diff or the file path is incorrect. Make sure you're commenting on lines that are part of the PR's changes."; + } else if (errorMessage.includes("Not Found")) { + helpMessage = + "\n\nThis usually means the PR number, repository, or file path is incorrect."; + } + + return { + content: [ + { + type: "text", + text: `Error creating inline comment: ${errorMessage}${helpMessage}`, + }, + ], + error: errorMessage, + isError: true, + }; + } + }, +); + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); + process.on("exit", () => { + server.close(); + }); +} + +runServer().catch(console.error); diff --git a/src/mcp/install-mcp-server.ts b/src/mcp/install-mcp-server.ts index 61b11d6..9a87f12 100644 --- a/src/mcp/install-mcp-server.ts +++ b/src/mcp/install-mcp-server.ts @@ -111,6 +111,24 @@ export async function prepareMcpConfig( }; } + // Include inline comment server for experimental review mode + if (context.inputs.mode === "experimental-review" && context.isPR) { + baseMcpConfig.mcpServers.github_inline_comment = { + command: "bun", + args: [ + "run", + `${process.env.GITHUB_ACTION_PATH}/src/mcp/github-inline-comment-server.ts`, + ], + env: { + GITHUB_TOKEN: githubToken, + REPO_OWNER: owner, + REPO_NAME: repo, + PR_NUMBER: context.entityNumber?.toString() || "", + GITHUB_API_URL: GITHUB_API_URL, + }, + }; + } + // Only add CI server if we have actions:read permission and we're in a PR context const hasActionsReadPermission = context.inputs.additionalPermissions.get("actions") === "read";