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
hanus/hooks/manager.py ADDED
@@ -0,0 +1,247 @@
1
+ # hanus/hooks/manager.py
2
+ """
3
+ Sistema de hooks de ciclo de vida.
4
+
5
+ Permite ejecutar callbacks en puntos específicos del flujo de ejecución.
6
+ """
7
+ from __future__ import annotations
8
+ import time
9
+ from dataclasses import dataclass, field
10
+ from typing import Dict, List, Optional, Any, Callable
11
+ from enum import Enum
12
+ from collections import defaultdict
13
+ import threading
14
+
15
+
16
+ class HookType(Enum):
17
+ """Tipos de hooks disponibles."""
18
+ # File operations
19
+ PRE_READ = "pre_read"
20
+ POST_READ = "post_read"
21
+ PRE_WRITE = "pre_write"
22
+ POST_WRITE = "post_write"
23
+ PRE_EDIT = "pre_edit"
24
+ POST_EDIT = "post_edit"
25
+
26
+ # Command execution
27
+ PRE_COMMAND = "pre_command"
28
+ POST_COMMAND = "post_command"
29
+
30
+ # Tool execution
31
+ PRE_TOOL = "pre_tool"
32
+ POST_TOOL = "post_tool"
33
+
34
+ # Error handling
35
+ ON_ERROR = "on_error"
36
+
37
+ # Session lifecycle
38
+ ON_SESSION_START = "on_session_start"
39
+ ON_SESSION_END = "on_session_end"
40
+
41
+ # Plugin lifecycle
42
+ ON_PLUGIN_LOAD = "on_plugin_load"
43
+ ON_PLUGIN_UNLOAD = "on_plugin_unload"
44
+
45
+
46
+ @dataclass
47
+ class HookContext:
48
+ """Contexto pasado a los hooks."""
49
+ hook_type: HookType
50
+ timestamp: float
51
+ data: Dict[str, Any] = field(default_factory=dict)
52
+ result: Any = None
53
+ error: Optional[str] = None
54
+
55
+ def to_dict(self) -> Dict:
56
+ return {
57
+ "hook_type": self.hook_type.value,
58
+ "timestamp": self.timestamp,
59
+ "data": self.data,
60
+ "result": str(self.result)[:200] if self.result else None,
61
+ "error": self.error,
62
+ }
63
+
64
+
65
+ @dataclass
66
+ class HookRegistration:
67
+ """Registro de un hook."""
68
+ hook_type: HookType
69
+ callback: Callable
70
+ priority: int = 0 # Lower = runs first
71
+ name: str = ""
72
+ enabled: bool = True
73
+
74
+
75
+ class HookManager:
76
+ """
77
+ Gestiona hooks de ciclo de vida.
78
+
79
+ Uso:
80
+ hooks = HookManager()
81
+
82
+ @hooks.register(HookType.PRE_WRITE)
83
+ def my_hook(ctx):
84
+ print(f"About to write: {ctx.data['path']}")
85
+
86
+ # O usando decorador
87
+ @hook(HookType.POST_COMMAND)
88
+ def log_command(ctx):
89
+ print(f"Command executed: {ctx.data['command']}")
90
+ """
91
+
92
+ def __init__(self):
93
+ self._hooks: Dict[HookType, List[HookRegistration]] = defaultdict(list)
94
+ self._lock = threading.RLock()
95
+ self._hook_counter = 0
96
+
97
+ def register(
98
+ self,
99
+ hook_type: HookType,
100
+ callback: Callable,
101
+ priority: int = 0,
102
+ name: str = ""
103
+ ) -> str:
104
+ """
105
+ Registra un hook.
106
+
107
+ Args:
108
+ hook_type: Tipo de hook
109
+ callback: Función a ejecutar
110
+ priority: Prioridad (menor = se ejecuta antes)
111
+ name: Nombre opcional
112
+
113
+ Returns:
114
+ ID del hook registrado
115
+ """
116
+ with self._lock:
117
+ self._hook_counter += 1
118
+ hook_id = f"hook_{self._hook_counter}"
119
+
120
+ registration = HookRegistration(
121
+ hook_type=hook_type,
122
+ callback=callback,
123
+ priority=priority,
124
+ name=name or callback.__name__,
125
+ )
126
+
127
+ self._hooks[hook_type].append(registration)
128
+ # Ordenar por prioridad
129
+ self._hooks[hook_type].sort(key=lambda h: h.priority)
130
+
131
+ return hook_id
132
+
133
+ def unregister(self, hook_id: str) -> bool:
134
+ """Elimina un hook por ID."""
135
+ # Simplificado - necesitaríamos tracking de IDs
136
+ return False
137
+
138
+ def trigger(
139
+ self,
140
+ hook_type: HookType,
141
+ data: Dict[str, Any] = None,
142
+ result: Any = None,
143
+ error: str = None
144
+ ) -> List[Any]:
145
+ """
146
+ Dispara todos los hooks de un tipo.
147
+
148
+ Args:
149
+ hook_type: Tipo de hook a disparar
150
+ data: Datos del contexto
151
+ result: Resultado (si aplica)
152
+ error: Error (si aplica)
153
+
154
+ Returns:
155
+ Lista de resultados de los hooks
156
+ """
157
+ context = HookContext(
158
+ hook_type=hook_type,
159
+ timestamp=time.time(),
160
+ data=data or {},
161
+ result=result,
162
+ error=error,
163
+ )
164
+
165
+ results = []
166
+
167
+ for registration in self._hooks.get(hook_type, []):
168
+ if not registration.enabled:
169
+ continue
170
+
171
+ try:
172
+ result = registration.callback(context)
173
+ results.append(result)
174
+ except Exception as e:
175
+ # No propagar errores de hooks
176
+ print(f"[HookManager] Error in hook {registration.name}: {e}")
177
+
178
+ return results
179
+
180
+ def trigger_sync(
181
+ self,
182
+ hook_type: HookType,
183
+ data: Dict[str, Any] = None
184
+ ) -> None:
185
+ """Dispara hooks sincrónicamente."""
186
+ self.trigger(hook_type, data)
187
+
188
+ def enable(self, name: str) -> None:
189
+ """Habilita un hook por nombre."""
190
+ for hooks in self._hooks.values():
191
+ for h in hooks:
192
+ if h.name == name:
193
+ h.enabled = True
194
+
195
+ def disable(self, name: str) -> None:
196
+ """Deshabilita un hook por nombre."""
197
+ for hooks in self._hooks.values():
198
+ for h in hooks:
199
+ if h.name == name:
200
+ h.enabled = False
201
+
202
+ def list_hooks(self) -> List[Dict]:
203
+ """Lista todos los hooks registrados."""
204
+ result = []
205
+ for hook_type, registrations in self._hooks.items():
206
+ for r in registrations:
207
+ result.append({
208
+ "type": hook_type.value,
209
+ "name": r.name,
210
+ "priority": r.priority,
211
+ "enabled": r.enabled,
212
+ })
213
+ return result
214
+
215
+ def clear(self, hook_type: Optional[HookType] = None) -> None:
216
+ """Limpia hooks."""
217
+ if hook_type:
218
+ self._hooks[hook_type] = []
219
+ else:
220
+ self._hooks.clear()
221
+
222
+
223
+ def hook(hook_type: HookType, priority: int = 0):
224
+ """
225
+ Decorador para registrar hooks fácilmente.
226
+
227
+ Uso:
228
+ @hook(HookType.PRE_COMMAND)
229
+ def my_hook(ctx):
230
+ print(f"Before: {ctx.data}")
231
+ """
232
+ def decorator(func: Callable) -> Callable:
233
+ # Registrar cuando se define
234
+ get_hook_manager().register(hook_type, func, priority)
235
+ return func
236
+ return decorator
237
+
238
+
239
+ # Instancia global
240
+ _manager: Optional[HookManager] = None
241
+
242
+
243
+ def get_hook_manager() -> HookManager:
244
+ global _manager
245
+ if _manager is None:
246
+ _manager = HookManager()
247
+ return _manager
@@ -0,0 +1,44 @@
1
+ # hanus/instincts/__init__.py
2
+ """
3
+ Sistema de Instintos - Continuous Learning.
4
+
5
+ Los instintos son comportamientos aprendidos automáticamente desde
6
+ sesiones de interacción, con scoring de confianza y scope por proyecto.
7
+
8
+ Comandos:
9
+ - instinct-status: Ver instintos aprendidos
10
+ - instinct-import: Importar instintos
11
+ - instinct-export: Exportar instintos
12
+ - evolve: Evolucionar instintos a skills/comandos/agentes
13
+ """
14
+ from __future__ import annotations
15
+
16
+ from hanus.instincts.types import (
17
+ Instinct,
18
+ InstinctScope,
19
+ InstinctStatus,
20
+ InstinctDomain,
21
+ InstinctEvidence,
22
+ InstinctGroup,
23
+ ProjectInfo,
24
+ )
25
+ from hanus.instincts.manager import (
26
+ InstinctManager,
27
+ get_instinct_manager,
28
+ )
29
+ from hanus.instincts.detector import PatternDetector
30
+ from hanus.instincts.evolver import InstinctEvolver
31
+
32
+ __all__ = [
33
+ 'Instinct',
34
+ 'InstinctScope',
35
+ 'InstinctStatus',
36
+ 'InstinctDomain',
37
+ 'InstinctEvidence',
38
+ 'InstinctGroup',
39
+ 'ProjectInfo',
40
+ 'InstinctManager',
41
+ 'get_instinct_manager',
42
+ 'PatternDetector',
43
+ 'InstinctEvolver',
44
+ ]
hanus/instincts/cli.py ADDED
@@ -0,0 +1,372 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CLI para el sistema de instintos.
4
+
5
+ Comandos:
6
+ - instinct-status: Muestra instintos aprendidos
7
+ - instinct-import: Importa instintos desde archivo/URL
8
+ - instinct-export: Exporta instintos a archivo
9
+ - evolve: Analiza y evoluciona instintos
10
+ """
11
+ from __future__ import annotations
12
+ import argparse
13
+ import json
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import Optional
17
+
18
+ from hanus.instincts.manager import get_instinct_manager, InstinctManager
19
+ from hanus.instincts.evolver import InstinctEvolver
20
+ from hanus.instincts.types import InstinctScope, InstinctDomain
21
+
22
+
23
+ def cmd_status(args):
24
+ """Muestra el estado de los instintos."""
25
+ manager = get_instinct_manager()
26
+ manager.load_all()
27
+
28
+ stats = manager.get_stats()
29
+ all_instincts = manager.get_all_instincts()
30
+
31
+ # Header
32
+ print("=" * 60)
33
+ print(f" INSTINCT STATUS - {stats['total']} total")
34
+ print("=" * 60)
35
+ print()
36
+ print(f" Project: {stats['project_info']['name']} ({stats['project_info']['id'][:8]}...)")
37
+ print(f" Project instincts: {stats['project']}")
38
+ print(f" Global instincts: {stats['global']}")
39
+ print()
40
+
41
+ # Agrupar por dominio
42
+ by_domain = manager.group_by_domain()
43
+
44
+ if not by_domain:
45
+ print(" No instincts learned yet.")
46
+ return 0
47
+
48
+ # Mostrar instintos del proyecto primero
49
+ project_instincts = [i for i in all_instincts.values()
50
+ if i.scope == InstinctScope.PROJECT]
51
+ if project_instincts:
52
+ print(f"## PROJECT-SCOPED ({stats['project_info']['name']})")
53
+ _print_instincts_by_domain(project_instincts, "project")
54
+ print()
55
+
56
+ # Mostrar instintos globales
57
+ global_instincts = [i for i in all_instincts.values()
58
+ if i.scope == InstinctScope.GLOBAL]
59
+ if global_instincts:
60
+ print("## GLOBAL (apply to all projects)")
61
+ _print_instincts_by_domain(global_instincts, "global")
62
+ print()
63
+
64
+ return 0
65
+
66
+
67
+ def _print_instincts_by_domain(instincts: list, scope: str):
68
+ """Imprime instintos agrupados por dominio."""
69
+ from collections import defaultdict
70
+
71
+ by_domain = defaultdict(list)
72
+ for i in instincts:
73
+ by_domain[i.domain.value].append(i)
74
+
75
+ for domain, domain_instincts in sorted(by_domain.items()):
76
+ print(f" ### {domain.upper()} ({len(domain_instincts)})")
77
+ for instinct in sorted(domain_instincts, key=lambda x: x.confidence, reverse=True):
78
+ confidence_bar = instinct.confidence_bar
79
+ print(f" {confidence_bar} {instinct.confidence:.0%} {instinct.name} [{scope}]")
80
+ print(f" trigger: {instinct.trigger}")
81
+ print()
82
+
83
+
84
+ def cmd_import(args):
85
+ """Importa instintos desde archivo o URL."""
86
+ manager = get_instinct_manager()
87
+
88
+ # Leer archivo o URL
89
+ if args.file.startswith("http"):
90
+ import urllib.request
91
+ try:
92
+ with urllib.request.urlopen(args.file, timeout=10) as response:
93
+ content = response.read().decode("utf-8")
94
+ except Exception as e:
95
+ print(f"Error fetching URL: {e}")
96
+ return 1
97
+ else:
98
+ file_path = Path(args.file)
99
+ if not file_path.exists():
100
+ print(f"File not found: {file_path}")
101
+ return 1
102
+ content = file_path.read_text(encoding="utf-8")
103
+
104
+ # Parsear JSON
105
+ try:
106
+ data = json.loads(content)
107
+ except json.JSONDecodeError as e:
108
+ print(f"Invalid JSON: {e}")
109
+ return 1
110
+
111
+ # Determinar scope
112
+ scope = InstinctScope.PROJECT if args.scope == "project" else InstinctScope.GLOBAL
113
+
114
+ # Importar
115
+ if args.dry_run:
116
+ print("DRY RUN - Would import:")
117
+ for i in data.get("instincts", []):
118
+ print(f" - {i.get('name', 'unknown')} ({i.get('confidence', 0):.0%})")
119
+ return 0
120
+
121
+ imported, messages = manager.import_instincts(
122
+ data,
123
+ scope=scope,
124
+ force=args.force,
125
+ min_confidence=args.min_confidence
126
+ )
127
+
128
+ print(f"Imported {imported} instincts:")
129
+ for msg in messages:
130
+ print(f" {msg}")
131
+
132
+ return 0 if imported > 0 else 1
133
+
134
+
135
+ def cmd_export(args):
136
+ """Exporta instintos a archivo."""
137
+ manager = get_instinct_manager()
138
+
139
+ # Determinar scope
140
+ scope = None
141
+ if args.scope == "global":
142
+ scope = InstinctScope.GLOBAL
143
+ elif args.scope == "project":
144
+ scope = InstinctScope.PROJECT
145
+
146
+ # Filtrar dominios
147
+ domains = None
148
+ if args.domains:
149
+ domains = [InstinctDomain(d) for d in args.domains.split(",")]
150
+
151
+ # Exportar
152
+ data = manager.export_instincts(
153
+ scope=scope,
154
+ min_confidence=args.min_confidence,
155
+ domains=domains
156
+ )
157
+
158
+ # Escribir archivo
159
+ output_path = Path(args.output) if args.output else Path("instincts.json")
160
+ output_path.write_text(
161
+ json.dumps(data, indent=2, ensure_ascii=False),
162
+ encoding="utf-8"
163
+ )
164
+
165
+ print(f"Exported {len(data['instincts'])} instincts to {output_path}")
166
+ return 0
167
+
168
+
169
+ def cmd_evolve(args):
170
+ """Analiza instintos y sugiere evoluciones."""
171
+ manager = get_instinct_manager()
172
+ manager.load_all()
173
+
174
+ evolver = InstinctEvolver(manager)
175
+ candidates = evolver.analyze(min_confidence=args.min_confidence)
176
+
177
+ stats = manager.get_stats()
178
+
179
+ # Header
180
+ print("=" * 60)
181
+ print(f" EVOLVE ANALYSIS - {stats['total']} instincts")
182
+ print(f" Project: {stats['project_info']['name']} ({stats['project_info']['id'][:8]}...)")
183
+ print(f" Project-scoped: {stats['project']} | Global: {stats['global']}")
184
+ print("=" * 60)
185
+ print()
186
+
187
+ high_conf = [c for c in candidates if c.avg_confidence >= 0.8]
188
+ print(f"High confidence instincts (>=80%): {len(high_conf)}")
189
+ print()
190
+
191
+ # Mostrar candidatos por tipo
192
+ skill_candidates = [c for c in candidates if c.type == "skill"]
193
+ command_candidates = [c for c in candidates if c.type == "command"]
194
+ agent_candidates = [c for c in candidates if c.type == "agent"]
195
+
196
+ if skill_candidates:
197
+ print("## SKILL CANDIDATES")
198
+ for i, c in enumerate(skill_candidates[:5], 1):
199
+ print(f" {i}. Cluster: \"{c.name}\"")
200
+ print(f" Instincts: {len(c.instincts)}")
201
+ print(f" Avg confidence: {c.avg_confidence:.0%}")
202
+ print(f" Domains: {', '.join(d.value for d in c.domains)}")
203
+ print(f" Reason: {c.reason}")
204
+ print()
205
+
206
+ if command_candidates:
207
+ print("## COMMAND CANDIDATES")
208
+ for i, c in enumerate(command_candidates[:5], 1):
209
+ print(f" /{c.name}")
210
+ print(f" From: {', '.join(i.name for i in c.instincts[:3])}")
211
+ print(f" Confidence: {c.avg_confidence:.0%}")
212
+ print()
213
+
214
+ if agent_candidates:
215
+ print("## AGENT CANDIDATES")
216
+ for i, c in enumerate(agent_candidates[:3], 1):
217
+ print(f" {c.name}-agent")
218
+ print(f" Covers {len(c.instincts)} instincts")
219
+ print(f" Avg confidence: {c.avg_confidence:.0%}")
220
+ print()
221
+
222
+ # Generar archivos si --generate
223
+ if args.generate:
224
+ evolved_dir = manager.project_instincts_dir / "evolved"
225
+ evolved_dir.mkdir(parents=True, exist_ok=True)
226
+
227
+ generated = 0
228
+ for candidate in candidates[:args.max_generate]:
229
+ structure = evolver.evolve(candidate)
230
+
231
+ # Determinar subdirectorio
232
+ subdir = evolved_dir / f"{structure.type}s"
233
+ subdir.mkdir(parents=True, exist_ok=True)
234
+
235
+ # Escribir archivo
236
+ file_path = subdir / f"{structure.name}.md"
237
+ file_path.write_text(structure.content, encoding="utf-8")
238
+
239
+ # Marcar instintos como evolucionados
240
+ for instinct_id in structure.instincts:
241
+ instinct = manager.get_instinct(instinct_id)
242
+ if instinct:
243
+ instinct.status = InstinctStatus.EVOLVED
244
+ instinct.evolved_to = structure.name
245
+ manager.update_instinct(instinct)
246
+
247
+ print(f" Generated: {structure.type}/{structure.name}.md")
248
+ generated += 1
249
+
250
+ print(f"\nGenerated {generated} structures in {evolved_dir}")
251
+
252
+ return 0
253
+
254
+
255
+ def cmd_promote(args):
256
+ """Promueve instintos del proyecto a global."""
257
+ manager = get_instinct_manager()
258
+ manager.load_all()
259
+
260
+ # Obtener instintos del proyecto
261
+ project_instincts = [i for i in manager._project_instincts.values()
262
+ if i.confidence >= args.min_confidence]
263
+
264
+ if not project_instincts:
265
+ print("No project instincts meet the confidence threshold.")
266
+ return 1
267
+
268
+ promoted = 0
269
+ for instinct in project_instincts:
270
+ if args.dry_run:
271
+ print(f"Would promote: {instinct.name} ({instinct.confidence:.0%})")
272
+ continue
273
+
274
+ # Cambiar scope
275
+ instinct.scope = InstinctScope.GLOBAL
276
+ instinct.project_id = None
277
+
278
+ # Mover a global
279
+ manager._global_instincts[instinct.id] = instinct
280
+ del manager._project_instincts[instinct.id]
281
+ manager._save_instinct(instinct, manager.global_dir)
282
+
283
+ print(f"Promoted: {instinct.name}")
284
+ promoted += 1
285
+
286
+ if args.dry_run:
287
+ print(f"Would promote {len(project_instincts)} instincts")
288
+ else:
289
+ print(f"Promoted {promoted} instincts to global scope")
290
+
291
+ return 0
292
+
293
+
294
+ def main():
295
+ parser = argparse.ArgumentParser(
296
+ description="Hanuscode Instincts CLI",
297
+ formatter_class=argparse.RawDescriptionHelpFormatter
298
+ )
299
+
300
+ subparsers = parser.add_subparsers(dest="command", help="Command to run")
301
+
302
+ # instinct-status
303
+ status_parser = subparsers.add_parser(
304
+ "status",
305
+ help="Show learned instincts with confidence"
306
+ )
307
+ status_parser.set_defaults(func=cmd_status)
308
+
309
+ # instinct-import
310
+ import_parser = subparsers.add_parser(
311
+ "import",
312
+ help="Import instincts from file or URL"
313
+ )
314
+ import_parser.add_argument("file", help="File path or URL to import")
315
+ import_parser.add_argument("--scope", choices=["project", "global"],
316
+ default="project", help="Target scope")
317
+ import_parser.add_argument("--dry-run", action="store_true",
318
+ help="Preview without importing")
319
+ import_parser.add_argument("--force", action="store_true",
320
+ help="Skip confirmation prompts")
321
+ import_parser.add_argument("--min-confidence", type=float, default=0.5,
322
+ help="Minimum confidence threshold")
323
+ import_parser.set_defaults(func=cmd_import)
324
+
325
+ # instinct-export
326
+ export_parser = subparsers.add_parser(
327
+ "export",
328
+ help="Export instincts to file"
329
+ )
330
+ export_parser.add_argument("--output", "-o", help="Output file path")
331
+ export_parser.add_argument("--scope", choices=["project", "global", "all"],
332
+ default="all", help="Scope to export")
333
+ export_parser.add_argument("--domains", help="Comma-separated domains")
334
+ export_parser.add_argument("--min-confidence", type=float, default=0.5,
335
+ help="Minimum confidence threshold")
336
+ export_parser.set_defaults(func=cmd_export)
337
+
338
+ # evolve
339
+ evolve_parser = subparsers.add_parser(
340
+ "evolve",
341
+ help="Analyze instincts and suggest evolutions"
342
+ )
343
+ evolve_parser.add_argument("--generate", action="store_true",
344
+ help="Generate evolved files")
345
+ evolve_parser.add_argument("--min-confidence", type=float, default=0.7,
346
+ help="Minimum confidence for candidates")
347
+ evolve_parser.add_argument("--max-generate", type=int, default=10,
348
+ help="Maximum structures to generate")
349
+ evolve_parser.set_defaults(func=cmd_evolve)
350
+
351
+ # promote
352
+ promote_parser = subparsers.add_parser(
353
+ "promote",
354
+ help="Promote project instincts to global scope"
355
+ )
356
+ promote_parser.add_argument("--min-confidence", type=float, default=0.8,
357
+ help="Minimum confidence to promote")
358
+ promote_parser.add_argument("--dry-run", action="store_true",
359
+ help="Preview without promoting")
360
+ promote_parser.set_defaults(func=cmd_promote)
361
+
362
+ args = parser.parse_args()
363
+
364
+ if args.command is None:
365
+ parser.print_help()
366
+ return 1
367
+
368
+ return args.func(args)
369
+
370
+
371
+ if __name__ == "__main__":
372
+ sys.exit(main())