claude-mpm 4.1.7__py3-none-any.whl → 4.1.10__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 (109) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +26 -1
  3. claude_mpm/agents/OUTPUT_STYLE.md +73 -0
  4. claude_mpm/agents/agents_metadata.py +57 -0
  5. claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
  6. claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
  7. claude_mpm/agents/templates/agent-manager.json +263 -17
  8. claude_mpm/agents/templates/agent-manager.md +248 -10
  9. claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
  10. claude_mpm/agents/templates/code_analyzer.json +18 -8
  11. claude_mpm/agents/templates/engineer.json +1 -1
  12. claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
  13. claude_mpm/agents/templates/qa.json +1 -1
  14. claude_mpm/agents/templates/research.json +1 -1
  15. claude_mpm/cli/__init__.py +4 -0
  16. claude_mpm/cli/commands/__init__.py +6 -0
  17. claude_mpm/cli/commands/analyze.py +547 -0
  18. claude_mpm/cli/commands/analyze_code.py +524 -0
  19. claude_mpm/cli/commands/configure.py +223 -25
  20. claude_mpm/cli/commands/configure_tui.py +65 -61
  21. claude_mpm/cli/commands/debug.py +1387 -0
  22. claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
  23. claude_mpm/cli/parsers/analyze_parser.py +135 -0
  24. claude_mpm/cli/parsers/base_parser.py +29 -0
  25. claude_mpm/cli/parsers/configure_parser.py +23 -0
  26. claude_mpm/cli/parsers/debug_parser.py +319 -0
  27. claude_mpm/config/socketio_config.py +21 -21
  28. claude_mpm/constants.py +3 -1
  29. claude_mpm/core/framework_loader.py +148 -6
  30. claude_mpm/core/log_manager.py +16 -13
  31. claude_mpm/core/logger.py +1 -1
  32. claude_mpm/core/unified_agent_registry.py +1 -1
  33. claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
  34. claude_mpm/dashboard/analysis_runner.py +428 -0
  35. claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
  36. claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
  37. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  38. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
  39. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  40. claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
  41. claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
  42. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  43. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  44. claude_mpm/dashboard/static/css/activity.css +549 -0
  45. claude_mpm/dashboard/static/css/code-tree.css +846 -0
  46. claude_mpm/dashboard/static/css/dashboard.css +245 -0
  47. claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
  48. claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
  49. claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
  50. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  51. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  52. claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
  53. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  54. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  55. claude_mpm/dashboard/static/js/components/activity-tree.js +1139 -0
  56. claude_mpm/dashboard/static/js/components/code-tree.js +1357 -0
  57. claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
  58. claude_mpm/dashboard/static/js/components/event-viewer.js +11 -0
  59. claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
  60. claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
  61. claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
  62. claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
  63. claude_mpm/dashboard/static/js/dashboard.js +39 -0
  64. claude_mpm/dashboard/static/js/socket-client.js +414 -20
  65. claude_mpm/dashboard/templates/index.html +184 -4
  66. claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
  67. claude_mpm/hooks/claude_hooks/installer.py +728 -0
  68. claude_mpm/scripts/claude-hook-handler.sh +161 -0
  69. claude_mpm/scripts/socketio_daemon.py +121 -8
  70. claude_mpm/services/agents/deployment/agent_config_provider.py +127 -27
  71. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
  72. claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
  73. claude_mpm/services/agents/memory/memory_format_service.py +1 -5
  74. claude_mpm/services/cli/agent_cleanup_service.py +1 -2
  75. claude_mpm/services/cli/agent_dependency_service.py +1 -1
  76. claude_mpm/services/cli/agent_validation_service.py +3 -4
  77. claude_mpm/services/cli/dashboard_launcher.py +2 -3
  78. claude_mpm/services/cli/startup_checker.py +0 -10
  79. claude_mpm/services/core/cache_manager.py +1 -2
  80. claude_mpm/services/core/path_resolver.py +1 -4
  81. claude_mpm/services/core/service_container.py +2 -2
  82. claude_mpm/services/diagnostics/checks/instructions_check.py +2 -5
  83. claude_mpm/services/event_bus/direct_relay.py +98 -20
  84. claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
  85. claude_mpm/services/infrastructure/monitoring.py +11 -11
  86. claude_mpm/services/project/architecture_analyzer.py +1 -1
  87. claude_mpm/services/project/dependency_analyzer.py +4 -4
  88. claude_mpm/services/project/language_analyzer.py +3 -3
  89. claude_mpm/services/project/metrics_collector.py +3 -6
  90. claude_mpm/services/socketio/handlers/__init__.py +2 -0
  91. claude_mpm/services/socketio/handlers/code_analysis.py +170 -0
  92. claude_mpm/services/socketio/handlers/registry.py +2 -0
  93. claude_mpm/services/socketio/server/connection_manager.py +95 -65
  94. claude_mpm/services/socketio/server/core.py +125 -17
  95. claude_mpm/services/socketio/server/main.py +44 -5
  96. claude_mpm/services/visualization/__init__.py +19 -0
  97. claude_mpm/services/visualization/mermaid_generator.py +938 -0
  98. claude_mpm/tools/__main__.py +208 -0
  99. claude_mpm/tools/code_tree_analyzer.py +778 -0
  100. claude_mpm/tools/code_tree_builder.py +632 -0
  101. claude_mpm/tools/code_tree_events.py +318 -0
  102. claude_mpm/tools/socketio_debug.py +671 -0
  103. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/METADATA +1 -1
  104. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/RECORD +108 -77
  105. claude_mpm/agents/schema/agent_schema.json +0 -314
  106. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/WHEEL +0 -0
  107. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/entry_points.txt +0 -0
  108. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/licenses/LICENSE +0 -0
  109. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,524 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Analyze Code Command
