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.
Files changed (93) hide show
  1. hanus/__init__.py +5 -0
  2. hanus/__main__.py +10 -0
  3. hanus/action_handlers.py +76 -0
  4. hanus/action_parser.py +82 -0
  5. hanus/agent_runner.py +1445 -0
  6. hanus/analysis/__init__.py +5 -0
  7. hanus/analysis/debt.py +702 -0
  8. hanus/analysis/dependencies.py +475 -0
  9. hanus/cache/__init__.py +5 -0
  10. hanus/cache/response_cache.py +560 -0
  11. hanus/config.py +401 -0
  12. hanus/connectors/__init__.py +19 -0
  13. hanus/connectors/base.py +114 -0
  14. hanus/connectors/claude_connector.py +146 -0
  15. hanus/connectors/gemini_connector.py +141 -0
  16. hanus/connectors/glm_connector.py +160 -0
  17. hanus/connectors/ollama_connector.py +174 -0
  18. hanus/connectors/openai_connector.py +122 -0
  19. hanus/connectors/registry.py +26 -0
  20. hanus/context/__init__.py +7 -0
  21. hanus/context/manager.py +837 -0
  22. hanus/context/selective.py +626 -0
  23. hanus/error_recovery/__init__.py +5 -0
  24. hanus/error_recovery/auto_fix.py +605 -0
  25. hanus/hooks/__init__.py +5 -0
  26. hanus/hooks/manager.py +247 -0
  27. hanus/instincts/__init__.py +44 -0
  28. hanus/instincts/cli.py +372 -0
  29. hanus/instincts/detector.py +281 -0
  30. hanus/instincts/evolver.py +361 -0
  31. hanus/instincts/manager.py +343 -0
  32. hanus/instincts/types.py +253 -0
  33. hanus/logger.py +81 -0
  34. hanus/memory/__init__.py +8 -0
  35. hanus/memory/manager.py +265 -0
  36. hanus/memory/types.py +119 -0
  37. hanus/monitor.py +341 -0
  38. hanus/parallel/__init__.py +5 -0
  39. hanus/parallel/executor.py +300 -0
  40. hanus/permissions.py +182 -0
  41. hanus/plan/__init__.py +8 -0
  42. hanus/plan/mode.py +267 -0
  43. hanus/plan/models.py +152 -0
  44. hanus/plugin_manager.py +754 -0
  45. hanus/plugin_registry.py +391 -0
  46. hanus/plugins/__init__.py +1 -0
  47. hanus/plugins/arena.py +630 -0
  48. hanus/plugins/code_review.py +123 -0
  49. hanus/plugins/cortex.py +1750 -0
  50. hanus/plugins/deps_check.py +27 -0
  51. hanus/plugins/git_ops.py +33 -0
  52. hanus/plugins/metasploit.py +530 -0
  53. hanus/plugins/notes.py +583 -0
  54. hanus/plugins/search_code.py +59 -0
  55. hanus/plugins/searchsploit.py +495 -0
  56. hanus/plugins/strategist.py +175 -0
  57. hanus/plugins/webui.py +5200 -0
  58. hanus/profiles.py +479 -0
  59. hanus/profiles_builtin/__init__.py +0 -0
  60. hanus/profiles_builtin/architect/profile.yaml +12 -0
  61. hanus/profiles_builtin/architect/system_prompt.txt +71 -0
  62. hanus/profiles_builtin/deep/profile.yaml +12 -0
  63. hanus/profiles_builtin/deep/system_prompt.txt +66 -0
  64. hanus/profiles_builtin/developer/__init__.py +0 -0
  65. hanus/profiles_builtin/developer/profile.yaml +9 -0
  66. hanus/profiles_builtin/developer/system_prompt.txt +176 -0
  67. hanus/profiles_builtin/speed/profile.yaml +12 -0
  68. hanus/profiles_builtin/speed/system_prompt.txt +51 -0
  69. hanus/project_tools.py +177 -0
  70. hanus/query_engine.py +1594 -0
  71. hanus/rules/__init__.py +237 -0
  72. hanus/search/__init__.py +5 -0
  73. hanus/search/semantic.py +596 -0
  74. hanus/session_manager.py +547 -0
  75. hanus/skill_manager.py +702 -0
  76. hanus/skills/__init__.py +4 -0
  77. hanus/subagent/__init__.py +8 -0
  78. hanus/subagent/agents/__init__.py +253 -0
  79. hanus/subagent/manager.py +309 -0
  80. hanus/subagent/types.py +266 -0
  81. hanus/suggestions/__init__.py +5 -0
  82. hanus/suggestions/proactive.py +451 -0
  83. hanus/tasks/__init__.py +8 -0
  84. hanus/tasks/manager.py +330 -0
  85. hanus/tasks/models.py +106 -0
  86. hanus/terminal_prompt.py +166 -0
  87. hanus/tools.py +1849 -0
  88. hanus/ui.py +939 -0
  89. hanuscode-1.0.0.dist-info/METADATA +1151 -0
  90. hanuscode-1.0.0.dist-info/RECORD +93 -0
  91. hanuscode-1.0.0.dist-info/WHEEL +5 -0
  92. hanuscode-1.0.0.dist-info/entry_points.txt +2 -0
  93. hanuscode-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,451 @@
