/ go / internal / learning / brain.go
brain.go
  1  package learning
  2  
  3  import (
  4  	"encoding/json"
  5  	"fmt"
  6  	"os"
  7  	"path/filepath"
  8  	"strings"
  9  	"time"
 10  )
 11  
 12  type Knowledge struct {
 13  	ID          string            `json:"id"`
 14  	Type        string            `json:"type"` // "pattern", "solution", "insight", "context"
 15  	Content     string            `json:"content"`
 16  	Context     map[string]string `json:"context"`
 17  	Tags        []string          `json:"tags"`
 18  	Confidence  float64           `json:"confidence"`
 19  	Usage       int               `json:"usage"`
 20  	CreatedAt   time.Time         `json:"created_at"`
 21  	UpdatedAt   time.Time         `json:"updated_at"`
 22  	ProjectPath string            `json:"project_path,omitempty"`
 23  }
 24  
 25  type Brain struct {
 26  	ProjectPath string
 27  	GlobalPath  string
 28  }
 29  
 30  func NewBrain(projectPath string) *Brain {
 31  	home, _ := os.UserHomeDir()
 32  	return &Brain{
 33  		ProjectPath: filepath.Join(projectPath, ".kamaji", "brain.json"),
 34  		GlobalPath:  filepath.Join(home, ".kamaji", "global_brain.json"),
 35  	}
 36  }
 37  
 38  func (b *Brain) Learn(knowledge Knowledge) error {
 39  	knowledge.ID = generateID()
 40  	knowledge.CreatedAt = time.Now()
 41  	knowledge.UpdatedAt = time.Now()
 42  	
 43  	// Store in project brain
 44  	if err := b.addToFile(b.ProjectPath, knowledge); err != nil {
 45  		return err
 46  	}
 47  	
 48  	// Store in global brain if significant
 49  	if knowledge.Confidence > 0.7 {
 50  		knowledge.ProjectPath = filepath.Dir(filepath.Dir(b.ProjectPath))
 51  		return b.addToFile(b.GlobalPath, knowledge)
 52  	}
 53  	
 54  	return nil
 55  }
 56  
 57  func (b *Brain) Recall(query string, contextType string) ([]Knowledge, error) {
 58  	var results []Knowledge
 59  	
 60  	// Search project brain
 61  	projectKnowledge, _ := b.searchFile(b.ProjectPath, query, contextType)
 62  	results = append(results, projectKnowledge...)
 63  	
 64  	// Search global brain
 65  	globalKnowledge, _ := b.searchFile(b.GlobalPath, query, contextType)
 66  	results = append(results, globalKnowledge...)
 67  	
 68  	// Sort by relevance and usage
 69  	return b.rankResults(results, query), nil
 70  }
 71  
 72  func (b *Brain) GetInsights(projectPath string) ([]Knowledge, error) {
 73  	knowledge, err := b.loadFile(b.ProjectPath)
 74  	if err != nil {
 75  		return nil, err
 76  	}
 77  	
 78  	var insights []Knowledge
 79  	for _, k := range knowledge {
 80  		if k.Type == "insight" || k.Usage > 5 {
 81  			insights = append(insights, k)
 82  		}
 83  	}
 84  	
 85  	return insights, nil
 86  }
 87  
 88  func (b *Brain) addToFile(filePath string, knowledge Knowledge) error {
 89  	existing, _ := b.loadFile(filePath)
 90  	
 91  	// Check for duplicates
 92  	for i, k := range existing {
 93  		if b.isSimilar(k, knowledge) {
 94  			existing[i].Usage++
 95  			existing[i].UpdatedAt = time.Now()
 96  			existing[i].Confidence = (existing[i].Confidence + knowledge.Confidence) / 2
 97  			return b.saveFile(filePath, existing)
 98  		}
 99  	}
100  	
101  	existing = append(existing, knowledge)
102  	return b.saveFile(filePath, existing)
103  }
104  
105  func (b *Brain) loadFile(filePath string) ([]Knowledge, error) {
106  	if _, err := os.Stat(filePath); os.IsNotExist(err) {
107  		return []Knowledge{}, nil
108  	}
109  	
110  	data, err := os.ReadFile(filePath)
111  	if err != nil {
112  		return nil, err
113  	}
114  	
115  	var knowledge []Knowledge
116  	if err := json.Unmarshal(data, &knowledge); err != nil {
117  		return nil, err
118  	}
119  	
120  	return knowledge, nil
121  }
122  
123  func (b *Brain) saveFile(filePath string, knowledge []Knowledge) error {
124  	dir := filepath.Dir(filePath)
125  	if err := os.MkdirAll(dir, 0755); err != nil {
126  		return err
127  	}
128  	
129  	data, err := json.MarshalIndent(knowledge, "", "  ")
130  	if err != nil {
131  		return err
132  	}
133  	
134  	return os.WriteFile(filePath, data, 0644)
135  }
136  
137  func (b *Brain) searchFile(filePath, query, contextType string) ([]Knowledge, error) {
138  	knowledge, err := b.loadFile(filePath)
139  	if err != nil {
140  		return nil, err
141  	}
142  	
143  	var results []Knowledge
144  	queryLower := strings.ToLower(query)
145  	
146  	for _, k := range knowledge {
147  		if contextType != "" && k.Type != contextType {
148  			continue
149  		}
150  		
151  		if strings.Contains(strings.ToLower(k.Content), queryLower) ||
152  			b.matchesTags(k.Tags, queryLower) {
153  			results = append(results, k)
154  		}
155  	}
156  	
157  	return results, nil
158  }
159  
160  func (b *Brain) rankResults(results []Knowledge, query string) []Knowledge {
161  	// Simple ranking by usage and confidence
162  	for i := 0; i < len(results)-1; i++ {
163  		for j := i + 1; j < len(results); j++ {
164  			score1 := float64(results[i].Usage)*0.3 + results[i].Confidence*0.7
165  			score2 := float64(results[j].Usage)*0.3 + results[j].Confidence*0.7
166  			
167  			if score2 > score1 {
168  				results[i], results[j] = results[j], results[i]
169  			}
170  		}
171  	}
172  	
173  	return results
174  }
175  
176  func (b *Brain) isSimilar(k1, k2 Knowledge) bool {
177  	return strings.Contains(strings.ToLower(k1.Content), strings.ToLower(k2.Content[:min(len(k2.Content), 50)]))
178  }
179  
180  func (b *Brain) matchesTags(tags []string, query string) bool {
181  	for _, tag := range tags {
182  		if strings.Contains(strings.ToLower(tag), query) {
183  			return true
184  		}
185  	}
186  	return false
187  }
188  
189  func generateID() string {
190  	return fmt.Sprintf("k_%d", time.Now().UnixNano())
191  }
192  
193  func min(a, b int) int {
194  	if a < b {
195  		return a
196  	}
197  	return b
198  }