Pārlūkot izejas kodu

feat: add automatic issue handling with Claude Code

Add automation to handle issues assigned to Claude by automatically calling Claude Code with the issue information.

Changes:
- Add handle-issue.sh script that:
  - Checks if pusher is claude (avoid loops)
  - Checks if issue is assigned to claude
  - Clones repo if it doesn't exist locally
  - Calls claude -p with issue information
- Add repo_owner and issue_assignee variables to commandExecutor
- Update commands.json with claude-issue-handler and claude-comment-handler
- Update commands.json.example with automation examples
- Update documentation (README.md and CLAUDE.md) with new variables

The automation enables Claude Code to automatically respond to issues and comments when assigned, creating a fully automated issue management system.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude 9 mēneši atpakaļ
vecāks
revīzija
f7666d96d2
5 mainītis faili ar 131 papildinājumiem un 2 dzēšanām
  1. 3 1
      CLAUDE.md
  2. 2 0
      README.md
  3. 21 1
      commands.json.example
  4. 89 0
      scripts/handle-issue.sh
  5. 16 0
      src/commandExecutor.js

+ 3 - 1
CLAUDE.md

@@ -196,12 +196,13 @@ See `examples/with-callbacks.js` for complete example.
 
 ## Variable Substitution System
 
-Commands in `.env` support variable substitution using `{{variable}}` syntax.
+Commands in `commands.json` and legacy `.env` commands support variable substitution using `{{variable}}` syntax.
 
 **Available variables:**
 - `{{event}}` - Event type (push, create, delete, etc.)
 - `{{repo}}` - Repository name only
 - `{{full_repo}}` - User/repository format
+- `{{repo_owner}}` - Repository owner username
 - `{{branch}}` - Branch name (extracted from refs)
 - `{{pusher}}` - Username of person who triggered event
 - `{{commit}}` - Short commit hash (push events)
@@ -214,6 +215,7 @@ Commands in `.env` support variable substitution using `{{variable}}` syntax.
 - `{{issue_title}}` - Issue title (issue events)
 - `{{issue_action}}` - Issue action (opened, closed, reopened, etc.)
 - `{{issue_body}}` - Issue description/body (issue events)
+- `{{issue_assignee}}` - Username of person assigned to issue (issue events)
 - `{{comment_body}}` - Comment text (issue_comment events)
 
 Variables are extracted in `commandExecutor.js` via `extractVariables()` and substituted via `substituteVariables()`.

+ 2 - 0
README.md

@@ -137,6 +137,7 @@ Commands are configured in a JSON file with the following structure:
 **Available Variables:**
 - `{{repo}}` - Repository name (e.g., "my-repo")
 - `{{full_repo}}` - Full repository name (e.g., "user/my-repo")
+- `{{repo_owner}}` - Repository owner username (e.g., "user")
 - `{{branch}}` - Branch name (e.g., "main", "feature/new-feature")
 - `{{pusher}}` - Username of person who triggered the event
 - `{{event}}` - Event type (e.g., "push", "pull_request")
@@ -150,6 +151,7 @@ Commands are configured in a JSON file with the following structure:
 - `{{issue_title}}` - Issue title (for issue events)
 - `{{issue_action}}` - Issue action (opened, closed, reopened, etc.)
 - `{{issue_body}}` - Issue description/body (for issue events)
+- `{{issue_assignee}}` - Username of the person assigned to the issue (for issue events)
 - `{{comment_body}}` - Comment text (for issue_comment events)
 
 **Supported Event Types:**

