9 Commits

Author SHA1 Message Date
Mark Wylde
d2b03c9183 Merge branch 'feat/give-claude-access-to-switch-branch' of github.com:markwylde/claude-code-gitea-action into gitea 2025-05-31 09:48:49 +01:00
Mark Wylde
05a2e7ea87 fix 2025-05-31 09:45:52 +01:00
Mark Wylde
4b26673a39 Merge pull request #1 from markwylde/feat/give-claude-access-to-switch-branch
Give claude access to switch branch
2025-05-31 09:38:17 +01:00
Mark Wylde
ccf7081358 improve git logic 2025-05-31 09:36:25 +01:00
Mark Wylde
5c040da573 Fix job id 2025-05-31 09:26:06 +01:00
Mark Wylde
e5b2574f8c Implement switch branch 2025-05-31 09:20:48 +01:00
Mark Wylde
799a5cd961 chore: update readme version 2025-05-31 09:08:46 +01:00
Mark Wylde
8406629c9f chore: add screenshot to readme 2025-05-31 09:07:46 +01:00
Mark Wylde
9714bd59a5 chore: simplify action config 2025-05-31 09:03:12 +01:00
19 changed files with 272 additions and 362 deletions

View File

@@ -56,3 +56,12 @@ src/
- The action creates branches for issues and pushes to PR branches directly - The action creates branches for issues and pushes to PR branches directly
- All actions create OIDC tokens for secure authentication - All actions create OIDC tokens for secure authentication
- Progress is tracked through dynamic comment updates with checkboxes - Progress is tracked through dynamic comment updates with checkboxes
## MCP Tool Development
When adding new MCP tools:
1. **Add to MCP Server**: Implement the tool in the appropriate MCP server file (e.g., `src/mcp/local-git-ops-server.ts`)
2. **Expose to Claude**: Add the tool name to `BASE_ALLOWED_TOOLS` array in `src/create-prompt/index.ts`
3. **Tool Naming**: Follow the pattern `mcp__server_name__tool_name` (e.g., `mcp__local_git_ops__checkout_branch`)
4. **Documentation**: Update the prompt's "What You CAN Do" section if the tool adds new capabilities

2
FAQ.md
View File

@@ -22,7 +22,7 @@ permissions:
id-token: write # Required for OIDC authentication id-token: write # Required for OIDC authentication
``` ```
The OIDC token is required in order for the Claude GitHub app to function. If you wish to not use the GitHub app, you can instead provide a `github_token` input to the action for Claude to operate with. See the [Claude Code permissions documentation][perms] for more. The OIDC token is required in order for the Claude GitHub app to function. If you wish to not use the GitHub app, you can instead provide a `gitea_token` input to the action for Claude to operate with. See the [Claude Code permissions documentation][perms] for more.
## Claude's Capabilities and Limitations ## Claude's Capabilities and Limitations

View File

@@ -56,7 +56,7 @@ Now required to explicitly provide a GitHub token:
# After (required) # After (required)
- uses: anthropics/claude-code-action@beta - uses: anthropics/claude-code-action@beta
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} gitea_token: ${{ secrets.GITHUB_TOKEN }}
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
``` ```
@@ -94,7 +94,7 @@ jobs:
- name: Run Claude Assistant - name: Run Claude Assistant
uses: ./ # Adjust path as needed for your Gitea setup uses: ./ # Adjust path as needed for your Gitea setup
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} gitea_token: ${{ secrets.GITHUB_TOKEN }}
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
``` ```

View File

@@ -1,5 +1,7 @@
# Claude Code Action (Gitea Fork) # Claude Code Action (Gitea Fork)
![Claude Code Action in action](assets/screenshot.png)
A fork of the [Claude Code Action](https://github.com/anthropics/claude-code-action) that adds support for Gitea alongside GitHub. This action provides a general-purpose [Claude Code](https://claude.ai/code) assistant for PRs and issues that can answer questions and implement code changes. It listens for a trigger phrase in comments and activates Claude to act on the request. Supports multiple authentication methods including Anthropic direct API, Amazon Bedrock, and Google Vertex AI. A fork of the [Claude Code Action](https://github.com/anthropics/claude-code-action) that adds support for Gitea alongside GitHub. This action provides a general-purpose [Claude Code](https://claude.ai/code) assistant for PRs and issues that can answer questions and implement code changes. It listens for a trigger phrase in comments and activates Claude to act on the request. Supports multiple authentication methods including Anthropic direct API, Amazon Bedrock, and Google Vertex AI.
> **Note**: This is an unofficial fork that extends the original action to work with Gitea installations. The core functionality remains the same, with additional support for Gitea APIs and local git operations. > **Note**: This is an unofficial fork that extends the original action to work with Gitea installations. The core functionality remains the same, with additional support for Gitea APIs and local git operations.
@@ -58,31 +60,29 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: markwylde/claude-code-gitea-action@v1.0.1 - uses: markwylde/claude-code-gitea-action@v1.0.2
with: with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITEA_TOKEN }} gitea_token: ${{ secrets.GITEA_TOKEN }}
gitea_api_url: https://gitea.example.com
``` ```
## Inputs ## Inputs
| Input | Description | Required | Default | | Input | Description | Required | Default |
| --------------------- | -------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | | --------------------- | ------------------------------------------------------------------------------------------------------------------- | -------- | --------- |
| `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex) | No\* | - | | `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex) | No\* | - |
| `direct_prompt` | Direct prompt for Claude to execute automatically without needing a trigger (for automated workflows) | No | - | | `direct_prompt` | Direct prompt for Claude to execute automatically without needing a trigger (for automated workflows) | No | - |
| `timeout_minutes` | Timeout in minutes for execution | No | `30` | | `timeout_minutes` | Timeout in minutes for execution | No | `30` |
| `github_token` | GitHub token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - | | `gitea_token` | Gitea token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - |
| `gitea_api_url` | Gitea server URL (e.g., `https://gitea.example.com`) for Gitea installations. Leave empty for GitHub. | No | GitHub API | | `model` | Model to use (provider-specific format required for Bedrock/Vertex) | No | - |
| `model` | Model to use (provider-specific format required for Bedrock/Vertex) | No | - | | `anthropic_model` | **DEPRECATED**: Use `model` instead. Kept for backward compatibility. | No | - |
| `anthropic_model` | **DEPRECATED**: Use `model` instead. Kept for backward compatibility. | No | - | | `use_bedrock` | Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API | No | `false` |
| `use_bedrock` | Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API | No | `false` | | `use_vertex` | Use Google Vertex AI with OIDC authentication instead of direct Anthropic API | No | `false` |
| `use_vertex` | Use Google Vertex AI with OIDC authentication instead of direct Anthropic API | No | `false` | | `allowed_tools` | Additional tools for Claude to use (the base GitHub tools will always be included) | No | "" |
| `allowed_tools` | Additional tools for Claude to use (the base GitHub tools will always be included) | No | "" | | `disallowed_tools` | Tools that Claude should never use | No | "" |
| `disallowed_tools` | Tools that Claude should never use | No | "" | | `custom_instructions` | Additional custom instructions to include in the prompt for Claude | No | "" |
| `custom_instructions` | Additional custom instructions to include in the prompt for Claude | No | "" | | `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - |
| `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - | | `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` |
| `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` |
\*Required when using direct Anthropic API (default and when not using Bedrock or Vertex) \*Required when using direct Anthropic API (default and when not using Bedrock or Vertex)
@@ -112,8 +112,7 @@ jobs:
- uses: anthropics/claude-code-action@beta - uses: anthropics/claude-code-action@beta
with: with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
gitea_api_url: "https://gitea.example.com" gitea_token: ${{ secrets.GITEA_TOKEN }}
github_token: ${{ secrets.GITEA_TOKEN }}
``` ```
### Gitea Setup Notes ### Gitea Setup Notes

