thinker-research 0.1.3__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.
- code_design_improvement_tools.py +807 -0
- deep_research_system.py +762 -0
- evaluar_proyecto.py +1121 -0
- evidence.py +93 -0
- llm_providers.py +400 -0
- pensamiento_layer.py +452 -0
- plugins/__init__.py +16 -0
- plugins/cli.py +231 -0
- plugins/dashboard.py +128 -0
- plugins/interfaces.py +168 -0
- plugins/llm_providers/deepseek_plugin.py +74 -0
- plugins/llm_providers/mock_llm_plugin.py +54 -0
- plugins/llm_providers/openrouter_plugin.py +74 -0
- plugins/registry.py +423 -0
- plugins/search_providers/duckduckgo_plugin.py +60 -0
- plugins/search_providers/mock_search_plugin.py +58 -0
- plugins/search_providers/wikipedia_plugin.py +62 -0
- scripts/preflight.py +398 -0
- scripts/probe_model.py +114 -0
- scripts/sbom_fallback.py +62 -0
- thinker_research-0.1.3.dist-info/METADATA +265 -0
- thinker_research-0.1.3.dist-info/RECORD +28 -0
- thinker_research-0.1.3.dist-info/WHEEL +5 -0
- thinker_research-0.1.3.dist-info/entry_points.txt +5 -0
- thinker_research-0.1.3.dist-info/licenses/LICENSE +674 -0
- thinker_research-0.1.3.dist-info/top_level.txt +10 -0
- thinking_engine.py +655 -0
- web_search.py +240 -0
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Herramientas para Diseño y Mejora de Proyectos de Código
|
|
4
|
+
==========================================================
|
|
5
|
+
|
|
6
|
+
Aplicando los principios de eficiencia ("hacer las cosas de manera eficiente"),
|
|
7
|
+
la capa de pensamiento estructurado y el sistema de toma de decisiones autónomas
|
|
8
|
+
para el diseño, análisis y mejora de proyectos de software.
|
|
9
|
+
|
|
10
|
+
Principios clave:
|
|
11
|
+
1. Eficiencia - Minimizar redundancia, maximizar mantenibilidad
|
|
12
|
+
2. Pensamiento estructurado - Razonamiento en pasos válidos y verificables
|
|
13
|
+
3. Decisión autónoma - El sistema sugiere, el humano decide
|
|
14
|
+
4. Validación antes de actuar - No "prueba y error" repetitivo
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import ast
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
import time
|
|
21
|
+
from abc import abstractmethod
|
|
22
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
23
|
+
|
|
24
|
+
from thinking_engine import StepThinkingEngine
|
|
25
|
+
|
|
26
|
+
# Orden de severidad para priorizar issues (menor = más urgente)
|
|
27
|
+
SEVERITY_ORDER = {"critical": 0, "high": 1, "medium": 2, "low": 3}
|
|
28
|
+
|
|
29
|
+
# Referencia única del "score de eficiencia": issues detectados / este valor,
|
|
30
|
+
# acotado a 1. Se usa en el contexto y en el analizador para no tener dos
|
|
31
|
+
# definiciones distintas del mismo concepto (antes: /50 y /30).
|
|
32
|
+
EFFICIENCY_REFERENCE_ISSUES = 30
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CodeAnalysisContext:
|
|
36
|
+
"""Contexto que mantiene el estado del análisis de código."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, project_path: str, max_issues: int = 20):
|
|
39
|
+
self.project_path = project_path
|
|
40
|
+
self.max_issues = max_issues
|
|
41
|
+
self.issues: List[Dict[str, Any]] = []
|
|
42
|
+
self.metrics: Dict[str, Any] = {}
|
|
43
|
+
self.design_decisions: List[Dict[str, Any]] = []
|
|
44
|
+
self.start_time = time.time()
|
|
45
|
+
self.thinking_steps: List[Dict[str, Any]] = []
|
|
46
|
+
|
|
47
|
+
def add_issue(self, category: str, severity: str, description: str,
|
|
48
|
+
line: Optional[int] = None, file: Optional[str] = None,
|
|
49
|
+
suggestion: Optional[str] = None, thinking_reasoning: str = ""):
|
|
50
|
+
issue = {
|
|
51
|
+
"category": category,
|
|
52
|
+
"severity": severity, # "critical", "high", "medium", "low"
|
|
53
|
+
"description": description,
|
|
54
|
+
"file": file or "unknown",
|
|
55
|
+
"line": line,
|
|
56
|
+
"suggestion": suggestion,
|
|
57
|
+
"thinking_reasoning": thinking_reasoning,
|
|
58
|
+
"detected_at": time.time()
|
|
59
|
+
}
|
|
60
|
+
self.issues.append(issue)
|
|
61
|
+
# Mantener solo los max_issues más críticos
|
|
62
|
+
self.issues.sort(key=lambda x: (SEVERITY_ORDER.get(x["severity"], 99), x["detected_at"]))
|
|
63
|
+
if len(self.issues) > self.max_issues:
|
|
64
|
+
self.issues = self.issues[:self.max_issues]
|
|
65
|
+
|
|
66
|
+
def add_thinking_step(self, step: str, reasoning: str, result: Any):
|
|
67
|
+
self.thinking_steps.append({
|
|
68
|
+
"step": step,
|
|
69
|
+
"reasoning": reasoning,
|
|
70
|
+
"result": result,
|
|
71
|
+
"timestamp": time.time()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
75
|
+
return {
|
|
76
|
+
"project_path": self.project_path,
|
|
77
|
+
"total_issues": len(self.issues),
|
|
78
|
+
"issue_breakdown": {
|
|
79
|
+
"critical": sum(1 for i in self.issues if i["severity"] == "critical"),
|
|
80
|
+
"high": sum(1 for i in self.issues if i["severity"] == "high"),
|
|
81
|
+
"medium": sum(1 for i in self.issues if i["severity"] == "medium"),
|
|
82
|
+
"low": sum(1 for i in self.issues if i["severity"] == "low")
|
|
83
|
+
},
|
|
84
|
+
"issues": self.issues,
|
|
85
|
+
"metrics": self.metrics,
|
|
86
|
+
"thinking_steps": len(self.thinking_steps),
|
|
87
|
+
"efficiency_score": self._compute_efficiency_score()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
def _compute_efficiency_score(self) -> float:
|
|
91
|
+
"""Cobertura del análisis: issues detectados / referencia, acotado a 1.
|
|
92
|
+
|
|
93
|
+
Es una medida heurística de cuánto se ha inspeccionado, no de calidad
|
|
94
|
+
del código: más issues detectados suben el score.
|
|
95
|
+
"""
|
|
96
|
+
if not self.project_path:
|
|
97
|
+
return 0.0
|
|
98
|
+
return min(round(len(self.issues) / EFFICIENCY_REFERENCE_ISSUES, 2), 1.0)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ThinkingDecisionEngine(StepThinkingEngine):
|
|
102
|
+
"""Motor de decisiones con capa de pensamiento estructurado.
|
|
103
|
+
|
|
104
|
+
Reutiliza el validador, el cálculo de confianza y la extracción de
|
|
105
|
+
decisión comunes (`thinking_engine.StepThinkingEngine`); aquí sólo se
|
|
106
|
+
añade la interfaz `decide(context, prompt)`.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def __init__(self, name: str = "thinking_decision_engine", llm_provider=None,
|
|
110
|
+
max_steps: int = 7):
|
|
111
|
+
super().__init__(llm_provider, name, max_steps=max_steps)
|
|
112
|
+
self.max_thinking_steps = max_steps
|
|
113
|
+
|
|
114
|
+
def decide(self, context: CodeAnalysisContext, prompt: str,
|
|
115
|
+
progress=None) -> Dict[str, Any]:
|
|
116
|
+
"""Ejecuta un ciclo de pensamiento para tomar una decisión de diseño."""
|
|
117
|
+
self.step_count = 0
|
|
118
|
+
return self._execute_thinking_cycle(context, prompt, progress)
|
|
119
|
+
|
|
120
|
+
@abstractmethod
|
|
121
|
+
def _execute_thinking_cycle(self, context: CodeAnalysisContext, prompt: str,
|
|
122
|
+
progress=None) -> Dict[str, Any]:
|
|
123
|
+
pass # pragma: no cover - cuerpo abstracto, nunca se ejecuta
|
|
124
|
+
|
|
125
|
+
def _validate_thought(self, thought: str) -> bool:
|
|
126
|
+
"""Compatibilidad: delega en el validador común."""
|
|
127
|
+
return self.validate_step(thought)
|
|
128
|
+
|
|
129
|
+
# Este motor define su propio ciclo, así que no aporta constructores.
|
|
130
|
+
def _step_builders(self):
|
|
131
|
+
return []
|
|
132
|
+
|
|
133
|
+
def _render_thought(self, steps):
|
|
134
|
+
return ""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class CodeDesignThinker(ThinkingDecisionEngine):
|
|
138
|
+
"""
|
|
139
|
+
Thinker para decisiones de diseño de código.
|
|
140
|
+
Implementa 7 pasos estructurados para evitar decisiones precipitadas.
|
|
141
|
+
|
|
142
|
+
Sin proveedor LLM usa MockLLM (offline, declarado como simulado).
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
# Orden canónico de los pasos del ciclo (índice → número de paso).
|
|
146
|
+
_NOMBRES_PASOS = ("analysis", "objectives", "design_options", "evaluation",
|
|
147
|
+
"selection", "implementation_plan", "validation")
|
|
148
|
+
|
|
149
|
+
_ETIQUETAS_PASOS = ("ANÁLISIS", "OBJETIVOS", "OPCIONES", "EVALUACIÓN",
|
|
150
|
+
"SELECCIÓN", "PLAN", "VALIDACIÓN")
|
|
151
|
+
|
|
152
|
+
def _resultado_valido(self, steps: List[Tuple[str, str]]) -> Dict[str, Any]:
|
|
153
|
+
self.step_count = len(steps)
|
|
154
|
+
return {
|
|
155
|
+
"thought": self._render_steps(steps),
|
|
156
|
+
"decision": self._extract_decision(steps),
|
|
157
|
+
"confidence": self._compute_confidence(steps),
|
|
158
|
+
"valid": True,
|
|
159
|
+
"invalid_steps": [],
|
|
160
|
+
"steps": steps,
|
|
161
|
+
"step_count": len(steps),
|
|
162
|
+
"error": None,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
def _resultado_invalido(self, steps: List[Tuple[str, str]],
|
|
166
|
+
nombre: str) -> Dict[str, Any]:
|
|
167
|
+
numero = self._NOMBRES_PASOS.index(nombre) + 1
|
|
168
|
+
self.step_count = len(steps)
|
|
169
|
+
return {
|
|
170
|
+
"thought": self._render_steps(steps),
|
|
171
|
+
"decision": self._extract_decision(steps),
|
|
172
|
+
"confidence": self._compute_confidence(steps),
|
|
173
|
+
"valid": False,
|
|
174
|
+
"invalid_steps": [nombre],
|
|
175
|
+
"steps": steps,
|
|
176
|
+
"step_count": len(steps),
|
|
177
|
+
"error": f"Paso {numero} inválido: {nombre}",
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
def _render_steps(self, steps: List[Tuple[str, str]]) -> str:
|
|
181
|
+
if not steps:
|
|
182
|
+
return ""
|
|
183
|
+
lines = [
|
|
184
|
+
f" CAPA DE PENSAMIENTO DISEÑO: {self.name}",
|
|
185
|
+
" ========================================",
|
|
186
|
+
"",
|
|
187
|
+
]
|
|
188
|
+
for i, (nombre, contenido) in enumerate(steps):
|
|
189
|
+
etiqueta = self._ETIQUETAS_PASOS[self._NOMBRES_PASOS.index(nombre)]
|
|
190
|
+
lines.append(f" PASO {i + 1} - {etiqueta}: {contenido[:150]}")
|
|
191
|
+
lines.extend([
|
|
192
|
+
"",
|
|
193
|
+
f" PASOS COMPLETADOS: {len(steps)}/{self.max_thinking_steps}",
|
|
194
|
+
f" DECISIÓN FINAL: {self._extract_decision(steps)}",
|
|
195
|
+
f" CONFIANZA: {self._compute_confidence(steps)}",
|
|
196
|
+
])
|
|
197
|
+
return "\n".join(lines)
|
|
198
|
+
|
|
199
|
+
def _registrar(self, steps: List[Tuple[str, str]], nombre: str, contenido: str,
|
|
200
|
+
progress) -> Optional[Dict[str, Any]]:
|
|
201
|
+
"""Valida y registra un paso; devuelve el cierre si hay que parar.
|
|
202
|
+
|
|
203
|
+
Validación antes de avanzar (regla 2 del proyecto): un paso inválido
|
|
204
|
+
detiene el ciclo en el momento, no al final. `max_steps` acota de
|
|
205
|
+
verdad el número de pasos ejecutados.
|
|
206
|
+
"""
|
|
207
|
+
if not self.validate_step(contenido):
|
|
208
|
+
return self._resultado_invalido(steps, nombre)
|
|
209
|
+
steps.append((nombre, contenido))
|
|
210
|
+
if progress:
|
|
211
|
+
progress(f"paso {len(steps)}/{self.max_thinking_steps}: {nombre}")
|
|
212
|
+
if len(steps) >= self.max_thinking_steps:
|
|
213
|
+
return self._resultado_valido(steps)
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
def _execute_thinking_cycle(self, context: CodeAnalysisContext, prompt: str,
|
|
217
|
+
progress=None) -> Dict[str, Any]:
|
|
218
|
+
"""Ciclo de 7 pasos de pensamiento para diseño de código."""
|
|
219
|
+
|
|
220
|
+
steps = []
|
|
221
|
+
|
|
222
|
+
# PASO 1: Análisis del código existente
|
|
223
|
+
step1 = self._llm_generate(f"""
|
|
224
|
+
PASO 1 - ANÁLISIS DE CÓDIGO EXISTENTE:
|
|
225
|
+
Analiza el código proporcionado. Identifica:
|
|
226
|
+
- Estructura general del proyecto
|
|
227
|
+
- Patrones de diseño utilizados
|
|
228
|
+
- Acoplamiento y cohesión
|
|
229
|
+
- Technical debt evidente
|
|
230
|
+
- Oportunidades de mejora
|
|
231
|
+
|
|
232
|
+
Código: {prompt}
|
|
233
|
+
|
|
234
|
+
Respuesta en JSON:
|
|
235
|
+
{{
|
|
236
|
+
"estructura": "descripción",
|
|
237
|
+
"patrones": ["..."],
|
|
238
|
+
"acoplamiento": "bajo/medio/alto",
|
|
239
|
+
"cohesion": "alta/media/baja",
|
|
240
|
+
"tech_debt": ["..."],
|
|
241
|
+
"mejora_oportunidades": ["..."]
|
|
242
|
+
}}
|
|
243
|
+
""")
|
|
244
|
+
|
|
245
|
+
fin = self._registrar(steps, "analysis", step1, progress)
|
|
246
|
+
if fin is not None:
|
|
247
|
+
return fin
|
|
248
|
+
|
|
249
|
+
# PASO 2: Definición de objetivos de mejora
|
|
250
|
+
step2 = self._llm_generate(f"""
|
|
251
|
+
PASO 2 - OBJETIVOS DE MEJORA:
|
|
252
|
+
Basado en el análisis, define objetivos específicos de mejora:
|
|
253
|
+
- Prioridades (seguridad, performance, maintainability, readability)
|
|
254
|
+
- Métricas objetivo
|
|
255
|
+
- Restricciones (tiempo, recursos, risk tolerance)
|
|
256
|
+
|
|
257
|
+
Análisis: {step1}
|
|
258
|
+
|
|
259
|
+
Objetivos en JSON:
|
|
260
|
+
{{
|
|
261
|
+
"prioridades": ["..."],
|
|
262
|
+
"metricas_objetivo": {{
|
|
263
|
+
"maintainability_index": 0-100,
|
|
264
|
+
"complexity_reduction": 0-100
|
|
265
|
+
}},
|
|
266
|
+
"restricciones": {{
|
|
267
|
+
"max_refactor_time": "horas",
|
|
268
|
+
"no_breaking_changes": true/false
|
|
269
|
+
}}
|
|
270
|
+
}}
|
|
271
|
+
""")
|
|
272
|
+
|
|
273
|
+
fin = self._registrar(steps, "objectives", step2, progress)
|
|
274
|
+
if fin is not None:
|
|
275
|
+
return fin
|
|
276
|
+
|
|
277
|
+
# PASO 3: Generación de opciones de diseño
|
|
278
|
+
step3 = self._llm_generate(f"""
|
|
279
|
+
PASO 3 - OPCIONES DE DISEÑO:
|
|
280
|
+
Genera opciones de diseño para lograr los objetivos.
|
|
281
|
+
|
|
282
|
+
Objetivos: {step2}
|
|
283
|
+
|
|
284
|
+
Para cada opción, proporciona:
|
|
285
|
+
- nombre: identificador único
|
|
286
|
+
- beneficios: lista de beneficios esperados
|
|
287
|
+
- costos: estimación de esfuerzo (person-horas)
|
|
288
|
+
- riesgos: posibles riesgos de implementación
|
|
289
|
+
|
|
290
|
+
Formato JSON:
|
|
291
|
+
[
|
|
292
|
+
{{
|
|
293
|
+
"nombre": "...",
|
|
294
|
+
"beneficios": ["..."],
|
|
295
|
+
"costos": "X person-horas",
|
|
296
|
+
"riesgos": ["..."]
|
|
297
|
+
}}
|
|
298
|
+
]
|
|
299
|
+
""")
|
|
300
|
+
|
|
301
|
+
fin = self._registrar(steps, "design_options", step3, progress)
|
|
302
|
+
if fin is not None:
|
|
303
|
+
return fin
|
|
304
|
+
|
|
305
|
+
# PASO 4: Evaluación de opciones
|
|
306
|
+
step4 = self._llm_generate(f"""
|
|
307
|
+
PASO 4 - EVALUACIÓN DE OPCIONES:
|
|
308
|
+
Evalúa cada opción contra los objetivos.
|
|
309
|
+
|
|
310
|
+
Opciones: {step3}
|
|
311
|
+
|
|
312
|
+
Para cada opción, determina:
|
|
313
|
+
- idoneidad: 0-1 (qué tan bien cumple los objetivos)
|
|
314
|
+
- complejidad: 0-1
|
|
315
|
+
- riesgo de regresión: 0-1
|
|
316
|
+
- relación beneficio/costo
|
|
317
|
+
|
|
318
|
+
JSON:
|
|
319
|
+
[
|
|
320
|
+
{{
|
|
321
|
+
"nombre": "...",
|
|
322
|
+
"idoneidad": 0-1,
|
|
323
|
+
"complejidad": 0-1,
|
|
324
|
+
"riesgo_regresion": 0-1,
|
|
325
|
+
"beneficio_costo_ratio": 0-1
|
|
326
|
+
}}
|
|
327
|
+
]
|
|
328
|
+
""")
|
|
329
|
+
|
|
330
|
+
fin = self._registrar(steps, "evaluation", step4, progress)
|
|
331
|
+
if fin is not None:
|
|
332
|
+
return fin
|
|
333
|
+
|
|
334
|
+
# PASO 5: Selección de enfoque
|
|
335
|
+
step5 = self._llm_generate(f"""
|
|
336
|
+
PASO 5 - SELECCIÓN DE ENFOQUE:
|
|
337
|
+
Selecciona la mejor opción o combina opciones.
|
|
338
|
+
|
|
339
|
+
Evaluaciones: {step4}
|
|
340
|
+
|
|
341
|
+
Decisión:
|
|
342
|
+
- enfoque_seleccionado: nombre de la opción elegida
|
|
343
|
+
- justificación: explicación breve
|
|
344
|
+
- siguiente_paso: qué hacer primero
|
|
345
|
+
|
|
346
|
+
JSON:
|
|
347
|
+
{{
|
|
348
|
+
"enfoque_seleccionado": "...",
|
|
349
|
+
"justificación": "...",
|
|
350
|
+
"siguiente_paso": "..."
|
|
351
|
+
}}
|
|
352
|
+
""")
|
|
353
|
+
|
|
354
|
+
fin = self._registrar(steps, "selection", step5, progress)
|
|
355
|
+
if fin is not None:
|
|
356
|
+
return fin
|
|
357
|
+
|
|
358
|
+
# PASO 6: Plan de implementación
|
|
359
|
+
step6 = self._llm_generate(f"""
|
|
360
|
+
PASO 6 - PLAN DE IMPLEMENTACIÓN:
|
|
361
|
+
Crea un plan detallado para implementar el enfoque seleccionado.
|
|
362
|
+
|
|
363
|
+
Enfoque: {step5}
|
|
364
|
+
|
|
365
|
+
Plan en JSON con:
|
|
366
|
+
- pasos: lista de acciones específicas
|
|
367
|
+
- responsables: quién ejecuta cada paso
|
|
368
|
+
- timeline: estimación de tiempo por paso
|
|
369
|
+
- checkpoints: puntos de validación/intermediarios
|
|
370
|
+
|
|
371
|
+
[
|
|
372
|
+
{{
|
|
373
|
+
"paso": "...",
|
|
374
|
+
"responsable": "rol",
|
|
375
|
+
"tiempo_estimado": "horas",
|
|
376
|
+
"checkpoint": "entregable o validación"
|
|
377
|
+
}}
|
|
378
|
+
]
|
|
379
|
+
""")
|
|
380
|
+
|
|
381
|
+
fin = self._registrar(steps, "implementation_plan", step6, progress)
|
|
382
|
+
if fin is not None:
|
|
383
|
+
return fin
|
|
384
|
+
|
|
385
|
+
# PASO 7: Verificación y validación
|
|
386
|
+
step7 = self._llm_generate(f"""
|
|
387
|
+
PASO 7 - VERIFICACIÓN Y VALIDACIÓN:
|
|
388
|
+
Define cómo verificar que la mejora fue exitosa.
|
|
389
|
+
|
|
390
|
+
Plan: {step6}
|
|
391
|
+
|
|
392
|
+
Criterios de éxito en JSON:
|
|
393
|
+
{{
|
|
394
|
+
"metricas_mejoradas": ["..."],
|
|
395
|
+
"umbral_exito": 0-1,
|
|
396
|
+
"pruebas_necesarias": ["..."],
|
|
397
|
+
"rollback_plan": "descripción si falla"
|
|
398
|
+
}}
|
|
399
|
+
""")
|
|
400
|
+
|
|
401
|
+
fin = self._registrar(steps, "validation", step7, progress)
|
|
402
|
+
if fin is not None:
|
|
403
|
+
return fin
|
|
404
|
+
|
|
405
|
+
return self._resultado_valido(steps)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class CodeImprovementOrchestrator:
|
|
409
|
+
"""
|
|
410
|
+
Orquestador que integra el pensamiento en la mejora de código.
|
|
411
|
+
Ejecuta el ciclo completo de análisis → diseño → mejora → validación.
|
|
412
|
+
"""
|
|
413
|
+
|
|
414
|
+
def __init__(self, thinking_engine: CodeDesignThinker, source_code: str = ""):
|
|
415
|
+
self.thinking = thinking_engine
|
|
416
|
+
self.source_code = source_code
|
|
417
|
+
self.context = CodeAnalysisContext("project_analysis")
|
|
418
|
+
self.results: Dict[str, Any] = {}
|
|
419
|
+
|
|
420
|
+
def run_analysis(self, progress=None) -> Dict[str, Any]:
|
|
421
|
+
"""Núcleo del análisis: SIN impresión, sólo lógica y resultados.
|
|
422
|
+
|
|
423
|
+
`progress` es un callback opcional (recibe el texto del paso) para que
|
|
424
|
+
la presentación sea responsabilidad del llamador. Así el núcleo es
|
|
425
|
+
comprobable de forma precisa (valores y efectos) sin capturar stdout.
|
|
426
|
+
"""
|
|
427
|
+
decir = progress or (lambda _mensaje: None)
|
|
428
|
+
|
|
429
|
+
# Análisis estático real del código fuente: alimenta el contexto
|
|
430
|
+
if self.source_code:
|
|
431
|
+
static = analyze_python_source(self.source_code)
|
|
432
|
+
if "syntax_error" in static:
|
|
433
|
+
# Sin sintaxis válida no hay análisis posible: se detiene y se
|
|
434
|
+
# reporta (antes seguía 7 fases sobre código roto y decía "0
|
|
435
|
+
# problemas"; error silencioso, P1.19/P1.26).
|
|
436
|
+
decir(f">>> Fuente con error de sintaxis: {static['syntax_error']}")
|
|
437
|
+
return {
|
|
438
|
+
"error": f"Fuente con error de sintaxis: {static['syntax_error']}",
|
|
439
|
+
"steps": [("fallido", static)],
|
|
440
|
+
}
|
|
441
|
+
for issue in static.get("metrics", {}).get("issues", []):
|
|
442
|
+
self.context.add_issue(**issue)
|
|
443
|
+
self.context.metrics = static.get("source_summary", {})
|
|
444
|
+
decir(f">>> Análisis estático: "
|
|
445
|
+
f"{self.context.to_dict()['total_issues']} issues detectados")
|
|
446
|
+
|
|
447
|
+
# UN ciclo de 7 pasos (antes se repetía el ciclo completo por fase:
|
|
448
|
+
# 7 decisiones × 7 pasos = 49 llamadas con prompts de relleno, lo
|
|
449
|
+
# contrario al principio rector de eficiencia).
|
|
450
|
+
decir(">>> Ciclo de pensamiento de diseño...")
|
|
451
|
+
ciclo = self.thinking.decide(self.context, self.source_code,
|
|
452
|
+
progress=lambda mensaje: decir(f" {mensaje}"))
|
|
453
|
+
decir(f" pasos={ciclo['step_count']} válido={ciclo['valid']}")
|
|
454
|
+
|
|
455
|
+
if not ciclo["valid"]:
|
|
456
|
+
return {
|
|
457
|
+
"error": f"El ciclo de diseño falló (paso inválido: "
|
|
458
|
+
f"{ciclo['invalid_steps'][0]})",
|
|
459
|
+
"steps": [("fallido", ciclo)],
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
# Mapear cada paso del ciclo único a su clave histórica de fase.
|
|
463
|
+
claves = {
|
|
464
|
+
"analysis": "analysis",
|
|
465
|
+
"objectives": "objectives",
|
|
466
|
+
"design_options": "design_options_considered",
|
|
467
|
+
"evaluation": "evaluations",
|
|
468
|
+
"selection": "selection",
|
|
469
|
+
"implementation_plan": "implementation_plan",
|
|
470
|
+
"validation": "validation_criteria",
|
|
471
|
+
}
|
|
472
|
+
resultados: Dict[str, Any] = {}
|
|
473
|
+
for nombre, contenido in ciclo["steps"]:
|
|
474
|
+
resultados[claves.get(nombre, nombre)] = {
|
|
475
|
+
"thought": contenido,
|
|
476
|
+
"decision": contenido,
|
|
477
|
+
"confidence": self.thinking._compute_confidence([(nombre, contenido)]),
|
|
478
|
+
"valid": True,
|
|
479
|
+
"invalid_steps": [],
|
|
480
|
+
"steps": [(nombre, contenido)],
|
|
481
|
+
"step_count": 1,
|
|
482
|
+
"error": None,
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
self.results = {
|
|
486
|
+
**resultados,
|
|
487
|
+
"total_thinking_steps": self.thinking.step_count,
|
|
488
|
+
"efficiency_principle": "Hacer las cosas de manera eficiente",
|
|
489
|
+
"project_metrics": self.context.to_dict(),
|
|
490
|
+
}
|
|
491
|
+
return self.results
|
|
492
|
+
|
|
493
|
+
def analyze_and_improve(self) -> Dict[str, Any]:
|
|
494
|
+
"""Ejecuta el ciclo completo y muestra el progreso por consola."""
|
|
495
|
+
|
|
496
|
+
print("=" * 70)
|
|
497
|
+
print("ANÁLISIS Y MEJORA DE PROYECTO DE CÓDIGO")
|
|
498
|
+
print("=" * 70)
|
|
499
|
+
print("Aplicando capa de pensamiento estructurado (7 pasos)")
|
|
500
|
+
print("Principio rector: 'Hacer las cosas de manera eficiente'\n")
|
|
501
|
+
|
|
502
|
+
resultado = self.run_analysis(progress=lambda mensaje: print(f" {mensaje}"))
|
|
503
|
+
|
|
504
|
+
if "error" in resultado:
|
|
505
|
+
# El fallo se reporta por stderr (no silencioso): `self.results`
|
|
506
|
+
# queda vacío y generate_report lo avisa.
|
|
507
|
+
print(f"ERROR: {resultado['error']}", file=sys.stderr)
|
|
508
|
+
return resultado
|
|
509
|
+
|
|
510
|
+
metricas = self.results["project_metrics"]
|
|
511
|
+
print(f"\n{'=' * 70}")
|
|
512
|
+
print("ANÁLISIS COMPLETADO")
|
|
513
|
+
print("=" * 70)
|
|
514
|
+
print(f"Pasos de pensamiento ejecutados: {self.results['total_thinking_steps']}")
|
|
515
|
+
print(f"Problemas identificados: {metricas['total_issues']}")
|
|
516
|
+
print(f"Score de eficiencia: {metricas['efficiency_score']:.2f}")
|
|
517
|
+
print(f"Principio aplicado: {self.results['efficiency_principle']}")
|
|
518
|
+
print("=" * 70)
|
|
519
|
+
|
|
520
|
+
return self.results
|
|
521
|
+
|
|
522
|
+
def report_lines(self) -> List[str]:
|
|
523
|
+
"""Líneas del reporte markdown (sin unir): núcleo comprobable."""
|
|
524
|
+
r = self.results
|
|
525
|
+
lines = [
|
|
526
|
+
"# Análisis y Mejora de Proyecto de Código",
|
|
527
|
+
"",
|
|
528
|
+
"## Resumen Ejecutivo",
|
|
529
|
+
f"- **Pasos de pensamiento**: {r['total_thinking_steps']}",
|
|
530
|
+
f"- **Problemas identificados**: {r['project_metrics']['total_issues']}",
|
|
531
|
+
f"- **Score de eficiencia**: {r['project_metrics']['efficiency_score']:.2f}",
|
|
532
|
+
f"- **Principio rector**: {r['efficiency_principle']}",
|
|
533
|
+
"",
|
|
534
|
+
"## Problemas Detectados",
|
|
535
|
+
]
|
|
536
|
+
|
|
537
|
+
for issue in r['project_metrics'].get('issues', []):
|
|
538
|
+
if isinstance(issue, dict) and 'description' in issue:
|
|
539
|
+
lines.append(f"- **[{issue['severity'].upper()}]** {issue['description']}")
|
|
540
|
+
if issue.get('suggestion'):
|
|
541
|
+
lines.append(f" - Sugerencia: {issue['suggestion']}")
|
|
542
|
+
lines.append("")
|
|
543
|
+
|
|
544
|
+
lines.extend([
|
|
545
|
+
"## Decisiones de Diseño",
|
|
546
|
+
f"- **Enfoque seleccionado**: {r.get('selection', {}).get('decision', 'N/A')}",
|
|
547
|
+
f"- **Confianza**: {r.get('selection', {}).get('confidence', 'N/A')}",
|
|
548
|
+
"",
|
|
549
|
+
"## Plan de Implementación",
|
|
550
|
+
])
|
|
551
|
+
|
|
552
|
+
steps = r.get('implementation_plan', {}).get('steps', [])
|
|
553
|
+
if isinstance(steps, list):
|
|
554
|
+
for i, step in enumerate(steps[:5], 1): # Máx 5 pasos en reporte
|
|
555
|
+
descripcion = step[1][:80] if isinstance(step, tuple) else str(step)[:80]
|
|
556
|
+
lines.append(f"{i}. {descripcion}")
|
|
557
|
+
else:
|
|
558
|
+
lines.append("No steps detail available")
|
|
559
|
+
|
|
560
|
+
lines.extend([
|
|
561
|
+
"",
|
|
562
|
+
"## Criterios de Validación",
|
|
563
|
+
f"- **Decisión de validación**: {r.get('validation_criteria', {}).get('decision', 'N/A')}",
|
|
564
|
+
f"- **Confianza**: {r.get('validation_criteria', {}).get('confidence', 'N/A')}",
|
|
565
|
+
"",
|
|
566
|
+
"---",
|
|
567
|
+
"*Generado por sistema de diseño de código con capa de pensamiento estructurado*",
|
|
568
|
+
f"*Pasos deliberados: {r['total_thinking_steps']}/7*"
|
|
569
|
+
])
|
|
570
|
+
return lines
|
|
571
|
+
|
|
572
|
+
def generate_report(self) -> str:
|
|
573
|
+
"""Reporte markdown completo (presentación sobre `report_lines`)."""
|
|
574
|
+
if not self.results:
|
|
575
|
+
return "No results to report."
|
|
576
|
+
return "\n".join(self.report_lines())
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def _max_nesting(tree: ast.AST) -> int:
|
|
580
|
+
"""Profundidad máxima de anidamiento de bloques de control (if/for/while/
|
|
581
|
+
try/with, incluidos try/except* y match). 0 = código plano.
|
|
582
|
+
|
|
583
|
+
`elif` no tiene nodo propio en el AST: se representa como un `If` dentro
|
|
584
|
+
de `orelse` y comparte indentación con el `if`, así que no suma un nivel.
|
|
585
|
+
Un `if` dentro de un `else` real sí está indentado y sí suma.
|
|
586
|
+
"""
|
|
587
|
+
anidables = (ast.If, ast.While, ast.For, ast.AsyncFor, ast.Try,
|
|
588
|
+
ast.TryStar, ast.With, ast.AsyncWith, ast.Match)
|
|
589
|
+
|
|
590
|
+
def profundidad(nodo: ast.AST, nivel: int,
|
|
591
|
+
padre: Optional[ast.AST] = None) -> int:
|
|
592
|
+
es_elif = (
|
|
593
|
+
isinstance(nodo, ast.If)
|
|
594
|
+
and isinstance(padre, ast.If)
|
|
595
|
+
and nodo.col_offset == padre.col_offset
|
|
596
|
+
and any(nodo is hijo for hijo in padre.orelse)
|
|
597
|
+
)
|
|
598
|
+
if isinstance(nodo, anidables) and not es_elif:
|
|
599
|
+
nivel += 1
|
|
600
|
+
hijos = [profundidad(hijo, nivel, nodo)
|
|
601
|
+
for hijo in ast.iter_child_nodes(nodo)]
|
|
602
|
+
return max([nivel, *hijos])
|
|
603
|
+
|
|
604
|
+
return profundidad(tree, 0)
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def analyze_python_source(source_code: str) -> Dict[str, Any]:
|
|
608
|
+
"""Analiza código Python (string) y retorna métricas estructuradas."""
|
|
609
|
+
try:
|
|
610
|
+
tree = ast.parse(source_code)
|
|
611
|
+
except SyntaxError as e:
|
|
612
|
+
return {"syntax_error": str(e)}
|
|
613
|
+
|
|
614
|
+
# Métricas básicas
|
|
615
|
+
metrics = {
|
|
616
|
+
"total_lines": len(source_code.splitlines()),
|
|
617
|
+
"total_functions": 0,
|
|
618
|
+
"total_classes": 0,
|
|
619
|
+
"complexity": 0,
|
|
620
|
+
"max_nesting": 0,
|
|
621
|
+
"issues": []
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
for node in ast.walk(tree):
|
|
625
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
626
|
+
metrics["total_functions"] += 1
|
|
627
|
+
elif isinstance(node, ast.ClassDef):
|
|
628
|
+
metrics["total_classes"] += 1
|
|
629
|
+
elif isinstance(node, (ast.If, ast.While, ast.For, ast.AsyncFor, ast.Try)):
|
|
630
|
+
# Cada condicional se cuenta UNA vez (antes se sumaba otra vez
|
|
631
|
+
# dentro de cada función que lo contenía: doble conteo).
|
|
632
|
+
metrics["complexity"] += 1
|
|
633
|
+
|
|
634
|
+
metrics["max_nesting"] = _max_nesting(tree)
|
|
635
|
+
|
|
636
|
+
# Detectar problemas comunes
|
|
637
|
+
lines = source_code.splitlines()
|
|
638
|
+
for i, line in enumerate(lines, 1):
|
|
639
|
+
line_stripped = line.strip()
|
|
640
|
+
|
|
641
|
+
# Problemas de línea larga
|
|
642
|
+
if len(line_stripped) > 120:
|
|
643
|
+
metrics["issues"].append({
|
|
644
|
+
"category": "style",
|
|
645
|
+
"severity": "low",
|
|
646
|
+
"description": f"Línea {i}: línea demasiado larga ({len(line_stripped)} chars)",
|
|
647
|
+
"suggestion": "Dividir línea o reestructurar código",
|
|
648
|
+
"thinking_reasoning": "Eficiencia: líneas largas reducen legibilidad"
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
# Importaciones múltiples en una línea
|
|
652
|
+
if re.match(r'^\s*import\s+.+,\s+.+', line):
|
|
653
|
+
metrics["issues"].append({
|
|
654
|
+
"category": "style",
|
|
655
|
+
"severity": "medium",
|
|
656
|
+
"description": f"Línea {i}: importaciones múltiples en una línea",
|
|
657
|
+
"suggestion": "Separar importaciones en líneas individuales",
|
|
658
|
+
"thinking_reasoning": "Mantenimiento: importaciones separadas son más fáciles de modificar"
|
|
659
|
+
})
|
|
660
|
+
|
|
661
|
+
# Reducir issues a máximo max_issues
|
|
662
|
+
max_issues = 50
|
|
663
|
+
metrics["issues"] = metrics["issues"][:max_issues]
|
|
664
|
+
|
|
665
|
+
# Recalcular score (misma referencia que el contexto)
|
|
666
|
+
metrics["efficiency_score"] = min(len(metrics["issues"]) / EFFICIENCY_REFERENCE_ISSUES, 1.0)
|
|
667
|
+
|
|
668
|
+
return {
|
|
669
|
+
"metrics": metrics,
|
|
670
|
+
"source_summary": {
|
|
671
|
+
"total_lines": metrics["total_lines"],
|
|
672
|
+
"functions": metrics["total_functions"],
|
|
673
|
+
"classes": metrics["total_classes"],
|
|
674
|
+
"complexity": metrics["complexity"]
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
def analyze_python_code(file_path: str) -> Dict[str, Any]:
|
|
680
|
+
"""Analiza un archivo Python real leyéndolo desde `file_path`."""
|
|
681
|
+
try:
|
|
682
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
683
|
+
source = f.read()
|
|
684
|
+
except (OSError, UnicodeDecodeError) as e:
|
|
685
|
+
# Un archivo legible pero no-UTF-8 también es un error de entrada:
|
|
686
|
+
# el contrato devuelve {"error": ...}, no eleva (P1.19).
|
|
687
|
+
return {"error": str(e)}
|
|
688
|
+
return analyze_python_source(source)
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
# CLI principal (modo demo offline si no se configura proveedor)
|
|
692
|
+
def main():
|
|
693
|
+
print("=" * 70)
|
|
694
|
+
print("HERRAMIENTAS PARA DISEÑO Y MEJORA DE PROYECTOS DE CÓDIGO")
|
|
695
|
+
print("==================================================================")
|
|
696
|
+
print()
|
|
697
|
+
print("Definición de inteligencia: 'Hacer las cosas de manera eficiente'")
|
|
698
|
+
print("Aplicación: Análisis, diseño y mejora de proyectos de software")
|
|
699
|
+
print()
|
|
700
|
+
print("Arquitectura:")
|
|
701
|
+
print(" CodeDesignThinker (7 pasos de pensamiento)")
|
|
702
|
+
print(" │")
|
|
703
|
+
print(" ├── PASO 1: Análisis código existente")
|
|
704
|
+
print(" ├── PASO 2: Definir objetivos")
|
|
705
|
+
print(" ├── PASO 3: Generar opciones")
|
|
706
|
+
print(" ├── PASO 4: Evaluar opciones")
|
|
707
|
+
print(" ├── PASO 5: Seleccionar enfoque")
|
|
708
|
+
print(" ├── PASO 6: Plan implementación")
|
|
709
|
+
print(" └── PASO 7: Verificar validación")
|
|
710
|
+
print()
|
|
711
|
+
print(" CodeAnalysisContext (estado del proyecto)")
|
|
712
|
+
print(" │")
|
|
713
|
+
print(" ├── Métricas: líneas, funciones, clases, complejidad")
|
|
714
|
+
print(" ├── Issues: problemas detectados con severidad")
|
|
715
|
+
print(" └── Decisiones: decisiones de diseño registradas")
|
|
716
|
+
print()
|
|
717
|
+
print(" CodeImprovementOrchestrator (orquestador completo)")
|
|
718
|
+
print(" │")
|
|
719
|
+
print(" └── Ejecuta ciclo completo: análisis → diseño → mejora → validación")
|
|
720
|
+
print()
|
|
721
|
+
print("Funcionalidades incluidas:")
|
|
722
|
+
print(" • Análisis estático de código Python (AST)")
|
|
723
|
+
print(" • Detección de problemas de estilo y maintainability")
|
|
724
|
+
print(" • Capa de pensamiento estructurado (7 pasos)")
|
|
725
|
+
print(" • Decisiones de diseño con validación en cada paso")
|
|
726
|
+
print(" • Generación de planes de refactoring")
|
|
727
|
+
print(" • Criterios de validación y éxito")
|
|
728
|
+
print(" • Reportes markdown completos")
|
|
729
|
+
print()
|
|
730
|
+
print("=" * 70)
|
|
731
|
+
|
|
732
|
+
# Ejemplo con código de demo
|
|
733
|
+
demo_code = '''
|
|
734
|
+
def calcular_factorial(n):
|
|
735
|
+
"""Calcula el factorial de un número entero no negativo."""
|
|
736
|
+
if n < 0:
|
|
737
|
+
raise ValueError("No factorial para números negativos")
|
|
738
|
+
result = 1
|
|
739
|
+
for i in range(1, n + 1):
|
|
740
|
+
result *= i
|
|
741
|
+
return result
|
|
742
|
+
|
|
743
|
+
# Variable global no usada
|
|
744
|
+
GLOBAL_CONST = 42
|
|
745
|
+
|
|
746
|
+
# Importaciones múltiples
|
|
747
|
+
import os, sys, json, math, re, collections, datetime, uuid
|
|
748
|
+
|
|
749
|
+
def proceso_principal(datos):
|
|
750
|
+
"""Procesa los datos de entrada."""
|
|
751
|
+
resultado = []
|
|
752
|
+
for item in datos:
|
|
753
|
+
if item is not None:
|
|
754
|
+
resultado.append(item * 2)
|
|
755
|
+
return resultado
|
|
756
|
+
|
|
757
|
+
# Código repetido
|
|
758
|
+
def utilidad_a():
|
|
759
|
+
return 42
|
|
760
|
+
|
|
761
|
+
def utilidad_b():
|
|
762
|
+
return 42
|
|
763
|
+
|
|
764
|
+
if __name__ == "__main__":
|
|
765
|
+
datos = [1, 2, 3, 4, 5]
|
|
766
|
+
print(calcular_factorial(5))
|
|
767
|
+
print(proceso_principal(datos))
|
|
768
|
+
'''
|
|
769
|
+
|
|
770
|
+
# Análisis estático
|
|
771
|
+
print("\n" + "=" * 70)
|
|
772
|
+
print("ANÁLISIS ESTÁTICO DE EJEMPLO DE CÓDIGO")
|
|
773
|
+
print("=" * 70)
|
|
774
|
+
|
|
775
|
+
analysis_result = analyze_python_source(demo_code)
|
|
776
|
+
print("\nArchivo de ejemplo analizado:")
|
|
777
|
+
print(f" Líneas totales: {analysis_result['source_summary']['total_lines']}")
|
|
778
|
+
print(f" Funciones: {analysis_result['source_summary']['functions']}")
|
|
779
|
+
print(f" Clases: {analysis_result['source_summary']['classes']}")
|
|
780
|
+
print(f" Complejidad ciclomática: {analysis_result['source_summary']['complexity']}")
|
|
781
|
+
print(f" Issues detectados: {len(analysis_result['metrics']['issues'])}")
|
|
782
|
+
print(f" Score de eficiencia: {analysis_result['metrics']['efficiency_score']:.2f}")
|
|
783
|
+
|
|
784
|
+
# Mostrar algunos issues
|
|
785
|
+
if analysis_result['metrics']['issues']:
|
|
786
|
+
print("\nIssues destacados:")
|
|
787
|
+
for issue in analysis_result['metrics']['issues'][:5]:
|
|
788
|
+
print(f" - [{issue['severity'].upper()}] {issue['description']}")
|
|
789
|
+
|
|
790
|
+
# Sistema completo con pensamiento (offline: sin proveedor usa MockLLM)
|
|
791
|
+
print("\n" + "=" * 70)
|
|
792
|
+
print("SISTEMA COMPLETO CON CAPA DE PENSAMIENTO")
|
|
793
|
+
print("=" * 70)
|
|
794
|
+
|
|
795
|
+
thinking_engine = CodeDesignThinker(name="code_design_thinker")
|
|
796
|
+
orchestrator = CodeImprovementOrchestrator(thinking_engine, source_code=demo_code)
|
|
797
|
+
|
|
798
|
+
orchestrator.analyze_and_improve()
|
|
799
|
+
|
|
800
|
+
print("\n" + "=" * 70)
|
|
801
|
+
print("REPORTE MARKDOWN")
|
|
802
|
+
print("=" * 70)
|
|
803
|
+
print(orchestrator.generate_report())
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
if __name__ == "__main__":
|
|
807
|
+
main()
|