Skip to content

[User Story] Build Claude Integration for Conflict Resolution #47

@prodigy

Description

@prodigy

User Story

As a conflict resolution system, I want to leverage Claude's understanding to intelligently resolve Git conflicts, so that developers can automate complex merge conflict resolution with high confidence.

Acceptance Criteria

  • Integrate Claude API with proper authentication and rate limiting
  • Build comprehensive prompts with safety guidelines and context
  • Include full function/class context in conflict resolution requests
  • Support multiple Claude models with fallback (Opus → Sonnet → Haiku)
  • Implement response caching for similar conflicts
  • Parse and validate Claude's responses for code correctness
  • Handle API failures gracefully with retry logic
  • Track token usage and costs per resolution
  • Support custom prompt templates per language/framework
  • Generate explanation for each resolution decision

Technical Implementation

Claude Integration Service

// pkg/claude/service.go
package claude

import (
    "context"
    "github.com/anthropics/anthropic-sdk-go"
)

type ClaudeService struct {
    client       *anthropic.Client
    config       *ClaudeConfig
    rateLimiter  *RateLimiter
    costManager  *CostManager
    cache        *ResponseCache
    metrics      *MetricsCollector
}

type ClaudeConfig struct {
    APIKey            string
    DefaultModel      string
    FallbackModels    []string
    MaxTokens         int
    Temperature       float64
    MaxRetries        int
    CacheEnabled      bool
    CacheTTL          time.Duration
    SafetyMode        bool
}

type ConflictResolutionRequest struct {
    Conflict     Conflict
    Context      ConflictContext
    Repository   RepositoryInfo
    Guidelines   []string
    Examples     []ResolutionExample
}

type ConflictResolutionResponse struct {
    Resolution    string
    Explanation   string
    Confidence    float64
    Model         string
    TokensUsed    TokenUsage
    CacheHit      bool
}

func (s *ClaudeService) ResolveConflict(ctx context.Context, req ConflictResolutionRequest) (*ConflictResolutionResponse, error) {
    // Check cache first
    if s.config.CacheEnabled {
        if cached := s.cache.Get(req.Conflict); cached != nil {
            s.metrics.RecordCacheHit()
            return cached, nil
        }
    }
    
    // Check rate limits and budget
    if err := s.rateLimiter.Wait(ctx); err != nil {
        return nil, fmt.Errorf("rate limit exceeded: %w", err)
    }
    
    estimatedTokens := s.estimateTokens(req)
    if err := s.costManager.CheckBudget(ctx, estimatedTokens); err != nil {
        return nil, fmt.Errorf("budget exceeded: %w", err)
    }
    
    // Build prompt
    prompt := s.buildPrompt(req)
    
    // Try models in order
    var lastErr error
    models := append([]string{s.config.DefaultModel}, s.config.FallbackModels...)
    
    for _, model := range models {
        response, err := s.callClaude(ctx, model, prompt)
        if err == nil {
            // Parse and validate response
            resolution, err := s.parseResponse(response, req.Conflict)
            if err == nil {
                // Track usage
                s.costManager.RecordUsage(model, response.Usage)
                s.metrics.RecordResolution(model, true)
                
                // Cache if enabled
                if s.config.CacheEnabled {
                    s.cache.Put(req.Conflict, resolution, s.config.CacheTTL)
                }
                
                return resolution, nil
            }
        }
        lastErr = err
        s.metrics.RecordResolution(model, false)
    }
    
    return nil, fmt.Errorf("all models failed: %w", lastErr)
}

Prompt Building

// pkg/claude/prompt_builder.go
type PromptBuilder struct {
    templates map[string]*PromptTemplate
    safety    *SafetyGuidelines
}

type PromptTemplate struct {
    System   string
    User     string
    Examples []Example
}

func (b *PromptBuilder) BuildConflictPrompt(req ConflictResolutionRequest) *anthropic.MessageRequest {
    template := b.getTemplate(req.Conflict.FileType)
    
    systemPrompt := b.formatSystemPrompt(template.System, req)
    userPrompt := b.formatUserPrompt(template.User, req)
    
    // Add safety guidelines
    if req.Guidelines != nil {
        systemPrompt = b.safety.ApplyGuidelines(systemPrompt, req.Guidelines)
    }
    
    messages := []anthropic.Message{
        {
            Role:    "user",
            Content: userPrompt,
        },
    }
    
    // Add examples if available
    for _, example := range template.Examples {
        messages = append(messages, 
            anthropic.Message{Role: "user", Content: example.Input},
            anthropic.Message{Role: "assistant", Content: example.Output},
        )
    }
    
    return &anthropic.MessageRequest{
        Model:       req.Model,
        System:      systemPrompt,
        Messages:    messages,
        MaxTokens:   req.MaxTokens,
        Temperature: req.Temperature,
    }
}