View File

@@ -44,11 +44,8 @@ inputs:
anthropic_api_key: anthropic_api_key:
description: "Anthropic API key (required for direct API, not needed for Bedrock/Vertex)" description: "Anthropic API key (required for direct API, not needed for Bedrock/Vertex)"
required: false required: false
github_token: gitea_token:
description: "GitHub token with repo and pull request permissions (defaults to GITHUB_TOKEN)" description: "Gitea token with repo and pull request permissions (defaults to GITHUB_TOKEN)"
required: false
gitea_api_url:
description: "Gitea server URL (e.g., https://gitea.example.com, defaults to GitHub API)"
required: false required: false
use_bedrock: use_bedrock:
description: "Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API" description: "Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API"
@@ -95,10 +92,10 @@ runs:
ALLOWED_TOOLS: ${{ inputs.allowed_tools }} ALLOWED_TOOLS: ${{ inputs.allowed_tools }}
CUSTOM_INSTRUCTIONS: ${{ inputs.custom_instructions }} CUSTOM_INSTRUCTIONS: ${{ inputs.custom_instructions }}
DIRECT_PROMPT: ${{ inputs.direct_prompt }} DIRECT_PROMPT: ${{ inputs.direct_prompt }}
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }} OVERRIDE_GITHUB_TOKEN: ${{ inputs.gitea_token }}
GITHUB_TOKEN: ${{ github.token }} GITHUB_TOKEN: ${{ github.token }}
GITHUB_RUN_ID: ${{ github.run_id }} GITHUB_RUN_ID: ${{ github.run_id }}
GITEA_API_URL: ${{ inputs.gitea_api_url }} GITEA_API_URL: ${{ env.GITHUB_SERVER_URL }}
- name: Run Claude Code - name: Run Claude Code
id: claude-code id: claude-code
@@ -129,7 +126,7 @@ runs:
# GitHub token for repository access # GitHub token for repository access
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
GITEA_API_URL: ${{ inputs.gitea_api_url }} GITEA_API_URL: ${{ env.GITHUB_SERVER_URL }}
# Provider configuration (for future cloud provider support) # Provider configuration (for future cloud provider support)
ANTHROPIC_BASE_URL: ${{ env.ANTHROPIC_BASE_URL }} ANTHROPIC_BASE_URL: ${{ env.ANTHROPIC_BASE_URL }}
@@ -167,7 +164,7 @@ runs:
TRIGGER_USERNAME: ${{ github.event.comment.user.login || github.event.issue.user.login || github.event.pull_request.user.login || github.event.sender.login || github.triggering_actor || github.actor || '' }} TRIGGER_USERNAME: ${{ github.event.comment.user.login || github.event.issue.user.login || github.event.pull_request.user.login || github.event.sender.login || github.triggering_actor || github.actor || '' }}
PREPARE_SUCCESS: ${{ steps.prepare.outcome == 'success' }} PREPARE_SUCCESS: ${{ steps.prepare.outcome == 'success' }}
PREPARE_ERROR: ${{ steps.prepare.outputs.prepare_error || '' }} PREPARE_ERROR: ${{ steps.prepare.outputs.prepare_error || '' }}
GITEA_API_URL: ${{ inputs.gitea_api_url }} GITEA_API_URL: ${{ env.GITHUB_SERVER_URL }}
- name: Display Claude Code Report - name: Display Claude Code Report
if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != '' if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != ''

BIN
assets/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

View File

@@ -31,6 +31,6 @@ jobs:
- name: Run Claude PR Action - name: Run Claude PR Action
uses: anthropics/claude-code-action@beta uses: anthropics/claude-code-action@beta
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} gitea_token: ${{ secrets.GITHUB_TOKEN }}
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
timeout_minutes: "60" timeout_minutes: "60"

View File

@@ -31,7 +31,7 @@ jobs:
- name: Run Claude Assistant - name: Run Claude Assistant
uses: ./ # Use local action (adjust path as needed) uses: ./ # Use local action (adjust path as needed)
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} # Use standard workflow token gitea_token: ${{ secrets.GITHUB_TOKEN }} # Use standard workflow token
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
timeout_minutes: "60" timeout_minutes: "60"
trigger_phrase: "@claude" trigger_phrase: "@claude"

View File