1
+ # hanus/suggestions/proactive.py
2
+ """
3
+ Sistema de sugerencias proactivas.
4
+
5
+ Analiza código durante operaciones de lectura y sugiere mejoras
6
+ sin ser solicitado explícitamente.
7
+ """
8
+ from __future__ import annotations
9
+ import re
10
+ import ast
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+ from typing import Dict, List, Optional, Any, Set, Tuple
14
+ from enum import Enum
15
+ from collections import defaultdict
16
+
17
+
18
+ class SuggestionType(Enum):
19
+ """Tipo de sugerencia."""
20
+ UNUSED_IMPORT = "unused_import"
21
+ CODE_DUPLICATION = "code_duplication"
22
+ MISSING_DOCSTRING = "missing_docstring"
23
+ COMPLEX_FUNCTION = "complex_function"
24
+ NAMING_CONVENTION = "naming_convention"
25
+ SECURITY_ISSUE = "security_issue"
26
+ PERFORMANCE = "performance"
27
+ DEPRECATED_USAGE = "deprecated_usage"
28
+ MISSING_TYPE_HINT = "missing_type_hint"
29
+ TODO_COMMENT = "todo_comment"
30
+ HARDCODED_VALUE = "hardcoded_value"
31
+ ERROR_HANDLING = "error_handling"
32
+
33
+
34
+ class SuggestionPriority(Enum):
35
+ """Prioridad de la sugerencia."""
36
+ LOW = 1 # Nice to have
37
+ MEDIUM = 2 # Should fix
38
+ HIGH = 3 # Important
39
+ CRITICAL = 4 # Security or correctness issue
40
+
41
+
42
+ @dataclass
43
+ class Suggestion:
44
+ """Una sugerencia proactiva."""
45
+ id: str
46
+ type: SuggestionType
47
+ priority: SuggestionPriority
48
+ title: str
49
+ description: str
50
+ file_path: str
51
+ line_start: int
52
+ line_end: int
53
+ suggestion: str # Código sugerido o acción
54
+ auto_fixable: bool = False
55
+ fix_template: Optional[str] = None # Template para auto-fix
56
+ metadata: Dict[str, Any] = field(default_factory=dict)
57
+
58
+ def to_dict(self) -> Dict:
59
+ return {
60
+ "id": self.id,
61
+ "type": self.type.value,
62
+ "priority": self.priority.value,
63
+ "title": self.title,
64
+ "description": self.description,
65
+ "file_path": self.file_path,
66
+ "line_start": self.line_start,
67
+ "line_end": self.line_end,
68
+ "suggestion": self.suggestion,
69
+ "auto_fixable": self.auto_fixable,
70
+ "fix_template": self.fix_template,
71
+ }
72
+
73
+
74
+ class ProactiveSuggester:
75
+ """
76
+ Analiza código y genera sugerencias proactivas.
77
+
78
+ Características:
79
+ - Detección de imports sin usar
80
+ - Detección de código duplicado
81
+ - Detección de problemas de seguridad
82
+ - Detección de complejidad excesiva
83
+ - Detección de convenciones de nombre
84
+ """
85
+
86
+ def __init__(self, proactivity_level: str = "normal"):
87
+ """
88
+ Args:
89
+ proactivity_level: "minimal", "normal", or "aggressive"
90
+ """
91
+ self.proactivity_level = proactivity_level
92
+ self._seen_files: Set[str] = set()
93
+ self._all_imports: Dict[str, Set[str]] = {} # file -> imports
94
+ self._all_functions: Dict[str, List[str]] = {} # file -> function names
95
+ self._suggestion_counter = 0
96
+
97
+ def analyze_file(
98
+ self,
99
+ file_path: str,
100
+ content: str
101
+ ) -> List[Suggestion]:
102
+ """
103
+ Analiza un archivo y genera sugerencias.
104
+
105
+ Args:
106
+ file_path: Ruta del archivo
107
+ content: Contenido del archivo
108
+
109
+ Returns:
110
+ Lista de sugerencias encontradas
111
+ """
112
+ suggestions = []
113
+ ext = Path(file_path).suffix.lower()
114
+
115
+ # Solo analizar archivos de código
116
+ if ext not in (".py", ".js", ".ts", ".jsx", ".tsx", ".java", ".go"):
117
+ return suggestions
118
+
119
+ self._seen_files.add(file_path)
120
+
121
+ if ext == ".py":
122
+ suggestions.extend(self._analyze_python(file_path, content))
123
+ elif ext in (".js", ".ts", ".jsx", ".tsx"):
124
+ suggestions.extend(self._analyze_javascript(file_path, content))
125
+
126
+ return suggestions
127
+
128
+ def get_summary(self) -> Dict[str, Any]:
129
+ """Obtiene un resumen del análisis."""
130
+ return {
131
+ "files_analyzed": len(self._seen_files),
132
+ "total_imports": sum(len(i) for i in self._all_imports.values()),
133
+ "total_functions": sum(len(f) for f in self._all_functions.values()),
134
+ }
135
+
136
+ # ══════════════════════════════════════════════════════════════════════════
137
+ # ANÁLISIS PYTHON
138
+ # ══════════════════════════════════════════════════════════════════════════
139
+
140
+ def _analyze_python(self, file_path: str, content: str) -> List[Suggestion]:
141
+ """Analiza código Python."""
142
+ suggestions = []
143
+ lines = content.split('\n')
144
+
145
+ try:
146
+ tree = ast.parse(content)
147
+ except SyntaxError:
148
+ # Si hay error de sintaxis, no analizar
149
+ return suggestions
150
+
151
+ # Analizar imports
152
+ imports = set()
153
+ import_nodes = []
154
+
155
+ for node in ast.walk(tree):
156
+ if isinstance(node, ast.Import):
157
+ for alias in node.names:
158
+ name = alias.asname if alias.asname else alias.name
159
+ imports.add(name.split('.')[0])
160
+ import_nodes.append((node.lineno, name))
161
+
162
+ elif isinstance(node, ast.ImportFrom):
163
+ module = node.module or ""
164
+ for alias in node.names:
165
+ name = alias.asname if alias.asname else alias.name
166
+ imports.add(name)
167
+ import_nodes.append((node.lineno, name))
168
+
169
+ self._all_imports[file_path] = imports
170
+
171
+ # Verificar imports usados
172
+ used_names = set()
173
+ for node in ast.walk(tree):
174
+ if isinstance(node, ast.Name):
175
+ used_names.add(node.id)
176
+ elif isinstance(node, ast.Attribute):
177
+ if isinstance(node.value, ast.Name):
178
+ used_names.add(node.value.id)
179
+
180
+ unused = imports - used_names - {'__future__'}
181
+ if unused and self._should_suggest("unused_import"):
182
+ for line, imp in import_nodes:
183
+ if imp in unused or imp.split('.')[0] in unused:
184
+ suggestions.append(self._create_suggestion(
185
+ SuggestionType.UNUSED_IMPORT,
186
+ SuggestionPriority.LOW,
187
+ "Unused Import",
188
+ f"Import '{imp}' is not used in this file",
189
+ file_path, line, line,
190
+ f"Remove: import {imp}",
191
+ auto_fixable=True,
192
+ fix_template=f"Remove line {line}"
193
+ ))
194
+
195
+ # Analizar funciones
196
+ for node in ast.walk(tree):
197
+ if isinstance(node, ast.FunctionDef):
198
+ # Verificar docstring
199
+ if not ast.get_docstring(node):
200
+ if self._should_suggest("missing_docstring"):
201
+ suggestions.append(self._create_suggestion(
202
+ SuggestionType.MISSING_DOCSTRING,
203
+ SuggestionPriority.LOW,
204
+ "Missing Docstring",
205
+ f"Function '{node.name}' has no docstring",
206
+ file_path, node.lineno, node.lineno,
207
+ f'Add docstring: """Description"""',
208
+ ))
209
+
210
+ # Verificar complejidad
211
+ complexity = self._calculate_complexity(node)
212
+ if complexity > 10 and self._should_suggest("complex_function"):
213
+ suggestions.append(self._create_suggestion(
214
+ SuggestionType.COMPLEX_FUNCTION,
215
+ SuggestionPriority.MEDIUM,
216
+ "Complex Function",
217
+ f"Function '{node.name}' has high complexity ({complexity})",
218
+ file_path, node.lineno, node.end_lineno or node.lineno,
219
+ "Consider breaking into smaller functions",
220
+ ))
221
+
222
+ # Verificar type hints
223
+ if not node.returns and self._should_suggest("missing_type_hint"):
224
+ suggestions.append(self._create_suggestion(
225
+ SuggestionType.MISSING_TYPE_HINT,
226
+ SuggestionPriority.LOW,
227
+ "Missing Return Type",
228
+ f"Function '{node.name}' has no return type hint",
229
+ file_path, node.lineno, node.lineno,
230
+ "Add return type hint: -> ReturnType",
231
+ ))
232
+
233
+ # Verificar parámetros sin type hint
234
+ for arg in node.args.args:
235
+ if arg.annotation is None and arg.arg != 'self' and arg.arg != 'cls':
236
+ if self._should_suggest("missing_type_hint"):
237
+ suggestions.append(self._create_suggestion(
238
+ SuggestionType.MISSING_TYPE_HINT,
239
+ SuggestionPriority.LOW,
240
+ "Missing Parameter Type",
241
+ f"Parameter '{arg.arg}' in '{node.name}' has no type hint",
242
+ file_path, node.lineno, node.lineno,
243
+ f"Add type hint: {arg.arg}: Type",
244
+ ))
245
+
246
+ # Buscar TODO comments
247
+ for i, line in enumerate(lines, 1):
248
+ if 'TODO' in line or 'FIXME' in line or 'XXX' in line:
249
+ if self._should_suggest("todo_comment"):
250
+ suggestions.append(self._create_suggestion(
251
+ SuggestionType.TODO_COMMENT,
252
+ SuggestionPriority.LOW,
253
+ "TODO Comment",
254
+ f"Pending task found",
255
+ file_path, i, i,
256
+ "Consider implementing or tracking this task",
257
+ ))
258
+
259
+ # Buscar problemas de seguridad potenciales
260
+ security_patterns = [
261
+ (r'password\s*=\s*["\'][^"\']+["\']', "Hardcoded password"),
262
+ (r'api_key\s*=\s*["\'][^"\']+["\']', "Hardcoded API key"),
263
+ (r'secret\s*=\s*["\'][^"\']+["\']', "Hardcoded secret"),
264
+ (r'eval\s*\(', "Use of eval() - potential security risk"),
265
+ (r'exec\s*\(', "Use of exec() - potential security risk"),
266
+ (r'shell=True', "subprocess with shell=True - potential injection risk"),
267
+ ]
268
+
269
+ for i, line in enumerate(lines, 1):
270
+ for pattern, message in security_patterns:
271
+ if re.search(pattern, line, re.IGNORECASE):
272
+ if self._should_suggest("security_issue"):
273
+ suggestions.append(self._create_suggestion(
274
+ SuggestionType.SECURITY_ISSUE,
275
+ SuggestionPriority.HIGH,
276
+ "Security Issue",
277
+ message,
278
+ file_path, i, i,
279
+ "Review and secure this code",
280
+ ))
281
+
282
+ # Buscar hardcoded values (IPs, URLs, paths)
283
+ hardcoded_patterns = [
284
+ (r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', "Hardcoded IP address"),
285
+ (r'["\']https?://[^"\']+["\']', "Hardcoded URL"),
286
+ ]
287
+
288
+ for i, line in enumerate(lines, 1):
289
+ # Skip comments
290
+ if line.strip().startswith('#'):
291
+ continue
292
+ for pattern, message in hardcoded_patterns:
293
+ if re.search(pattern, line):
294
+ if self._should_suggest("hardcoded_value"):
295
+ suggestions.append(self._create_suggestion(
296
+ SuggestionType.HARDCODED_VALUE,
297
+ SuggestionPriority.LOW,
298
+ "Hardcoded Value",
299
+ message,
300
+ file_path, i, i,
301
+ "Consider using a configuration file or environment variable",
302
+ ))
303
+
304
+ return suggestions
305
+
306
+ # ══════════════════════════════════════════════════════════════════════════
307
+ # ANÁLISIS JAVASCRIPT
308
+ # ══════════════════════════════════════════════════════════════════════════
309
+
310
+ def _analyze_javascript(self, file_path: str, content: str) -> List[Suggestion]:
311
+ """Analiza código JavaScript/TypeScript."""
312
+ suggestions = []
313
+ lines = content.split('\n')
314
+
315
+ # Buscar console.log
316
+ for i, line in enumerate(lines, 1):
317
+ if 'console.log' in line:
318
+ if self._should_suggest("deprecated_usage"):
319
+ suggestions.append(self._create_suggestion(
320
+ SuggestionType.DEPRECATED_USAGE,
321
+ SuggestionPriority.LOW,
322
+ "Console Log",
323
+ "console.log found - remove before production",
324
+ file_path, i, i,
325
+ "Remove or replace with proper logging",
326
+ ))
327
+
328
+ # Buscar var (debería usar let/const)
329
+ for i, line in enumerate(lines, 1):
330
+ if re.search(r'\bvar\s+\w+', line):
331
+ if self._should_suggest("deprecated_usage"):
332
+ suggestions.append(self._create_suggestion(
333
+ SuggestionType.DEPRECATED_USAGE,
334
+ SuggestionPriority.MEDIUM,
335
+ "Use of 'var'",
336
+ "Use 'let' or 'const' instead of 'var'",
337
+ file_path, i, i,
338
+ "Replace 'var' with 'let' or 'const'",
339
+ ))
340
+
341
+ # Buscar problemas de seguridad
342
+ security_patterns = [
343
+ (r'innerHTML\s*=', "innerHTML - potential XSS risk"),
344
+ (r'dangerouslySetInnerHTML', "dangerouslySetInnerHTML - potential XSS risk"),
345
+ (r'eval\s*\(', "Use of eval() - potential security risk"),
346
+ (r'password\s*[=:]\s*["\'][^"\']+["\']', "Hardcoded password"),
347
+ (r'apiKey\s*[=:]\s*["\'][^"\']+["\']', "Hardcoded API key"),
348
+ ]
349
+
350
+ for i, line in enumerate(lines, 1):
351
+ for pattern, message in security_patterns:
352
+ if re.search(pattern, line, re.IGNORECASE):
353
+ if self._should_suggest("security_issue"):
354
+ suggestions.append(self._create_suggestion(
355
+ SuggestionType.SECURITY_ISSUE,
356
+ SuggestionPriority.HIGH,
357
+ "Security Issue",
358
+ message,
359
+ file_path, i, i,
360
+ "Review and secure this code",
361
+ ))
362
+
363
+ # Buscar TODO comments
364
+ for i, line in enumerate(lines, 1):
365
+ if 'TODO' in line or 'FIXME' in line or 'XXX' in line:
366
+ if self._should_suggest("todo_comment"):
367
+ suggestions.append(self._create_suggestion(
368
+ SuggestionType.TODO_COMMENT,
369
+ SuggestionPriority.LOW,
370
+ "TODO Comment",
371
+ "Pending task found",
372
+ file_path, i, i,
373
+ "Consider implementing or tracking this task",
374
+ ))
375
+
376
+ return suggestions
377
+
378
+ # ══════════════════════════════════════════════════════════════════════════
379
+ # MÉTODOS PRIVADOS
380
+ # ══════════════════════════════════════════════════════════════════════════
381
+
382
+ def _calculate_complexity(self, node: ast.FunctionDef) -> int:
383
+ """Calcula la complejidad ciclomática de una función."""
384
+ complexity = 1 # Base
385
+
386
+ for child in ast.walk(node):
387
+ if isinstance(child, (ast.If, ast.While, ast.For, ast.ExceptHandler)):
388
+ complexity += 1
389
+ elif isinstance(child, ast.BoolOp):
390
+ complexity += len(child.values) - 1
391
+ elif isinstance(child, ast.comprehension):
392
+ complexity += 1
393
+ if child.ifs:
394
+ complexity += len(child.ifs)
395
+
396
+ return complexity
397
+
398
+ def _should_suggest(self, suggestion_type: str) -> bool:
399
+ """Determina si se debe hacer una sugerencia según el nivel de proactividad."""
400
+ if self.proactivity_level == "minimal":
401
+ # Solo sugerencias críticas
402
+ return suggestion_type in ("security_issue",)
403
+ elif self.proactivity_level == "normal":
404
+ # Excluir algunas sugerencias de baja prioridad
405
+ return suggestion_type not in ("missing_docstring", "todo_comment")
406
+ else: # aggressive
407
+ return True
408
+
409
+ def _create_suggestion(
410
+ self,
411
+ type: SuggestionType,
412
+ priority: SuggestionPriority,
413
+ title: str,
414
+ description: str,
415
+ file_path: str,
416
+ line_start: int,
417
+ line_end: int,
418
+ suggestion: str,
419
+ auto_fixable: bool = False,
420
+ fix_template: str = None,
421
+ ) -> Suggestion:
422
+ """Crea una nueva sugerencia."""
423
+ self._suggestion_counter += 1
424
+ return Suggestion(
425
+ id=f"sugg_{self._suggestion_counter}_{type.value}",
426
+ type=type,
427
+ priority=priority,
428
+ title=title,
429
+ description=description,
430
+ file_path=file_path,
431
+ line_start=line_start,
432
+ line_end=line_end,
433
+ suggestion=suggestion,
434
+ auto_fixable=auto_fixable,
435
+ fix_template=fix_template,
436
+ )
437
+
438
+
439
+ # ══════════════════════════════════════════════════════════════════════════════
440
+ # INSTANCIA GLOBAL
441
+ # ══════════════════════════════════════════════════════════════════════════════
442
+
443
+ _suggester_instance: Optional[ProactiveSuggester] = None
444
+
445
+
446
+ def get_suggester() -> ProactiveSuggester:
447
+ """Obtiene la instancia global del sugeridor."""
448
+ global _suggester_instance
449
+ if _suggester_instance is None:
450
+ _suggester_instance = ProactiveSuggester()
451
+ return _suggester_instance
@@ -0,0 +1,8 @@
1
+ # hanus/tasks/__init__.py
2
+ """
3
+ Sistema de tareas integrado en el motor del agente.
4
+ """
5
+ from hanus.tasks.models import Task, TaskStatus
6
+ from hanus.tasks.manager import TaskManager
7
+
8
+ __all__ = ["Task", "TaskStatus", "TaskManager"]