4
+ ====================
5
+
6
+ WHY: Provides a CLI interface for analyzing codebases and generating
7
+ code trees with real-time visualization support.
8
+
9
+ DESIGN DECISIONS:
10
+ - Support multiple output formats (JSON, tree, dashboard)
11
+ - Show progress bar for terminal output
12
+ - Support filtering by language and patterns
13
+ - Enable real-time event streaming to dashboard
14
+ """
15
+
16
+ import json
17
+ import sys
18
+ import time
19
+ from pathlib import Path
20
+ from typing import Any, List, Optional
21
+
22
+ try:
23
+ from rich import print as rprint
24
+ from rich.console import Console
25
+ from rich.progress import (
26
+ BarColumn,
27
+ Progress,
28
+ SpinnerColumn,
29
+ TaskProgressColumn,
30
+ TextColumn,
31
+ TimeRemainingColumn,
32
+ )
33
+ from rich.table import Table
34
+ from rich.tree import Tree
35
+
36
+ RICH_AVAILABLE = True
37
+ except ImportError:
38
+ RICH_AVAILABLE = False
39
+ Console = None
40
+
41
+ from ...core.logging_config import get_logger
42
+ from ...tools.code_tree_analyzer import CodeTreeAnalyzer
43
+ from ...tools.code_tree_builder import CodeTreeBuilder
44
+ from ..shared import BaseCommand, CommandResult
45
+
46
+
47
+ class AnalyzeCodeCommand(BaseCommand):
48
+ """Command for analyzing code structure and metrics.
49
+
50
+ WHY: Provides users with insights into their codebase structure,
51
+ complexity, and organization through various visualization options.
52
+ """
53
+
54
+ def __init__(self):
55
+ super().__init__("analyze-code")
56
+ self.logger = get_logger(__name__)
57
+ self.console = Console() if RICH_AVAILABLE else None
58
+
59
+ def validate_args(self, args) -> Optional[str]:
60
+ """Validate command arguments.
61
+
62
+ Args:
63
+ args: Command arguments
64
+
65
+ Returns:
66
+ Error message if validation fails, None otherwise
67
+ """
68
+ # Validate path exists
69
+ path = Path(args.path)
70
+ if not path.exists():
71
+ return f"Path does not exist: {path}"
72
+
73
+ if not path.is_dir():
74
+ return f"Path is not a directory: {path}"
75
+
76
+ # Validate output format
77
+ if args.output and args.output not in ("json", "tree", "stats"):
78
+ return f"Invalid output format: {args.output}"
79
+
80
+ return None
81
+
82
+ def run(self, args) -> CommandResult:
83
+ """Execute the analyze-code command.
84
+
85
+ Args:
86
+ args: Command arguments
87
+
88
+ Returns:
89
+ Command execution result
90
+ """
91
+ path = Path(args.path).resolve()
92
+
93
+ # Parse languages filter
94
+ languages = None
95
+ if args.languages:
96
+ languages = [lang.strip().lower() for lang in args.languages.split(",")]
97
+
98
+ # Parse ignore patterns
99
+ ignore_patterns = None
100
+ if args.ignore:
101
+ ignore_patterns = [pattern.strip() for pattern in args.ignore.split(",")]
102
+
103
+ try:
104
+ # Phase 1: Build file tree
105
+ if not args.no_tree:
106
+ tree_result = self._build_file_tree(
107
+ path, ignore_patterns, args.max_depth
108
+ )
109
+
110
+ if args.output == "tree":
111
+ self._display_tree(tree_result)
112
+ return CommandResult(
113
+ success=True, message="File tree analysis complete"
114
+ )
115
+
116
+ # Phase 2: Analyze code structure
117
+ analysis_result = self._analyze_code(
118
+ path,
119
+ languages,
120
+ ignore_patterns,
121
+ args.max_depth,
122
+ args.emit_events,
123
+ args.no_cache,
124
+ )
125
+
126
+ # Handle output format
127
+ if args.output == "json":
128
+ # Output JSON to stdout
129
+ json.dump(analysis_result, sys.stdout, indent=2, default=str)
130
+ sys.stdout.write("\n")
131
+ elif args.output == "stats":
132
+ self._display_stats(analysis_result["stats"])
133
+ else:
134
+ # Default: display summary
135
+ self._display_summary(analysis_result)
136
+
137
+ # Save to file if specified
138
+ if args.save:
139
+ save_path = Path(args.save)
140
+ with open(save_path, "w") as f:
141
+ json.dump(analysis_result, f, indent=2, default=str)
142
+ self.logger.info(f"Analysis saved to {save_path}")
143
+
144
+ return CommandResult(
145
+ success=True,
146
+ message="Code analysis complete",
147
+ data=analysis_result["stats"],
148
+ )
149
+
150
+ except Exception as e:
151
+ self.logger.error(f"Analysis failed: {e}", exc_info=True)
152
+ return CommandResult(success=False, message=f"Analysis failed: {e!s}")
153
+
154
+ def _build_file_tree(
155
+ self, path: Path, ignore_patterns: Optional[List[str]], max_depth: Optional[int]
156
+ ) -> dict:
157
+ """Build file tree structure.
158
+
159
+ Args:
160
+ path: Root path to analyze
161
+ ignore_patterns: Patterns to ignore
162
+ max_depth: Maximum depth to traverse
163
+
164
+ Returns:
165
+ Tree structure dictionary
166
+ """
167
+ if RICH_AVAILABLE and self.console:
168
+ self.console.print(
169
+ f"\n[bold blue]Building file tree for:[/bold blue] {path}"
170
+ )
171
+
172
+ builder = CodeTreeBuilder()
173
+
174
+ # Progress tracking
175
+ def progress_callback(file_path: Path, stats: dict):
176
+ if RICH_AVAILABLE and self.console:
177
+ # Update progress (simplified without progress bar for file tree)
178
+ if stats["files_found"] % 100 == 0:
179
+ self.console.print(
180
+ f" Scanned {stats['directories_scanned']} directories, "
181
+ f"found {stats['files_found']} files"
182
+ )
183
+
184
+ start_time = time.time()
185
+ tree = builder.build_tree(
186
+ path,
187
+ ignore_patterns=ignore_patterns,
188
+ max_depth=max_depth,
189
+ use_gitignore=True,
190
+ calculate_hashes=False,
191
+ progress_callback=progress_callback if not self.console else None,
192
+ )
193
+
194
+ duration = time.time() - start_time
195
+ stats = builder.get_stats()
196
+ stats["duration"] = duration
197
+
198
+ if RICH_AVAILABLE and self.console:
199
+ self.console.print(
200
+ f"[green]✓[/green] Tree built in {duration:.2f}s - "
201
+ f"{stats['files_found']} files in {stats['directories_scanned']} directories"
202
+ )
203
+
204
+ return {"tree": tree.to_dict(), "stats": stats}
205
+
206
+ def _analyze_code(
207
+ self,
208
+ path: Path,
209
+ languages: Optional[List[str]],
210
+ ignore_patterns: Optional[List[str]],
211
+ max_depth: Optional[int],
212
+ emit_events: bool,
213
+ no_cache: bool,
214
+ ) -> dict:
215
+ """Analyze code structure and metrics.
216
+
217
+ Args:
218
+ path: Root path to analyze
219
+ languages: Languages to include
220
+ ignore_patterns: Patterns to ignore
221
+ max_depth: Maximum depth
222
+ emit_events: Whether to emit Socket.IO events
223
+ no_cache: Whether to skip cache
224
+
225
+ Returns:
226
+ Analysis results dictionary
227
+ """
228
+ if RICH_AVAILABLE and self.console:
229
+ self.console.print("\n[bold blue]Analyzing code structure...[/bold blue]")
230
+
231
+ if emit_events:
232
+ self.console.print(
233
+ "[yellow]→ Emitting real-time events to dashboard[/yellow]"
234
+ )
235
+
236
+ # Create analyzer
237
+ cache_dir = None if no_cache else Path.home() / ".claude-mpm" / "code-cache"
238
+ analyzer = CodeTreeAnalyzer(emit_events=emit_events, cache_dir=cache_dir)
239
+
240
+ # Show progress
241
+ if RICH_AVAILABLE and self.console and not emit_events:
242
+ with Progress(
243
+ SpinnerColumn(),
244
+ TextColumn("[progress.description]{task.description}"),
245
+ BarColumn(),
246
+ TaskProgressColumn(),
247
+ TimeRemainingColumn(),
248
+ console=self.console,
249
+ ) as progress:
250
+ task = progress.add_task("Analyzing files...", total=None)
251
+
252
+ # Run analysis
253
+ result = analyzer.analyze_directory(
254
+ path,
255
+ languages=languages,
256
+ ignore_patterns=ignore_patterns,
257
+ max_depth=max_depth,
258
+ )
259
+
260
+ progress.update(task, completed=100)
261
+ else:
262
+ # Run without progress bar
263
+ result = analyzer.analyze_directory(
264
+ path,
265
+ languages=languages,
266
+ ignore_patterns=ignore_patterns,
267
+ max_depth=max_depth,
268
+ )
269
+
270
+ return result
271
+
272
+ def _display_tree(self, tree_result: dict):
273
+ """Display file tree in terminal.
274
+
275
+ Args:
276
+ tree_result: Tree structure dictionary
277
+ """
278
+ if not RICH_AVAILABLE or not self.console:
279
+ # Fallback to simple text output
280
+ self._print_tree_text(tree_result["tree"])
281
+ return
282
+
283
+ # Create rich tree
284
+ tree = Tree(f"📁 {tree_result['tree']['name']}")
285
+ self._build_rich_tree(tree, tree_result["tree"])
286
+
287
+ self.console.print(tree)
288
+
289
+ # Show stats
290
+ stats = tree_result["stats"]
291
+ self.console.print(
292
+ f"\n[dim]Files: {stats['files_found']} | "
293
+ f"Directories: {stats['directories_scanned']} | "
294
+ f"Languages: {len(stats['languages'])}[/dim]"
295
+ )
296
+
297
+ def _build_rich_tree(
298
+ self, rich_tree: Tree, node: dict, depth: int = 0, max_depth: int = 10
299
+ ):
300
+ """Recursively build rich tree display.
301
+
302
+ Args:
303
+ rich_tree: Rich Tree object
304
+ node: Tree node dictionary
305
+ depth: Current depth
306
+ max_depth: Maximum depth to display
307
+ """
308
+ if depth >= max_depth:
309
+ if node.get("children"):
310
+ rich_tree.add("[dim]...[/dim]")
311
+ return
312
+
313
+ for child in node.get("children", []):
314
+ if child["type"] == "directory":
315
+ icon = "📁"
316
+ style = "blue"
317
+ else:
318
+ # Get file icon based on language
319
+ lang = child.get("metadata", {}).get("language", "")
320
+ icon = self._get_file_icon(lang)
321
+ style = "white"
322
+
323
+ label = f"{icon} {child['name']}"
324
+
325
+ if child["type"] == "directory" and child.get("children"):
326
+ subtree = rich_tree.add(f"[{style}]{label}[/{style}]")
327
+ self._build_rich_tree(subtree, child, depth + 1, max_depth)
328
+ else:
329
+ rich_tree.add(f"[{style}]{label}[/{style}]")
330
+
331
+ def _get_file_icon(self, language: str) -> str:
332
+ """Get icon for file based on language.
333
+
334
+ Args:
335
+ language: Programming language
336
+
337
+ Returns:
338
+ Icon string
339
+ """
340
+ icons = {
341
+ "python": "🐍",
342
+ "javascript": "📜",
343
+ "typescript": "📘",
344
+ "java": "☕",
345
+ "go": "🐹",
346
+ "rust": "🦀",
347
+ "ruby": "💎",
348
+ "php": "🐘",
349
+ "cpp": "⚡",
350
+ "c": "🔧",
351
+ "csharp": "🔷",
352
+ "swift": "🦉",
353
+ "kotlin": "🟣",
354
+ "html": "🌐",
355
+ "css": "🎨",
356
+ "json": "📋",
357
+ "yaml": "📄",
358
+ "markdown": "📝",
359
+ "dockerfile": "🐋",
360
+ "sql": "🗃️",
361
+ }
362
+ return icons.get(language, "📄")
363
+
364
+ def _print_tree_text(self, node: dict, prefix: str = "", is_last: bool = True):
365
+ """Print tree in simple text format.
366
+
367
+ Args:
368
+ node: Tree node
369
+ prefix: Line prefix
370
+ is_last: Whether this is the last child
371
+ """
372
+ if node["type"] == "directory":
373
+ connector = "└── " if is_last else "├── "
374
+ print(f"{prefix}{connector}{node['name']}/")
375
+
376
+ children = node.get("children", [])
377
+ for i, child in enumerate(children):
378
+ extension = " " if is_last else "│ "
379
+ self._print_tree_text(child, prefix + extension, i == len(children) - 1)
380
+ else:
381
+ connector = "└── " if is_last else "├── "
382
+ print(f"{prefix}{connector}{node['name']}")
383
+
384
+ def _display_stats(self, stats: dict):
385
+ """Display analysis statistics.
386
+
387
+ Args:
388
+ stats: Statistics dictionary
389
+ """
390
+ if not RICH_AVAILABLE or not self.console:
391
+ # Simple text output
392
+ print("\nCode Analysis Statistics:")
393
+ print("-" * 40)
394
+ for key, value in stats.items():
395
+ print(f"{key}: {value}")
396
+ return
397
+
398
+ # Create rich table
399
+ table = Table(title="Code Analysis Statistics", show_header=True)
400
+ table.add_column("Metric", style="cyan")
401
+ table.add_column("Value", style="yellow")
402
+
403
+ table.add_row("Files Processed", str(stats.get("files_processed", 0)))
404
+ table.add_row("Total Nodes", str(stats.get("total_nodes", 0)))
405
+ table.add_row("Classes", str(stats.get("classes", 0)))
406
+ table.add_row("Functions/Methods", str(stats.get("functions", 0)))
407
+ table.add_row("Imports", str(stats.get("imports", 0)))
408
+ table.add_row("Average Complexity", f"{stats.get('avg_complexity', 0):.2f}")
409
+ table.add_row("Duration", f"{stats.get('duration', 0):.2f}s")
410
+
411
+ if stats.get("languages"):
412
+ languages = ", ".join(stats["languages"])
413
+ table.add_row("Languages", languages)
414
+
415
+ self.console.print(table)
416
+
417
+ def _display_summary(self, result: dict):
418
+ """Display analysis summary.
419
+
420
+ Args:
421
+ result: Analysis result dictionary
422
+ """
423
+ stats = result.get("stats", {})
424
+
425
+ if not RICH_AVAILABLE or not self.console:
426
+ # Simple text output
427
+ print("\n=== Code Analysis Summary ===")
428
+ self._display_stats(stats)
429
+
430
+ if result.get("nodes"):
431
+ print(f"\nFound {len(result['nodes'])} code elements")
432
+
433
+ # Show top complex functions
434
+ complex_nodes = sorted(
435
+ [n for n in result["nodes"] if n.complexity > 5],
436
+ key=lambda x: x.complexity,
437
+ reverse=True,
438
+ )[:5]
439
+
440
+ if complex_nodes:
441
+ print("\nMost Complex Functions:")
442
+ for node in complex_nodes:
443
+ print(f" - {node.name} (complexity: {node.complexity})")
444
+ return
445
+
446
+ # Rich output
447
+ self.console.print("\n[bold green]Code Analysis Complete[/bold green]\n")
448
+
449
+ # Display stats table
450
+ self._display_stats(stats)
451
+
452
+ # Display tree structure summary
453
+ if result.get("tree"):
454
+ tree_data = result["tree"]
455
+ self.console.print(f"\n[bold]Project Structure:[/bold] {tree_data['name']}")
456
+
457
+ # Count immediate children by type
458
+ dirs = sum(
459
+ 1 for c in tree_data.get("children", []) if c["type"] == "directory"
460
+ )
461
+ files = sum(1 for c in tree_data.get("children", []) if c["type"] == "file")
462
+
463
+ self.console.print(f" Root contains: {dirs} directories, {files} files")
464
+
465
+ # Show complexity insights
466
+ if result.get("nodes"):
467
+ nodes = result["nodes"]
468
+
469
+ # Find most complex elements
470
+ complex_nodes = sorted(
471
+ [n for n in nodes if hasattr(n, "complexity") and n.complexity > 5],
472
+ key=lambda x: x.complexity,
473
+ reverse=True,
474
+ )[:5]
475
+
476
+ if complex_nodes:
477
+ self.console.print("\n[bold]High Complexity Elements:[/bold]")
478
+ for node in complex_nodes:
479
+ self.console.print(
480
+ f" [red]⚠[/red] {node.name} "
481
+ f"([yellow]complexity: {node.complexity}[/yellow]) "
482
+ f"in {Path(node.file_path).name}"
483
+ )
484
+
485
+ # Show files without docstrings
486
+ no_docs = [
487
+ n
488
+ for n in nodes
489
+ if n.node_type in ("class", "function", "method")
490
+ and not n.has_docstring
491
+ ]
492
+
493
+ if no_docs:
494
+ self.console.print(
495
+ f"\n[dim]{len(no_docs)} elements missing docstrings[/dim]"
496
+ )
497
+
498
+
499
+ def manage_analyze_code(args: Any) -> int:
500
+ """
501
+ Entry point for the analyze-code command.
502
+
503
+ WHY: Provides a consistent interface for CLI command execution,
504
+ matching the pattern used by other commands in the CLI.
505
+
506
+ Args:
507
+ args: Parsed command line arguments
508
+
509
+ Returns:
510
+ Exit code (0 for success, non-zero for failure)
511
+ """
512
+ command = AnalyzeCodeCommand()
513
+
514
+ # Validate arguments
515
+ error = command.validate_args(args)
516
+ if error:
517
+ print(f"Error: {error}", file=sys.stderr)
518
+ return 1
519
+
520
+ # Execute command
521
+ result = command.run(args)
522
+
523
+ # Return appropriate exit code
524
+ return 0 if result.success else 1