func (b *PromptBuilder) formatSystemPrompt(template string, req ConflictResolutionRequest) string {
    return fmt.Sprintf(`You are helping resolve git merge conflicts in a secure environment.
Your solutions must:
1. Preserve the intent of both conflicting changes
2. Maintain code correctness and safety
3. Not introduce security vulnerabilities
4. Follow the existing code style and patterns
5. Be syntactically valid for %s

IMPORTANT SAFETY RULES:
- Never include code that executes system commands
- Never include code that evaluates dynamic strings
- Never include hardcoded credentials or secrets
- Never include code that accesses external resources without validation
- Always validate user input
- Always handle errors appropriately

Language: %s
Repository: %s
Branch Context: Merging '%s' into '%s'

%s`, 
        req.Conflict.FileType,
        req.Conflict.FileType,
        req.Repository.Name,
        req.Context.CurrentBranch,
        req.Context.TargetBranch,
        template,
    )
}

func (b *PromptBuilder) formatUserPrompt(template string, req ConflictResolutionRequest) string {
    contextLines := 50 // Configurable
    
    return fmt.Sprintf(`File: %s
Conflict Location: Line %d-%d

Context before conflict (%d lines):
\`\`\`%s
%s
\`\`\`

CONFLICT SECTION:
\`\`\`
<<<<<<< %s
%s
=======
%s
>>>>>>> %s
\`\`\`

Context after conflict (%d lines):
\`\`\`%s
%s
\`\`\`

Function/Class Context:
- Function: %s
- Class: %s

Please provide:
1. A resolved version that merges both changes appropriately
2. A brief explanation of your resolution approach
3. Any potential issues to watch for

Format your response as:
RESOLUTION:
\`\`\`%s
[resolved code here]
\`\`\`

EXPLANATION:
[Your explanation here]

CONFIDENCE: [HIGH/MEDIUM/LOW]`,
        req.Conflict.FilePath,
        req.Conflict.CurrentBlock.StartLine,
        req.Conflict.CurrentBlock.EndLine,
        contextLines,
        req.Conflict.FileType,
        req.Context.BeforeContext,
        req.Context.CurrentBranch,
        req.Conflict.CurrentBlock.Content,
        req.Conflict.IncomingBlock.Content,
        req.Context.TargetBranch,
        contextLines,
        req.Conflict.FileType,
        req.Context.AfterContext,
        req.Context.FunctionName,
        req.Context.ClassName,
        req.Conflict.FileType,
    )
}

Response Parsing and Validation

// pkg/claude/response_parser.go
type ResponseParser struct {
    validators map[string]CodeValidator
}

