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.
@@ -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()