@@ -33,6 +33,8 @@ const BASE_ALLOWED_TOOLS = [
"mcp__local_git_ops__delete_files", "mcp__local_git_ops__delete_files",
"mcp__local_git_ops__push_branch", "mcp__local_git_ops__push_branch",
"mcp__local_git_ops__create_pull_request", "mcp__local_git_ops__create_pull_request",
"mcp__local_git_ops__checkout_branch",
"mcp__local_git_ops__create_branch",
"mcp__local_git_ops__git_status", "mcp__local_git_ops__git_status",
]; ];
const DISALLOWED_TOOLS = ["WebSearch", "WebFetch"]; const DISALLOWED_TOOLS = ["WebSearch", "WebFetch"];
@@ -217,8 +219,6 @@ export function prepareContext(
...(baseBranch && { baseBranch }), ...(baseBranch && { baseBranch }),
}; };
break; break;
} else if (!claudeBranch) {
throw new Error("CLAUDE_BRANCH is required for issue_comment event");
} else if (!baseBranch) { } else if (!baseBranch) {
throw new Error("BASE_BRANCH is required for issue_comment event"); throw new Error("BASE_BRANCH is required for issue_comment event");
} else if (!issueNumber) { } else if (!issueNumber) {
@@ -231,10 +231,10 @@ export function prepareContext(
eventName: "issue_comment", eventName: "issue_comment",
commentId, commentId,
isPR: false, isPR: false,
claudeBranch: claudeBranch,
baseBranch, baseBranch,
issueNumber, issueNumber,
commentBody, commentBody,
...(claudeBranch && { claudeBranch }),
}; };
break; break;
@@ -251,9 +251,6 @@ export function prepareContext(
if (!baseBranch) { if (!baseBranch) {
throw new Error("BASE_BRANCH is required for issues event"); throw new Error("BASE_BRANCH is required for issues event");
} }
if (!claudeBranch) {
throw new Error("CLAUDE_BRANCH is required for issues event");
}
if (eventAction === "assigned") { if (eventAction === "assigned") {
if (!assigneeTrigger) { if (!assigneeTrigger) {
@@ -267,8 +264,8 @@ export function prepareContext(
isPR: false, isPR: false,
issueNumber, issueNumber,
baseBranch, baseBranch,
claudeBranch,
assigneeTrigger, assigneeTrigger,
...(claudeBranch && { claudeBranch }),
}; };
} else if (eventAction === "opened") { } else if (eventAction === "opened") {
eventData = { eventData = {
@@ -277,7 +274,7 @@ export function prepareContext(
isPR: false, isPR: false,
issueNumber, issueNumber,
baseBranch, baseBranch,
claudeBranch, ...(claudeBranch && { claudeBranch }),
}; };
} else { } else {
throw new Error(`Unsupported issue action: ${eventAction}`); throw new Error(`Unsupported issue action: ${eventAction}`);
@@ -512,7 +509,20 @@ ${context.directPrompt ? ` - DIRECT INSTRUCTION: A direct instruction was prov
- For implementation requests, assess if they are straightforward or complex. - For implementation requests, assess if they are straightforward or complex.
- Mark this todo as complete by checking the box. - Mark this todo as complete by checking the box.
4. Execute Actions: ${
!eventData.isPR || !eventData.claudeBranch
? `
4. Check for Existing Branch (for issues and closed PRs):
- Before implementing changes, check if there's already a claude branch for this ${eventData.isPR ? "PR" : "issue"}.
- Use Bash to run \`git branch -r | grep "claude/${eventData.isPR ? "pr" : "issue"}-${eventData.isPR ? eventData.prNumber : eventData.issueNumber}"\` to search for existing branches.
- If found, use mcp__local_git_ops__checkout_branch to switch to the existing branch (set fetch_remote=true).
- If not found, you'll create a new branch when making changes (see Execute Actions section).
- Mark this todo as complete by checking the box.
5. Execute Actions:`
: `
4. Execute Actions:`
}
- Continually update your todo list as you discover new requirements or realize tasks can be broken down. - Continually update your todo list as you discover new requirements or realize tasks can be broken down.
A. For Answering Questions and Code Reviews: A. For Answering Questions and Code Reviews:
@@ -537,15 +547,14 @@ ${context.directPrompt ? ` - DIRECT INSTRUCTION: A direct instruction was prov
- Use mcp__local_git_ops__commit_files to commit files atomically in a single commit (supports single or multiple files). - Use mcp__local_git_ops__commit_files to commit files atomically in a single commit (supports single or multiple files).
- CRITICAL: After committing, you MUST push the branch to the remote repository using mcp__local_git_ops__push_branch - CRITICAL: After committing, you MUST push the branch to the remote repository using mcp__local_git_ops__push_branch
- When pushing changes with this tool and TRIGGER_USERNAME is not "Unknown", include a "Co-authored-by: ${context.triggerUsername} <${context.triggerUsername}@users.noreply.local>" line in the commit message.` - When pushing changes with this tool and TRIGGER_USERNAME is not "Unknown", include a "Co-authored-by: ${context.triggerUsername} <${context.triggerUsername}@users.noreply.local>" line in the commit message.`
: ` : eventData.claudeBranch
- You are already on the correct branch (${eventData.claudeBranch || "the PR branch"}). Do not create a new branch. ? `
- You are already on the correct branch (${eventData.claudeBranch}). Do not create a new branch.
- Commit changes using mcp__local_git_ops__commit_files (works for both new and existing files) - Commit changes using mcp__local_git_ops__commit_files (works for both new and existing files)
- Use mcp__local_git_ops__commit_files to commit files atomically in a single commit (supports single or multiple files). - Use mcp__local_git_ops__commit_files to commit files atomically in a single commit (supports single or multiple files).
- CRITICAL: After committing, you MUST push the branch to the remote repository using mcp__local_git_ops__push_branch - CRITICAL: After committing, you MUST push the branch to the remote repository using mcp__local_git_ops__push_branch
- When pushing changes and TRIGGER_USERNAME is not "Unknown", include a "Co-authored-by: ${context.triggerUsername} <${context.triggerUsername}@users.noreply.local>" line in the commit message. - When pushing changes and TRIGGER_USERNAME is not "Unknown", include a "Co-authored-by: ${context.triggerUsername} <${context.triggerUsername}@users.noreply.local>" line in the commit message.
${ - Provide a URL to create a PR manually in this format:
eventData.claudeBranch
? `- Provide a URL to create a PR manually in this format:
[Create a PR](${GITEA_SERVER_URL}/${context.repository}/compare/${eventData.baseBranch}...<branch-name>?quick_pull=1&title=<url-encoded-title>&body=<url-encoded-body>) [Create a PR](${GITEA_SERVER_URL}/${context.repository}/compare/${eventData.baseBranch}...<branch-name>?quick_pull=1&title=<url-encoded-title>&body=<url-encoded-body>)
- IMPORTANT: Use THREE dots (...) between branch names, not two (..) - IMPORTANT: Use THREE dots (...) between branch names, not two (..)
Example: ${GITEA_SERVER_URL}/${context.repository}/compare/main...feature-branch (correct) Example: ${GITEA_SERVER_URL}/${context.repository}/compare/main...feature-branch (correct)
@@ -559,8 +568,34 @@ ${context.directPrompt ? ` - DIRECT INSTRUCTION: A direct instruction was prov
- Reference to the original ${eventData.isPR ? "PR" : "issue"} - Reference to the original ${eventData.isPR ? "PR" : "issue"}
- The signature: "Generated with [Claude Code](https://claude.ai/code)" - The signature: "Generated with [Claude Code](https://claude.ai/code)"
- Just include the markdown link with text "Create a PR" - do not add explanatory text before it like "You can create a PR using this link"` - Just include the markdown link with text "Create a PR" - do not add explanatory text before it like "You can create a PR using this link"`
: "" : `
}` - IMPORTANT: You are currently on the base branch (${eventData.baseBranch}). Before making changes, you should first check if there's already an existing claude branch for this ${eventData.isPR ? "PR" : "issue"}.
- FIRST: Use Bash to run \`git branch -r | grep "claude/${eventData.isPR ? "pr" : "issue"}-${eventData.isPR ? eventData.prNumber : eventData.issueNumber}"\` to check for existing branches.
- If an existing claude branch is found:
- Use mcp__local_git_ops__checkout_branch to switch to the existing branch (set fetch_remote=true)
- Continue working on that branch rather than creating a new one
- If NO existing claude branch is found:
- Create a new branch using mcp__local_git_ops__create_branch
- Use a descriptive branch name following the pattern: claude/${eventData.isPR ? "pr" : "issue"}-${eventData.isPR ? eventData.prNumber : eventData.issueNumber}-<short-description>
- Example: claude/issue-123-fix-login-bug or claude/issue-456-add-user-profile
- After being on the correct branch (existing or new), commit changes using mcp__local_git_ops__commit_files (works for both new and existing files)
- Use mcp__local_git_ops__commit_files to commit files atomically in a single commit (supports single or multiple files).
- CRITICAL: After committing, you MUST push the branch to the remote repository using mcp__local_git_ops__push_branch
- When pushing changes and TRIGGER_USERNAME is not "Unknown", include a "Co-authored-by: ${context.triggerUsername} <${context.triggerUsername}@users.noreply.local>" line in the commit message.
- Provide a URL to create a PR manually in this format:
[Create a PR](${GITEA_SERVER_URL}/${context.repository}/compare/${eventData.baseBranch}...<branch-name>?quick_pull=1&title=<url-encoded-title>&body=<url-encoded-body>)
- IMPORTANT: Use THREE dots (...) between branch names, not two (..)
Example: ${GITEA_SERVER_URL}/${context.repository}/compare/main...feature-branch (correct)
NOT: ${GITEA_SERVER_URL}/${context.repository}/compare/main..feature-branch (incorrect)
- IMPORTANT: Ensure all URL parameters are properly encoded - spaces should be encoded as %20, not left as spaces
Example: Instead of "fix: update welcome message", use "fix%3A%20update%20welcome%20message"
- The target-branch should be '${eventData.baseBranch}'.
- The branch-name is your created branch name
- The body should include:
- A clear description of the changes
- Reference to the original ${eventData.isPR ? "PR" : "issue"}
- The signature: "Generated with [Claude Code](https://claude.ai/code)"
- Just include the markdown link with text "Create a PR" - do not add explanatory text before it like "You can create a PR using this link"`
} }
C. For Complex Changes: C. For Complex Changes:
@@ -572,12 +607,12 @@ ${context.directPrompt ? ` - DIRECT INSTRUCTION: A direct instruction was prov
- Follow the same pushing strategy as for straightforward changes (see section B above). - Follow the same pushing strategy as for straightforward changes (see section B above).
- Or explain why it's too complex: mark todo as completed in checklist with explanation. - Or explain why it's too complex: mark todo as completed in checklist with explanation.
5. Final Update: ${!eventData.isPR || !eventData.claudeBranch ? `6. Final Update:` : `5. Final Update:`}
- Always update the GitHub comment to reflect the current todo state. - Always update the GitHub comment to reflect the current todo state.
- When all todos are completed, remove the spinner and add a brief summary of what was accomplished, and what was not done. - When all todos are completed, remove the spinner and add a brief summary of what was accomplished, and what was not done.
- Note: If you see previous Claude comments with headers like "**Claude finished @user's task**" followed by "---", do not include this in your comment. The system adds this automatically. - Note: If you see previous Claude comments with headers like "**Claude finished @user's task**" followed by "---", do not include this in your comment. The system adds this automatically.
- If you changed any files locally, you must commit them using mcp__local_git_ops__commit_files AND push the branch using mcp__local_git_ops__push_branch before saying that you're done. - If you changed any files locally, you must commit them using mcp__local_git_ops__commit_files AND push the branch using mcp__local_git_ops__push_branch before saying that you're done.
${eventData.claudeBranch ? `- If you created anything in your branch, your comment must include the PR URL with prefilled title and body mentioned above.` : ""} ${!eventData.isPR || !eventData.claudeBranch ? `- If you created a branch and made changes, your comment must include the PR URL with prefilled title and body mentioned above.` : ""}
Important Notes: Important Notes:
- All communication must happen through GitHub PR comments. - All communication must happen through GitHub PR comments.
@@ -585,7 +620,7 @@ Important Notes:
- This includes ALL responses: code reviews, answers to questions, progress updates, and final results.${eventData.isPR ? "\n- PR CRITICAL: After reading files and forming your response, you MUST post it by calling mcp__github__update_issue_comment. Do NOT just respond with a normal response, the user will not see it." : ""} - This includes ALL responses: code reviews, answers to questions, progress updates, and final results.${eventData.isPR ? "\n- PR CRITICAL: After reading files and forming your response, you MUST post it by calling mcp__github__update_issue_comment. Do NOT just respond with a normal response, the user will not see it." : ""}
- You communicate exclusively by editing your single comment - not through any other means. - You communicate exclusively by editing your single comment - not through any other means.
- Use this spinner HTML when work is in progress: <img src="https://raw.githubusercontent.com/markwylde/claude-code-gitea-action/refs/heads/gitea/assets/spinner.gif" width="14px" height="14px" style="vertical-align: middle; margin-left: 4px;" /> - Use this spinner HTML when work is in progress: <img src="https://raw.githubusercontent.com/markwylde/claude-code-gitea-action/refs/heads/gitea/assets/spinner.gif" width="14px" height="14px" style="vertical-align: middle; margin-left: 4px;" />
${eventData.isPR && !eventData.claudeBranch ? `- Always push to the existing branch when triggered on a PR.` : `- IMPORTANT: You are already on the correct branch (${eventData.claudeBranch || "the created branch"}). Never create new branches when triggered on issues or closed/merged PRs.`} ${eventData.isPR && !eventData.claudeBranch ? `- Always push to the existing branch when triggered on a PR.` : eventData.claudeBranch ? `- IMPORTANT: You are already on the correct branch (${eventData.claudeBranch}). Do not create additional branches.` : `- IMPORTANT: You are currently on the base branch (${eventData.baseBranch}). First check for existing claude branches for this ${eventData.isPR ? "PR" : "issue"} and use them if found, otherwise create a new branch using mcp__local_git_ops__create_branch.`}
- Use mcp__local_git_ops__commit_files for making commits (works for both new and existing files, single or multiple). Use mcp__local_git_ops__delete_files for deleting files (supports deleting single or multiple files atomically), or mcp__github__delete_file for deleting a single file. Edit files locally, and the tool will read the content from the same path on disk. - Use mcp__local_git_ops__commit_files for making commits (works for both new and existing files, single or multiple). Use mcp__local_git_ops__delete_files for deleting files (supports deleting single or multiple files atomically), or mcp__github__delete_file for deleting a single file. Edit files locally, and the tool will read the content from the same path on disk.
Tool usage examples: Tool usage examples:
- mcp__local_git_ops__commit_files: {"files": ["path/to/file1.js", "path/to/file2.py"], "message": "feat: add new feature"} - mcp__local_git_ops__commit_files: {"files": ["path/to/file1.js", "path/to/file2.py"], "message": "feat: add new feature"}
@@ -606,9 +641,10 @@ What You CAN Do:
- Implement code changes (simple to moderate complexity) when explicitly requested - Implement code changes (simple to moderate complexity) when explicitly requested
- Create pull requests for changes to human-authored code - Create pull requests for changes to human-authored code
- Smart branch handling: - Smart branch handling:
- When triggered on an issue: Always create a new branch - When triggered on an issue: Create a new branch using mcp__local_git_ops__create_branch
- When triggered on an open PR: Always push directly to the existing PR branch - When triggered on an open PR: Push directly to the existing PR branch
- When triggered on a closed PR: Create a new branch - When triggered on a closed PR: Create a new branch using mcp__local_git_ops__create_branch
- Create new branches when needed using the create_branch tool
What You CANNOT Do: What You CANNOT Do:
- Submit formal GitHub PR reviews - Submit formal GitHub PR reviews
@@ -616,7 +652,7 @@ What You CANNOT Do:
- Post multiple comments (you only update your initial comment) - Post multiple comments (you only update your initial comment)
- Execute commands outside the repository context - Execute commands outside the repository context
- Run arbitrary Bash commands (unless explicitly allowed via allowed_tools configuration) - Run arbitrary Bash commands (unless explicitly allowed via allowed_tools configuration)
- Perform branch operations (cannot merge branches, rebase, or perform other git operations beyond pushing commits) - Perform advanced branch operations (cannot merge branches, rebase, or perform other complex git operations beyond creating, checking out, and pushing branches)
- Modify files in the .github/workflows directory (GitHub App permissions do not allow workflow modifications) - Modify files in the .github/workflows directory (GitHub App permissions do not allow workflow modifications)
- View CI/CD results or workflow run outputs (cannot access GitHub Actions logs or test results) - View CI/CD results or workflow run outputs (cannot access GitHub Actions logs or test results)

View File

@@ -34,7 +34,7 @@ type IssueCommentEvent = {
issueNumber: string; issueNumber: string;
isPR: false; isPR: false;
baseBranch: string; baseBranch: string;
claudeBranch: string; claudeBranch?: string;
commentBody: string; commentBody: string;
}; };
@@ -55,7 +55,7 @@ type IssueOpenedEvent = {
isPR: false; isPR: false;
issueNumber: string; issueNumber: string;
baseBranch: string; baseBranch: string;
claudeBranch: string; claudeBranch?: string;
}; };
type IssueAssignedEvent = { type IssueAssignedEvent = {
@@ -64,7 +64,7 @@ type IssueAssignedEvent = {
isPR: false; isPR: false;
issueNumber: string; issueNumber: string;
baseBranch: string; baseBranch: string;
claudeBranch: string; claudeBranch?: string;
assigneeTrigger: string; assigneeTrigger: string;
}; };

View File

@@ -73,7 +73,7 @@ async function run() {
core.setOutput("CLAUDE_BRANCH", branchInfo.claudeBranch); core.setOutput("CLAUDE_BRANCH", branchInfo.claudeBranch);
} }
// Step 9: Update initial comment with branch link (only for issues that created a new branch) // Step 9: Update initial comment with branch link (only if a claude branch was created)
if (branchInfo.claudeBranch) { if (branchInfo.claudeBranch) {
await updateTrackingComment( await updateTrackingComment(
client, client,

View File

@@ -32,7 +32,7 @@ async function run() {
const client = createClient(githubToken); const client = createClient(githubToken);
const serverUrl = GITEA_SERVER_URL; const serverUrl = GITEA_SERVER_URL;
const jobUrl = `${serverUrl}/${owner}/${repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; const jobUrl = `${serverUrl}/${owner}/${repo}/actions/runs/${process.env.GITHUB_RUN_NUMBER}`;
let comment; let comment;
let isPRReviewComment = false; let isPRReviewComment = false;

View File

@@ -1,14 +1,14 @@
export const GITEA_API_URL = // Derive API URL from server URL for Gitea instances
process.env.GITEA_API_URL || "https://api.github.com"; function deriveApiUrl(serverUrl: string): string {
if (serverUrl.includes("github.com")) {
// Derive server URL from API URL for Gitea instances return "https://api.github.com";
function deriveServerUrl(apiUrl: string): string {
if (apiUrl.includes("api.github.com")) {
return "https://github.com";
} }
// For Gitea, remove /api/v1 from the API URL to get the server URL // For Gitea, add /api/v1 to the server URL to get the API URL
return apiUrl.replace(/\/api\/v1\/?$/, ""); return `${serverUrl}/api/v1`;
} }
export const GITEA_SERVER_URL = export const GITEA_SERVER_URL =
process.env.GITEA_SERVER_URL || deriveServerUrl(GITEA_API_URL); process.env.GITHUB_SERVER_URL || "https://github.com";
export const GITEA_API_URL =
process.env.GITEA_API_URL || deriveApiUrl(GITEA_SERVER_URL);

View File

@@ -40,7 +40,7 @@ export function parseGitHubContext(): ParsedGitHubContext {
const context = github.context; const context = github.context;
const commonFields = { const commonFields = {
runId: process.env.GITHUB_RUN_ID!, runId: process.env.GITHUB_RUN_NUMBER!,
eventName: context.eventName, eventName: context.eventName,
eventAction: context.payload.action, eventAction: context.payload.action,
repository: { repository: {

View File

@@ -29,6 +29,18 @@ export async function setupBranch(
const { baseBranch } = context.inputs; const { baseBranch } = context.inputs;
const isPR = context.isPR; const isPR = context.isPR;
// Determine base branch - use baseBranch if provided, otherwise fetch default
let sourceBranch: string;
if (baseBranch) {
// Use provided base branch for source
sourceBranch = baseBranch;
} else {
// No base branch provided, fetch the default branch to use as source
const repoResponse = await client.api.getRepo(owner, repo);
sourceBranch = repoResponse.data.default_branch;
}
if (isPR) { if (isPR) {
const prData = githubData.contextData as GitHubPullRequest; const prData = githubData.contextData as GitHubPullRequest;
const prState = prData.state; const prState = prData.state;
@@ -36,9 +48,18 @@ export async function setupBranch(
// Check if PR is closed or merged // Check if PR is closed or merged
if (prState === "CLOSED" || prState === "MERGED") { if (prState === "CLOSED" || prState === "MERGED") {
console.log( console.log(
`PR #${entityNumber} is ${prState}, creating new branch from source...`, `PR #${entityNumber} is ${prState}, will let Claude create a new branch when needed`,
); );
// Fall through to create a new branch like we do for issues
// Check out the base branch and let Claude create branches as needed
await $`git fetch origin ${sourceBranch}`;
await $`git checkout ${sourceBranch}`;
await $`git pull origin ${sourceBranch}`;
return {
baseBranch: sourceBranch,
currentBranch: sourceBranch,
};
} else { } else {
// Handle open PR: Checkout the PR branch // Handle open PR: Checkout the PR branch
console.log("This is an open PR, checking out PR branch..."); console.log("This is an open PR, checking out PR branch...");
@@ -62,97 +83,54 @@ export async function setupBranch(
} }
} }
// Determine source branch - use baseBranch if provided, otherwise fetch default // For issues, check out the base branch and let Claude create branches as needed
let sourceBranch: string;
if (baseBranch) {
// Use provided base branch for source
sourceBranch = baseBranch;
} else {
// No base branch provided, fetch the default branch to use as source
const repoResponse = await client.api.getRepo(owner, repo);
sourceBranch = repoResponse.data.default_branch;
}
// Creating a new branch for either an issue or closed/merged PR
const entityType = isPR ? "pr" : "issue";
console.log( console.log(
`Creating new branch for ${entityType} #${entityNumber} from source branch: ${sourceBranch}...`, `Setting up base branch ${sourceBranch} for issue #${entityNumber}, Claude will create branch when needed...`,
); );
const timestamp = new Date()
.toISOString()
.replace(/[:-]/g, "")
.replace(/\.\d{3}Z/, "")
.split("T")
.join("_");
const newBranch = `claude/${entityType}-${entityNumber}-${timestamp}`;
try { try {
// Use local git operations instead of API since Gitea's API is unreliable
console.log(
`Setting up local git branch: ${newBranch} from: ${sourceBranch}`,
);
// Ensure we're in the repository directory // Ensure we're in the repository directory
const repoDir = process.env.GITHUB_WORKSPACE || process.cwd(); const repoDir = process.env.GITHUB_WORKSPACE || process.cwd();
console.log(`Working in directory: ${repoDir}`); console.log(`Working in directory: ${repoDir}`);
try { // Check if we're in a git repository
// Check if we're in a git repository console.log(`Checking if we're in a git repository...`);
console.log(`Checking if we're in a git repository...`); await $`git status`;
await $`git status`;
// Ensure we have the latest version of the source branch // Ensure we have the latest version of the source branch
console.log(`Fetching latest ${sourceBranch}...`); console.log(`Fetching latest ${sourceBranch}...`);
await $`git fetch origin ${sourceBranch}`; await $`git fetch origin ${sourceBranch}`;
// Checkout the source branch // Checkout the source branch
console.log(`Checking out ${sourceBranch}...`); console.log(`Checking out ${sourceBranch}...`);
await $`git checkout ${sourceBranch}`; await $`git checkout ${sourceBranch}`;
// Pull latest changes // Pull latest changes
console.log(`Pulling latest changes for ${sourceBranch}...`); console.log(`Pulling latest changes for ${sourceBranch}...`);
await $`git pull origin ${sourceBranch}`; await $`git pull origin ${sourceBranch}`;
// Create and checkout the new branch // Verify the branch was checked out
console.log(`Creating new branch: ${newBranch}`); const currentBranch = await $`git branch --show-current`;
await $`git checkout -b ${newBranch}`; const branchName = currentBranch.text().trim();
console.log(`Current branch: ${branchName}`);
// Verify the branch was created if (branchName === sourceBranch) {
const currentBranch = await $`git branch --show-current`; console.log(`✅ Successfully checked out base branch: ${sourceBranch}`);
const branchName = currentBranch.text().trim(); } else {
console.log(`Current branch after creation: ${branchName}`);
if (branchName === newBranch) {
console.log(
`✅ Successfully created and checked out branch: ${newBranch}`,
);
} else {
throw new Error(
`Branch creation failed. Expected ${newBranch}, got ${branchName}`,
);
}
} catch (gitError: any) {
console.error(`❌ Git operations failed:`, gitError);
console.error(`Error message: ${gitError.message || gitError}`);
// This is a critical failure - the branch MUST be created for Claude to work
throw new Error( throw new Error(
`Failed to create branch ${newBranch}: ${gitError.message || gitError}`, `Branch checkout failed. Expected ${sourceBranch}, got ${branchName}`,
); );
} }
console.log(`Branch setup completed for: ${newBranch}`); console.log(
`Branch setup completed, ready for Claude to create branches as needed`,
);
// Set outputs for GitHub Actions // Set outputs for GitHub Actions
core.setOutput("CLAUDE_BRANCH", newBranch);
core.setOutput("BASE_BRANCH", sourceBranch); core.setOutput("BASE_BRANCH", sourceBranch);
return { return {
baseBranch: sourceBranch, baseBranch: sourceBranch,
claudeBranch: newBranch, currentBranch: sourceBranch,
currentBranch: newBranch,
}; };
} catch (error) { } catch (error) {
console.error("Error setting up branch:", error); console.error("Error setting up branch:", error);

View File

@@ -23,11 +23,11 @@ export async function setupGitHubToken(): Promise<string> {
} }
throw new Error( throw new Error(
"No GitHub token available. Please provide a github_token input or ensure GITHUB_TOKEN is available in the workflow environment.", "No GitHub token available. Please provide a gitea_token input or ensure GITHUB_TOKEN is available in the workflow environment.",
); );
} catch (error) { } catch (error) {
core.setFailed( core.setFailed(
`Failed to setup GitHub token: ${error}.\n\nPlease provide a \`github_token\` in the \`with\` section of the action in your workflow yml file, or ensure the workflow has access to the default GITHUB_TOKEN.`, `Failed to setup GitHub token: ${error}.\n\nPlease provide a \`gitea_token\` in the \`with\` section of the action in your workflow yml file, or ensure the workflow has access to the default GITHUB_TOKEN.`,
); );
process.exit(1); process.exit(1);
} }

View File

@@ -1,199 +0,0 @@
#!/usr/bin/env node
// GitHub File Operations MCP Server
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { readFile } from "fs/promises";
import { join } from "path";
import fetch from "node-fetch";
import { GITEA_API_URL } from "../github/api/config";
type GitHubRef = {
object: {
sha: string;
};
};
type GitHubCommit = {
tree: {
sha: string;
};
};
type GitHubTree = {
sha: string;
};
type GitHubNewCommit = {
sha: string;
message: string;
author: {
name: string;
date: string;
};
};
// Get repository information from environment variables
const REPO_OWNER = process.env.REPO_OWNER;
const REPO_NAME = process.env.REPO_NAME;
const BRANCH_NAME = process.env.BRANCH_NAME;
const REPO_DIR = process.env.REPO_DIR || process.cwd();
if (!REPO_OWNER || !REPO_NAME || !BRANCH_NAME) {
console.error(
"Error: REPO_OWNER, REPO_NAME, and BRANCH_NAME environment variables are required",
);
process.exit(1);
}
const server = new McpServer({
name: "GitHub File Operations Server",
version: "0.0.1",
});
// Commit files tool
server.tool(
"commit_files",
"Commit one or more files to a repository in a single commit (this will commit them atomically in the remote repository)",
{
files: z
.array(z.string())
.describe(
'Array of file paths relative to repository root (e.g. ["src/main.js", "README.md"]). All files must exist locally.',
),
message: z.string().describe("Commit message"),
},
async ({ files, message }) => {
const owner = REPO_OWNER;
const repo = REPO_NAME;
const branch = BRANCH_NAME;
try {
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN environment variable is required");
}
const processedFiles = files.map((filePath) => {
if (filePath.startsWith("/")) {
return filePath.slice(1);
}
return filePath;
});
// NOTE: Gitea does not support GitHub's low-level git API operations
// (creating trees, commits, etc.). We need to use the contents API instead.
// For now, throw an error indicating this functionality is not available.
throw new Error(
"Multi-file commits are not supported with Gitea. " +
"Gitea does not provide the low-level git API operations (trees, commits) " +
"that are required for atomic multi-file commits. " +
"Please commit files individually using the contents API.",
);
return {
content: [
{
type: "text",
text: JSON.stringify(simplifiedResult, null, 2),
},
],
};
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
error: errorMessage,
isError: true,
};
}
},
);
// Delete files tool
server.tool(
"delete_files",
"Delete one or more files from a repository in a single commit",
{
paths: z
.array(z.string())
.describe(
'Array of file paths to delete relative to repository root (e.g. ["src/old-file.js", "docs/deprecated.md"])',
),
message: z.string().describe("Commit message"),
},
async ({ paths, message }) => {
const owner = REPO_OWNER;
const repo = REPO_NAME;
const branch = BRANCH_NAME;
try {
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN environment variable is required");
}
// Convert absolute paths to relative if they match CWD
const cwd = process.cwd();
const processedPaths = paths.map((filePath) => {
if (filePath.startsWith("/")) {
if (filePath.startsWith(cwd)) {
// Strip CWD from absolute path
return filePath.slice(cwd.length + 1);
} else {
throw new Error(
`Path '${filePath}' must be relative to repository root or within current working directory`,
);
}
}
return filePath;
});
// NOTE: Gitea does not support GitHub's low-level git API operations
// (creating trees, commits, etc.). We need to use the contents API instead.
// For now, throw an error indicating this functionality is not available.
throw new Error(
"Multi-file deletions are not supported with Gitea. " +
"Gitea does not provide the low-level git API operations (trees, commits) " +
"that are required for atomic multi-file operations. " +
"Please delete files individually using the contents API.",
);
return {
content: [
{
type: "text",
text: JSON.stringify(simplifiedResult, null, 2),
},
],
};
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
error: errorMessage,
isError: true,
};
}
},
);
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
process.on("exit", () => {
server.close();
});
}
runServer().catch(console.error);

View File

@@ -127,6 +127,102 @@ server.tool(
}, },
); );
// Checkout branch tool
server.tool(
"checkout_branch",
"Checkout an existing branch using local git operations",
{
branch_name: z.string().describe("Name of the existing branch to checkout"),
create_if_missing: z
.boolean()
.optional()
.describe(
"Create branch if it doesn't exist locally (defaults to false)",
),
fetch_remote: z
.boolean()
.optional()
.describe(
"Fetch from remote if branch doesn't exist locally (defaults to true)",
),
},
async ({ branch_name, create_if_missing = false, fetch_remote = true }) => {
try {
// Check if branch exists locally
let branchExists = false;
try {
runGitCommand(`git rev-parse --verify ${branch_name}`);
branchExists = true;
} catch (error) {
console.log(
`[LOCAL-GIT-MCP] Branch ${branch_name} doesn't exist locally`,
);
}
// If branch doesn't exist locally, try to fetch from remote
if (!branchExists && fetch_remote) {
try {
console.log(
`[LOCAL-GIT-MCP] Attempting to fetch ${branch_name} from remote`,
);
runGitCommand(`git fetch origin ${branch_name}:${branch_name}`);
branchExists = true;
} catch (error) {
console.log(
`[LOCAL-GIT-MCP] Branch ${branch_name} doesn't exist on remote`,
);
}
}
// If branch still doesn't exist and create_if_missing is true, create it
if (!branchExists && create_if_missing) {
console.log(`[LOCAL-GIT-MCP] Creating new branch ${branch_name}`);
runGitCommand(`git checkout -b ${branch_name}`);
return {
content: [
{
type: "text",
text: `Successfully created and checked out new branch: ${branch_name}`,
},
],
};
}
// If branch doesn't exist and we can't/won't create it, throw error
if (!branchExists) {
throw new Error(
`Branch '${branch_name}' does not exist locally or on remote. Use create_if_missing=true to create it.`,
);
}
// Checkout the existing branch
runGitCommand(`git checkout ${branch_name}`);
return {
content: [
{
type: "text",
text: `Successfully checked out branch: ${branch_name}`,
},
],
};
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text",
text: `Error checking out branch: ${errorMessage}`,
},
],
error: errorMessage,
isError: true,
};
}
},
);
// Commit files tool // Commit files tool
server.tool( server.tool(
"commit_files", "commit_files",

View File

@@ -317,7 +317,7 @@ describe("generatePrompt", () => {
expect(prompt).toContain("<trigger_username>johndoe</trigger_username>"); expect(prompt).toContain("<trigger_username>johndoe</trigger_username>");
expect(prompt).toContain( expect(prompt).toContain(
"Co-authored-by: johndoe <johndoe@users.noreply.github.com>", "Co-authored-by: johndoe <johndoe@users.noreply.local>",
); );
}); });
@@ -338,7 +338,7 @@ describe("generatePrompt", () => {
// Should contain PR-specific instructions // Should contain PR-specific instructions
expect(prompt).toContain( expect(prompt).toContain(
"Push directly using mcp__github_file_ops__commit_files to the existing branch", "Commit changes using mcp__local_git_ops__commit_files to the existing branch",
); );
expect(prompt).toContain( expect(prompt).toContain(
"Always push to the existing branch when triggered on a PR", "Always push to the existing branch when triggered on a PR",
@@ -378,12 +378,12 @@ describe("generatePrompt", () => {
); );
expect(prompt).toContain("Create a PR](https://github.com/"); expect(prompt).toContain("Create a PR](https://github.com/");
expect(prompt).toContain( expect(prompt).toContain(
"If you created anything in your branch, your comment must include the PR URL", "If you created a branch and made changes, your comment must include the PR URL",
); );
// Should NOT contain PR-specific instructions // Should NOT contain PR-specific instructions
expect(prompt).not.toContain( expect(prompt).not.toContain(
"Push directly using mcp__github_file_ops__commit_files to the existing branch", "Commit changes using mcp__local_git_ops__commit_files to the existing branch",
); );
expect(prompt).not.toContain( expect(prompt).not.toContain(
"Always push to the existing branch when triggered on a PR", "Always push to the existing branch when triggered on a PR",
@@ -449,13 +449,10 @@ describe("generatePrompt", () => {
"The branch-name is the current branch: claude/pr-456-20240101_120000", "The branch-name is the current branch: claude/pr-456-20240101_120000",
); );
expect(prompt).toContain("Reference to the original PR"); expect(prompt).toContain("Reference to the original PR");
expect(prompt).toContain(
"If you created anything in your branch, your comment must include the PR URL",
);
// Should NOT contain open PR instructions // Should NOT contain open PR instructions
expect(prompt).not.toContain( expect(prompt).not.toContain(
"Push directly using mcp__github_file_ops__commit_files to the existing branch", "Commit changes using mcp__local_git_ops__commit_files to the existing branch",
); );
}); });
@@ -478,7 +475,7 @@ describe("generatePrompt", () => {
// Should contain open PR instructions // Should contain open PR instructions
expect(prompt).toContain( expect(prompt).toContain(
"Push directly using mcp__github_file_ops__commit_files to the existing branch", "Commit changes using mcp__local_git_ops__commit_files to the existing branch",
); );
expect(prompt).toContain( expect(prompt).toContain(
"Always push to the existing branch when triggered on a PR", "Always push to the existing branch when triggered on a PR",
@@ -543,9 +540,6 @@ describe("generatePrompt", () => {
); );
expect(prompt).toContain("Create a PR](https://github.com/"); expect(prompt).toContain("Create a PR](https://github.com/");
expect(prompt).toContain("Reference to the original PR"); expect(prompt).toContain("Reference to the original PR");
expect(prompt).toContain(
"If you created anything in your branch, your comment must include the PR URL",
);
}); });
test("should handle pull_request event on closed PR with new branch", () => { test("should handle pull_request event on closed PR with new branch", () => {
@@ -638,8 +632,8 @@ describe("buildAllowedToolsString", () => {
expect(result).toContain("Write"); expect(result).toContain("Write");
expect(result).toContain("mcp__github__update_issue_comment"); expect(result).toContain("mcp__github__update_issue_comment");
expect(result).not.toContain("mcp__github__update_pull_request_comment"); expect(result).not.toContain("mcp__github__update_pull_request_comment");
expect(result).toContain("mcp__github_file_ops__commit_files"); expect(result).toContain("mcp__local_git_ops__commit_files");
expect(result).toContain("mcp__github_file_ops__delete_files"); expect(result).toContain("mcp__local_git_ops__delete_files");
}); });
test("should return PR comment tool for inline review comments", () => { test("should return PR comment tool for inline review comments", () => {
@@ -662,8 +656,8 @@ describe("buildAllowedToolsString", () => {
expect(result).toContain("Write"); expect(result).toContain("Write");
expect(result).not.toContain("mcp__github__update_issue_comment"); expect(result).not.toContain("mcp__github__update_issue_comment");
expect(result).toContain("mcp__github__update_pull_request_comment"); expect(result).toContain("mcp__github__update_pull_request_comment");
expect(result).toContain("mcp__github_file_ops__commit_files"); expect(result).toContain("mcp__local_git_ops__commit_files");
expect(result).toContain("mcp__github_file_ops__delete_files"); expect(result).toContain("mcp__local_git_ops__delete_files");
}); });
test("should append custom tools when provided", () => { test("should append custom tools when provided", () => {