func (p *ResponseParser) ParseResponse(response *anthropic.MessageResponse, conflict Conflict) (*ConflictResolutionResponse, error) {
    content := response.Content[0].Text
    
    // Extract resolution code
    resolutionPattern := regexp.MustCompile(`(?s)RESOLUTION:\s*\` + "`" + `{3}[a-zA-Z]*\n(.*?)\` + "`" + `{3}`)
    matches := resolutionPattern.FindStringSubmatch(content)
    if len(matches) < 2 {
        return nil, fmt.Errorf("could not extract resolution from response")
    }
    resolution := matches[1]
    
    // Extract explanation
    explanationPattern := regexp.MustCompile(`(?s)EXPLANATION:\s*\n(.*?)(?:\n\nCONFIDENCE:|$)`)
    explanationMatches := explanationPattern.FindStringSubmatch(content)
    explanation := ""
    if len(explanationMatches) >= 2 {
        explanation = strings.TrimSpace(explanationMatches[1])
    }
    
    // Extract confidence
    confidencePattern := regexp.MustCompile(`CONFIDENCE:\s*(HIGH|MEDIUM|LOW)`)
    confidenceMatches := confidencePattern.FindStringSubmatch(content)
    confidence := 0.5
    if len(confidenceMatches) >= 2 {
        switch confidenceMatches[1] {
        case "HIGH":
            confidence = 0.9
        case "MEDIUM":
            confidence = 0.7
        case "LOW":
            confidence = 0.4
        }
    }
    
    // Validate the resolution
    validator := p.validators[conflict.FileType]
    if validator != nil {
        if err := validator.Validate(resolution); err != nil {
            return nil, fmt.Errorf("invalid resolution code: %w", err)
        }
    }
    
    return &ConflictResolutionResponse{
        Resolution:  resolution,
        Explanation: explanation,
        Confidence:  confidence,
        Model:       response.Model,
        TokensUsed: TokenUsage{
            Prompt:     response.Usage.InputTokens,
            Completion: response.Usage.OutputTokens,
            Total:      response.Usage.InputTokens + response.Usage.OutputTokens,
        },
        CacheHit: false,
    }, nil
}

Cost Management

// pkg/claude/cost_manager.go
type CostManager struct {
    pricing  map[string]PricingTier
    budgets  *BudgetManager
    usage    *UsageTracker
}

type PricingTier struct {
    InputCostPer1K  float64
    OutputCostPer1K float64
}

var DefaultPricing = map[string]PricingTier{
    "claude-3-opus":   {InputCostPer1K: 0.015, OutputCostPer1K: 0.075},
    "claude-3-sonnet": {InputCostPer1K: 0.003, OutputCostPer1K: 0.015},
    "claude-3-haiku":  {InputCostPer1K: 0.00025, OutputCostPer1K: 0.00125},
}

func (m *CostManager) CheckBudget(ctx context.Context, estimatedTokens int) error {
    userID := getUserID(ctx)
    budget := m.budgets.GetUserBudget(userID)
    
    estimatedCost := m.estimateCost("claude-3-opus", estimatedTokens)
    currentUsage := m.usage.GetMonthlyUsage(userID)
    
    if currentUsage + estimatedCost > budget {
        return fmt.Errorf("monthly budget exceeded: used $%.2f of $%.2f", currentUsage, budget)
    }
    
    return nil
}

func (m *CostManager) RecordUsage(model string, usage anthropic.Usage) {
    cost := m.calculateCost(model, usage)
    
    m.usage.Record(UsageRecord{
        Model:        model,
        InputTokens:  usage.InputTokens,
        OutputTokens: usage.OutputTokens,
        Cost:         cost,
        Timestamp:    time.Now(),
    })
}

Response Caching

// pkg/claude/cache.go
type ResponseCache struct {
    storage      CacheStorage
    hasher       *ConflictHasher
    similarity   *SimilarityChecker
    minSimilarity float64
}

func (c *ResponseCache) Get(conflict Conflict) *ConflictResolutionResponse {
    // Generate hash for exact match
    hash := c.hasher.Hash(conflict)
    
    if exact := c.storage.GetExact(hash); exact != nil {
        return exact
    }
    
    // Try similarity matching
    similar := c.storage.FindSimilar(conflict, c.minSimilarity)
    if len(similar) > 0 {
        // Return most similar
        return similar[0].Response
    }
    
    return nil
}

func (c *ResponseCache) Put(conflict Conflict, response *ConflictResolutionResponse, ttl time.Duration) {
    hash := c.hasher.Hash(conflict)
    
    entry := CacheEntry{
        Hash:       hash,
        Conflict:   conflict,
        Response:   response,
        CreatedAt:  time.Now(),
        ExpiresAt:  time.Now().Add(ttl),
        Embeddings: c.generateEmbeddings(conflict),
    }
    
    c.storage.Store(entry)
}

Architecture References

Claude Integration in Architecture

Reference: /docs/02-system-components.md:383-411

The Claude Integration Service is designed with cost management and safety:

graph TD
    A[Claude Service] --> B[Cost Manager]
    B --> C[Rate Limiter]
    C --> D[Request Queue]
    D --> E[Priority Manager]
    E --> F[Claude SDK]
    F --> G[Response Cache]
    G --> H[Result Validator]
    H --> I[Audit Logger]
Loading

Conflict Resolution with Claude

Reference: /docs/02-system-components.md:269-307

async def resolve_with_validation(self, conflicts: List[Conflict], target_branch: str) -> List[ResolvedConflict]:
    """Resolve conflicts with multiple validation layers"""
    resolved = []
    
    for conflict in conflicts:
        # Get Claude's suggestion
        claude_solution = await self._get_claude_resolution(conflict)
        
        # Validate the solution
        validation_result = await self.validator.validate_resolution(
            conflict, claude_solution
        )

Prompt Templates

Reference: /docs/02-system-components.md:412-456

Enhanced prompt templates with safety guidelines:

conflict_resolution:
  system: |
    You are helping resolve git merge conflicts in a secure environment.
    IMPORTANT: Do not include any code that:
    - Executes system commands
    - Evaluates dynamic code
    - Accesses external resources without validation
    - Contains hardcoded credentials or secrets

Cost Tracking Schema

Reference: /docs/02-system-components.md:629-644

CREATE TABLE claude_api_usage (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    model VARCHAR(50) NOT NULL,
    operation_type VARCHAR(50),
    prompt_tokens INTEGER,
    completion_tokens INTEGER,
    total_tokens INTEGER,
    cost_usd DECIMAL(10, 6),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Dependencies

  • Anthropic Go SDK: Claude API integration
  • PostgreSQL: Usage tracking and caching
  • Redis: Response cache indexing
  • Prometheus: Metrics collection

Definition of Done

  • Unit tests cover prompt building and response parsing with 90%+ coverage
  • Integration tests verify Claude API calls with mock responses
  • Safety validation catches dangerous code patterns
  • Cost tracking accurately calculates usage
  • Cache hit rate >40% for similar conflicts
  • Documentation includes prompt engineering guide
  • Metrics dashboard shows resolution success rates

Effort Estimate

13 Story Points - Complex integration with multiple validation layers

Labels

  • backend
  • claude
  • integration
  • epic-5

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions