hanuscode 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hanus/__init__.py +5 -0
- hanus/__main__.py +10 -0
- hanus/action_handlers.py +76 -0
- hanus/action_parser.py +82 -0
- hanus/agent_runner.py +1445 -0
- hanus/analysis/__init__.py +5 -0
- hanus/analysis/debt.py +702 -0
- hanus/analysis/dependencies.py +475 -0
- hanus/cache/__init__.py +5 -0
- hanus/cache/response_cache.py +560 -0
- hanus/config.py +401 -0
- hanus/connectors/__init__.py +19 -0
- hanus/connectors/base.py +114 -0
- hanus/connectors/claude_connector.py +146 -0
- hanus/connectors/gemini_connector.py +141 -0
- hanus/connectors/glm_connector.py +160 -0
- hanus/connectors/ollama_connector.py +174 -0
- hanus/connectors/openai_connector.py +122 -0
- hanus/connectors/registry.py +26 -0
- hanus/context/__init__.py +7 -0
- hanus/context/manager.py +837 -0
- hanus/context/selective.py +626 -0
- hanus/error_recovery/__init__.py +5 -0
- hanus/error_recovery/auto_fix.py +605 -0
- hanus/hooks/__init__.py +5 -0
- hanus/hooks/manager.py +247 -0
- hanus/instincts/__init__.py +44 -0
- hanus/instincts/cli.py +372 -0
- hanus/instincts/detector.py +281 -0
- hanus/instincts/evolver.py +361 -0
- hanus/instincts/manager.py +343 -0
- hanus/instincts/types.py +253 -0
- hanus/logger.py +81 -0
- hanus/memory/__init__.py +8 -0
- hanus/memory/manager.py +265 -0
- hanus/memory/types.py +119 -0
- hanus/monitor.py +341 -0
- hanus/parallel/__init__.py +5 -0
- hanus/parallel/executor.py +300 -0
- hanus/permissions.py +182 -0
- hanus/plan/__init__.py +8 -0
- hanus/plan/mode.py +267 -0
- hanus/plan/models.py +152 -0
- hanus/plugin_manager.py +754 -0
- hanus/plugin_registry.py +391 -0
- hanus/plugins/__init__.py +1 -0
- hanus/plugins/arena.py +630 -0
- hanus/plugins/code_review.py +123 -0
- hanus/plugins/cortex.py +1750 -0
- hanus/plugins/deps_check.py +27 -0
- hanus/plugins/git_ops.py +33 -0
- hanus/plugins/metasploit.py +530 -0
- hanus/plugins/notes.py +583 -0
- hanus/plugins/search_code.py +59 -0
- hanus/plugins/searchsploit.py +495 -0
- hanus/plugins/strategist.py +175 -0
- hanus/plugins/webui.py +5200 -0
- hanus/profiles.py +479 -0
- hanus/profiles_builtin/__init__.py +0 -0
- hanus/profiles_builtin/architect/profile.yaml +12 -0
- hanus/profiles_builtin/architect/system_prompt.txt +71 -0
- hanus/profiles_builtin/deep/profile.yaml +12 -0
- hanus/profiles_builtin/deep/system_prompt.txt +66 -0
- hanus/profiles_builtin/developer/__init__.py +0 -0
- hanus/profiles_builtin/developer/profile.yaml +9 -0
- hanus/profiles_builtin/developer/system_prompt.txt +176 -0
- hanus/profiles_builtin/speed/profile.yaml +12 -0
- hanus/profiles_builtin/speed/system_prompt.txt +51 -0
- hanus/project_tools.py +177 -0
- hanus/query_engine.py +1594 -0
- hanus/rules/__init__.py +237 -0
- hanus/search/__init__.py +5 -0
- hanus/search/semantic.py +596 -0
- hanus/session_manager.py +547 -0
- hanus/skill_manager.py +702 -0
- hanus/skills/__init__.py +4 -0
- hanus/subagent/__init__.py +8 -0
- hanus/subagent/agents/__init__.py +253 -0
- hanus/subagent/manager.py +309 -0
- hanus/subagent/types.py +266 -0
- hanus/suggestions/__init__.py +5 -0
- hanus/suggestions/proactive.py +451 -0
- hanus/tasks/__init__.py +8 -0
- hanus/tasks/manager.py +330 -0
- hanus/tasks/models.py +106 -0
- hanus/terminal_prompt.py +166 -0
- hanus/tools.py +1849 -0
- hanus/ui.py +939 -0
- hanuscode-1.0.0.dist-info/METADATA +1151 -0
- hanuscode-1.0.0.dist-info/RECORD +93 -0
- hanuscode-1.0.0.dist-info/WHEEL +5 -0
- hanuscode-1.0.0.dist-info/entry_points.txt +2 -0
- hanuscode-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
# hanus/error_recovery/auto_fix.py
|
|
2
|
+
"""
|
|
3
|
+
Sistema de auto-corrección de errores.
|
|
4
|
+
|
|
5
|
+
Funcionalidades:
|
|
6
|
+
- Detectar errores en salida de comandos
|
|
7
|
+
- Proponer fixes automáticamente
|
|
8
|
+
- Learning de errores comunes por proyecto
|
|
9
|
+
- Base de conocimiento de soluciones
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
import re
|
|
13
|
+
import json
|
|
14
|
+
import time
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Dict, List, Optional, Any, Callable, Tuple
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from enum import Enum
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ErrorSeverity(Enum):
|
|
23
|
+
"""Severidad del error."""
|
|
24
|
+
LOW = "low" # Warning, puede ignorarse
|
|
25
|
+
MEDIUM = "medium" # Error pero hay workaround
|
|
26
|
+
HIGH = "high" # Error crítico, requiere fix
|
|
27
|
+
CRITICAL = "critical" # Error que impide continuar
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FixType(Enum):
|
|
31
|
+
"""Tipo de fix sugerido."""
|
|
32
|
+
COMMAND_FIX = "command_fix" # Corregir comando
|
|
33
|
+
FILE_EDIT = "file_edit" # Editar archivo
|
|
34
|
+
PACKAGE_INSTALL = "package" # Instalar paquete
|
|
35
|
+
CONFIG_CHANGE = "config" # Cambiar configuración
|
|
36
|
+
PERMISSION_FIX = "permission" # Arreglar permisos
|
|
37
|
+
ENVIRONMENT = "environment" # Problema de entorno
|
|
38
|
+
SYNTAX_FIX = "syntax" # Error de sintaxis
|
|
39
|
+
DEPENDENCY = "dependency" # Problema de dependencia
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class ErrorPattern:
|
|
44
|
+
"""Patrón de error reconocido."""
|
|
45
|
+
id: str
|
|
46
|
+
name: str
|
|
47
|
+
pattern: str # Regex pattern
|
|
48
|
+
severity: ErrorSeverity
|
|
49
|
+
fix_type: FixType
|
|
50
|
+
description: str
|
|
51
|
+
fix_template: str # Template para el fix
|
|
52
|
+
fix_command: Optional[str] = None # Comando para arreglar
|
|
53
|
+
auto_fixable: bool = True
|
|
54
|
+
tags: List[str] = field(default_factory=list)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class FixSuggestion:
|
|
59
|
+
"""Sugerencia de fix para un error."""
|
|
60
|
+
error_id: str
|
|
61
|
+
error_message: str
|
|
62
|
+
error_type: str
|
|
63
|
+
severity: ErrorSeverity
|
|
64
|
+
fix_type: FixType
|
|
65
|
+
description: str
|
|
66
|
+
fix_command: Optional[str] = None
|
|
67
|
+
fix_file: Optional[str] = None
|
|
68
|
+
fix_content: Optional[str] = None
|
|
69
|
+
confidence: float = 0.0 # 0-1, confianza en el fix
|
|
70
|
+
auto_apply: bool = False # Si se puede aplicar automáticamente
|
|
71
|
+
requires_confirmation: bool = True
|
|
72
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
73
|
+
|
|
74
|
+
def to_dict(self) -> Dict:
|
|
75
|
+
return {
|
|
76
|
+
"error_id": self.error_id,
|
|
77
|
+
"error_message": self.error_message,
|
|
78
|
+
"error_type": self.error_type,
|
|
79
|
+
"severity": self.severity.value,
|
|
80
|
+
"fix_type": self.fix_type.value,
|
|
81
|
+
"description": self.description,
|
|
82
|
+
"fix_command": self.fix_command,
|
|
83
|
+
"fix_file": self.fix_file,
|
|
84
|
+
"fix_content": self.fix_content,
|
|
85
|
+
"confidence": self.confidence,
|
|
86
|
+
"auto_apply": self.auto_apply,
|
|
87
|
+
"requires_confirmation": self.requires_confirmation,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ErrorRecovery:
|
|
92
|
+
"""
|
|
93
|
+
Sistema de detección y corrección automática de errores.
|
|
94
|
+
|
|
95
|
+
Características:
|
|
96
|
+
- Patrones de error predefinidos
|
|
97
|
+
- Learning de errores del proyecto
|
|
98
|
+
- Sugerencias de fix con confianza
|
|
99
|
+
- Auto-aplicación de fixes seguros
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
# Patrones de error predefinidos
|
|
103
|
+
BUILTIN_PATTERNS = [
|
|
104
|
+
# Python errors
|
|
105
|
+
ErrorPattern(
|
|
106
|
+
id="python_module_not_found",
|
|
107
|
+
name="Module Not Found",
|
|
108
|
+
pattern=r"ModuleNotFoundError: No module named '([^']+)'",
|
|
109
|
+
severity=ErrorSeverity.HIGH,
|
|
110
|
+
fix_type=FixType.PACKAGE_INSTALL,
|
|
111
|
+
description="Missing Python module",
|
|
112
|
+
fix_template="pip install {module}",
|
|
113
|
+
fix_command="pip install {module}",
|
|
114
|
+
auto_fixable=True,
|
|
115
|
+
tags=["python", "import"]
|
|
116
|
+
),
|
|
117
|
+
ErrorPattern(
|
|
118
|
+
id="python_syntax_error",
|
|
119
|
+
name="Syntax Error",
|
|
120
|
+
pattern=r"SyntaxError: (.+)",
|
|
121
|
+
severity=ErrorSeverity.HIGH,
|
|
122
|
+
fix_type=FixType.SYNTAX_FIX,
|
|
123
|
+
description="Python syntax error",
|
|
124
|
+
fix_template="Check syntax in file",
|
|
125
|
+
auto_fixable=False,
|
|
126
|
+
tags=["python", "syntax"]
|
|
127
|
+
),
|
|
128
|
+
ErrorPattern(
|
|
129
|
+
id="python_indentation_error",
|
|
130
|
+
name="Indentation Error",
|
|
131
|
+
pattern=r"IndentationError: (.+)",
|
|
132
|
+
severity=ErrorSeverity.HIGH,
|
|
133
|
+
fix_type=FixType.SYNTAX_FIX,
|
|
134
|
+
description="Python indentation error",
|
|
135
|
+
fix_template="Fix indentation",
|
|
136
|
+
auto_fixable=False,
|
|
137
|
+
tags=["python", "indentation"]
|
|
138
|
+
),
|
|
139
|
+
ErrorPattern(
|
|
140
|
+
id="python_attribute_error",
|
|
141
|
+
name="Attribute Error",
|
|
142
|
+
pattern=r"AttributeError: '(\w+)' object has no attribute '(\w+)'",
|
|
143
|
+
severity=ErrorSeverity.MEDIUM,
|
|
144
|
+
fix_type=FixType.FILE_EDIT,
|
|
145
|
+
description="Object missing attribute",
|
|
146
|
+
fix_template="Check if attribute exists or object type is correct",
|
|
147
|
+
auto_fixable=False,
|
|
148
|
+
tags=["python", "attribute"]
|
|
149
|
+
),
|
|
150
|
+
ErrorPattern(
|
|
151
|
+
id="python_type_error",
|
|
152
|
+
name="Type Error",
|
|
153
|
+
pattern=r"TypeError: (.+)",
|
|
154
|
+
severity=ErrorSeverity.MEDIUM,
|
|
155
|
+
fix_type=FixType.FILE_EDIT,
|
|
156
|
+
description="Type mismatch",
|
|
157
|
+
fix_template="Check types and conversions",
|
|
158
|
+
auto_fixable=False,
|
|
159
|
+
tags=["python", "type"]
|
|
160
|
+
),
|
|
161
|
+
ErrorPattern(
|
|
162
|
+
id="python_index_error",
|
|
163
|
+
name="Index Error",
|
|
164
|
+
pattern=r"IndexError: (.+)",
|
|
165
|
+
severity=ErrorSeverity.MEDIUM,
|
|
166
|
+
fix_type=FixType.FILE_EDIT,
|
|
167
|
+
description="List index out of range",
|
|
168
|
+
fix_template="Check list length before indexing",
|
|
169
|
+
auto_fixable=False,
|
|
170
|
+
tags=["python", "index"]
|
|
171
|
+
),
|
|
172
|
+
ErrorPattern(
|
|
173
|
+
id="python_key_error",
|
|
174
|
+
name="Key Error",
|
|
175
|
+
pattern=r"KeyError: '([^']+)'",
|
|
176
|
+
severity=ErrorSeverity.MEDIUM,
|
|
177
|
+
fix_type=FixType.FILE_EDIT,
|
|
178
|
+
description="Dictionary key not found",
|
|
179
|
+
fix_template="Check if key exists: if '{key}' in dict:",
|
|
180
|
+
auto_fixable=False,
|
|
181
|
+
tags=["python", "dict"]
|
|
182
|
+
),
|
|
183
|
+
ErrorPattern(
|
|
184
|
+
id="python_file_not_found",
|
|
185
|
+
name="File Not Found",
|
|
186
|
+
pattern=r"FileNotFoundError: \[Errno 2\] No such file or directory: '([^']+)'",
|
|
187
|
+
severity=ErrorSeverity.HIGH,
|
|
188
|
+
fix_type=FixType.FILE_EDIT,
|
|
189
|
+
description="File does not exist",
|
|
190
|
+
fix_template="Create file or check path",
|
|
191
|
+
auto_fixable=False,
|
|
192
|
+
tags=["python", "file"]
|
|
193
|
+
),
|
|
194
|
+
|
|
195
|
+
# Node.js errors
|
|
196
|
+
ErrorPattern(
|
|
197
|
+
id="node_module_not_found",
|
|
198
|
+
name="Node Module Not Found",
|
|
199
|
+
pattern=r"Error: Cannot find module '([^']+)'",
|
|
200
|
+
severity=ErrorSeverity.HIGH,
|
|
201
|
+
fix_type=FixType.PACKAGE_INSTALL,
|
|
202
|
+
description="Missing Node.js module",
|
|
203
|
+
fix_template="npm install {module}",
|
|
204
|
+
fix_command="npm install {module}",
|
|
205
|
+
auto_fixable=True,
|
|
206
|
+
tags=["nodejs", "import"]
|
|
207
|
+
),
|
|
208
|
+
ErrorPattern(
|
|
209
|
+
id="node_syntax_error",
|
|
210
|
+
name="JavaScript Syntax Error",
|
|
211
|
+
pattern=r"SyntaxError: (.+)",
|
|
212
|
+
severity=ErrorSeverity.HIGH,
|
|
213
|
+
fix_type=FixType.SYNTAX_FIX,
|
|
214
|
+
description="JavaScript syntax error",
|
|
215
|
+
fix_template="Check syntax",
|
|
216
|
+
auto_fixable=False,
|
|
217
|
+
tags=["nodejs", "syntax"]
|
|
218
|
+
),
|
|
219
|
+
|
|
220
|
+
# Git errors
|
|
221
|
+
ErrorPattern(
|
|
222
|
+
id="git_not_a_repository",
|
|
223
|
+
name="Not a Git Repository",
|
|
224
|
+
pattern=r"fatal: not a git repository",
|
|
225
|
+
severity=ErrorSeverity.HIGH,
|
|
226
|
+
fix_type=FixType.COMMAND_FIX,
|
|
227
|
+
description="Not in a git repository",
|
|
228
|
+
fix_template="git init",
|
|
229
|
+
fix_command="git init",
|
|
230
|
+
auto_fixable=True,
|
|
231
|
+
tags=["git"]
|
|
232
|
+
),
|
|
233
|
+
ErrorPattern(
|
|
234
|
+
id="git_merge_conflict",
|
|
235
|
+
name="Git Merge Conflict",
|
|
236
|
+
pattern=r"CONFLICT \\(content\\): Merge conflict in (.+)",
|
|
237
|
+
severity=ErrorSeverity.HIGH,
|
|
238
|
+
fix_type=FixType.FILE_EDIT,
|
|
239
|
+
description="Merge conflict in file",
|
|
240
|
+
fix_template="Resolve conflicts in {file}",
|
|
241
|
+
auto_fixable=False,
|
|
242
|
+
tags=["git", "merge"]
|
|
243
|
+
),
|
|
244
|
+
ErrorPattern(
|
|
245
|
+
id="git_uncommitted_changes",
|
|
246
|
+
name="Uncommitted Changes",
|
|
247
|
+
pattern=r"error: Your local changes to the following files would be overwritten",
|
|
248
|
+
severity=ErrorSeverity.MEDIUM,
|
|
249
|
+
fix_type=FixType.COMMAND_FIX,
|
|
250
|
+
description="Uncommitted local changes",
|
|
251
|
+
fix_template="git stash or commit changes",
|
|
252
|
+
auto_fixable=False,
|
|
253
|
+
tags=["git"]
|
|
254
|
+
),
|
|
255
|
+
|
|
256
|
+
# Permission errors
|
|
257
|
+
ErrorPattern(
|
|
258
|
+
id="permission_denied",
|
|
259
|
+
name="Permission Denied",
|
|
260
|
+
pattern=r"Permission denied: '([^']+)'|EACCES: permission denied",
|
|
261
|
+
severity=ErrorSeverity.HIGH,
|
|
262
|
+
fix_type=FixType.PERMISSION_FIX,
|
|
263
|
+
description="Permission denied",
|
|
264
|
+
fix_template="chmod +x {file} or run with sudo",
|
|
265
|
+
fix_command="chmod +x {file}",
|
|
266
|
+
auto_fixable=True,
|
|
267
|
+
tags=["permission"]
|
|
268
|
+
),
|
|
269
|
+
|
|
270
|
+
# Command not found
|
|
271
|
+
ErrorPattern(
|
|
272
|
+
id="command_not_found",
|
|
273
|
+
name="Command Not Found",
|
|
274
|
+
pattern=r"command not found: (\w+)|'(\w+)' is not recognized",
|
|
275
|
+
severity=ErrorSeverity.HIGH,
|
|
276
|
+
fix_type=FixType.ENVIRONMENT,
|
|
277
|
+
description="Command not installed or not in PATH",
|
|
278
|
+
fix_template="Install {command} or check PATH",
|
|
279
|
+
auto_fixable=False,
|
|
280
|
+
tags=["environment", "command"]
|
|
281
|
+
),
|
|
282
|
+
|
|
283
|
+
# Network errors
|
|
284
|
+
ErrorPattern(
|
|
285
|
+
id="connection_refused",
|
|
286
|
+
name="Connection Refused",
|
|
287
|
+
pattern=r"Connection refused|ECONNREFUSED",
|
|
288
|
+
severity=ErrorSeverity.MEDIUM,
|
|
289
|
+
fix_type=FixType.ENVIRONMENT,
|
|
290
|
+
description="Cannot connect to server",
|
|
291
|
+
fix_template="Check if service is running",
|
|
292
|
+
auto_fixable=False,
|
|
293
|
+
tags=["network"]
|
|
294
|
+
),
|
|
295
|
+
ErrorPattern(
|
|
296
|
+
id="dns_resolution",
|
|
297
|
+
name="DNS Resolution Failed",
|
|
298
|
+
pattern=r"getaddrinfo ENOTFOUND|Temporary failure in name resolution",
|
|
299
|
+
severity=ErrorSeverity.MEDIUM,
|
|
300
|
+
fix_type=FixType.ENVIRONMENT,
|
|
301
|
+
description="DNS resolution failed",
|
|
302
|
+
fix_template="Check internet connection or DNS settings",
|
|
303
|
+
auto_fixable=False,
|
|
304
|
+
tags=["network", "dns"]
|
|
305
|
+
),
|
|
306
|
+
|
|
307
|
+
# Build errors
|
|
308
|
+
ErrorPattern(
|
|
309
|
+
id="missing_make_target",
|
|
310
|
+
name="Missing Make Target",
|
|
311
|
+
pattern=r"make: \*\*\* No rule to make target '([^']+)'.",
|
|
312
|
+
severity=ErrorSeverity.MEDIUM,
|
|
313
|
+
fix_type=FixType.FILE_EDIT,
|
|
314
|
+
description="Makefile missing target",
|
|
315
|
+
fix_template="Add target to Makefile or check spelling",
|
|
316
|
+
auto_fixable=False,
|
|
317
|
+
tags=["build", "make"]
|
|
318
|
+
),
|
|
319
|
+
]
|
|
320
|
+
|
|
321
|
+
def __init__(self, project_dir: Optional[Path] = None):
|
|
322
|
+
self.project_dir = project_dir or Path.cwd()
|
|
323
|
+
|
|
324
|
+
# Patrones de error (builtins + aprendidos)
|
|
325
|
+
self._patterns: Dict[str, ErrorPattern] = {p.id: p for p in self.BUILTIN_PATTERNS}
|
|
326
|
+
self._learned_patterns: Dict[str, ErrorPattern] = {}
|
|
327
|
+
|
|
328
|
+
# Historial de errores del proyecto
|
|
329
|
+
self._error_history: List[Dict] = []
|
|
330
|
+
|
|
331
|
+
# Base de conocimiento de fixes aplicados
|
|
332
|
+
self._fixes_applied: Dict[str, int] = {} # error_id -> count
|
|
333
|
+
|
|
334
|
+
# Archivos de persistencia
|
|
335
|
+
self._learned_file = Path.home() / ".hanus" / "error_recovery" / "learned_patterns.json"
|
|
336
|
+
self._history_file = Path.home() / ".hanus" / "error_recovery" / "history.json"
|
|
337
|
+
self._load_learned()
|
|
338
|
+
|
|
339
|
+
def analyze_error(self, output: str) -> Optional[FixSuggestion]:
|
|
340
|
+
"""
|
|
341
|
+
Analiza la salida de un comando para detectar errores y sugerir fixes.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
output: Salida del comando (stdout + stderr)
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
FixSuggestion si se encontró un error conocido, None si no
|
|
348
|
+
"""
|
|
349
|
+
# Buscar en patrones builtin y aprendidos
|
|
350
|
+
all_patterns = {**self._patterns, **self._learned_patterns}
|
|
351
|
+
|
|
352
|
+
for pattern in all_patterns.values():
|
|
353
|
+
match = re.search(pattern.pattern, output, re.IGNORECASE | re.MULTILINE)
|
|
354
|
+
if match:
|
|
355
|
+
suggestion = self._create_suggestion(pattern, match, output)
|
|
356
|
+
self._record_error(suggestion)
|
|
357
|
+
return suggestion
|
|
358
|
+
|
|
359
|
+
# Si no encontramos patrón, intentar análisis genérico
|
|
360
|
+
generic = self._generic_error_analysis(output)
|
|
361
|
+
if generic:
|
|
362
|
+
self._record_error(generic)
|
|
363
|
+
return generic
|
|
364
|
+
|
|
365
|
+
return None
|
|
366
|
+
|
|
367
|
+
def apply_fix(self, suggestion: FixSuggestion) -> Tuple[bool, str]:
|
|
368
|
+
"""
|
|
369
|
+
Aplica un fix sugerido.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
suggestion: La sugerencia de fix
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
(success, message)
|
|
376
|
+
"""
|
|
377
|
+
if not suggestion.auto_apply and suggestion.requires_confirmation:
|
|
378
|
+
return False, "This fix requires confirmation before applying"
|
|
379
|
+
|
|
380
|
+
if suggestion.fix_type == FixType.PACKAGE_INSTALL and suggestion.fix_command:
|
|
381
|
+
# Registrar fix aplicado
|
|
382
|
+
self._fixes_applied[suggestion.error_id] = self._fixes_applied.get(suggestion.error_id, 0) + 1
|
|
383
|
+
return True, f"Run: {suggestion.fix_command}"
|
|
384
|
+
|
|
385
|
+
if suggestion.fix_type == FixType.COMMAND_FIX and suggestion.fix_command:
|
|
386
|
+
self._fixes_applied[suggestion.error_id] = self._fixes_applied.get(suggestion.error_id, 0) + 1
|
|
387
|
+
return True, f"Run: {suggestion.fix_command}"
|
|
388
|
+
|
|
389
|
+
if suggestion.fix_type == FixType.FILE_EDIT and suggestion.fix_file:
|
|
390
|
+
self._fixes_applied[suggestion.error_id] = self._fixes_applied.get(suggestion.error_id, 0) + 1
|
|
391
|
+
return True, f"Edit {suggestion.fix_file}: {suggestion.description}"
|
|
392
|
+
|
|
393
|
+
return False, "No automatic fix available"
|
|
394
|
+
|
|
395
|
+
def learn_from_fix(self, error_message: str, fix_description: str, fix_command: str = None):
|
|
396
|
+
"""
|
|
397
|
+
Aprende de un fix aplicado para futuras sugerencias.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
error_message: El mensaje de error original
|
|
401
|
+
fix_description: Descripción del fix aplicado
|
|
402
|
+
fix_command: Comando ejecutado para arreglar (si aplica)
|
|
403
|
+
"""
|
|
404
|
+
# Crear patrón simple
|
|
405
|
+
pattern_id = f"learned_{int(time.time())}"
|
|
406
|
+
|
|
407
|
+
# Extraer patrón del error
|
|
408
|
+
# Simplificar el mensaje para crear un patrón regex básico
|
|
409
|
+
simplified = re.escape(error_message[:50])
|
|
410
|
+
simplified = re.sub(r'\\s+', r'\\s+', simplified)
|
|
411
|
+
|
|
412
|
+
pattern = ErrorPattern(
|
|
413
|
+
id=pattern_id,
|
|
414
|
+
name=f"Learned: {fix_description[:30]}",
|
|
415
|
+
pattern=simplified,
|
|
416
|
+
severity=ErrorSeverity.MEDIUM,
|
|
417
|
+
fix_type=FixType.COMMAND_FIX if fix_command else FixType.FILE_EDIT,
|
|
418
|
+
description=fix_description,
|
|
419
|
+
fix_template=fix_description,
|
|
420
|
+
fix_command=fix_command,
|
|
421
|
+
auto_fixable=bool(fix_command),
|
|
422
|
+
tags=["learned"]
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
self._learned_patterns[pattern_id] = pattern
|
|
426
|
+
self._save_learned()
|
|
427
|
+
|
|
428
|
+
def get_error_stats(self) -> Dict[str, Any]:
|
|
429
|
+
"""Obtiene estadísticas de errores."""
|
|
430
|
+
return {
|
|
431
|
+
"total_errors": len(self._error_history),
|
|
432
|
+
"patterns_known": len(self._patterns) + len(self._learned_patterns),
|
|
433
|
+
"learned_patterns": len(self._learned_patterns),
|
|
434
|
+
"fixes_applied": sum(self._fixes_applied.values()),
|
|
435
|
+
"top_errors": sorted(
|
|
436
|
+
self._fixes_applied.items(),
|
|
437
|
+
key=lambda x: -x[1]
|
|
438
|
+
)[:5],
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
def get_error_history(self, limit: int = 50) -> List[Dict]:
|
|
442
|
+
"""Obtiene el historial de errores."""
|
|
443
|
+
return self._error_history[-limit:]
|
|
444
|
+
|
|
445
|
+
# ══════════════════════════════════════════════════════════════════════════
|
|
446
|
+
# MÉTODOS PRIVADOS
|
|
447
|
+
# ══════════════════════════════════════════════════════════════════════════
|
|
448
|
+
|
|
449
|
+
def _create_suggestion(
|
|
450
|
+
self,
|
|
451
|
+
pattern: ErrorPattern,
|
|
452
|
+
match: re.Match,
|
|
453
|
+
output: str
|
|
454
|
+
) -> FixSuggestion:
|
|
455
|
+
"""Crea una sugerencia de fix a partir de un patrón encontrado."""
|
|
456
|
+
|
|
457
|
+
# Extraer grupos del match para personalizar el fix
|
|
458
|
+
groups = match.groups() if match.groups() else []
|
|
459
|
+
group_dict = match.groupdict() if hasattr(match, 'groupdict') else {}
|
|
460
|
+
|
|
461
|
+
# Personalizar fix con grupos capturados
|
|
462
|
+
fix_command = pattern.fix_command
|
|
463
|
+
fix_template = pattern.fix_template
|
|
464
|
+
|
|
465
|
+
if groups:
|
|
466
|
+
# Primer grupo como variable principal
|
|
467
|
+
main_var = groups[0] if groups else ""
|
|
468
|
+
if fix_command:
|
|
469
|
+
fix_command = fix_command.format(module=main_var, file=main_var, command=main_var, key=main_var)
|
|
470
|
+
fix_template = fix_template.format(module=main_var, file=main_var, command=main_var, key=main_var)
|
|
471
|
+
|
|
472
|
+
# Calcular confianza basada en historial
|
|
473
|
+
confidence = 0.5
|
|
474
|
+
if pattern.id in self._fixes_applied:
|
|
475
|
+
# Más confianza si el fix se ha aplicado antes con éxito
|
|
476
|
+
confidence = min(0.95, 0.5 + self._fixes_applied[pattern.id] * 0.1)
|
|
477
|
+
|
|
478
|
+
return FixSuggestion(
|
|
479
|
+
error_id=pattern.id,
|
|
480
|
+
error_message=match.group(0),
|
|
481
|
+
error_type=pattern.name,
|
|
482
|
+
severity=pattern.severity,
|
|
483
|
+
fix_type=pattern.fix_type,
|
|
484
|
+
description=fix_template,
|
|
485
|
+
fix_command=fix_command,
|
|
486
|
+
confidence=confidence,
|
|
487
|
+
auto_apply=pattern.auto_fixable and confidence > 0.7,
|
|
488
|
+
requires_confirmation=not pattern.auto_fixable,
|
|
489
|
+
metadata={"pattern": pattern.id, "groups": groups}
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
def _generic_error_analysis(self, output: str) -> Optional[FixSuggestion]:
|
|
493
|
+
"""Análisis genérico cuando no hay patrón conocido."""
|
|
494
|
+
# Buscar palabras clave de error
|
|
495
|
+
error_keywords = ["error", "failed", "exception", "fatal", "critical"]
|
|
496
|
+
|
|
497
|
+
for keyword in error_keywords:
|
|
498
|
+
if keyword in output.lower():
|
|
499
|
+
# Encontrar la línea con el error
|
|
500
|
+
lines = output.split('\n')
|
|
501
|
+
error_line = None
|
|
502
|
+
for line in lines:
|
|
503
|
+
if keyword in line.lower():
|
|
504
|
+
error_line = line.strip()
|
|
505
|
+
break
|
|
506
|
+
|
|
507
|
+
if error_line:
|
|
508
|
+
return FixSuggestion(
|
|
509
|
+
error_id="generic",
|
|
510
|
+
error_message=error_line[:200],
|
|
511
|
+
error_type="Generic Error",
|
|
512
|
+
severity=ErrorSeverity.MEDIUM,
|
|
513
|
+
fix_type=FixType.FILE_EDIT,
|
|
514
|
+
description="Review error and fix manually",
|
|
515
|
+
confidence=0.3,
|
|
516
|
+
auto_apply=False,
|
|
517
|
+
requires_confirmation=True,
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
return None
|
|
521
|
+
|
|
522
|
+
def _record_error(self, suggestion: FixSuggestion) -> None:
|
|
523
|
+
"""Registra un error en el historial."""
|
|
524
|
+
self._error_history.append({
|
|
525
|
+
"timestamp": time.time(),
|
|
526
|
+
"error_id": suggestion.error_id,
|
|
527
|
+
"error_type": suggestion.error_type,
|
|
528
|
+
"error_message": suggestion.error_message[:200],
|
|
529
|
+
"severity": suggestion.severity.value,
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
# Limitar historial
|
|
533
|
+
if len(self._error_history) > 1000:
|
|
534
|
+
self._error_history = self._error_history[-500:]
|
|
535
|
+
|
|
536
|
+
def _load_learned(self) -> None:
|
|
537
|
+
"""Carga patrones aprendidos desde disco."""
|
|
538
|
+
if self._learned_file.exists():
|
|
539
|
+
try:
|
|
540
|
+
data = json.loads(self._learned_file.read_text(encoding="utf-8"))
|
|
541
|
+
for pattern_data in data.get("patterns", []):
|
|
542
|
+
pattern = ErrorPattern(
|
|
543
|
+
id=pattern_data["id"],
|
|
544
|
+
name=pattern_data["name"],
|
|
545
|
+
pattern=pattern_data["pattern"],
|
|
546
|
+
severity=ErrorSeverity(pattern_data["severity"]),
|
|
547
|
+
fix_type=FixType(pattern_data["fix_type"]),
|
|
548
|
+
description=pattern_data["description"],
|
|
549
|
+
fix_template=pattern_data["fix_template"],
|
|
550
|
+
fix_command=pattern_data.get("fix_command"),
|
|
551
|
+
auto_fixable=pattern_data.get("auto_fixable", False),
|
|
552
|
+
tags=pattern_data.get("tags", []),
|
|
553
|
+
)
|
|
554
|
+
self._learned_patterns[pattern.id] = pattern
|
|
555
|
+
|
|
556
|
+
self._fixes_applied = data.get("fixes_applied", {})
|
|
557
|
+
except Exception:
|
|
558
|
+
pass
|
|
559
|
+
|
|
560
|
+
def _save_learned(self) -> None:
|
|
561
|
+
"""Guarda patrones aprendidos a disco."""
|
|
562
|
+
try:
|
|
563
|
+
self._learned_file.parent.mkdir(parents=True, exist_ok=True)
|
|
564
|
+
|
|
565
|
+
data = {
|
|
566
|
+
"patterns": [
|
|
567
|
+
{
|
|
568
|
+
"id": p.id,
|
|
569
|
+
"name": p.name,
|
|
570
|
+
"pattern": p.pattern,
|
|
571
|
+
"severity": p.severity.value,
|
|
572
|
+
"fix_type": p.fix_type.value,
|
|
573
|
+
"description": p.description,
|
|
574
|
+
"fix_template": p.fix_template,
|
|
575
|
+
"fix_command": p.fix_command,
|
|
576
|
+
"auto_fixable": p.auto_fixable,
|
|
577
|
+
"tags": p.tags,
|
|
578
|
+
}
|
|
579
|
+
for p in self._learned_patterns.values()
|
|
580
|
+
],
|
|
581
|
+
"fixes_applied": self._fixes_applied,
|
|
582
|
+
"updated": datetime.now().isoformat(),
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
self._learned_file.write_text(
|
|
586
|
+
json.dumps(data, indent=2, ensure_ascii=False),
|
|
587
|
+
encoding="utf-8"
|
|
588
|
+
)
|
|
589
|
+
except Exception:
|
|
590
|
+
pass
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
594
|
+
# INSTANCIA GLOBAL
|
|
595
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
596
|
+
|
|
597
|
+
_recovery_instance: Optional[ErrorRecovery] = None
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def get_error_recovery() -> ErrorRecovery:
|
|
601
|
+
"""Obtiene la instancia global del sistema de recuperación."""
|
|
602
|
+
global _recovery_instance
|
|
603
|
+
if _recovery_instance is None:
|
|
604
|
+
_recovery_instance = ErrorRecovery()
|
|
605
|
+
return _recovery_instance
|