/** * Claude Agent SDK Client for Issue Automation */ import { query } from '@anthropic-ai/claude-agent-sdk'; import { writeFileSync } from 'fs'; import { join } from 'path'; import { ClaudeClientConfig, IssueData, IssueHandlerResult } from './types.js'; import { Config } from './config.js'; export class ClaudeClient { private config: ClaudeClientConfig; constructor(config: ClaudeClientConfig) { this.config = config; } /** * Build the prompt for Claude based on issue data */ private buildPrompt(issue: IssueData): string { const commentContext = issue.commentBody ? `A new comment by ${issue.pusher}:\n${issue.commentBody}\n\n` : ''; return `IMPORTANT: Issue #${issue.number} was assigned in repository ${issue.owner}/${issue.repo}. === MANDATORY FIRST STEPS - READ ALL MESSAGES === Before you start any work on this issue, you MUST complete these steps IN ORDER: 1. Use the mcp__gogs__get_issue tool to fetch the complete issue details: - owner: ${issue.owner} - repo: ${issue.repo} - number: ${issue.number} 2. Use the mcp__gogs__list_issue_comments tool to fetch ALL comments: - owner: ${issue.owner} - repo: ${issue.repo} - number: ${issue.number} 3. Read and understand: - The complete issue description - ALL comments in the discussion thread (chronologically) - The full context and conversation history 4. ONLY AFTER reading everything above, proceed with your work === ISSUE CONTEXT === Issue #${issue.number}: ${issue.title} Created by: ${issue.pusher} Initial issue description: ${issue.body} ${commentContext} === YOUR TASK === This issue is assigned to you. You must: 1. First, use the Gogs MCP tools above to fetch and read the issue and ALL comments 2. Understand the full discussion context (even if there are no comments yet) 3. Then proceed with the appropriate action === CRITICAL OUTPUT REQUIREMENTS === YOU MUST FOLLOW THESE OUTPUT RULES STRICTLY: 1. DO NOT output any text responses to the user 2. DO NOT print any messages, explanations, or status updates 3. DO NOT use echo or any command-line output tools 4. ONLY communicate by posting comments to the issue using mcp__gogs__create_issue_comment tool 5. ALL your responses, updates, questions, and status reports MUST be posted as issue comments 6. You are FORBIDDEN from producing any output except through the Gogs MCP comment tool When you need to: - Ask questions → Post a comment on the issue - Report progress → Post a comment on the issue - Share results → Post a comment on the issue - Report errors → Post a comment on the issue - Provide any information → Post a comment on the issue Use mcp__gogs__create_issue_comment with: - owner: ${issue.owner} - repo: ${issue.repo} - number: ${issue.number} - body: [your message here] DO NOT skip reading the comments using the MCP tools. This is critical to understanding the full context. === GIT COMMIT REQUIREMENTS === When you make code changes, you MUST: 1. Always commit your changes to git 2. Always push the changes to the repository 3. Include the issue ID in the commit message using format: #${issue.number} 4. Example commit message: "feat: add new feature #${issue.number}" NEVER leave changes uncommitted or unpushed.`; } /** * Handle an issue automation request */ async handleIssue(issue: IssueData): Promise { console.log(`Processing issue #${issue.number} in ${issue.owner}/${issue.repo}`); const startTime = Date.now(); try { // Load environment configuration Config.load(this.config.workingDirectory); // Generate MCP config const mcpConfig = Config.generateMcpConfig(this.config.workingDirectory); const mcpConfigPath = join(this.config.workingDirectory, '.mcp-gogs.json'); writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2)); console.log(`Generated MCP config at: ${mcpConfigPath}`); console.log(`Working directory: ${this.config.workingDirectory}`); // Build the prompt const prompt = this.buildPrompt(issue); // Create a Map to store MCP servers const mcpServers: Record = {}; mcpConfig.mcpServers && Object.keys(mcpConfig.mcpServers).forEach(key => { mcpServers[key] = mcpConfig.mcpServers[key]; }); console.log('Starting Claude agent session...'); // Create query with options const queryInstance = query({ prompt, options: { cwd: this.config.workingDirectory, mcpServers, allowedTools: this.config.allowedTools, allowDangerouslySkipPermissions: this.config.dangerouslySkipPermissions || true } }); // Iterate through messages let sessionId: string | undefined; let totalCost = 0; let messageCount = 0; for await (const message of queryInstance) { messageCount++; // Extract session ID and cost information if ('sessionId' in message) { sessionId = message.sessionId as string; } if ('cost' in message && typeof message.cost === 'number') { totalCost = message.cost; } // Log progress if (message.type === 'assistant' || message.type === 'result') { console.log(`Message ${messageCount}: ${message.type}`); } } const duration = Date.now() - startTime; console.log('Claude agent session completed'); console.log(`Total messages: ${messageCount}`); console.log(`Total cost: $${totalCost}`); console.log(`Duration: ${duration}ms`); return { success: true, sessionId, cost: totalCost, duration, output: `Processed ${messageCount} messages` }; } catch (error) { console.error('Error handling issue:', error); return { success: false, error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime }; } } }