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,265 @@
1
+ # hanus/memory/manager.py
2
+ """
3
+ Gestor de memoria persistente entre sesiones.
4
+
5
+ Inspired by Claude Code's memory system with MEMORY.md index.
6
+ """
7
+ from __future__ import annotations
8
+ import json
9
+ import os
10
+ from pathlib import Path
11
+ from typing import Dict, List, Optional
12
+ from hanus.memory.types import MemoryType, MemoryEntry
13
+
14
+
15
+ class MemoryManager:
16
+ """
17
+ Gestiona la memoria persistente del agente.
18
+
19
+ Estructura de directorios:
20
+ .hanus/memory/
21
+ ├── MEMORY.md # Índice de memorias
22
+ ├── user.md # Preferencias del usuario
23
+ ├── feedback.md # Lecciones aprendidas
24
+ ├── project.md # Conocimiento del proyecto
25
+ └── reference/
26
+ ├── patterns.md # Patrones de código
27
+ └── apis.md # Referencias de APIs
28
+
29
+ Features:
30
+ - Cargar/salvar memorias
31
+ - Índice automático en MEMORY.md
32
+ - Búsqueda de memorias
33
+ - Token-aware loading (solo memorías relevantes)
34
+ """
35
+
36
+ MEMORY_DIR = ".hanus/memory"
37
+ INDEX_FILE = "MEMORY.md"
38
+
39
+ def __init__(self, root_dir: Path, max_memory_tokens: int = 8000):
40
+ self.root_dir = Path(root_dir)
41
+ self.memory_dir = self.root_dir / self.MEMORY_DIR
42
+ self.index_file = self.memory_dir / self.INDEX_FILE
43
+ self.max_tokens = max_memory_tokens
44
+ self._memories: Dict[str, MemoryEntry] = {}
45
+ self._ensure_dirs()
46
+
47
+ def _ensure_dirs(self):
48
+ """Crea los directorios de memoria si no existen."""
49
+ self.memory_dir.mkdir(parents=True, exist_ok=True)
50
+ (self.memory_dir / "reference").mkdir(exist_ok=True)
51
+
52
+ def load_memories(self) -> str:
53
+ """
54
+ Carga todas las memorías y retorna un string formateado para contexto.
55
+
56
+ Returns:
57
+ String con todas las memorías formateadas para inyección en contexto
58
+ """
59
+ self._load_all()
60
+
61
+ if not self._memories:
62
+ return ""
63
+
64
+ lines = ["## Memoria Persistente\n"]
65
+
66
+ # Agrupar por tipo
67
+ by_type: Dict[MemoryType, List[MemoryEntry]] = {}
68
+ for mem in self._memories.values():
69
+ by_type.setdefault(mem.memory_type, []).append(mem)
70
+
71
+ type_labels = {
72
+ MemoryType.USER: "### Preferencias del Usuario",
73
+ MemoryType.FEEDBACK: "### Lecciones Aprendidas",
74
+ MemoryType.PROJECT: "### Conocimiento del Proyecto",
75
+ MemoryType.REFERENCE: "### Referencias",
76
+ }
77
+
78
+ for mem_type in [MemoryType.USER, MemoryType.FEED, MemoryType.PROJECT, MemoryType.REFERENCE]:
79
+ if mem_type in by_type:
80
+ lines.append(type_labels[mem_type])
81
+ for mem in by_type[mem_type]:
82
+ lines.append(f"\n**{mem.name}**")
83
+ if mem.description:
84
+ lines.append(f"*{mem.description}*")
85
+ lines.append(mem.content)
86
+ lines.append("")
87
+
88
+ return "\n".join(lines)
89
+
90
+ def _load_all(self):
91
+ """Carga todas las memorias desde disco."""
92
+ self._memories.clear()
93
+
94
+ # Buscar todos los archivos .md
95
+ for md_file in self.memory_dir.rglob("*.md"):
96
+ if md_file.name == self.INDEX_FILE:
97
+ continue
98
+
99
+ try:
100
+ content = md_file.read_text(encoding="utf-8")
101
+ mem = MemoryEntry.from_markdown(content)
102
+ mem.name = md_file.stem # Usar nombre del archivo
103
+ self._memories[mem.name] = mem
104
+ except Exception:
105
+ continue
106
+
107
+ def save_memory(
108
+ self,
109
+ name: str,
110
+ memory_type: MemoryType,
111
+ content: str,
112
+ description: str = "",
113
+ tags: List[str] = None
114
+ ) -> MemoryEntry:
115
+ """
116
+ Guarda una nueva memoria.
117
+
118
+ Args:
119
+ name: Nombre identificador (se usará como nombre de archivo)
120
+ memory_type: Tipo de memoria
121
+ content: Contenido de la memoria
122
+ description: Descripción corta
123
+ tags: Etiquetas opcionales
124
+
125
+ Returns:
126
+ La entrada de memoria guardada
127
+ """
128
+ self._ensure_dirs()
129
+
130
+ # Sanitizar nombre
131
+ safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in name)
132
+
133
+ # Determinar directorio
134
+ if memory_type == MemoryType.REFERENCE:
135
+ mem_dir = self.memory_dir / "reference"
136
+ else:
137
+ mem_dir = self.memory_dir
138
+
139
+ mem_dir.mkdir(parents=True, exist_ok=True)
140
+ mem_file = mem_dir / f"{safe_name}.md"
141
+
142
+ # Crear entrada
143
+ entry = MemoryEntry(
144
+ name=safe_name,
145
+ memory_type=memory_type,
146
+ content=content,
147
+ description=description,
148
+ tags=tags or [],
149
+ )
150
+
151
+ # Guardar archivo
152
+ mem_file.write_text(entry.to_markdown(), encoding="utf-8")
153
+
154
+ # Actualizar índice
155
+ self._update_index()
156
+
157
+ # Guardar en memoria
158
+ self._memories[safe_name] = entry
159
+
160
+ return entry
161
+
162
+ def _update_index(self):
163
+ """Actualiza el archivo MEMORY.md con un índice de todas las memorias."""
164
+ self._load_all()
165
+
166
+ lines = [
167
+ "# Hanus Memory Index\n",
168
+ "Este archivo es generado automáticamente. No editar directamente.\n",
169
+ ]
170
+
171
+ # Agrupar por tipo
172
+ by_type: Dict[MemoryType, List[MemoryEntry]] = {}
173
+ for mem in self._memories.values():
174
+ by_type.setdefault(mem.memory_type, []).append(mem)
175
+
176
+ type_labels = {
177
+ MemoryType.USER: "## Preferencias del Usuario",
178
+ MemoryType.FEEDBACK: "## Lecciones Aprendidas",
179
+ MemoryType.PROJECT: "## Proyecto",
180
+ MemoryType.REFERENCE: "## Referencias",
181
+ }
182
+
183
+ for mem_type, label in type_labels.items():
184
+ if mem_type in by_type:
185
+ lines.append(f"\n{label}\n")
186
+ for mem in sorted(by_type[mem_type], key=lambda m: m.name):
187
+ desc = mem.description[:100] if mem.description else mem.content[:100]
188
+ lines.append(f"- [{mem.name}]({mem.memory_type.value}/{mem.name}.md) — {desc}")
189
+
190
+ self.index_file.write_text("\n".join(lines), encoding="utf-8")
191
+
192
+ def get(self, name: str) -> Optional[MemoryEntry]:
193
+ """Obtiene una memoria por nombre."""
194
+ if not self._memories:
195
+ self._load_all()
196
+ return self._memories.get(name)
197
+
198
+ def delete(self, name: str) -> bool:
199
+ """Elimina una memoria por nombre."""
200
+ mem = self.get(name)
201
+ if not mem:
202
+ return False
203
+
204
+ # Determinar archivo
205
+ if mem.memory_type == MemoryType.REFERENCE:
206
+ mem_file = self.memory_dir / "reference" / f"{name}.md"
207
+ else:
208
+ mem_file = self.memory_dir / f"{name}.md"
209
+
210
+ if mem_file.exists():
211
+ mem_file.unlink()
212
+
213
+ del self._memories[name]
214
+ self._update_index()
215
+ return True
216
+
217
+ def search(self, query: str, limit: int = 5) -> List[MemoryEntry]:
218
+ """
219
+ Busca memorias que contengan el query.
220
+
221
+ Args:
222
+ query: Texto a buscar
223
+ limit: Máximo de resultados
224
+
225
+ Returns:
226
+ Lista de memorias que coinciden
227
+ """
228
+ if not self._memories:
229
+ self._load_all()
230
+
231
+ query_lower = query.lower()
232
+ results = []
233
+
234
+ for mem in self._memories.values():
235
+ score = 0
236
+ # Buscar en nombre (peso 2)
237
+ if query_lower in mem.name.lower():
238
+ score += 2
239
+ # Buscar en descripción (peso 1)
240
+ if mem.description and query_lower in mem.description.lower():
241
+ score += 1
242
+ # Buscar en contenido (peso 1)
243
+ if query_lower in mem.content.lower():
244
+ score += 1
245
+ # Buscar en tags (peso 2)
246
+ if any(query_lower in tag.lower() for tag in mem.tags):
247
+ score += 2
248
+
249
+ if score > 0:
250
+ results.append((score, mem))
251
+
252
+ results.sort(key=lambda x: -x[0])
253
+ return [mem for _, mem in results[:limit]]
254
+
255
+ def list_by_type(self, memory_type: MemoryType) -> List[MemoryEntry]:
256
+ """Lista todas las memorias de un tipo."""
257
+ if not self._memories:
258
+ self._load_all()
259
+ return [mem for mem in self._memories.values() if mem.memory_type == memory_type]
260
+
261
+ def list_all(self) -> List[MemoryEntry]:
262
+ """Lista todas las memorias."""
263
+ if not self._memories:
264
+ self._load_all()
265
+ return list(self._memories.values())
hanus/memory/types.py ADDED
@@ -0,0 +1,119 @@
1
+ # hanus/memory/types.py
2
+ """
3
+ Tipos de datos para el sistema de memoria.
4
+ """
5
+ from __future__ import annotations
6
+ import time
7
+ from dataclasses import dataclass, field
8
+ from typing import List, Dict, Any, Optional
9
+ from enum import Enum
10
+
11
+
12
+ class MemoryType(Enum):
13
+ """
14
+ Tipos de memoria disponibles.
15
+
16
+ - USER: Preferencias del usuario, rol, conocimientos
17
+ - FEEDBACK: Lecciones aprendidas de errores y aciertos
18
+ - PROJECT: Conocimiento del proyecto actual
19
+ - REFERENCE: Referencias externas (APIs, patrones)
20
+ """
21
+ USER = "user"
22
+ FEEDBACK = "feedback"
23
+ PROJECT = "project"
24
+ REFERENCE = "reference"
25
+
26
+
27
+ @dataclass
28
+ class MemoryEntry:
29
+ """
30
+ Una entrada de memoria individual.
31
+ """
32
+ name: str # Nombre identificador
33
+ memory_type: MemoryType # Tipo de memoria
34
+ content: str # Contenido de la memoria
35
+ description: str = "" # Descripción corta (para el índice)
36
+ created_at: float = field(default_factory=time.time)
37
+ updated_at: float = field(default_factory=time.time)
38
+ metadata: Dict[str, Any] = field(default_factory=dict)
39
+ tags: List[str] = field(default_factory=list)
40
+
41
+ def to_dict(self) -> Dict[str, Any]:
42
+ """Serializa la entrada a diccionario."""
43
+ return {
44
+ "name": self.name,
45
+ "type": self.memory_type.value,
46
+ "content": self.content,
47
+ "description": self.description,
48
+ "created_at": self.created_at,
49
+ "updated_at": self.updated_at,
50
+ "metadata": self.metadata,
51
+ "tags": self.tags,
52
+ }
53
+
54
+ @classmethod
55
+ def from_dict(cls, d: Dict[str, Any]) -> "MemoryEntry":
56
+ """Deserializa desde diccionario."""
57
+ return cls(
58
+ name=d["name"],
59
+ memory_type=MemoryType(d.get("type", "project")),
60
+ content=d.get("content", ""),
61
+ description=d.get("description", ""),
62
+ created_at=d.get("created_at", time.time()),
63
+ updated_at=d.get("updated_at", time.time()),
64
+ metadata=d.get("metadata", {}),
65
+ tags=d.get("tags", []),
66
+ )
67
+
68
+ def to_markdown(self) -> str:
69
+ """
70
+ Formatea la memoria como markdown con frontmatter.
71
+
72
+ Formato compatible con Claude Code.
73
+ """
74
+ lines = [
75
+ "---",
76
+ f"name: {self.name}",
77
+ f"description: {self.description}",
78
+ f"type: {self.memory_type.value}",
79
+ ]
80
+ if self.tags:
81
+ lines.append(f"tags: {', '.join(self.tags)}")
82
+ lines.extend([
83
+ "---",
84
+ "",
85
+ self.content,
86
+ ])
87
+ return "\n".join(lines)
88
+
89
+ @classmethod
90
+ def from_markdown(cls, content: str) -> "MemoryEntry":
91
+ """Parsea una memoria desde formato markdown con frontmatter."""
92
+ import re
93
+
94
+ # Parsear frontmatter
95
+ frontmatter = {}
96
+ if content.startswith("---"):
97
+ parts = content.split("---", 2)
98
+ if len(parts) >= 3:
99
+ for line in parts[1].strip().split("\n"):
100
+ if ":" in line:
101
+ key, _, value = line.partition(":")
102
+ key = key.strip()
103
+ value = value.strip()
104
+ if key == "tags":
105
+ value = [t.strip() for t in value.split(",")]
106
+ frontmatter[key] = value
107
+ body = parts[2].strip()
108
+ else:
109
+ body = content
110
+ else:
111
+ body = content
112
+
113
+ return cls(
114
+ name=frontmatter.get("name", "unnamed"),
115
+ memory_type=MemoryType(frontmatter.get("type", "project")),
116
+ content=body,
117
+ description=frontmatter.get("description", ""),
118
+ tags=frontmatter.get("tags", []),
119
+ )