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 }