Files
CHORUS/pkg/slurp/intelligence/engine_impl.go
2025-09-27 21:03:12 +10:00

653 lines
19 KiB
Go

package intelligence
import (
"context"
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"sync"
"time"
slurpContext "chorus/pkg/slurp/context"
"chorus/pkg/ucxl"
)
// AnalyzeFile analyzes a single file and generates contextual understanding
func (e *DefaultIntelligenceEngine) AnalyzeFile(ctx context.Context, filePath string, role string) (*slurpContext.ContextNode, error) {
start := time.Now()
defer func() {
e.updateStats("file_analysis", time.Since(start), true)
}()
// Check cache first
cacheKey := fmt.Sprintf("file:%s:%s", filePath, role)
if cached, ok := e.cache.Load(cacheKey); ok {
if entry, ok := cached.(*CacheEntry); ok && time.Now().Before(entry.ExpiresAt) {
e.mu.Lock()
e.stats.CacheHitRate = (e.stats.CacheHitRate*float64(e.stats.TotalAnalyses) + 1) / float64(e.stats.TotalAnalyses+1)
e.mu.Unlock()
return entry.ContextNode, nil
}
}
// Read file content
content, err := e.readFileContent(filePath)
if err != nil {
e.updateStats("file_analysis", time.Since(start), false)
return nil, fmt.Errorf("failed to read file %s: %w", filePath, err)
}
// Skip files that are too large
if int64(len(content)) > e.config.MaxFileSize {
e.updateStats("file_analysis", time.Since(start), false)
return nil, fmt.Errorf("file %s too large (%d bytes > %d bytes)", filePath, len(content), e.config.MaxFileSize)
}
// Perform file analysis
analysis, err := e.fileAnalyzer.AnalyzeContent(ctx, filePath, content)
if err != nil {
e.updateStats("file_analysis", time.Since(start), false)
return nil, fmt.Errorf("failed to analyze file content: %w", err)
}
// Generate UCXL address for the file
ucxlAddr, err := e.generateUCXLAddress(filePath)
if err != nil {
e.updateStats("file_analysis", time.Since(start), false)
return nil, fmt.Errorf("failed to generate UCXL address: %w", err)
}
// Extract purpose and summary
purpose, purposeConf, err := e.fileAnalyzer.IdentifyPurpose(ctx, analysis)
if err != nil {
purpose = "Unknown purpose"
purposeConf = 0.0
}
summary, err := e.fileAnalyzer.GenerateSummary(ctx, analysis)
if err != nil {
summary = "File analysis summary unavailable"
}
// Extract technologies
technologies, err := e.fileAnalyzer.ExtractTechnologies(ctx, analysis)
if err != nil {
technologies = []string{}
}
// Generate basic tags
tags := e.generateFileTags(analysis, filePath)
// Generate role-specific insights
insights, err := e.GenerateRoleInsights(ctx, nil, role)
if err != nil {
insights = []string{}
}
// Enhance with RAG if enabled
ragConfidence := 0.0
if e.config.RAGEnabled {
// This would be enhanced in a real implementation
ragConfidence = 0.7
}
// Create context node
contextNode := &slurpContext.ContextNode{
Path: filePath,
UCXLAddress: *ucxlAddr,
Summary: summary,
Purpose: purpose,
Technologies: technologies,
Tags: tags,
Insights: insights,
OverridesParent: false,
ContextSpecificity: e.calculateSpecificity(analysis),
AppliesToChildren: false,
GeneratedAt: time.Now(),
RAGConfidence: ragConfidence,
EncryptedFor: e.determineEncryptionRoles(role, purposeConf),
AccessLevel: e.determineAccessLevel(analysis, role),
Metadata: make(map[string]interface{}),
}
// Add analysis metadata
contextNode.Metadata["analysis"] = analysis
contextNode.Metadata["purpose_confidence"] = purposeConf
contextNode.Metadata["role"] = role
// Cache the result
cacheEntry := &CacheEntry{
ContextNode: contextNode,
CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(e.config.CacheTTL),
}
e.cache.Store(cacheKey, cacheEntry)
return contextNode, nil
}
// AnalyzeDirectory analyzes directory structure for hierarchical patterns
func (e *DefaultIntelligenceEngine) AnalyzeDirectory(ctx context.Context, dirPath string) ([]*slurpContext.ContextNode, error) {
start := time.Now()
defer func() {
e.updateStats("directory_analysis", time.Since(start), true)
}()
// Analyze directory structure
if _, err := e.directoryAnalyzer.AnalyzeStructure(ctx, dirPath); err != nil {
e.updateStats("directory_analysis", time.Since(start), false)
return nil, fmt.Errorf("failed to analyze directory structure: %w", err)
}
// Generate hierarchy with bounded depth
hierarchy, err := e.directoryAnalyzer.GenerateHierarchy(ctx, dirPath, 5) // Max 5 levels deep
if err != nil {
e.updateStats("directory_analysis", time.Since(start), false)
return nil, fmt.Errorf("failed to generate hierarchy: %w", err)
}
return hierarchy, nil
}
// GenerateRoleInsights generates role-specific insights for existing context
func (e *DefaultIntelligenceEngine) GenerateRoleInsights(ctx context.Context, baseContext *slurpContext.ContextNode, role string) ([]string, error) {
insights := []string{}
// Get role profile
profile, exists := e.roleProfiles[role]
if !exists {
// Generate generic insights
insights = append(insights, "Generic insight: Consider code quality and maintainability")
return insights, nil
}
// Generate role-specific insights based on profile
for _, insightType := range profile.InsightTypes {
switch insightType {
case "security":
insights = append(insights, "Security: Review for potential vulnerabilities and secure coding practices")
case "performance":
insights = append(insights, "Performance: Analyze for optimization opportunities and bottlenecks")
case "architecture":
insights = append(insights, "Architecture: Ensure alignment with system design patterns")
case "testing":
insights = append(insights, "Testing: Consider test coverage and quality assurance requirements")
case "ui_ux":
insights = append(insights, "UI/UX: Focus on user experience and interface design principles")
case "api_design":
insights = append(insights, "API Design: Ensure RESTful principles and proper error handling")
case "database":
insights = append(insights, "Database: Consider data modeling and query optimization")
case "deployment":
insights = append(insights, "Deployment: Plan for scalability and infrastructure requirements")
}
}
// Add context-specific insights if baseContext is provided
if baseContext != nil {
contextInsights := e.generateContextSpecificInsights(baseContext, role)
insights = append(insights, contextInsights...)
}
return insights, nil
}
// AssessGoalAlignment assesses how well context aligns with project goals
func (e *DefaultIntelligenceEngine) AssessGoalAlignment(ctx context.Context, node *slurpContext.ContextNode) (float64, error) {
if len(e.projectGoals) == 0 {
return 0.5, nil // Default alignment score when no goals defined
}
totalAlignment := 0.0
totalWeight := 0.0
for _, goal := range e.projectGoals {
alignment := e.calculateGoalAlignment(node, goal)
weight := float64(10 - goal.Priority) // Higher priority = higher weight
totalAlignment += alignment * weight
totalWeight += weight
}
if totalWeight == 0 {
return 0.5, nil
}
return totalAlignment / totalWeight, nil
}
// AnalyzeBatch processes multiple files efficiently in parallel
func (e *DefaultIntelligenceEngine) AnalyzeBatch(ctx context.Context, filePaths []string, role string) (map[string]*slurpContext.ContextNode, error) {
results := make(map[string]*slurpContext.ContextNode)
mu := sync.Mutex{}
wg := sync.WaitGroup{}
errorCh := make(chan error, len(filePaths))
// Limit concurrency
semaphore := make(chan struct{}, e.config.MaxConcurrentAnalysis)
for _, filePath := range filePaths {
wg.Add(1)
go func(path string) {
defer wg.Done()
semaphore <- struct{}{} // Acquire semaphore
defer func() { <-semaphore }() // Release semaphore
ctxNode, err := e.AnalyzeFile(ctx, path, role)
if err != nil {
errorCh <- fmt.Errorf("failed to analyze %s: %w", path, err)
return
}
mu.Lock()
results[path] = ctxNode
mu.Unlock()
}(filePath)
}
wg.Wait()
close(errorCh)
// Collect any errors
var errs []error
for err := range errorCh {
errs = append(errs, err)
}
if len(errs) > 0 {
return results, fmt.Errorf("batch analysis errors: %v", errs)
}
return results, nil
}
// DetectPatterns identifies recurring patterns across multiple contexts
func (e *DefaultIntelligenceEngine) DetectPatterns(ctx context.Context, contexts []*slurpContext.ContextNode) ([]*Pattern, error) {
patterns := []*Pattern{}
// Use pattern detector to find code patterns
for _, context := range contexts {
if context.Metadata["analysis"] != nil {
if analysis, ok := context.Metadata["analysis"].(*FileAnalysis); ok {
codePatterns, err := e.patternDetector.DetectCodePatterns(ctx, context.Path, []byte(analysis.FilePath))
if err == nil {
for _, cp := range codePatterns {
patterns = append(patterns, &cp.Pattern)
}
}
}
}
}
// Detect naming patterns
namingPatterns, err := e.patternDetector.DetectNamingPatterns(ctx, contexts)
if err == nil {
for _, np := range namingPatterns {
patterns = append(patterns, &np.Pattern)
}
}
return patterns, nil
}
// EnhanceWithRAG enhances context using RAG system knowledge
func (e *DefaultIntelligenceEngine) EnhanceWithRAG(ctx context.Context, node *slurpContext.ContextNode) (*slurpContext.ContextNode, error) {
if !e.config.RAGEnabled {
return node, nil // Return unchanged if RAG is disabled
}
// Create query for RAG system
query := fmt.Sprintf("Provide insights for %s: %s", node.Purpose, node.Summary)
queryContext := map[string]interface{}{
"file_path": node.Path,
"technologies": node.Technologies,
"tags": node.Tags,
}
// Query RAG system
ragResponse, err := e.ragIntegration.Query(ctx, query, queryContext)
if err != nil {
return node, fmt.Errorf("RAG query failed: %w", err)
}
// Enhance context with RAG insights
enhanced := node.Clone()
if ragResponse.Confidence >= e.config.MinConfidenceThreshold {
enhanced.Insights = append(enhanced.Insights, fmt.Sprintf("RAG: %s", ragResponse.Answer))
enhanced.RAGConfidence = ragResponse.Confidence
// Add source information to metadata
if len(ragResponse.Sources) > 0 {
sources := make([]string, len(ragResponse.Sources))
for i, source := range ragResponse.Sources {
sources[i] = source.Title
}
enhanced.Metadata["rag_sources"] = sources
}
}
return enhanced, nil
}
// ValidateContext validates generated context quality and consistency
func (e *DefaultIntelligenceEngine) ValidateContext(ctx context.Context, node *slurpContext.ContextNode) (*ValidationResult, error) {
result := &ValidationResult{
Valid: true,
ConfidenceScore: 1.0,
QualityScore: 1.0,
Issues: []*ValidationIssue{},
Suggestions: []*Suggestion{},
ValidatedAt: time.Now(),
}
// Validate basic structure
if err := node.Validate(); err != nil {
result.Valid = false
result.Issues = append(result.Issues, &ValidationIssue{
Type: "structure",
Severity: "error",
Message: err.Error(),
Field: "context_node",
Suggestion: "Fix validation errors in context structure",
Impact: 0.8,
})
}
// Check quality thresholds
if node.RAGConfidence < e.config.MinConfidenceThreshold {
result.QualityScore *= 0.8
result.Suggestions = append(result.Suggestions, &Suggestion{
Type: "quality",
Title: "Low RAG confidence",
Description: "Consider enhancing context with additional analysis",
Confidence: 0.7,
Priority: 2,
Action: "re_analyze",
Impact: "medium",
})
}
// Validate content quality
if len(node.Summary) < 10 {
result.QualityScore *= 0.9
result.Issues = append(result.Issues, &ValidationIssue{
Type: "content",
Severity: "warning",
Message: "Summary too short",
Field: "summary",
Suggestion: "Provide more detailed summary",
Impact: 0.1,
})
}
return result, nil
}
// GetEngineStats returns engine performance and operational statistics
func (e *DefaultIntelligenceEngine) GetEngineStats() (*EngineStatistics, error) {
e.mu.RLock()
defer e.mu.RUnlock()
// Calculate cache hit rate
cacheSize := 0
e.cache.Range(func(key, value interface{}) bool {
cacheSize++
return true
})
stats := *e.stats // Copy current stats
stats.CacheHitRate = e.calculateCacheHitRate()
return &stats, nil
}
// SetConfiguration updates engine configuration
func (e *DefaultIntelligenceEngine) SetConfiguration(config *EngineConfig) error {
e.mu.Lock()
defer e.mu.Unlock()
if config == nil {
return fmt.Errorf("configuration cannot be nil")
}
e.config = config
e.projectGoals = config.ProjectGoals
e.roleProfiles = config.RoleProfiles
return nil
}
// Helper methods
// readFileContent reads and returns file content
func (e *DefaultIntelligenceEngine) readFileContent(filePath string) ([]byte, error) {
return ioutil.ReadFile(filePath)
}
// generateUCXLAddress generates a UCXL address for a file path
func (e *DefaultIntelligenceEngine) generateUCXLAddress(filePath string) (*ucxl.Address, error) {
// Simple implementation - in reality this would be more sophisticated
cleanPath := filepath.Clean(filePath)
addr, err := ucxl.Parse(fmt.Sprintf("file://%s", cleanPath))
if err != nil {
return nil, fmt.Errorf("failed to generate UCXL address: %w", err)
}
return addr, nil
}
// generateFileTags generates tags based on file analysis and path
func (e *DefaultIntelligenceEngine) generateFileTags(analysis *FileAnalysis, filePath string) []string {
tags := []string{}
// Add language tag
if analysis.Language != "" {
tags = append(tags, analysis.Language)
}
// Add file type tag
if analysis.FileType != "" {
tags = append(tags, analysis.FileType)
}
// Add directory-based tags
dir := filepath.Dir(filePath)
dirName := filepath.Base(dir)
if dirName != "." && dirName != "/" {
tags = append(tags, "dir:"+dirName)
}
// Add complexity tag
if analysis.Complexity > 10 {
tags = append(tags, "high-complexity")
} else if analysis.Complexity > 5 {
tags = append(tags, "medium-complexity")
} else {
tags = append(tags, "low-complexity")
}
return tags
}
// calculateSpecificity calculates context specificity based on analysis
func (e *DefaultIntelligenceEngine) calculateSpecificity(analysis *FileAnalysis) int {
specificity := 1
// More specific if it has many functions/classes
if len(analysis.Functions) > 5 || len(analysis.Classes) > 3 {
specificity += 2
}
// More specific if it has dependencies
if len(analysis.Dependencies) > 0 {
specificity += 1
}
// More specific if it's complex
if analysis.Complexity > 10 {
specificity += 1
}
return specificity
}
// determineEncryptionRoles determines which roles can access this context
func (e *DefaultIntelligenceEngine) determineEncryptionRoles(role string, confidence float64) []string {
roles := []string{role}
// Add senior roles that can access everything
seniorRoles := []string{"senior_architect", "project_manager"}
for _, senior := range seniorRoles {
if senior != role {
roles = append(roles, senior)
}
}
// If high confidence, allow broader access
if confidence > 0.8 {
roles = append(roles, "*")
}
return roles
}
// determineAccessLevel determines the required access level for context
func (e *DefaultIntelligenceEngine) determineAccessLevel(analysis *FileAnalysis, role string) slurpContext.RoleAccessLevel {
// Default to low access
level := slurpContext.AccessLow
// Increase level based on content sensitivity
sensitive := false
for _, comment := range analysis.Comments {
if strings.Contains(strings.ToLower(comment), "password") ||
strings.Contains(strings.ToLower(comment), "secret") ||
strings.Contains(strings.ToLower(comment), "private") {
sensitive = true
break
}
}
if sensitive {
level = slurpContext.AccessHigh
} else if len(analysis.Dependencies) > 5 {
level = slurpContext.AccessMedium
}
return level
}
// generateContextSpecificInsights generates insights specific to the provided context
func (e *DefaultIntelligenceEngine) generateContextSpecificInsights(context *slurpContext.ContextNode, role string) []string {
insights := []string{}
// Technology-specific insights
for _, tech := range context.Technologies {
switch strings.ToLower(tech) {
case "react", "vue", "angular":
insights = append(insights, fmt.Sprintf("Frontend: %s component requires testing for accessibility and responsiveness", tech))
case "go", "python", "java":
insights = append(insights, fmt.Sprintf("Backend: %s code should follow language-specific best practices", tech))
case "docker", "kubernetes":
insights = append(insights, fmt.Sprintf("Infrastructure: %s configuration needs security review", tech))
}
}
// Purpose-specific insights
if strings.Contains(strings.ToLower(context.Purpose), "api") {
insights = append(insights, "API: Consider rate limiting, authentication, and proper error responses")
}
if strings.Contains(strings.ToLower(context.Purpose), "database") {
insights = append(insights, "Database: Review for proper indexing and query optimization")
}
return insights
}
// calculateGoalAlignment calculates alignment score between context and goal
func (e *DefaultIntelligenceEngine) calculateGoalAlignment(node *slurpContext.ContextNode, goal *ProjectGoal) float64 {
score := 0.0
checks := 0.0
// Check keyword overlap
nodeText := strings.ToLower(node.Summary + " " + node.Purpose + " " + strings.Join(node.Technologies, " "))
for _, keyword := range goal.Keywords {
checks += 1.0
if strings.Contains(nodeText, strings.ToLower(keyword)) {
score += 1.0
}
}
// Check tag overlap
for _, tag := range node.Tags {
checks += 1.0
for _, keyword := range goal.Keywords {
if strings.Contains(strings.ToLower(tag), strings.ToLower(keyword)) {
score += 0.5
break
}
}
}
if checks == 0 {
return 0.5 // Default score when no keywords to check
}
return score / checks
}
// updateStats updates engine statistics
func (e *DefaultIntelligenceEngine) updateStats(operation string, duration time.Duration, success bool) {
e.mu.Lock()
defer e.mu.Unlock()
e.stats.TotalAnalyses++
if success {
e.stats.SuccessfulAnalyses++
} else {
e.stats.FailedAnalyses++
}
// Update average analysis time
if e.stats.TotalAnalyses == 1 {
e.stats.AverageAnalysisTime = duration
} else {
e.stats.AverageAnalysisTime = time.Duration(
(int64(e.stats.AverageAnalysisTime)*(e.stats.TotalAnalyses-1) + int64(duration)) / e.stats.TotalAnalyses,
)
}
// Update operation-specific stats
switch operation {
case "file_analysis":
e.stats.FilesAnalyzed++
case "directory_analysis":
e.stats.DirectoriesAnalyzed++
}
}
// calculateCacheHitRate calculates the current cache hit rate
func (e *DefaultIntelligenceEngine) calculateCacheHitRate() float64 {
return e.stats.CacheHitRate // This would be calculated from cache access stats in a real implementation
}
// DefaultEngineConfig returns default configuration for the intelligence engine
func DefaultEngineConfig() *EngineConfig {
return &EngineConfig{
MaxConcurrentAnalysis: 4,
AnalysisTimeout: 30 * time.Second,
MaxFileSize: 10 * 1024 * 1024, // 10MB
RAGEndpoint: "",
RAGTimeout: 10 * time.Second,
RAGEnabled: false,
EnableRAG: false,
EnableGoalAlignment: false,
EnablePatternDetection: false,
EnableRoleAware: false,
MinConfidenceThreshold: 0.6,
RequireValidation: true,
CacheEnabled: true,
CacheTTL: 1 * time.Hour,
RoleProfiles: make(map[string]*RoleProfile),
ProjectGoals: []*ProjectGoal{},
}
}