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,281 @@
|
|
|
1
|
+
# hanus/instincts/detector.py
|
|
2
|
+
"""
|
|
3
|
+
Detector de patrones para crear instintos desde sesiones.
|
|
4
|
+
|
|
5
|
+
Analiza las interacciones y detecta patrones que pueden convertirse
|
|
6
|
+
en instintos.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import List, Dict, Optional, Set, Tuple
|
|
12
|
+
from collections import Counter
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
from hanus.instincts.types import (
|
|
16
|
+
Instinct, InstinctScope, InstinctDomain, InstinctEvidence
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class SessionAction:
|
|
22
|
+
"""Acción realizada en una sesión."""
|
|
23
|
+
timestamp: str
|
|
24
|
+
tool: str # Herramienta usada (read, write, edit, bash, etc.)
|
|
25
|
+
action: str # Acción específica
|
|
26
|
+
context: str # Contexto (archivo, comando, etc.)
|
|
27
|
+
outcome: str # success/failure
|
|
28
|
+
metadata: Dict = field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class Pattern:
|
|
33
|
+
"""Patrón detectado en las acciones."""
|
|
34
|
+
name: str
|
|
35
|
+
trigger: str # Cuándo aplicar
|
|
36
|
+
actions: List[str] # Secuencia de acciones
|
|
37
|
+
frequency: int # Frecuencia de aparición
|
|
38
|
+
confidence: float # Confianza del patrón
|
|
39
|
+
domain: InstinctDomain
|
|
40
|
+
evidence: List[SessionAction] = field(default_factory=list)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class PatternDetector:
|
|
44
|
+
"""
|
|
45
|
+
Detecta patrones en sesiones que pueden convertirse en instintos.
|
|
46
|
+
|
|
47
|
+
Tipos de patrones detectados:
|
|
48
|
+
1. WORKFLOW: Secuencia de acciones repetidas
|
|
49
|
+
2. ERROR_RECOVERY: Respuestas a errores
|
|
50
|
+
3. CODE_STYLE: Preferencias de código
|
|
51
|
+
4. SECURITY: Comportamientos de seguridad
|
|
52
|
+
5. TESTING: Patrones de testing
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
# Patrones predefinidos que buscamos
|
|
56
|
+
PREDEFINED_PATTERNS = {
|
|
57
|
+
"read_before_edit": {
|
|
58
|
+
"trigger": "when modifying code",
|
|
59
|
+
"domain": InstinctDomain.WORKFLOW,
|
|
60
|
+
"pattern": [
|
|
61
|
+
("read", "before", "edit"),
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
"test_after_write": {
|
|
65
|
+
"trigger": "when writing new code",
|
|
66
|
+
"domain": InstinctDomain.TESTING,
|
|
67
|
+
"pattern": [
|
|
68
|
+
("write", "then", "test"),
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
"validate_input": {
|
|
72
|
+
"trigger": "when handling user input",
|
|
73
|
+
"domain": InstinctDomain.SECURITY,
|
|
74
|
+
"pattern": [
|
|
75
|
+
("validate", "before", "process"),
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
"grep_for_context": {
|
|
79
|
+
"trigger": "when understanding codebase",
|
|
80
|
+
"domain": InstinctDomain.WORKFLOW,
|
|
81
|
+
"pattern": [
|
|
82
|
+
("grep", "before", "modify"),
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
"commit_after_changes": {
|
|
86
|
+
"trigger": "when completing feature",
|
|
87
|
+
"domain": InstinctDomain.GIT,
|
|
88
|
+
"pattern": [
|
|
89
|
+
("test", "then", "commit"),
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
"check_types": {
|
|
93
|
+
"trigger": "when modifying TypeScript/Python",
|
|
94
|
+
"domain": InstinctDomain.WORKFLOW,
|
|
95
|
+
"pattern": [
|
|
96
|
+
("edit", "then", "typecheck"),
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
"security_review": {
|
|
100
|
+
"trigger": "when handling sensitive data",
|
|
101
|
+
"domain": InstinctDomain.SECURITY,
|
|
102
|
+
"pattern": [
|
|
103
|
+
("check", "secrets", "before"),
|
|
104
|
+
("validate", "input", "always"),
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
def __init__(self, min_frequency: int = 2, min_confidence: float = 0.3):
|
|
110
|
+
self.min_frequency = min_frequency
|
|
111
|
+
self.min_confidence = min_confidence
|
|
112
|
+
self._actions: List[SessionAction] = []
|
|
113
|
+
self._detected_patterns: Dict[str, Pattern] = {}
|
|
114
|
+
|
|
115
|
+
def record_action(self, action: SessionAction) -> None:
|
|
116
|
+
"""Registra una acción para análisis."""
|
|
117
|
+
self._actions.append(action)
|
|
118
|
+
|
|
119
|
+
def analyze_session(self, actions: List[SessionAction]) -> List[Pattern]:
|
|
120
|
+
"""
|
|
121
|
+
Analiza una sesión y detecta patrones.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Lista de patrones detectados
|
|
125
|
+
"""
|
|
126
|
+
self._actions = actions
|
|
127
|
+
patterns = []
|
|
128
|
+
|
|
129
|
+
# Detectar patrones predefinidos
|
|
130
|
+
for pattern_name, pattern_def in self.PREDEFINED_PATTERNS.items():
|
|
131
|
+
detected = self._detect_pattern(pattern_name, pattern_def)
|
|
132
|
+
if detected:
|
|
133
|
+
patterns.append(detected)
|
|
134
|
+
|
|
135
|
+
# Detectar patrones de secuencia
|
|
136
|
+
sequence_patterns = self._detect_sequence_patterns()
|
|
137
|
+
patterns.extend(sequence_patterns)
|
|
138
|
+
|
|
139
|
+
# Detectar patrones de error/recovery
|
|
140
|
+
error_patterns = self._detect_error_recovery_patterns()
|
|
141
|
+
patterns.extend(error_patterns)
|
|
142
|
+
|
|
143
|
+
return patterns
|
|
144
|
+
|
|
145
|
+
def _detect_pattern(self, name: str, definition: Dict) -> Optional[Pattern]:
|
|
146
|
+
"""Detecta si un patrón predefinido aparece en las acciones."""
|
|
147
|
+
matching_actions = []
|
|
148
|
+
|
|
149
|
+
for action in self._actions:
|
|
150
|
+
# Verificar si la acción coincide con el patrón
|
|
151
|
+
for pattern_part in definition["pattern"]:
|
|
152
|
+
if self._action_matches_pattern(action, pattern_part):
|
|
153
|
+
matching_actions.append(action)
|
|
154
|
+
|
|
155
|
+
# Calcular frecuencia y confianza
|
|
156
|
+
frequency = len(matching_actions)
|
|
157
|
+
if frequency < self.min_frequency:
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
# Calcular confianza basada en frecuencia y consistencia
|
|
161
|
+
confidence = min(0.9, 0.3 + (frequency * 0.1))
|
|
162
|
+
|
|
163
|
+
return Pattern(
|
|
164
|
+
name=name,
|
|
165
|
+
trigger=definition["trigger"],
|
|
166
|
+
actions=[a.action for a in matching_actions],
|
|
167
|
+
frequency=frequency,
|
|
168
|
+
confidence=confidence,
|
|
169
|
+
domain=definition["domain"],
|
|
170
|
+
evidence=matching_actions,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def _action_matches_pattern(self, action: SessionAction, pattern_part: Tuple) -> bool:
|
|
174
|
+
"""Verifica si una acción coincide con un patrón."""
|
|
175
|
+
# Implementación simplificada
|
|
176
|
+
tool_match = pattern_part[0] in action.tool.lower()
|
|
177
|
+
action_match = pattern_part[1] in action.action.lower()
|
|
178
|
+
return tool_match and action_match
|
|
179
|
+
|
|
180
|
+
def _detect_sequence_patterns(self) -> List[Pattern]:
|
|
181
|
+
"""Detecta patrones de secuencia (acciones que ocurren en orden)."""
|
|
182
|
+
patterns = []
|
|
183
|
+
|
|
184
|
+
# Buscar secuencias de 2-4 acciones que se repiten
|
|
185
|
+
for length in range(2, 5):
|
|
186
|
+
sequences = []
|
|
187
|
+
for i in range(len(self._actions) - length + 1):
|
|
188
|
+
seq = tuple(a.tool for a in self._actions[i:i+length])
|
|
189
|
+
sequences.append(seq)
|
|
190
|
+
|
|
191
|
+
# Contar frecuencias
|
|
192
|
+
counter = Counter(sequences)
|
|
193
|
+
|
|
194
|
+
for seq, freq in counter.most_common():
|
|
195
|
+
if freq >= self.min_frequency:
|
|
196
|
+
# Generar patrón
|
|
197
|
+
pattern_name = "-".join(seq)
|
|
198
|
+
trigger = f"when performing {seq[0]}"
|
|
199
|
+
|
|
200
|
+
patterns.append(Pattern(
|
|
201
|
+
name=pattern_name,
|
|
202
|
+
trigger=trigger,
|
|
203
|
+
actions=list(seq),
|
|
204
|
+
frequency=freq,
|
|
205
|
+
confidence=min(0.9, 0.3 + (freq * 0.1)),
|
|
206
|
+
domain=InstinctDomain.WORKFLOW,
|
|
207
|
+
evidence=[a for a in self._actions if a.tool in seq],
|
|
208
|
+
))
|
|
209
|
+
|
|
210
|
+
return patterns
|
|
211
|
+
|
|
212
|
+
def _detect_error_recovery_patterns(self) -> List[Pattern]:
|
|
213
|
+
"""Detecta patrones de recuperación de errores."""
|
|
214
|
+
patterns = []
|
|
215
|
+
|
|
216
|
+
# Buscar fallos seguidos de acciones correctivas
|
|
217
|
+
for i, action in enumerate(self._actions):
|
|
218
|
+
if action.outcome == "failure":
|
|
219
|
+
# Ver qué acciones siguen al fallo
|
|
220
|
+
recovery_actions = []
|
|
221
|
+
for j in range(i + 1, min(i + 5, len(self._actions))):
|
|
222
|
+
if self._actions[j].outcome == "success":
|
|
223
|
+
recovery_actions.append(self._actions[j])
|
|
224
|
+
|
|
225
|
+
if recovery_actions:
|
|
226
|
+
pattern_name = f"recover-from-{action.tool}"
|
|
227
|
+
patterns.append(Pattern(
|
|
228
|
+
name=pattern_name,
|
|
229
|
+
trigger=f"when {action.tool} fails",
|
|
230
|
+
actions=[a.action for a in recovery_actions],
|
|
231
|
+
frequency=1,
|
|
232
|
+
confidence=0.5,
|
|
233
|
+
domain=InstinctDomain.DEBUGGING,
|
|
234
|
+
evidence=[action] + recovery_actions,
|
|
235
|
+
))
|
|
236
|
+
|
|
237
|
+
return patterns
|
|
238
|
+
|
|
239
|
+
def create_instinct_from_pattern(self, pattern: Pattern,
|
|
240
|
+
scope: InstinctScope = InstinctScope.PROJECT) -> Instinct:
|
|
241
|
+
"""Crea un instinto desde un patrón detectado."""
|
|
242
|
+
evidence = [
|
|
243
|
+
InstinctEvidence(
|
|
244
|
+
timestamp=a.timestamp,
|
|
245
|
+
action=a.action,
|
|
246
|
+
context=a.context,
|
|
247
|
+
outcome=a.outcome,
|
|
248
|
+
file_path=a.metadata.get("file_path"),
|
|
249
|
+
snippet=a.metadata.get("snippet"),
|
|
250
|
+
)
|
|
251
|
+
for a in pattern.evidence
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
return Instinct(
|
|
255
|
+
id=Instinct.generate_id(pattern.name, pattern.trigger),
|
|
256
|
+
name=pattern.name,
|
|
257
|
+
description=f"Learned behavior: {pattern.trigger}",
|
|
258
|
+
trigger=pattern.trigger,
|
|
259
|
+
action=", ".join(pattern.actions[:3]), # Top 3 actions
|
|
260
|
+
domain=pattern.domain,
|
|
261
|
+
scope=scope,
|
|
262
|
+
confidence=pattern.confidence,
|
|
263
|
+
evidence=evidence,
|
|
264
|
+
observations=pattern.frequency,
|
|
265
|
+
successes=pattern.frequency, # Asumimos éxito
|
|
266
|
+
failures=0,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def get_pattern_summary(self) -> Dict:
|
|
270
|
+
"""Obtiene un resumen de los patrones detectados."""
|
|
271
|
+
return {
|
|
272
|
+
"total_actions": len(self._actions),
|
|
273
|
+
"patterns_detected": len(self._detected_patterns),
|
|
274
|
+
"by_domain": {
|
|
275
|
+
domain.value: len([p for p in self._detected_patterns.values()
|
|
276
|
+
if p.domain == domain])
|
|
277
|
+
for domain in InstinctDomain
|
|
278
|
+
},
|
|
279
|
+
"high_confidence": len([p for p in self._detected_patterns.values()
|
|
280
|
+
if p.confidence >= 0.8]),
|
|
281
|
+
}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
# hanus/instincts/evolver.py
|
|
2
|
+
"""
|
|
3
|
+
Evolucionador de instintos.
|
|
4
|
+
|
|
5
|
+
Analiza instintos y los agrupa en estructuras de mayor nivel:
|
|
6
|
+
- Skills: Comportamientos auto-triggered
|
|
7
|
+
- Commands: Acciones invocadas por usuario
|
|
8
|
+
- Agents: Procesos multi-paso complejos
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import List, Dict, Optional, Tuple
|
|
13
|
+
from collections import defaultdict
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
|
|
16
|
+
from hanus.instincts.types import (
|
|
17
|
+
Instinct, InstinctScope, InstinctDomain, InstinctStatus
|
|
18
|
+
)
|
|
19
|
+
from hanus.instincts.manager import InstinctManager
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class EvolutionCandidate:
|
|
24
|
+
"""Candidato para evolución."""
|
|
25
|
+
name: str
|
|
26
|
+
type: str # "skill", "command", "agent"
|
|
27
|
+
instincts: List[Instinct]
|
|
28
|
+
avg_confidence: float
|
|
29
|
+
domains: List[InstinctDomain]
|
|
30
|
+
scope: InstinctScope
|
|
31
|
+
reason: str # Por qué es candidato
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class EvolvedStructure:
|
|
36
|
+
"""Estructura evolucionada."""
|
|
37
|
+
name: str
|
|
38
|
+
type: str # "skill", "command", "agent"
|
|
39
|
+
description: str
|
|
40
|
+
instincts: List[str] # IDs de instintos origen
|
|
41
|
+
content: str # Contenido generado
|
|
42
|
+
scope: InstinctScope
|
|
43
|
+
created_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class InstinctEvolver:
|
|
47
|
+
"""
|
|
48
|
+
Analiza instintos y sugiere o genera estructuras evolucionadas.
|
|
49
|
+
|
|
50
|
+
Reglas de evolución:
|
|
51
|
+
- → Command: Cuando instintos describen acciones invocadas por usuario
|
|
52
|
+
- → Skill: Cuando instintos describen comportamientos auto-triggered
|
|
53
|
+
- → Agent: Cuando instintos describen procesos multi-paso complejos
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# Umbrales
|
|
57
|
+
MIN_INSTINCTS_FOR_SKILL = 2
|
|
58
|
+
MIN_INSTINCTS_FOR_COMMAND = 2
|
|
59
|
+
MIN_INSTINCTS_FOR_AGENT = 3
|
|
60
|
+
MIN_CONFIDENCE = 0.7
|
|
61
|
+
|
|
62
|
+
def __init__(self, manager: InstinctManager):
|
|
63
|
+
self.manager = manager
|
|
64
|
+
|
|
65
|
+
def analyze(self, min_confidence: float = None) -> List[EvolutionCandidate]:
|
|
66
|
+
"""
|
|
67
|
+
Analiza instintos y encuentra candidatos para evolución.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Lista de candidatos de evolución
|
|
71
|
+
"""
|
|
72
|
+
min_conf = min_confidence or self.MIN_CONFIDENCE
|
|
73
|
+
|
|
74
|
+
# Obtener instintos con suficiente confianza
|
|
75
|
+
all_instincts = self.manager.get_by_confidence(min_conf)
|
|
76
|
+
|
|
77
|
+
# Agrupar por dominio y trigger patterns
|
|
78
|
+
groups = self._group_instincts(all_instincts)
|
|
79
|
+
|
|
80
|
+
candidates = []
|
|
81
|
+
|
|
82
|
+
for group_key, group_instincts in groups.items():
|
|
83
|
+
# Verificar si es candidato a skill
|
|
84
|
+
if len(group_instincts) >= self.MIN_INSTINCTS_FOR_SKILL:
|
|
85
|
+
if self._is_skill_candidate(group_instincts):
|
|
86
|
+
candidates.append(self._create_skill_candidate(group_instincts))
|
|
87
|
+
|
|
88
|
+
# Verificar si es candidato a command
|
|
89
|
+
if len(group_instincts) >= self.MIN_INSTINCTS_FOR_COMMAND:
|
|
90
|
+
if self._is_command_candidate(group_instincts):
|
|
91
|
+
candidates.append(self._create_command_candidate(group_instincts))
|
|
92
|
+
|
|
93
|
+
# Verificar si es candidato a agent
|
|
94
|
+
if len(group_instincts) >= self.MIN_INSTINCTS_FOR_AGENT:
|
|
95
|
+
if self._is_agent_candidate(group_instincts):
|
|
96
|
+
candidates.append(self._create_agent_candidate(group_instincts))
|
|
97
|
+
|
|
98
|
+
# Ordenar por confianza promedio
|
|
99
|
+
candidates.sort(key=lambda c: c.avg_confidence, reverse=True)
|
|
100
|
+
|
|
101
|
+
return candidates
|
|
102
|
+
|
|
103
|
+
def _group_instincts(self, instincts: List[Instinct]) -> Dict[str, List[Instinct]]:
|
|
104
|
+
"""Agrupa instintos por patrones de trigger y dominio."""
|
|
105
|
+
groups = defaultdict(list)
|
|
106
|
+
|
|
107
|
+
for instinct in instincts:
|
|
108
|
+
# Crear clave de grupo basada en palabras clave del trigger
|
|
109
|
+
trigger_words = set(instinct.trigger.lower().split())
|
|
110
|
+
for word in trigger_words:
|
|
111
|
+
if len(word) > 3: # Ignorar palabras cortas
|
|
112
|
+
groups[word].append(instinct)
|
|
113
|
+
|
|
114
|
+
# También agrupar por dominio
|
|
115
|
+
domain_key = f"domain:{instinct.domain.value}"
|
|
116
|
+
groups[domain_key].append(instinct)
|
|
117
|
+
|
|
118
|
+
# Filtrar grupos con pocos instintos
|
|
119
|
+
return {k: v for k, v in groups.items() if len(v) >= 2}
|
|
120
|
+
|
|
121
|
+
def _is_skill_candidate(self, instincts: List[Instinct]) -> bool:
|
|
122
|
+
"""Determina si un grupo de instintos es candidato a skill."""
|
|
123
|
+
# Skills son auto-triggered behaviors
|
|
124
|
+
trigger_keywords = ["when", "if", "on", "always", "before", "after"]
|
|
125
|
+
|
|
126
|
+
for instinct in instincts:
|
|
127
|
+
if any(kw in instinct.trigger.lower() for kw in trigger_keywords):
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
def _is_command_candidate(self, instincts: List[Instinct]) -> bool:
|
|
133
|
+
"""Determina si un grupo de instintos es candidato a command."""
|
|
134
|
+
# Commands son user-invoked actions
|
|
135
|
+
trigger_keywords = ["when user asks", "when creating", "when adding", "when running"]
|
|
136
|
+
|
|
137
|
+
for instinct in instincts:
|
|
138
|
+
if any(kw in instinct.trigger.lower() for kw in trigger_keywords):
|
|
139
|
+
return True
|
|
140
|
+
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
def _is_agent_candidate(self, instincts: List[Instinct]) -> bool:
|
|
144
|
+
"""Determina si un grupo de instintos es candidato a agent."""
|
|
145
|
+
# Agents son multi-step processes complejos
|
|
146
|
+
step_indicators = ["step", "first", "then", "finally", "check", "verify"]
|
|
147
|
+
|
|
148
|
+
# Verificar si hay secuencia de pasos
|
|
149
|
+
has_sequence = False
|
|
150
|
+
for instinct in instincts:
|
|
151
|
+
if any(ind in instinct.name.lower() for ind in step_indicators):
|
|
152
|
+
has_sequence = True
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
# Verificar si es dominio complejo
|
|
156
|
+
complex_domains = [
|
|
157
|
+
InstinctDomain.DEBUGGING,
|
|
158
|
+
InstinctDomain.ARCHITECTURE,
|
|
159
|
+
InstinctDomain.REFACTORING,
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
has_complex_domain = any(i.domain in complex_domains for i in instincts)
|
|
163
|
+
|
|
164
|
+
return len(instincts) >= 3 and (has_sequence or has_complex_domain)
|
|
165
|
+
|
|
166
|
+
def _create_skill_candidate(self, instincts: List[Instinct]) -> EvolutionCandidate:
|
|
167
|
+
"""Crea un candidato a skill."""
|
|
168
|
+
avg_conf = sum(i.confidence for i in instincts) / len(instincts)
|
|
169
|
+
domains = list(set(i.domain for i in instincts))
|
|
170
|
+
|
|
171
|
+
# Generar nombre
|
|
172
|
+
trigger_words = set()
|
|
173
|
+
for i in instincts:
|
|
174
|
+
trigger_words.update(i.trigger.lower().split())
|
|
175
|
+
|
|
176
|
+
name = "-".join(sorted(trigger_words)[:2]) or "learned-skill"
|
|
177
|
+
|
|
178
|
+
return EvolutionCandidate(
|
|
179
|
+
name=name,
|
|
180
|
+
type="skill",
|
|
181
|
+
instincts=instincts,
|
|
182
|
+
avg_confidence=avg_conf,
|
|
183
|
+
domains=domains,
|
|
184
|
+
scope=instincts[0].scope,
|
|
185
|
+
reason="Auto-triggered behaviors grouped by common trigger pattern",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def _create_command_candidate(self, instincts: List[Instinct]) -> EvolutionCandidate:
|
|
189
|
+
"""Crea un candidato a command."""
|
|
190
|
+
avg_conf = sum(i.confidence for i in instincts) / len(instincts)
|
|
191
|
+
domains = list(set(i.domain for i in instincts))
|
|
192
|
+
|
|
193
|
+
# Generar nombre
|
|
194
|
+
name = instincts[0].name.split("-")[0] if instincts else "learned-command"
|
|
195
|
+
|
|
196
|
+
return EvolutionCandidate(
|
|
197
|
+
name=name,
|
|
198
|
+
type="command",
|
|
199
|
+
instincts=instincts,
|
|
200
|
+
avg_confidence=avg_conf,
|
|
201
|
+
domains=domains,
|
|
202
|
+
scope=instincts[0].scope,
|
|
203
|
+
reason="User-invoked actions grouped by common workflow",
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def _create_agent_candidate(self, instincts: List[Instinct]) -> EvolutionCandidate:
|
|
207
|
+
"""Crea un candidato a agent."""
|
|
208
|
+
avg_conf = sum(i.confidence for i in instincts) / len(instincts)
|
|
209
|
+
domains = list(set(i.domain for i in instincts))
|
|
210
|
+
|
|
211
|
+
# Generar nombre basado en dominio
|
|
212
|
+
domain_name = domains[0].value if domains else "general"
|
|
213
|
+
name = f"{domain_name}-agent"
|
|
214
|
+
|
|
215
|
+
return EvolutionCandidate(
|
|
216
|
+
name=name,
|
|
217
|
+
type="agent",
|
|
218
|
+
instincts=instincts,
|
|
219
|
+
avg_confidence=avg_conf,
|
|
220
|
+
domains=domains,
|
|
221
|
+
scope=instincts[0].scope,
|
|
222
|
+
reason="Multi-step process requiring isolation and depth",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def evolve(self, candidate: EvolutionCandidate) -> EvolvedStructure:
|
|
226
|
+
"""
|
|
227
|
+
Genera la estructura evolucionada para un candidato.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Estructura evolucionada
|
|
231
|
+
"""
|
|
232
|
+
if candidate.type == "skill":
|
|
233
|
+
return self._generate_skill(candidate)
|
|
234
|
+
elif candidate.type == "command":
|
|
235
|
+
return self._generate_command(candidate)
|
|
236
|
+
elif candidate.type == "agent":
|
|
237
|
+
return self._generate_agent(candidate)
|
|
238
|
+
else:
|
|
239
|
+
raise ValueError(f"Unknown type: {candidate.type}")
|
|
240
|
+
|
|
241
|
+
def _generate_skill(self, candidate: EvolutionCandidate) -> EvolvedStructure:
|
|
242
|
+
"""Genera un skill desde un candidato."""
|
|
243
|
+
# Agrupar triggers
|
|
244
|
+
triggers = set(i.trigger for i in candidate.instincts)
|
|
245
|
+
actions = set(i.action for i in candidate.instincts)
|
|
246
|
+
|
|
247
|
+
content = f"""# {candidate.name}
|
|
248
|
+
|
|
249
|
+
## Description
|
|
250
|
+
Auto-generated skill from learned instincts.
|
|
251
|
+
|
|
252
|
+
## Triggers
|
|
253
|
+
{chr(10).join(f'- {t}' for t in triggers)}
|
|
254
|
+
|
|
255
|
+
## Actions
|
|
256
|
+
{chr(10).join(f'- {a}' for a in actions)}
|
|
257
|
+
|
|
258
|
+
## Confidence
|
|
259
|
+
{candidate.avg_confidence:.0%}
|
|
260
|
+
|
|
261
|
+
## Evolved From
|
|
262
|
+
{chr(10).join(f'- {i.name} ({i.confidence:.0%})' for i in candidate.instincts)}
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
return EvolvedStructure(
|
|
266
|
+
name=candidate.name,
|
|
267
|
+
type="skill",
|
|
268
|
+
description=f"Auto-triggered skill for {candidate.domains[0].value}",
|
|
269
|
+
instincts=[i.id for i in candidate.instincts],
|
|
270
|
+
content=content,
|
|
271
|
+
scope=candidate.scope,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def _generate_command(self, candidate: EvolutionCandidate) -> EvolvedStructure:
|
|
275
|
+
"""Genera un command desde un candidato."""
|
|
276
|
+
steps = []
|
|
277
|
+
for i, instinct in enumerate(sorted(candidate.instincts,
|
|
278
|
+
key=lambda x: x.created_at), 1):
|
|
279
|
+
steps.append(f"{i}. {instinct.action}")
|
|
280
|
+
|
|
281
|
+
content = f"""# /{candidate.name}
|
|
282
|
+
|
|
283
|
+
## Description
|
|
284
|
+
Auto-generated command from learned workflow.
|
|
285
|
+
|
|
286
|
+
## Usage
|
|
287
|
+
`/{candidate.name}`
|
|
288
|
+
|
|
289
|
+
## Steps
|
|
290
|
+
{chr(10).join(steps)}
|
|
291
|
+
|
|
292
|
+
## Confidence
|
|
293
|
+
{candidate.avg_confidence:.0%}
|
|
294
|
+
|
|
295
|
+
## Evolved From
|
|
296
|
+
{chr(10).join(f'- {i.name} ({i.confidence:.0%})' for i in candidate.instincts)}
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
return EvolvedStructure(
|
|
300
|
+
name=candidate.name,
|
|
301
|
+
type="command",
|
|
302
|
+
description=f"User-invoked command for {candidate.domains[0].value}",
|
|
303
|
+
instincts=[i.id for i in candidate.instincts],
|
|
304
|
+
content=content,
|
|
305
|
+
scope=candidate.scope,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def _generate_agent(self, candidate: EvolutionCandidate) -> EvolvedStructure:
|
|
309
|
+
"""Genera un agent desde un candidato."""
|
|
310
|
+
steps = []
|
|
311
|
+
for i, instinct in enumerate(sorted(candidate.instincts,
|
|
312
|
+
key=lambda x: x.created_at), 1):
|
|
313
|
+
steps.append(f"{i}. {instinct.trigger}: {instinct.action}")
|
|
314
|
+
|
|
315
|
+
model = "sonnet" if candidate.avg_confidence < 0.85 else "opus"
|
|
316
|
+
|
|
317
|
+
content = f"""# {candidate.name}
|
|
318
|
+
|
|
319
|
+
## Description
|
|
320
|
+
Auto-generated agent from learned multi-step process.
|
|
321
|
+
|
|
322
|
+
## Model
|
|
323
|
+
{model}
|
|
324
|
+
|
|
325
|
+
## Triggers
|
|
326
|
+
{chr(10).join(f'- {i.trigger}' for i in candidate.instincts[:3])}
|
|
327
|
+
|
|
328
|
+
## Process
|
|
329
|
+
{chr(10).join(steps)}
|
|
330
|
+
|
|
331
|
+
## Confidence
|
|
332
|
+
{candidate.avg_confidence:.0%}
|
|
333
|
+
|
|
334
|
+
## Evolved From
|
|
335
|
+
{chr(10).join(f'- {i.name} ({i.confidence:.0%})' for i in candidate.instincts)}
|
|
336
|
+
"""
|
|
337
|
+
|
|
338
|
+
return EvolvedStructure(
|
|
339
|
+
name=candidate.name,
|
|
340
|
+
type="agent",
|
|
341
|
+
description=f"Multi-step agent for {candidate.domains[0].value}",
|
|
342
|
+
instincts=[i.id for i in candidate.instincts],
|
|
343
|
+
content=content,
|
|
344
|
+
scope=candidate.scope,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def get_evolution_summary(self) -> Dict:
|
|
348
|
+
"""Obtiene un resumen de posibles evoluciones."""
|
|
349
|
+
candidates = self.analyze()
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
"skill_candidates": len([c for c in candidates if c.type == "skill"]),
|
|
353
|
+
"command_candidates": len([c for c in candidates if c.type == "command"]),
|
|
354
|
+
"agent_candidates": len([c for c in candidates if c.type == "agent"]),
|
|
355
|
+
"total_candidates": len(candidates),
|
|
356
|
+
"avg_confidence": sum(c.avg_confidence for c in candidates) / len(candidates) if candidates else 0,
|
|
357
|
+
"top_candidates": [
|
|
358
|
+
{"name": c.name, "type": c.type, "confidence": c.avg_confidence}
|
|
359
|
+
for c in candidates[:5]
|
|
360
|
+
],
|
|
361
|
+
}
|