+ 21 - 1
commands.json.example

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json-schema.org/draft-07/schema#",
-  "description": "Command configuration for webhook events. Available variables: {{repo}}, {{full_repo}}, {{branch}}, {{pusher}}, {{event}}, {{commit}}, {{commit_msg}}, {{pr_number}}, {{pr_action}}, {{tag}}, {{ref_type}}, {{issue_number}}, {{issue_title}}, {{issue_action}}, {{issue_body}}, {{comment_body}}",
+  "description": "Command configuration for webhook events. Available variables: {{repo}}, {{full_repo}}, {{repo_owner}}, {{branch}}, {{pusher}}, {{event}}, {{commit}}, {{commit_msg}}, {{pr_number}}, {{pr_action}}, {{tag}}, {{ref_type}}, {{issue_number}}, {{issue_title}}, {{issue_action}}, {{issue_body}}, {{issue_assignee}}, {{comment_body}}",
   "commands": {
     "push": [
       {
@@ -83,6 +83,16 @@
         "timeout": 300000,
         "filterBranch": null
       },
+      {
+        "name": "claude-issue-handler",
+        "description": "Automatically handle issues assigned to Claude with Claude Code (requires handle-issue.sh script)",
+        "type": "shell",
+        "command": "/home/claude/agent-manager/scripts/handle-issue.sh",
+        "args": ["{{repo_owner}}", "{{repo}}", "{{pusher}}", "{{issue_number}}", "{{issue_title}}", "{{issue_body}}", "{{issue_action}}", "", "{{issue_assignee}}"],
+        "cwd": "~",
+        "timeout": 600000,
+        "filterBranch": null
+      },
       {
         "name": "triage-issue",
         "description": "Auto-triage issues with a script",
@@ -115,6 +125,16 @@
         "timeout": 300000,
         "filterBranch": null
       },
+      {
+        "name": "claude-comment-handler",
+        "description": "Automatically handle issue comments for issues assigned to Claude (requires handle-issue.sh script)",
+        "type": "shell",
+        "command": "/home/claude/agent-manager/scripts/handle-issue.sh",
+        "args": ["{{repo_owner}}", "{{repo}}", "{{pusher}}", "{{issue_number}}", "{{issue_title}}", "{{issue_body}}", "{{issue_action}}", "{{comment_body}}", "{{issue_assignee}}"],
+        "cwd": "~",
+        "timeout": 600000,
+        "filterBranch": null
+      },
       {
         "name": "check-comment-commands",
         "description": "Check for special commands in comments",

+ 89 - 0
scripts/handle-issue.sh

@@ -0,0 +1,89 @@
+#!/bin/bash
+
+# Script to handle issue automation with Claude Code
+# This script is called when an issue is opened or commented on
+
+# Parse arguments
+OWNER="$1"
+REPO="$2"
+PUSHER="$3"
+ISSUE_NUMBER="$4"
+ISSUE_TITLE="$5"
+ISSUE_BODY="$6"
+ISSUE_ACTION="$7"
+COMMENT_BODY="$8"
+ASSIGNEE="$9"
+
+# Exit if pusher is claude (avoid loops)
+if [ "$PUSHER" = "claude" ]; then
+    echo "Pusher is claude, skipping automation to avoid loops"
+    exit 0
+fi
+
+# Exit if issue is not assigned to claude
+if [ "$ASSIGNEE" != "claude" ]; then
+    echo "Issue not assigned to claude (assignee: $ASSIGNEE), skipping"
+    exit 0
+fi
+
+# Determine repo path
+REPO_PATH="$HOME/$OWNER/$REPO"
+
+# Check if repo exists, if not clone it
+if [ ! -d "$REPO_PATH" ]; then
+    echo "Repository not found at $REPO_PATH, cloning..."
+    mkdir -p "$HOME/$OWNER"
+    GIT_URL="ssh://git@207.154.220.231:10022/$OWNER/$REPO.git"
+    git clone "$GIT_URL" "$REPO_PATH"
+
+    if [ $? -ne 0 ]; then
+        echo "Failed to clone repository"
+        exit 1
+    fi
+    echo "Repository cloned successfully"
+else
+    echo "Repository found at $REPO_PATH"
+    # Pull latest changes
+    cd "$REPO_PATH" || exit 1
+    echo "Pulling latest changes..."
+    git pull
+fi
+
+# Change to repo directory
+cd "$REPO_PATH" || exit 1
+echo "Working directory: $(pwd)"
+
+# Build the prompt for Claude
+if [ -n "$COMMENT_BODY" ]; then
+    # Issue comment event
+    PROMPT="A comment was added to issue #$ISSUE_NUMBER in $OWNER/$REPO by $PUSHER.
+
+Issue Title: $ISSUE_TITLE
+
+Original Issue:
+$ISSUE_BODY
+
+New Comment:
+$COMMENT_BODY
+
+Please review the comment and respond appropriately. This issue is assigned to you."
+else
+    # Issue opened/reopened event
+    PROMPT="Issue #$ISSUE_NUMBER was $ISSUE_ACTION in $OWNER/$REPO by $PUSHER.
+
+Issue Title: $ISSUE_TITLE
+
+Issue Description:
+$ISSUE_BODY
+
+This issue is assigned to you. Please review and handle it accordingly."
+fi
+
+echo "Calling Claude Code with the issue information..."
+echo "---"
+
+# Call claude with the prompt
+claude -p "$PROMPT"
+
+echo "---"
+echo "Claude Code execution completed"

+ 16 - 0
src/commandExecutor.js

@@ -16,6 +16,7 @@ export class CommandExecutor {
       event: eventType,
       repo: payload.repository?.name || '',
       full_repo: payload.repository?.full_name || payload.repository?.name || '',
+      repo_owner: '',
       branch: '',
       pusher: '',
       commit: '',
@@ -28,9 +29,20 @@ export class CommandExecutor {
       issue_title: '',
       issue_action: '',
       issue_body: '',
+      issue_assignee: '',
       comment_body: ''
     };
 
+    // Extract repository owner
+    if (payload.repository?.full_name) {
+      const parts = payload.repository.full_name.split('/');
+      variables.repo_owner = parts[0] || '';
+    } else if (payload.repository?.owner) {
+      variables.repo_owner = payload.repository.owner.username ||
+                            payload.repository.owner.login ||
+                            payload.repository.owner.name || '';
+    }
+
     // Extract pusher/sender information
     variables.pusher = payload.pusher?.username ||
                        payload.pusher?.name ||
@@ -86,6 +98,8 @@ export class CommandExecutor {
         variables.issue_title = payload.issue?.title || '';
         variables.issue_action = payload.action || '';
         variables.issue_body = payload.issue?.body || '';
+        variables.issue_assignee = payload.issue?.assignee?.username ||
+                                   payload.issue?.assignee?.login || '';
         break;
 
       case 'issue_comment':
@@ -93,6 +107,8 @@ export class CommandExecutor {
         variables.issue_title = payload.issue?.title || '';
         variables.issue_action = payload.action || '';
         variables.comment_body = payload.comment?.body || '';
+        variables.issue_assignee = payload.issue?.assignee?.username ||
+                                   payload.issue?.assignee?.login || '';
         break;
     }