mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 15:04:13 +08:00
Compare commits
1 Commits
ashwin/bum
...
ashwin/err
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08bd8114e8 |
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ""
|
|
||||||
labels: bug
|
|
||||||
assignees: ""
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Workflow yml file**
|
|
||||||
If it's not sensitive, consider including a paste of your full Claude workflow.yml file.
|
|
||||||
|
|
||||||
**API Provider**
|
|
||||||
|
|
||||||
[ ] Anthropic First-Party API (default)
|
|
||||||
[ ] AWS Bedrock
|
|
||||||
[ ] GCP Vertex
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
2
.github/workflows/claude.yml
vendored
2
.github/workflows/claude.yml
vendored
@@ -34,5 +34,3 @@ 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 }}
|
||||||
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."
|
|
||||||
|
|||||||
104
.github/workflows/issue-triage.yml
vendored
104
.github/workflows/issue-triage.yml
vendored
@@ -1,104 +0,0 @@
|
|||||||
name: Claude Issue Triage
|
|
||||||
description: Run Claude Code for issue triage in GitHub Actions
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
triage-issue:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 10
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup GitHub MCP Server
|
|
||||||
run: |
|
|
||||||
mkdir -p /tmp/mcp-config
|
|
||||||
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
|
||||||
{
|
|
||||||
"github": {
|
|
||||||
"command": "docker",
|
|
||||||
"args": [
|
|
||||||
"run",
|
|
||||||
"-i",
|
|
||||||
"--rm",
|
|
||||||
"-e",
|
|
||||||
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
||||||
"ghcr.io/github/github-mcp-server:sha-7aced2b"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Create triage prompt
|
|
||||||
run: |
|
|
||||||
mkdir -p /tmp/claude-prompts
|
|
||||||
cat > /tmp/claude-prompts/triage-prompt.txt << 'EOF'
|
|
||||||
You're an issue triage assistant for GitHub issues. Your task is to analyze the issue and select appropriate labels from the provided list.
|
|
||||||
|
|
||||||
IMPORTANT: Don't post any comments or messages to the issue. Your only action should be to apply labels.
|
|
||||||
|
|
||||||
Issue Information:
|
|
||||||
- REPO: ${{ github.repository }}
|
|
||||||
- ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
||||||
|
|
||||||
TASK OVERVIEW:
|
|
||||||
|
|
||||||
1. First, fetch the list of labels available in this repository by running: `gh label list`. Run exactly this command with nothing else.
|
|
||||||
|
|
||||||
2. Next, use the GitHub tools to get context about the issue:
|
|
||||||
- You have access to these tools:
|
|
||||||
- mcp__github__get_issue: Use this to retrieve the current issue's details including title, description, and existing labels
|
|
||||||
- mcp__github__get_issue_comments: Use this to read any discussion or additional context provided in the comments
|
|
||||||
- mcp__github__update_issue: Use this to apply labels to the issue (do not use this for commenting)
|
|
||||||
- mcp__github__search_issues: Use this to find similar issues that might provide context for proper categorization and to identify potential duplicate issues
|
|
||||||
- mcp__github__list_issues: Use this to understand patterns in how other issues are labeled
|
|
||||||
- Start by using mcp__github__get_issue to get the issue details
|
|
||||||
|
|
||||||
3. Analyze the issue content, considering:
|
|
||||||
- The issue title and description
|
|
||||||
- The type of issue (bug report, feature request, question, etc.)
|
|
||||||
- Technical areas mentioned
|
|
||||||
- Severity or priority indicators
|
|
||||||
- User impact
|
|
||||||
- Components affected
|
|
||||||
|
|
||||||
4. Select appropriate labels from the available labels list provided above:
|
|
||||||
- Choose labels that accurately reflect the issue's nature
|
|
||||||
- Be specific but comprehensive
|
|
||||||
- Select priority labels if you can determine urgency (high-priority, med-priority, or low-priority)
|
|
||||||
- Consider platform labels (android, ios) if applicable
|
|
||||||
- If you find similar issues using mcp__github__search_issues, consider using a "duplicate" label if appropriate. Only do so if the issue is a duplicate of another OPEN issue.
|
|
||||||
|
|
||||||
5. Apply the selected labels:
|
|
||||||
- Use mcp__github__update_issue to apply your selected labels
|
|
||||||
- DO NOT post any comments explaining your decision
|
|
||||||
- DO NOT communicate directly with users
|
|
||||||
- If no labels are clearly applicable, do not apply any labels
|
|
||||||
|
|
||||||
IMPORTANT GUIDELINES:
|
|
||||||
- Be thorough in your analysis
|
|
||||||
- Only select labels from the provided list above
|
|
||||||
- DO NOT post any comments to the issue
|
|
||||||
- Your ONLY action should be to apply labels using mcp__github__update_issue
|
|
||||||
- It's okay to not add any labels if none are clearly applicable
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Run Claude Code for Issue Triage
|
|
||||||
uses: anthropics/claude-code-base-action@beta
|
|
||||||
with:
|
|
||||||
prompt_file: /tmp/claude-prompts/triage-prompt.txt
|
|
||||||
allowed_tools: "Bash(gh label list),mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__update_issue,mcp__github__search_issues,mcp__github__list_issues"
|
|
||||||
mcp_config_file: /tmp/mcp-config/mcp-servers.json
|
|
||||||
timeout_minutes: "5"
|
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,3 @@
|
|||||||
.DS_Store
|
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
**/.claude/settings.local.json
|
**/.claude/settings.local.json
|
||||||
|
|||||||
@@ -446,7 +446,7 @@ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|||||||
```
|
```
|
||||||
|
|
||||||
This applies to all sensitive values including API keys, access tokens, and credentials.
|
This applies to all sensitive values including API keys, access tokens, and credentials.
|
||||||
We also recommend that you always use short-lived tokens when possible
|
We also reccomend that you always use short-lived tokens when possible
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
15
action.yml
15
action.yml
@@ -12,9 +12,6 @@ inputs:
|
|||||||
assignee_trigger:
|
assignee_trigger:
|
||||||
description: "The assignee username that triggers the action (e.g. @claude)"
|
description: "The assignee username that triggers the action (e.g. @claude)"
|
||||||
required: false
|
required: false
|
||||||
base_branch:
|
|
||||||
description: "The branch to use as the base/source when creating new branches (defaults to repository default branch)"
|
|
||||||
required: false
|
|
||||||
|
|
||||||
# Claude Code configuration
|
# Claude Code configuration
|
||||||
model:
|
model:
|
||||||
@@ -70,7 +67,7 @@ runs:
|
|||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Install Bun
|
- name: Install Bun
|
||||||
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # https://github.com/oven-sh/setup-bun/releases/tag/v2.0.2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: 1.2.11
|
bun-version: 1.2.11
|
||||||
|
|
||||||
@@ -88,7 +85,6 @@ runs:
|
|||||||
env:
|
env:
|
||||||
TRIGGER_PHRASE: ${{ inputs.trigger_phrase }}
|
TRIGGER_PHRASE: ${{ inputs.trigger_phrase }}
|
||||||
ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
|
ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
|
||||||
BASE_BRANCH: ${{ inputs.base_branch }}
|
|
||||||
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 }}
|
||||||
@@ -98,7 +94,7 @@ runs:
|
|||||||
- name: Run Claude Code
|
- name: Run Claude Code
|
||||||
id: claude-code
|
id: claude-code
|
||||||
if: steps.prepare.outputs.contains_trigger == 'true'
|
if: steps.prepare.outputs.contains_trigger == 'true'
|
||||||
uses: anthropics/claude-code-base-action@c8e31bd52d9a149b3f8309d7978c6edaa282688d # v0.0.8
|
uses: anthropics/claude-code-base-action@beta
|
||||||
with:
|
with:
|
||||||
prompt_file: /tmp/claude-prompts/claude-prompt.txt
|
prompt_file: /tmp/claude-prompts/claude-prompt.txt
|
||||||
allowed_tools: ${{ env.ALLOWED_TOOLS }}
|
allowed_tools: ${{ env.ALLOWED_TOOLS }}
|
||||||
@@ -114,9 +110,6 @@ runs:
|
|||||||
ANTHROPIC_MODEL: ${{ inputs.model || inputs.anthropic_model }}
|
ANTHROPIC_MODEL: ${{ inputs.model || inputs.anthropic_model }}
|
||||||
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
|
||||||
|
|
||||||
# Provider configuration
|
|
||||||
ANTHROPIC_BASE_URL: ${{ env.ANTHROPIC_BASE_URL }}
|
|
||||||
|
|
||||||
# AWS configuration
|
# AWS configuration
|
||||||
AWS_REGION: ${{ env.AWS_REGION }}
|
AWS_REGION: ${{ env.AWS_REGION }}
|
||||||
AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
|
||||||
@@ -150,12 +143,10 @@ runs:
|
|||||||
TRIGGER_COMMENT_ID: ${{ github.event.comment.id }}
|
TRIGGER_COMMENT_ID: ${{ github.event.comment.id }}
|
||||||
CLAUDE_BRANCH: ${{ steps.prepare.outputs.CLAUDE_BRANCH }}
|
CLAUDE_BRANCH: ${{ steps.prepare.outputs.CLAUDE_BRANCH }}
|
||||||
IS_PR: ${{ github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' }}
|
IS_PR: ${{ github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' }}
|
||||||
BASE_BRANCH: ${{ steps.prepare.outputs.BASE_BRANCH }}
|
DEFAULT_BRANCH: ${{ steps.prepare.outputs.DEFAULT_BRANCH }}
|
||||||
CLAUDE_SUCCESS: ${{ steps.claude-code.outputs.conclusion == 'success' }}
|
CLAUDE_SUCCESS: ${{ steps.claude-code.outputs.conclusion == 'success' }}
|
||||||
OUTPUT_FILE: ${{ steps.claude-code.outputs.execution_file || '' }}
|
OUTPUT_FILE: ${{ steps.claude-code.outputs.execution_file || '' }}
|
||||||
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_ERROR: ${{ steps.prepare.outputs.prepare_error || '' }}
|
|
||||||
|
|
||||||
- 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
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -58,27 +58,10 @@ export function buildAllowedToolsString(
|
|||||||
|
|
||||||
export function buildDisallowedToolsString(
|
export function buildDisallowedToolsString(
|
||||||
customDisallowedTools?: string,
|
customDisallowedTools?: string,
|
||||||
allowedTools?: string,
|
|
||||||
): string {
|
): string {
|
||||||
let disallowedTools = [...DISALLOWED_TOOLS];
|
let allDisallowedTools = DISALLOWED_TOOLS.join(",");
|
||||||
|
|
||||||
// If user has explicitly allowed some hardcoded disallowed tools, remove them from disallowed list
|
|
||||||
if (allowedTools) {
|
|
||||||
const allowedToolsArray = allowedTools
|
|
||||||
.split(",")
|
|
||||||
.map((tool) => tool.trim());
|
|
||||||
disallowedTools = disallowedTools.filter(
|
|
||||||
(tool) => !allowedToolsArray.includes(tool),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let allDisallowedTools = disallowedTools.join(",");
|
|
||||||
if (customDisallowedTools) {
|
if (customDisallowedTools) {
|
||||||
if (allDisallowedTools) {
|
allDisallowedTools = `${allDisallowedTools},${customDisallowedTools}`;
|
||||||
allDisallowedTools = `${allDisallowedTools},${customDisallowedTools}`;
|
|
||||||
} else {
|
|
||||||
allDisallowedTools = customDisallowedTools;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return allDisallowedTools;
|
return allDisallowedTools;
|
||||||
}
|
}
|
||||||
@@ -86,7 +69,7 @@ export function buildDisallowedToolsString(
|
|||||||
export function prepareContext(
|
export function prepareContext(
|
||||||
context: ParsedGitHubContext,
|
context: ParsedGitHubContext,
|
||||||
claudeCommentId: string,
|
claudeCommentId: string,
|
||||||
baseBranch?: string,
|
defaultBranch?: string,
|
||||||
claudeBranch?: string,
|
claudeBranch?: string,
|
||||||
): PreparedContext {
|
): PreparedContext {
|
||||||
const repository = context.repository.full_name;
|
const repository = context.repository.full_name;
|
||||||
@@ -164,7 +147,7 @@ export function prepareContext(
|
|||||||
...(commentId && { commentId }),
|
...(commentId && { commentId }),
|
||||||
commentBody,
|
commentBody,
|
||||||
...(claudeBranch && { claudeBranch }),
|
...(claudeBranch && { claudeBranch }),
|
||||||
...(baseBranch && { baseBranch }),
|
...(defaultBranch && { defaultBranch }),
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -186,7 +169,7 @@ export function prepareContext(
|
|||||||
prNumber,
|
prNumber,
|
||||||
commentBody,
|
commentBody,
|
||||||
...(claudeBranch && { claudeBranch }),
|
...(claudeBranch && { claudeBranch }),
|
||||||
...(baseBranch && { baseBranch }),
|
...(defaultBranch && { defaultBranch }),
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -211,13 +194,13 @@ export function prepareContext(
|
|||||||
prNumber,
|
prNumber,
|
||||||
commentBody,
|
commentBody,
|
||||||
...(claudeBranch && { claudeBranch }),
|
...(claudeBranch && { claudeBranch }),
|
||||||
...(baseBranch && { baseBranch }),
|
...(defaultBranch && { defaultBranch }),
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
} else if (!claudeBranch) {
|
} else if (!claudeBranch) {
|
||||||
throw new Error("CLAUDE_BRANCH is required for issue_comment event");
|
throw new Error("CLAUDE_BRANCH is required for issue_comment event");
|
||||||
} else if (!baseBranch) {
|
} else if (!defaultBranch) {
|
||||||
throw new Error("BASE_BRANCH is required for issue_comment event");
|
throw new Error("DEFAULT_BRANCH is required for issue_comment event");
|
||||||
} else if (!issueNumber) {
|
} else if (!issueNumber) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"ISSUE_NUMBER is required for issue_comment event for issues",
|
"ISSUE_NUMBER is required for issue_comment event for issues",
|
||||||
@@ -229,7 +212,7 @@ export function prepareContext(
|
|||||||
commentId,
|
commentId,
|
||||||
isPR: false,
|
isPR: false,
|
||||||
claudeBranch: claudeBranch,
|
claudeBranch: claudeBranch,
|
||||||
baseBranch,
|
defaultBranch,
|
||||||
issueNumber,
|
issueNumber,
|
||||||
commentBody,
|
commentBody,
|
||||||
};
|
};
|
||||||
@@ -245,8 +228,8 @@ export function prepareContext(
|
|||||||
if (isPR) {
|
if (isPR) {
|
||||||
throw new Error("IS_PR must be false for issues event");
|
throw new Error("IS_PR must be false for issues event");
|
||||||
}
|
}
|
||||||
if (!baseBranch) {
|
if (!defaultBranch) {
|
||||||
throw new Error("BASE_BRANCH is required for issues event");
|
throw new Error("DEFAULT_BRANCH is required for issues event");
|
||||||
}
|
}
|
||||||
if (!claudeBranch) {
|
if (!claudeBranch) {
|
||||||
throw new Error("CLAUDE_BRANCH is required for issues event");
|
throw new Error("CLAUDE_BRANCH is required for issues event");
|
||||||
@@ -263,7 +246,7 @@ export function prepareContext(
|
|||||||
eventAction: "assigned",
|
eventAction: "assigned",
|
||||||
isPR: false,
|
isPR: false,
|
||||||
issueNumber,
|
issueNumber,
|
||||||
baseBranch,
|
defaultBranch,
|
||||||
claudeBranch,
|
claudeBranch,
|
||||||
assigneeTrigger,
|
assigneeTrigger,
|
||||||
};
|
};
|
||||||
@@ -273,7 +256,7 @@ export function prepareContext(
|
|||||||
eventAction: "opened",
|
eventAction: "opened",
|
||||||
isPR: false,
|
isPR: false,
|
||||||
issueNumber,
|
issueNumber,
|
||||||
baseBranch,
|
defaultBranch,
|
||||||
claudeBranch,
|
claudeBranch,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@@ -294,7 +277,7 @@ export function prepareContext(
|
|||||||
isPR: true,
|
isPR: true,
|
||||||
prNumber,
|
prNumber,
|
||||||
...(claudeBranch && { claudeBranch }),
|
...(claudeBranch && { claudeBranch }),
|
||||||
...(baseBranch && { baseBranch }),
|
...(defaultBranch && { defaultBranch }),
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -541,13 +524,13 @@ ${context.directPrompt ? ` - DIRECT INSTRUCTION: A direct instruction was prov
|
|||||||
${
|
${
|
||||||
eventData.claudeBranch
|
eventData.claudeBranch
|
||||||
? `- Provide a URL to create a PR manually in this format:
|
? `- Provide a URL to create a PR manually in this format:
|
||||||
[Create a PR](${GITHUB_SERVER_URL}/${context.repository}/compare/${eventData.baseBranch}...<branch-name>?quick_pull=1&title=<url-encoded-title>&body=<url-encoded-body>)
|
[Create a PR](${GITHUB_SERVER_URL}/${context.repository}/compare/${eventData.defaultBranch}...<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: ${GITHUB_SERVER_URL}/${context.repository}/compare/main...feature-branch (correct)
|
Example: ${GITHUB_SERVER_URL}/${context.repository}/compare/main...feature-branch (correct)
|
||||||
NOT: ${GITHUB_SERVER_URL}/${context.repository}/compare/main..feature-branch (incorrect)
|
NOT: ${GITHUB_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
|
- 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"
|
Example: Instead of "fix: update welcome message", use "fix%3A%20update%20welcome%20message"
|
||||||
- The target-branch should be '${eventData.baseBranch}'.
|
- The target-branch should be '${eventData.defaultBranch}'.
|
||||||
- The branch-name is the current branch: ${eventData.claudeBranch}
|
- The branch-name is the current branch: ${eventData.claudeBranch}
|
||||||
- The body should include:
|
- The body should include:
|
||||||
- A clear description of the changes
|
- A clear description of the changes
|
||||||
@@ -632,7 +615,7 @@ f. If you are unable to complete certain steps, such as running a linter or test
|
|||||||
|
|
||||||
export async function createPrompt(
|
export async function createPrompt(
|
||||||
claudeCommentId: number,
|
claudeCommentId: number,
|
||||||
baseBranch: string | undefined,
|
defaultBranch: string | undefined,
|
||||||
claudeBranch: string | undefined,
|
claudeBranch: string | undefined,
|
||||||
githubData: FetchDataResult,
|
githubData: FetchDataResult,
|
||||||
context: ParsedGitHubContext,
|
context: ParsedGitHubContext,
|
||||||
@@ -641,7 +624,7 @@ export async function createPrompt(
|
|||||||
const preparedContext = prepareContext(
|
const preparedContext = prepareContext(
|
||||||
context,
|
context,
|
||||||
claudeCommentId.toString(),
|
claudeCommentId.toString(),
|
||||||
baseBranch,
|
defaultBranch,
|
||||||
claudeBranch,
|
claudeBranch,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -665,7 +648,6 @@ export async function createPrompt(
|
|||||||
);
|
);
|
||||||
const allDisallowedTools = buildDisallowedToolsString(
|
const allDisallowedTools = buildDisallowedToolsString(
|
||||||
preparedContext.disallowedTools,
|
preparedContext.disallowedTools,
|
||||||
preparedContext.allowedTools,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
core.exportVariable("ALLOWED_TOOLS", allAllowedTools);
|
core.exportVariable("ALLOWED_TOOLS", allAllowedTools);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type PullRequestReviewCommentEvent = {
|
|||||||
commentId?: string; // May be present for review comments
|
commentId?: string; // May be present for review comments
|
||||||
commentBody: string;
|
commentBody: string;
|
||||||
claudeBranch?: string;
|
claudeBranch?: string;
|
||||||
baseBranch?: string;
|
defaultBranch?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PullRequestReviewEvent = {
|
type PullRequestReviewEvent = {
|
||||||
@@ -25,7 +25,7 @@ type PullRequestReviewEvent = {
|
|||||||
prNumber: string;
|
prNumber: string;
|
||||||
commentBody: string;
|
commentBody: string;
|
||||||
claudeBranch?: string;
|
claudeBranch?: string;
|
||||||
baseBranch?: string;
|
defaultBranch?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type IssueCommentEvent = {
|
type IssueCommentEvent = {
|
||||||
@@ -33,7 +33,7 @@ type IssueCommentEvent = {
|
|||||||
commentId: string;
|
commentId: string;
|
||||||
issueNumber: string;
|
issueNumber: string;
|
||||||
isPR: false;
|
isPR: false;
|
||||||
baseBranch: string;
|
defaultBranch: string;
|
||||||
claudeBranch: string;
|
claudeBranch: string;
|
||||||
commentBody: string;
|
commentBody: string;
|
||||||
};
|
};
|
||||||
@@ -46,7 +46,7 @@ type PullRequestCommentEvent = {
|
|||||||
isPR: true;
|
isPR: true;
|
||||||
commentBody: string;
|
commentBody: string;
|
||||||
claudeBranch?: string;
|
claudeBranch?: string;
|
||||||
baseBranch?: string;
|
defaultBranch?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type IssueOpenedEvent = {
|
type IssueOpenedEvent = {
|
||||||
@@ -54,7 +54,7 @@ type IssueOpenedEvent = {
|
|||||||
eventAction: "opened";
|
eventAction: "opened";
|
||||||
isPR: false;
|
isPR: false;
|
||||||
issueNumber: string;
|
issueNumber: string;
|
||||||
baseBranch: string;
|
defaultBranch: string;
|
||||||
claudeBranch: string;
|
claudeBranch: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ type IssueAssignedEvent = {
|
|||||||
eventAction: "assigned";
|
eventAction: "assigned";
|
||||||
isPR: false;
|
isPR: false;
|
||||||
issueNumber: string;
|
issueNumber: string;
|
||||||
baseBranch: string;
|
defaultBranch: string;
|
||||||
claudeBranch: string;
|
claudeBranch: string;
|
||||||
assigneeTrigger: string;
|
assigneeTrigger: string;
|
||||||
};
|
};
|
||||||
@@ -74,7 +74,7 @@ type PullRequestEvent = {
|
|||||||
isPR: true;
|
isPR: true;
|
||||||
prNumber: string;
|
prNumber: string;
|
||||||
claudeBranch?: string;
|
claudeBranch?: string;
|
||||||
baseBranch?: string;
|
defaultBranch?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Union type for all possible event types
|
// Union type for all possible event types
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ async function run() {
|
|||||||
// Step 10: Create prompt file
|
// Step 10: Create prompt file
|
||||||
await createPrompt(
|
await createPrompt(
|
||||||
commentId,
|
commentId,
|
||||||
branchInfo.baseBranch,
|
branchInfo.defaultBranch,
|
||||||
branchInfo.claudeBranch,
|
branchInfo.claudeBranch,
|
||||||
githubData,
|
githubData,
|
||||||
context,
|
context,
|
||||||
@@ -92,10 +92,7 @@ async function run() {
|
|||||||
);
|
);
|
||||||
core.setOutput("mcp_config", mcpConfig);
|
core.setOutput("mcp_config", mcpConfig);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
core.setFailed(`Prepare step failed with error: ${error}`);
|
||||||
core.setFailed(`Prepare step failed with error: ${errorMessage}`);
|
|
||||||
// Also output the clean error message for the action to capture
|
|
||||||
core.setOutput("prepare_error", errorMessage);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ async function run() {
|
|||||||
const commentId = parseInt(process.env.CLAUDE_COMMENT_ID!);
|
const commentId = parseInt(process.env.CLAUDE_COMMENT_ID!);
|
||||||
const githubToken = process.env.GITHUB_TOKEN!;
|
const githubToken = process.env.GITHUB_TOKEN!;
|
||||||
const claudeBranch = process.env.CLAUDE_BRANCH;
|
const claudeBranch = process.env.CLAUDE_BRANCH;
|
||||||
const baseBranch = process.env.BASE_BRANCH || "main";
|
const defaultBranch = process.env.DEFAULT_BRANCH || "main";
|
||||||
const triggerUsername = process.env.TRIGGER_USERNAME;
|
const triggerUsername = process.env.TRIGGER_USERNAME;
|
||||||
|
|
||||||
const context = parseGitHubContext();
|
const context = parseGitHubContext();
|
||||||
@@ -92,7 +92,7 @@ async function run() {
|
|||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
claudeBranch,
|
claudeBranch,
|
||||||
baseBranch,
|
defaultBranch,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if we need to add PR URL when we have a new branch
|
// Check if we need to add PR URL when we have a new branch
|
||||||
@@ -102,7 +102,7 @@ async function run() {
|
|||||||
// Check if comment already contains a PR URL
|
// Check if comment already contains a PR URL
|
||||||
const serverUrlPattern = serverUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
const serverUrlPattern = serverUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
const prUrlPattern = new RegExp(
|
const prUrlPattern = new RegExp(
|
||||||
`${serverUrlPattern}\\/.+\\/compare\\/${baseBranch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.\\.\\.`,
|
`${serverUrlPattern}\\/.+\\/compare\\/${defaultBranch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.\\.\\.`,
|
||||||
);
|
);
|
||||||
const containsPRUrl = currentBody.match(prUrlPattern);
|
const containsPRUrl = currentBody.match(prUrlPattern);
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ async function run() {
|
|||||||
await octokit.rest.repos.compareCommitsWithBasehead({
|
await octokit.rest.repos.compareCommitsWithBasehead({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
basehead: `${baseBranch}...${claudeBranch}`,
|
basehead: `${defaultBranch}...${claudeBranch}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
// If there are changes (commits or file changes), add the PR URL
|
// If there are changes (commits or file changes), add the PR URL
|
||||||
@@ -128,7 +128,7 @@ async function run() {
|
|||||||
const prBody = encodeURIComponent(
|
const prBody = encodeURIComponent(
|
||||||
`This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`,
|
`This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`,
|
||||||
);
|
);
|
||||||
const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
|
const prUrl = `${serverUrl}/${owner}/${repo}/compare/${defaultBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
|
||||||
prLink = `\n[Create a PR](${prUrl})`;
|
prLink = `\n[Create a PR](${prUrl})`;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -145,48 +145,38 @@ async function run() {
|
|||||||
duration_api_ms?: number;
|
duration_api_ms?: number;
|
||||||
} | null = null;
|
} | null = null;
|
||||||
let actionFailed = false;
|
let actionFailed = false;
|
||||||
let errorDetails: string | undefined;
|
|
||||||
|
|
||||||
// First check if prepare step failed
|
// Check for existence of output file and parse it if available
|
||||||
const prepareSuccess = process.env.PREPARE_SUCCESS !== "false";
|
try {
|
||||||
const prepareError = process.env.PREPARE_ERROR;
|
const outputFile = process.env.OUTPUT_FILE;
|
||||||
|
if (outputFile) {
|
||||||
|
const fileContent = await fs.readFile(outputFile, "utf8");
|
||||||
|
const outputData = JSON.parse(fileContent);
|
||||||
|
|
||||||
if (!prepareSuccess && prepareError) {
|
// Output file is an array, get the last element which contains execution details
|
||||||
actionFailed = true;
|
if (Array.isArray(outputData) && outputData.length > 0) {
|
||||||
errorDetails = prepareError;
|
const lastElement = outputData[outputData.length - 1];
|
||||||
} else {
|
if (
|
||||||
// Check for existence of output file and parse it if available
|
lastElement.role === "system" &&
|
||||||
try {
|
"cost_usd" in lastElement &&
|
||||||
const outputFile = process.env.OUTPUT_FILE;
|
"duration_ms" in lastElement
|
||||||
if (outputFile) {
|
) {
|
||||||
const fileContent = await fs.readFile(outputFile, "utf8");
|
executionDetails = {
|
||||||
const outputData = JSON.parse(fileContent);
|
cost_usd: lastElement.cost_usd,
|
||||||
|
duration_ms: lastElement.duration_ms,
|
||||||
// Output file is an array, get the last element which contains execution details
|
duration_api_ms: lastElement.duration_api_ms,
|
||||||
if (Array.isArray(outputData) && outputData.length > 0) {
|
};
|
||||||
const lastElement = outputData[outputData.length - 1];
|
|
||||||
if (
|
|
||||||
lastElement.role === "system" &&
|
|
||||||
"cost_usd" in lastElement &&
|
|
||||||
"duration_ms" in lastElement
|
|
||||||
) {
|
|
||||||
executionDetails = {
|
|
||||||
cost_usd: lastElement.cost_usd,
|
|
||||||
duration_ms: lastElement.duration_ms,
|
|
||||||
duration_api_ms: lastElement.duration_api_ms,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the Claude action failed
|
|
||||||
const claudeSuccess = process.env.CLAUDE_SUCCESS !== "false";
|
|
||||||
actionFailed = !claudeSuccess;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error reading output file:", error);
|
|
||||||
// If we can't read the file, check for any failure markers
|
|
||||||
actionFailed = process.env.CLAUDE_SUCCESS === "false";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the action failed by looking at the exit code or error marker
|
||||||
|
const claudeSuccess = process.env.CLAUDE_SUCCESS !== "false";
|
||||||
|
actionFailed = !claudeSuccess;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error reading output file:", error);
|
||||||
|
// If we can't read the file, check for any failure markers
|
||||||
|
actionFailed = process.env.CLAUDE_SUCCESS === "false";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare input for updateCommentBody function
|
// Prepare input for updateCommentBody function
|
||||||
@@ -199,7 +189,6 @@ async function run() {
|
|||||||
prLink,
|
prLink,
|
||||||
branchName: shouldDeleteBranch ? undefined : claudeBranch,
|
branchName: shouldDeleteBranch ? undefined : claudeBranch,
|
||||||
triggerUsername,
|
triggerUsername,
|
||||||
errorDetails,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatedBody = updateCommentBody(commentInput);
|
const updatedBody = updateCommentBody(commentInput);
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ export type ParsedGitHubContext = {
|
|||||||
disallowedTools: string;
|
disallowedTools: string;
|
||||||
customInstructions: string;
|
customInstructions: string;
|
||||||
directPrompt: string;
|
directPrompt: string;
|
||||||
baseBranch?: string;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -56,7 +55,6 @@ export function parseGitHubContext(): ParsedGitHubContext {
|
|||||||
disallowedTools: process.env.DISALLOWED_TOOLS ?? "",
|
disallowedTools: process.env.DISALLOWED_TOOLS ?? "",
|
||||||
customInstructions: process.env.CUSTOM_INSTRUCTIONS ?? "",
|
customInstructions: process.env.CUSTOM_INSTRUCTIONS ?? "",
|
||||||
directPrompt: process.env.DIRECT_PROMPT ?? "",
|
directPrompt: process.env.DIRECT_PROMPT ?? "",
|
||||||
baseBranch: process.env.BASE_BRANCH,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export async function checkAndDeleteEmptyBranch(
|
|||||||
owner: string,
|
owner: string,
|
||||||
repo: string,
|
repo: string,
|
||||||
claudeBranch: string | undefined,
|
claudeBranch: string | undefined,
|
||||||
baseBranch: string,
|
defaultBranch: string,
|
||||||
): Promise<{ shouldDeleteBranch: boolean; branchLink: string }> {
|
): Promise<{ shouldDeleteBranch: boolean; branchLink: string }> {
|
||||||
let branchLink = "";
|
let branchLink = "";
|
||||||
let shouldDeleteBranch = false;
|
let shouldDeleteBranch = false;
|
||||||
@@ -18,7 +18,7 @@ export async function checkAndDeleteEmptyBranch(
|
|||||||
await octokit.rest.repos.compareCommitsWithBasehead({
|
await octokit.rest.repos.compareCommitsWithBasehead({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
basehead: `${baseBranch}...${claudeBranch}`,
|
basehead: `${defaultBranch}...${claudeBranch}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
// If there are no commits, mark branch for deletion
|
// If there are no commits, mark branch for deletion
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import type { Octokits } from "../api/client";
|
|||||||
import type { FetchDataResult } from "../data/fetcher";
|
import type { FetchDataResult } from "../data/fetcher";
|
||||||
|
|
||||||
export type BranchInfo = {
|
export type BranchInfo = {
|
||||||
baseBranch: string;
|
defaultBranch: string;
|
||||||
claudeBranch?: string;
|
claudeBranch?: string;
|
||||||
currentBranch: string;
|
currentBranch: string;
|
||||||
};
|
};
|
||||||
@@ -26,9 +26,15 @@ export async function setupBranch(
|
|||||||
): Promise<BranchInfo> {
|
): Promise<BranchInfo> {
|
||||||
const { owner, repo } = context.repository;
|
const { owner, repo } = context.repository;
|
||||||
const entityNumber = context.entityNumber;
|
const entityNumber = context.entityNumber;
|
||||||
const { baseBranch } = context.inputs;
|
|
||||||
const isPR = context.isPR;
|
const isPR = context.isPR;
|
||||||
|
|
||||||
|
// Get the default branch first
|
||||||
|
const repoResponse = await octokits.rest.repos.get({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
});
|
||||||
|
const defaultBranch = 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,7 +42,7 @@ 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}, creating new branch from default...`,
|
||||||
);
|
);
|
||||||
// Fall through to create a new branch like we do for issues
|
// Fall through to create a new branch like we do for issues
|
||||||
} else {
|
} else {
|
||||||
@@ -45,43 +51,23 @@ export async function setupBranch(
|
|||||||
|
|
||||||
const branchName = prData.headRefName;
|
const branchName = prData.headRefName;
|
||||||
|
|
||||||
// Execute git commands to checkout PR branch (shallow fetch for performance)
|
// Execute git commands to checkout PR branch
|
||||||
// Fetch the branch with a depth of 20 to avoid fetching too much history, while still allowing for some context
|
await $`git fetch origin ${branchName}`;
|
||||||
await $`git fetch origin --depth=20 ${branchName}`;
|
|
||||||
await $`git checkout ${branchName}`;
|
await $`git checkout ${branchName}`;
|
||||||
|
|
||||||
console.log(`Successfully checked out PR branch for PR #${entityNumber}`);
|
console.log(`Successfully checked out PR branch for PR #${entityNumber}`);
|
||||||
|
|
||||||
// For open PRs, we need to get the base branch of the PR
|
// For open PRs, return branch info
|
||||||
const baseBranch = prData.baseRefName;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
baseBranch,
|
defaultBranch,
|
||||||
currentBranch: branchName,
|
currentBranch: branchName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine source 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 octokits.rest.repos.get({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
});
|
|
||||||
sourceBranch = repoResponse.data.default_branch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creating a new branch for either an issue or closed/merged PR
|
// Creating a new branch for either an issue or closed/merged PR
|
||||||
const entityType = isPR ? "pr" : "issue";
|
const entityType = isPR ? "pr" : "issue";
|
||||||
console.log(
|
console.log(`Creating new branch for ${entityType} #${entityNumber}...`);
|
||||||
`Creating new branch for ${entityType} #${entityNumber} from source branch: ${sourceBranch}...`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const timestamp = new Date()
|
const timestamp = new Date()
|
||||||
.toISOString()
|
.toISOString()
|
||||||
@@ -93,14 +79,14 @@ export async function setupBranch(
|
|||||||
const newBranch = `claude/${entityType}-${entityNumber}-${timestamp}`;
|
const newBranch = `claude/${entityType}-${entityNumber}-${timestamp}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the SHA of the source branch
|
// Get the SHA of the default branch
|
||||||
const sourceBranchRef = await octokits.rest.git.getRef({
|
const defaultBranchRef = await octokits.rest.git.getRef({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
ref: `heads/${sourceBranch}`,
|
ref: `heads/${defaultBranch}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentSHA = sourceBranchRef.data.object.sha;
|
const currentSHA = defaultBranchRef.data.object.sha;
|
||||||
|
|
||||||
console.log(`Current SHA: ${currentSHA}`);
|
console.log(`Current SHA: ${currentSHA}`);
|
||||||
|
|
||||||
@@ -112,8 +98,8 @@ export async function setupBranch(
|
|||||||
sha: currentSHA,
|
sha: currentSHA,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Checkout the new branch (shallow fetch for performance)
|
// Checkout the new branch
|
||||||
await $`git fetch origin --depth=1 ${newBranch}`;
|
await $`git fetch origin ${newBranch}`;
|
||||||
await $`git checkout ${newBranch}`;
|
await $`git checkout ${newBranch}`;
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
@@ -122,9 +108,9 @@ export async function setupBranch(
|
|||||||
|
|
||||||
// Set outputs for GitHub Actions
|
// Set outputs for GitHub Actions
|
||||||
core.setOutput("CLAUDE_BRANCH", newBranch);
|
core.setOutput("CLAUDE_BRANCH", newBranch);
|
||||||
core.setOutput("BASE_BRANCH", sourceBranch);
|
core.setOutput("DEFAULT_BRANCH", defaultBranch);
|
||||||
return {
|
return {
|
||||||
baseBranch: sourceBranch,
|
defaultBranch,
|
||||||
claudeBranch: newBranch,
|
claudeBranch: newBranch,
|
||||||
currentBranch: newBranch,
|
currentBranch: newBranch,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ export type CommentUpdateInput = {
|
|||||||
prLink?: string;
|
prLink?: string;
|
||||||
branchName?: string;
|
branchName?: string;
|
||||||
triggerUsername?: string;
|
triggerUsername?: string;
|
||||||
errorDetails?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ensureProperlyEncodedUrl(url: string): string | null {
|
export function ensureProperlyEncodedUrl(url: string): string | null {
|
||||||
@@ -76,7 +75,6 @@ export function updateCommentBody(input: CommentUpdateInput): string {
|
|||||||
actionFailed,
|
actionFailed,
|
||||||
branchName,
|
branchName,
|
||||||
triggerUsername,
|
triggerUsername,
|
||||||
errorDetails,
|
|
||||||
} = input;
|
} = input;
|
||||||
|
|
||||||
// Extract content from the original comment body
|
// Extract content from the original comment body
|
||||||
@@ -179,14 +177,7 @@ export function updateCommentBody(input: CommentUpdateInput): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build the new body with blank line between header and separator
|
// Build the new body with blank line between header and separator
|
||||||
let newBody = `${header}${links}`;
|
let newBody = `${header}${links}\n\n---\n`;
|
||||||
|
|
||||||
// Add error details if available
|
|
||||||
if (actionFailed && errorDetails) {
|
|
||||||
newBody += `\n\n\`\`\`\n${errorDetails}\n\`\`\``;
|
|
||||||
}
|
|
||||||
|
|
||||||
newBody += `\n\n---\n`;
|
|
||||||
|
|
||||||
// Clean up the body content
|
// Clean up the body content
|
||||||
// Remove any existing View job run, branch links from the bottom
|
// Remove any existing View job run, branch links from the bottom
|
||||||
|
|||||||
@@ -39,25 +39,6 @@ describe("updateCommentBody", () => {
|
|||||||
expect(result).toContain("**Claude encountered an error after 45s**");
|
expect(result).toContain("**Claude encountered an error after 45s**");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includes error details when provided", () => {
|
|
||||||
const input = {
|
|
||||||
...baseInput,
|
|
||||||
currentBody: "Claude Code is working...",
|
|
||||||
actionFailed: true,
|
|
||||||
executionDetails: { duration_ms: 45000 },
|
|
||||||
errorDetails: "Failed to fetch issue data",
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = updateCommentBody(input);
|
|
||||||
expect(result).toContain("**Claude encountered an error after 45s**");
|
|
||||||
expect(result).toContain("[View job]");
|
|
||||||
expect(result).toContain("```\nFailed to fetch issue data\n```");
|
|
||||||
// Ensure error details come after the header/links
|
|
||||||
const errorIndex = result.indexOf("```");
|
|
||||||
const headerIndex = result.indexOf("**Claude encountered an error");
|
|
||||||
expect(errorIndex).toBeGreaterThan(headerIndex);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles username extraction from content when not provided", () => {
|
it("handles username extraction from content when not provided", () => {
|
||||||
const input = {
|
const input = {
|
||||||
...baseInput,
|
...baseInput,
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ describe("generatePrompt", () => {
|
|||||||
eventName: "issue_comment",
|
eventName: "issue_comment",
|
||||||
commentId: "67890",
|
commentId: "67890",
|
||||||
isPR: false,
|
isPR: false,
|
||||||
baseBranch: "main",
|
defaultBranch: "main",
|
||||||
claudeBranch: "claude/issue-67890-20240101_120000",
|
claudeBranch: "claude/issue-67890-20240101_120000",
|
||||||
issueNumber: "67890",
|
issueNumber: "67890",
|
||||||
commentBody: "@claude please fix this",
|
commentBody: "@claude please fix this",
|
||||||
@@ -183,7 +183,7 @@ describe("generatePrompt", () => {
|
|||||||
eventAction: "opened",
|
eventAction: "opened",
|
||||||
isPR: false,
|
isPR: false,
|
||||||
issueNumber: "789",
|
issueNumber: "789",
|
||||||
baseBranch: "main",
|
defaultBranch: "main",
|
||||||
claudeBranch: "claude/issue-789-20240101_120000",
|
claudeBranch: "claude/issue-789-20240101_120000",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -210,7 +210,7 @@ describe("generatePrompt", () => {
|
|||||||
eventAction: "assigned",
|
eventAction: "assigned",
|
||||||
isPR: false,
|
isPR: false,
|
||||||
issueNumber: "999",
|
issueNumber: "999",
|
||||||
baseBranch: "develop",
|
defaultBranch: "develop",
|
||||||
claudeBranch: "claude/issue-999-20240101_120000",
|
claudeBranch: "claude/issue-999-20240101_120000",
|
||||||
assigneeTrigger: "claude-bot",
|
assigneeTrigger: "claude-bot",
|
||||||
},
|
},
|
||||||
@@ -238,7 +238,7 @@ describe("generatePrompt", () => {
|
|||||||
eventAction: "opened",
|
eventAction: "opened",
|
||||||
isPR: false,
|
isPR: false,
|
||||||
issueNumber: "789",
|
issueNumber: "789",
|
||||||
baseBranch: "main",
|
defaultBranch: "main",
|
||||||
claudeBranch: "claude/issue-789-20240101_120000",
|
claudeBranch: "claude/issue-789-20240101_120000",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -285,7 +285,7 @@ describe("generatePrompt", () => {
|
|||||||
commentId: "67890",
|
commentId: "67890",
|
||||||
isPR: false,
|
isPR: false,
|
||||||
issueNumber: "123",
|
issueNumber: "123",
|
||||||
baseBranch: "main",
|
defaultBranch: "main",
|
||||||
claudeBranch: "claude/issue-67890-20240101_120000",
|
claudeBranch: "claude/issue-67890-20240101_120000",
|
||||||
commentBody: "@claude please fix this",
|
commentBody: "@claude please fix this",
|
||||||
},
|
},
|
||||||
@@ -307,7 +307,7 @@ describe("generatePrompt", () => {
|
|||||||
commentId: "67890",
|
commentId: "67890",
|
||||||
isPR: false,
|
isPR: false,
|
||||||
issueNumber: "123",
|
issueNumber: "123",
|
||||||
baseBranch: "main",
|
defaultBranch: "main",
|
||||||
claudeBranch: "claude/issue-67890-20240101_120000",
|
claudeBranch: "claude/issue-67890-20240101_120000",
|
||||||
commentBody: "@claude please fix this",
|
commentBody: "@claude please fix this",
|
||||||
},
|
},
|
||||||
@@ -362,7 +362,7 @@ describe("generatePrompt", () => {
|
|||||||
eventAction: "opened",
|
eventAction: "opened",
|
||||||
isPR: false,
|
isPR: false,
|
||||||
issueNumber: "789",
|
issueNumber: "789",
|
||||||
baseBranch: "main",
|
defaultBranch: "main",
|
||||||
claudeBranch: "claude/issue-789-20240101_120000",
|
claudeBranch: "claude/issue-789-20240101_120000",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -400,7 +400,7 @@ describe("generatePrompt", () => {
|
|||||||
commentId: "67890",
|
commentId: "67890",
|
||||||
isPR: false,
|
isPR: false,
|
||||||
issueNumber: "123",
|
issueNumber: "123",
|
||||||
baseBranch: "main",
|
defaultBranch: "main",
|
||||||
claudeBranch: "claude/issue-123-20240101_120000",
|
claudeBranch: "claude/issue-123-20240101_120000",
|
||||||
commentBody: "@claude please fix this",
|
commentBody: "@claude please fix this",
|
||||||
},
|
},
|
||||||
@@ -432,7 +432,7 @@ describe("generatePrompt", () => {
|
|||||||
prNumber: "456",
|
prNumber: "456",
|
||||||
commentBody: "@claude please fix this",
|
commentBody: "@claude please fix this",
|
||||||
claudeBranch: "claude/pr-456-20240101_120000",
|
claudeBranch: "claude/pr-456-20240101_120000",
|
||||||
baseBranch: "main",
|
defaultBranch: "main",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -470,7 +470,7 @@ describe("generatePrompt", () => {
|
|||||||
isPR: true,
|
isPR: true,
|
||||||
prNumber: "456",
|
prNumber: "456",
|
||||||
commentBody: "@claude please fix this",
|
commentBody: "@claude please fix this",
|
||||||
// No claudeBranch or baseBranch for open PRs
|
// No claudeBranch or defaultBranch for open PRs
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -503,7 +503,7 @@ describe("generatePrompt", () => {
|
|||||||
prNumber: "789",
|
prNumber: "789",
|
||||||
commentBody: "@claude please update this",
|
commentBody: "@claude please update this",
|
||||||
claudeBranch: "claude/pr-789-20240101_123000",
|
claudeBranch: "claude/pr-789-20240101_123000",
|
||||||
baseBranch: "develop",
|
defaultBranch: "develop",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -531,7 +531,7 @@ describe("generatePrompt", () => {
|
|||||||
commentId: "review-comment-123",
|
commentId: "review-comment-123",
|
||||||
commentBody: "@claude fix this issue",
|
commentBody: "@claude fix this issue",
|
||||||
claudeBranch: "claude/pr-999-20240101_140000",
|
claudeBranch: "claude/pr-999-20240101_140000",
|
||||||
baseBranch: "main",
|
defaultBranch: "main",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -559,7 +559,7 @@ describe("generatePrompt", () => {
|
|||||||
isPR: true,
|
isPR: true,
|
||||||
prNumber: "555",
|
prNumber: "555",
|
||||||
claudeBranch: "claude/pr-555-20240101_150000",
|
claudeBranch: "claude/pr-555-20240101_150000",
|
||||||
baseBranch: "main",
|
defaultBranch: "main",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -604,7 +604,7 @@ describe("getEventTypeAndContext", () => {
|
|||||||
eventAction: "assigned",
|
eventAction: "assigned",
|
||||||
isPR: false,
|
isPR: false,
|
||||||
issueNumber: "999",
|
issueNumber: "999",
|
||||||
baseBranch: "main",
|
defaultBranch: "main",
|
||||||
claudeBranch: "claude/issue-999-20240101_120000",
|
claudeBranch: "claude/issue-999-20240101_120000",
|
||||||
assigneeTrigger: "claude-bot",
|
assigneeTrigger: "claude-bot",
|
||||||
},
|
},
|
||||||
@@ -722,51 +722,4 @@ describe("buildDisallowedToolsString", () => {
|
|||||||
expect(parts).toContain("BadTool1");
|
expect(parts).toContain("BadTool1");
|
||||||
expect(parts).toContain("BadTool2");
|
expect(parts).toContain("BadTool2");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should remove hardcoded disallowed tools if they are in allowed tools", () => {
|
|
||||||
const customDisallowedTools = "BadTool1,BadTool2";
|
|
||||||
const allowedTools = "WebSearch,SomeOtherTool";
|
|
||||||
const result = buildDisallowedToolsString(
|
|
||||||
customDisallowedTools,
|
|
||||||
allowedTools,
|
|
||||||
);
|
|
||||||
|
|
||||||
// WebSearch should be removed from disallowed since it's in allowed
|
|
||||||
expect(result).not.toContain("WebSearch");
|
|
||||||
|
|
||||||
// WebFetch should still be disallowed since it's not in allowed
|
|
||||||
expect(result).toContain("WebFetch");
|
|
||||||
|
|
||||||
// Custom disallowed tools should still be present
|
|
||||||
expect(result).toContain("BadTool1");
|
|
||||||
expect(result).toContain("BadTool2");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should remove all hardcoded disallowed tools if they are all in allowed tools", () => {
|
|
||||||
const allowedTools = "WebSearch,WebFetch,SomeOtherTool";
|
|
||||||
const result = buildDisallowedToolsString(undefined, allowedTools);
|
|
||||||
|
|
||||||
// Both hardcoded disallowed tools should be removed
|
|
||||||
expect(result).not.toContain("WebSearch");
|
|
||||||
expect(result).not.toContain("WebFetch");
|
|
||||||
|
|
||||||
// Result should be empty since no custom disallowed tools provided
|
|
||||||
expect(result).toBe("");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should handle custom disallowed tools when all hardcoded tools are overridden", () => {
|
|
||||||
const customDisallowedTools = "BadTool1,BadTool2";
|
|
||||||
const allowedTools = "WebSearch,WebFetch";
|
|
||||||
const result = buildDisallowedToolsString(
|
|
||||||
customDisallowedTools,
|
|
||||||
allowedTools,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Hardcoded tools should be removed
|
|
||||||
expect(result).not.toContain("WebSearch");
|
|
||||||
expect(result).not.toContain("WebFetch");
|
|
||||||
|
|
||||||
// Only custom disallowed tools should remain
|
|
||||||
expect(result).toBe("BadTool1,BadTool2");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ describe("parseEnvVarsWithContext", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
process.env = {
|
process.env = {
|
||||||
...BASE_ENV,
|
...BASE_ENV,
|
||||||
BASE_BRANCH: "main",
|
DEFAULT_BRANCH: "main",
|
||||||
CLAUDE_BRANCH: "claude/issue-67890-20240101_120000",
|
CLAUDE_BRANCH: "claude/issue-67890-20240101_120000",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -62,7 +62,7 @@ describe("parseEnvVarsWithContext", () => {
|
|||||||
expect(result.eventData.claudeBranch).toBe(
|
expect(result.eventData.claudeBranch).toBe(
|
||||||
"claude/issue-67890-20240101_120000",
|
"claude/issue-67890-20240101_120000",
|
||||||
);
|
);
|
||||||
expect(result.eventData.baseBranch).toBe("main");
|
expect(result.eventData.defaultBranch).toBe("main");
|
||||||
expect(result.eventData.commentBody).toBe(
|
expect(result.eventData.commentBody).toBe(
|
||||||
"@claude can you help explain how to configure the logging system?",
|
"@claude can you help explain how to configure the logging system?",
|
||||||
);
|
);
|
||||||
@@ -75,7 +75,7 @@ describe("parseEnvVarsWithContext", () => {
|
|||||||
).toThrow("CLAUDE_BRANCH is required for issue_comment event");
|
).toThrow("CLAUDE_BRANCH is required for issue_comment event");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should throw error when BASE_BRANCH is missing", () => {
|
test("should throw error when DEFAULT_BRANCH is missing", () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
prepareContext(
|
prepareContext(
|
||||||
mockIssueCommentContext,
|
mockIssueCommentContext,
|
||||||
@@ -83,7 +83,7 @@ describe("parseEnvVarsWithContext", () => {
|
|||||||
undefined,
|
undefined,
|
||||||
"claude/issue-67890-20240101_120000",
|
"claude/issue-67890-20240101_120000",
|
||||||
),
|
),
|
||||||
).toThrow("BASE_BRANCH is required for issue_comment event");
|
).toThrow("DEFAULT_BRANCH is required for issue_comment event");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ describe("parseEnvVarsWithContext", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
process.env = {
|
process.env = {
|
||||||
...BASE_ENV,
|
...BASE_ENV,
|
||||||
BASE_BRANCH: "main",
|
DEFAULT_BRANCH: "main",
|
||||||
CLAUDE_BRANCH: "claude/issue-42-20240101_120000",
|
CLAUDE_BRANCH: "claude/issue-42-20240101_120000",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -172,7 +172,7 @@ describe("parseEnvVarsWithContext", () => {
|
|||||||
result.eventData.eventAction === "opened"
|
result.eventData.eventAction === "opened"
|
||||||
) {
|
) {
|
||||||
expect(result.eventData.issueNumber).toBe("42");
|
expect(result.eventData.issueNumber).toBe("42");
|
||||||
expect(result.eventData.baseBranch).toBe("main");
|
expect(result.eventData.defaultBranch).toBe("main");
|
||||||
expect(result.eventData.claudeBranch).toBe(
|
expect(result.eventData.claudeBranch).toBe(
|
||||||
"claude/issue-42-20240101_120000",
|
"claude/issue-42-20240101_120000",
|
||||||
);
|
);
|
||||||
@@ -195,7 +195,7 @@ describe("parseEnvVarsWithContext", () => {
|
|||||||
result.eventData.eventAction === "assigned"
|
result.eventData.eventAction === "assigned"
|
||||||
) {
|
) {
|
||||||
expect(result.eventData.issueNumber).toBe("123");
|
expect(result.eventData.issueNumber).toBe("123");
|
||||||
expect(result.eventData.baseBranch).toBe("main");
|
expect(result.eventData.defaultBranch).toBe("main");
|
||||||
expect(result.eventData.claudeBranch).toBe(
|
expect(result.eventData.claudeBranch).toBe(
|
||||||
"claude/issue-123-20240101_120000",
|
"claude/issue-123-20240101_120000",
|
||||||
);
|
);
|
||||||
@@ -209,7 +209,7 @@ describe("parseEnvVarsWithContext", () => {
|
|||||||
).toThrow("CLAUDE_BRANCH is required for issues event");
|
).toThrow("CLAUDE_BRANCH is required for issues event");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should throw error when BASE_BRANCH is missing for issues", () => {
|
test("should throw error when DEFAULT_BRANCH is missing for issues", () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
prepareContext(
|
prepareContext(
|
||||||
mockIssueOpenedContext,
|
mockIssueOpenedContext,
|
||||||
@@ -217,7 +217,7 @@ describe("parseEnvVarsWithContext", () => {
|
|||||||
undefined,
|
undefined,
|
||||||
"claude/issue-42-20240101_120000",
|
"claude/issue-42-20240101_120000",
|
||||||
),
|
),
|
||||||
).toThrow("BASE_BRANCH is required for issues event");
|
).toThrow("DEFAULT_BRANCH is required for issues event");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user