| 1 | package llm |
| 2 | |
| 3 | import ( |
| 4 | "regexp" |
| 5 | "strings" |
| 6 | ) |
| 7 | |
| 8 | // ErrorCategory represents the type of error that occurred |
| 9 | type ErrorCategory string |
| 10 | |
| 11 | const ( |
| 12 | ErrorPermission ErrorCategory = "permission" |
| 13 | ErrorSyntax ErrorCategory = "syntax" |
| 14 | ErrorNetwork ErrorCategory = "network" |
| 15 | ErrorDependency ErrorCategory = "dependency" |
| 16 | ErrorConfiguration ErrorCategory = "config" |
| 17 | ErrorMergeConflict ErrorCategory = "merge_conflict" |
| 18 | ErrorTestFailure ErrorCategory = "test_failure" |
| 19 | ErrorBuildFailure ErrorCategory = "build_failure" |
| 20 | ErrorTimeout ErrorCategory = "timeout" |
| 21 | ErrorNotFound ErrorCategory = "not_found" |
| 22 | ErrorAuthentication ErrorCategory = "authentication" |
| 23 | ErrorDiskSpace ErrorCategory = "disk_space" |
| 24 | ErrorMemory ErrorCategory = "memory" |
| 25 | ErrorSegfault ErrorCategory = "segfault" |
| 26 | ErrorRaceCondition ErrorCategory = "race_condition" |
| 27 | ErrorDeprecated ErrorCategory = "deprecated" |
| 28 | ErrorLinting ErrorCategory = "linting" |
| 29 | ErrorTypeMismatch ErrorCategory = "type_mismatch" |
| 30 | ErrorNullPointer ErrorCategory = "null_pointer" |
| 31 | ErrorInfiniteLoop ErrorCategory = "infinite_loop" |
| 32 | ErrorGeneric ErrorCategory = "generic" |
| 33 | ) |
| 34 | |
| 35 | // ErrorClassifier analyzes commands and exit codes to determine error types |
| 36 | type ErrorClassifier struct { |
| 37 | exitCodePatterns map[int][]ErrorCategory |
| 38 | commandPatterns map[*regexp.Regexp]ErrorCategory |
| 39 | keywordPatterns map[string]ErrorCategory |
| 40 | } |
| 41 | |
| 42 | // NewErrorClassifier creates a new error classifier |
| 43 | func NewErrorClassifier() *ErrorClassifier { |
| 44 | ec := &ErrorClassifier{ |
| 45 | exitCodePatterns: make(map[int][]ErrorCategory), |
| 46 | commandPatterns: make(map[*regexp.Regexp]ErrorCategory), |
| 47 | keywordPatterns: make(map[string]ErrorCategory), |
| 48 | } |
| 49 | ec.initializePatterns() |
| 50 | return ec |
| 51 | } |
| 52 | |
| 53 | func (ec *ErrorClassifier) initializePatterns() { |
| 54 | // Exit code mappings (common Unix exit codes) |
| 55 | ec.exitCodePatterns[1] = []ErrorCategory{ErrorGeneric} |
| 56 | ec.exitCodePatterns[2] = []ErrorCategory{ErrorSyntax} |
| 57 | ec.exitCodePatterns[126] = []ErrorCategory{ErrorPermission} |
| 58 | ec.exitCodePatterns[127] = []ErrorCategory{ErrorNotFound} |
| 59 | ec.exitCodePatterns[128] = []ErrorCategory{ErrorSegfault} |
| 60 | ec.exitCodePatterns[130] = []ErrorCategory{ErrorTimeout} |
| 61 | ec.exitCodePatterns[137] = []ErrorCategory{ErrorMemory} // SIGKILL |
| 62 | ec.exitCodePatterns[139] = []ErrorCategory{ErrorSegfault} // SIGSEGV |
| 63 | ec.exitCodePatterns[143] = []ErrorCategory{ErrorTimeout} // SIGTERM |
| 64 | |
| 65 | // Command pattern mappings |
| 66 | ec.commandPatterns[regexp.MustCompile(`chmod|chown|sudo|permission`)] = ErrorPermission |
| 67 | ec.commandPatterns[regexp.MustCompile(`git\s+(merge|rebase|cherry-pick)`)] = ErrorMergeConflict |
| 68 | ec.commandPatterns[regexp.MustCompile(`(npm|yarn|pip|cargo|go)\s+(install|add)`)] = ErrorDependency |
| 69 | ec.commandPatterns[regexp.MustCompile(`(pytest|jest|cargo test|go test|npm test)`)] = ErrorTestFailure |
| 70 | ec.commandPatterns[regexp.MustCompile(`(make|cargo build|npm run build|go build|mvn compile)`)] = ErrorBuildFailure |
| 71 | ec.commandPatterns[regexp.MustCompile(`curl|wget|fetch|ping|ssh|scp`)] = ErrorNetwork |
| 72 | ec.commandPatterns[regexp.MustCompile(`docker login|git push.*http|auth|token`)] = ErrorAuthentication |
| 73 | ec.commandPatterns[regexp.MustCompile(`eslint|pylint|clippy|golint|rubocop`)] = ErrorLinting |
| 74 | |
| 75 | // Keyword patterns (for error messages/command text analysis) |
| 76 | ec.keywordPatterns["permission denied"] = ErrorPermission |
| 77 | ec.keywordPatterns["eacces"] = ErrorPermission |
| 78 | ec.keywordPatterns["eperm"] = ErrorPermission |
| 79 | ec.keywordPatterns["access denied"] = ErrorPermission |
| 80 | ec.keywordPatterns["forbidden"] = ErrorPermission |
| 81 | |
| 82 | ec.keywordPatterns["syntax error"] = ErrorSyntax |
| 83 | ec.keywordPatterns["parse error"] = ErrorSyntax |
| 84 | ec.keywordPatterns["unexpected token"] = ErrorSyntax |
| 85 | ec.keywordPatterns["invalid syntax"] = ErrorSyntax |
| 86 | |
| 87 | ec.keywordPatterns["connection refused"] = ErrorNetwork |
| 88 | ec.keywordPatterns["timeout"] = ErrorTimeout |
| 89 | ec.keywordPatterns["connection timed out"] = ErrorTimeout |
| 90 | ec.keywordPatterns["network unreachable"] = ErrorNetwork |
| 91 | ec.keywordPatterns["could not resolve host"] = ErrorNetwork |
| 92 | ec.keywordPatterns["econnrefused"] = ErrorNetwork |
| 93 | |
| 94 | ec.keywordPatterns["module not found"] = ErrorDependency |
| 95 | ec.keywordPatterns["package not found"] = ErrorDependency |
| 96 | ec.keywordPatterns["cannot find module"] = ErrorDependency |
| 97 | ec.keywordPatterns["no such file or directory"] = ErrorNotFound |
| 98 | ec.keywordPatterns["command not found"] = ErrorNotFound |
| 99 | |
| 100 | ec.keywordPatterns["merge conflict"] = ErrorMergeConflict |
| 101 | ec.keywordPatterns["conflict (content)"] = ErrorMergeConflict |
| 102 | ec.keywordPatterns["both modified"] = ErrorMergeConflict |
| 103 | |
| 104 | ec.keywordPatterns["authentication failed"] = ErrorAuthentication |
| 105 | ec.keywordPatterns["401"] = ErrorAuthentication |
| 106 | ec.keywordPatterns["403"] = ErrorPermission |
| 107 | ec.keywordPatterns["unauthorized"] = ErrorAuthentication |
| 108 | |
| 109 | ec.keywordPatterns["no space left"] = ErrorDiskSpace |
| 110 | ec.keywordPatterns["disk full"] = ErrorDiskSpace |
| 111 | ec.keywordPatterns["enospc"] = ErrorDiskSpace |
| 112 | |
| 113 | ec.keywordPatterns["out of memory"] = ErrorMemory |
| 114 | ec.keywordPatterns["enomem"] = ErrorMemory |
| 115 | ec.keywordPatterns["killed"] = ErrorMemory |
| 116 | |
| 117 | ec.keywordPatterns["segmentation fault"] = ErrorSegfault |
| 118 | ec.keywordPatterns["sigsegv"] = ErrorSegfault |
| 119 | ec.keywordPatterns["core dumped"] = ErrorSegfault |
| 120 | |
| 121 | ec.keywordPatterns["race detected"] = ErrorRaceCondition |
| 122 | ec.keywordPatterns["data race"] = ErrorRaceCondition |
| 123 | |
| 124 | ec.keywordPatterns["deprecated"] = ErrorDeprecated |
| 125 | ec.keywordPatterns["no longer supported"] = ErrorDeprecated |
| 126 | |
| 127 | ec.keywordPatterns["test failed"] = ErrorTestFailure |
| 128 | ec.keywordPatterns["assertion failed"] = ErrorTestFailure |
| 129 | ec.keywordPatterns["expected"] = ErrorTestFailure |
| 130 | |
| 131 | ec.keywordPatterns["type error"] = ErrorTypeMismatch |
| 132 | ec.keywordPatterns["type mismatch"] = ErrorTypeMismatch |
| 133 | ec.keywordPatterns["cannot convert"] = ErrorTypeMismatch |
| 134 | |
| 135 | ec.keywordPatterns["null pointer"] = ErrorNullPointer |
| 136 | ec.keywordPatterns["nil pointer"] = ErrorNullPointer |
| 137 | ec.keywordPatterns["nullptr"] = ErrorNullPointer |
| 138 | } |
| 139 | |
| 140 | // ClassifyError analyzes the command and exit code to determine error categories |
| 141 | func (ec *ErrorClassifier) ClassifyError(command string, exitCode int, errorOutput string) []ErrorCategory { |
| 142 | categories := make(map[ErrorCategory]bool) |
| 143 | |
| 144 | // Check exit code patterns |
| 145 | if exitCategories, exists := ec.exitCodePatterns[exitCode]; exists { |
| 146 | for _, cat := range exitCategories { |
| 147 | categories[cat] = true |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | // Check command patterns |
| 152 | commandLower := strings.ToLower(command) |
| 153 | for pattern, category := range ec.commandPatterns { |
| 154 | if pattern.MatchString(commandLower) { |
| 155 | categories[category] = true |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | // Check keyword patterns in command and error output |
| 160 | combinedText := strings.ToLower(command + " " + errorOutput) |
| 161 | for keyword, category := range ec.keywordPatterns { |
| 162 | if strings.Contains(combinedText, keyword) { |
| 163 | categories[category] = true |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | // Convert map to slice |
| 168 | result := make([]ErrorCategory, 0, len(categories)) |
| 169 | for cat := range categories { |
| 170 | result = append(result, cat) |
| 171 | } |
| 172 | |
| 173 | // If no specific category found, return generic |
| 174 | if len(result) == 0 { |
| 175 | result = append(result, ErrorGeneric) |
| 176 | } |
| 177 | |
| 178 | return result |
| 179 | } |
| 180 | |
| 181 | // GetPrimaryError returns the most specific error category |
| 182 | func (ec *ErrorClassifier) GetPrimaryError(command string, exitCode int, errorOutput string) ErrorCategory { |
| 183 | categories := ec.ClassifyError(command, exitCode, errorOutput) |
| 184 | |
| 185 | // Priority order: specific errors first, generic last |
| 186 | priority := []ErrorCategory{ |
| 187 | ErrorSegfault, |
| 188 | ErrorMergeConflict, |
| 189 | ErrorRaceCondition, |
| 190 | ErrorPermission, |
| 191 | ErrorAuthentication, |
| 192 | ErrorDiskSpace, |
| 193 | ErrorMemory, |
| 194 | ErrorTimeout, |
| 195 | ErrorNetwork, |
| 196 | ErrorDependency, |
| 197 | ErrorTestFailure, |
| 198 | ErrorBuildFailure, |
| 199 | ErrorLinting, |
| 200 | ErrorSyntax, |
| 201 | ErrorTypeMismatch, |
| 202 | ErrorNullPointer, |
| 203 | ErrorConfiguration, |
| 204 | ErrorDeprecated, |
| 205 | ErrorNotFound, |
| 206 | ErrorInfiniteLoop, |
| 207 | ErrorGeneric, |
| 208 | } |
| 209 | |
| 210 | for _, prioCategory := range priority { |
| 211 | for _, foundCategory := range categories { |
| 212 | if foundCategory == prioCategory { |
| 213 | return prioCategory |
| 214 | } |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | return ErrorGeneric |
| 219 | } |
| 220 |