claude-mpm 4.1.8__py3-none-any.whl → 4.1.11__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 (111) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +26 -1
  3. claude_mpm/agents/agents_metadata.py +57 -0
  4. claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
  5. claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
  6. claude_mpm/agents/templates/agent-manager.json +263 -17
  7. claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
  8. claude_mpm/agents/templates/code_analyzer.json +18 -8
  9. claude_mpm/agents/templates/engineer.json +1 -1
  10. claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
  11. claude_mpm/agents/templates/qa.json +1 -1
  12. claude_mpm/agents/templates/research.json +1 -1
  13. claude_mpm/cli/__init__.py +15 -0
  14. claude_mpm/cli/commands/__init__.py +6 -0
  15. claude_mpm/cli/commands/analyze.py +548 -0
  16. claude_mpm/cli/commands/analyze_code.py +524 -0
  17. claude_mpm/cli/commands/configure.py +78 -28
  18. claude_mpm/cli/commands/configure_tui.py +62 -60
  19. claude_mpm/cli/commands/dashboard.py +288 -0
  20. claude_mpm/cli/commands/debug.py +1386 -0
  21. claude_mpm/cli/commands/mpm_init.py +427 -0
  22. claude_mpm/cli/commands/mpm_init_handler.py +83 -0
  23. claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
  24. claude_mpm/cli/parsers/analyze_parser.py +135 -0
  25. claude_mpm/cli/parsers/base_parser.py +44 -0
  26. claude_mpm/cli/parsers/dashboard_parser.py +113 -0
  27. claude_mpm/cli/parsers/debug_parser.py +319 -0
  28. claude_mpm/cli/parsers/mpm_init_parser.py +122 -0
  29. claude_mpm/constants.py +13 -1
  30. claude_mpm/core/framework_loader.py +148 -6
  31. claude_mpm/core/log_manager.py +16 -13
  32. claude_mpm/core/logger.py +1 -1
  33. claude_mpm/core/unified_agent_registry.py +1 -1
  34. claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
  35. claude_mpm/dashboard/analysis_runner.py +455 -0
  36. claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
  37. claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
  38. claude_mpm/dashboard/static/built/components/code-tree.js +2 -0
  39. claude_mpm/dashboard/static/built/components/code-viewer.js +2 -0
  40. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  41. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
  42. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  43. claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
  44. claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
  45. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  46. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  47. claude_mpm/dashboard/static/css/activity.css +549 -0
  48. claude_mpm/dashboard/static/css/code-tree.css +1175 -0
  49. claude_mpm/dashboard/static/css/dashboard.css +245 -0
  50. claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
  51. claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
  52. claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
  53. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  54. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  55. claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
  56. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  57. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  58. claude_mpm/dashboard/static/js/components/activity-tree.js +1338 -0
  59. claude_mpm/dashboard/static/js/components/code-tree.js +2535 -0
  60. claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
  61. claude_mpm/dashboard/static/js/components/event-viewer.js +59 -9
  62. claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
  63. claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
  64. claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
  65. claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
  66. claude_mpm/dashboard/static/js/dashboard.js +51 -0
  67. claude_mpm/dashboard/static/js/socket-client.js +465 -29
  68. claude_mpm/dashboard/templates/index.html +182 -4
  69. claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
  70. claude_mpm/hooks/claude_hooks/installer.py +386 -113
  71. claude_mpm/scripts/claude-hook-handler.sh +161 -0
  72. claude_mpm/scripts/socketio_daemon.py +121 -8
  73. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
  74. claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
  75. claude_mpm/services/agents/memory/memory_format_service.py +1 -3
  76. claude_mpm/services/cli/agent_cleanup_service.py +1 -5
  77. claude_mpm/services/cli/agent_dependency_service.py +1 -1
  78. claude_mpm/services/cli/agent_validation_service.py +3 -4
  79. claude_mpm/services/cli/dashboard_launcher.py +2 -3
  80. claude_mpm/services/cli/startup_checker.py +0 -11
  81. claude_mpm/services/core/cache_manager.py +1 -3
  82. claude_mpm/services/core/path_resolver.py +1 -4
  83. claude_mpm/services/core/service_container.py +2 -2
  84. claude_mpm/services/diagnostics/checks/instructions_check.py +1 -2
  85. claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
  86. claude_mpm/services/infrastructure/monitoring.py +11 -11
  87. claude_mpm/services/project/architecture_analyzer.py +1 -1
  88. claude_mpm/services/project/dependency_analyzer.py +4 -4
  89. claude_mpm/services/project/language_analyzer.py +3 -3
  90. claude_mpm/services/project/metrics_collector.py +3 -6
  91. claude_mpm/services/socketio/event_normalizer.py +64 -0
  92. claude_mpm/services/socketio/handlers/__init__.py +2 -0
  93. claude_mpm/services/socketio/handlers/code_analysis.py +672 -0
  94. claude_mpm/services/socketio/handlers/registry.py +2 -0
  95. claude_mpm/services/socketio/server/connection_manager.py +6 -4
  96. claude_mpm/services/socketio/server/core.py +100 -11
  97. claude_mpm/services/socketio/server/main.py +8 -2
  98. claude_mpm/services/visualization/__init__.py +19 -0
  99. claude_mpm/services/visualization/mermaid_generator.py +938 -0
  100. claude_mpm/tools/__main__.py +208 -0
  101. claude_mpm/tools/code_tree_analyzer.py +1596 -0
  102. claude_mpm/tools/code_tree_builder.py +631 -0
  103. claude_mpm/tools/code_tree_events.py +416 -0
  104. claude_mpm/tools/socketio_debug.py +671 -0
  105. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/METADATA +2 -1
  106. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/RECORD +110 -74
  107. claude_mpm/agents/schema/agent_schema.json +0 -314
  108. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/WHEEL +0 -0
  109. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/entry_points.txt +0 -0
  110. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/licenses/LICENSE +0 -0
  111. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.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
@@ -35,7 +35,7 @@ class AgentConfig:
35
35
  """Simple agent configuration model."""
36
36
 
37
37
  def __init__(
38
- self, name: str, description: str = "", dependencies: List[str] = None
38
+ self, name: str, description: str = "", dependencies: Optional[List[str]] = None
39
39
  ):
40
40
  self.name = name
41
41
  self.description = description
@@ -107,7 +107,7 @@ class SimpleAgentManager:
107
107
 
108
108
  # Get metadata for display info
109
109
  metadata = template_data.get("metadata", {})
110
- name = metadata.get("name", agent_id)
110
+ metadata.get("name", agent_id)
111
111
  description = metadata.get(
112
112
  "description", "No description available"
113
113
  )
@@ -574,18 +574,18 @@ class ConfigureCommand(BaseCommand):
574
574
  self.console.print("[bold]Current Template:[/bold]")
575
575
  # Truncate for display if too large
576
576
  display_template = template.copy()
577
- if "instructions" in display_template and isinstance(
578
- display_template["instructions"], dict
579
- ):
580
- if (
577
+ if (
578
+ "instructions" in display_template
579
+ and isinstance(display_template["instructions"], dict)
580
+ and (
581
581
  "custom_instructions" in display_template["instructions"]
582
582
  and len(str(display_template["instructions"]["custom_instructions"]))
583
583
  > 200
584
- ):
585
- display_template["instructions"]["custom_instructions"] = (
586
- display_template["instructions"]["custom_instructions"][:200]
587
- + "..."
588
- )
584
+ )
585
+ ):
586
+ display_template["instructions"]["custom_instructions"] = (
587
+ display_template["instructions"]["custom_instructions"][:200] + "..."
588
+ )
589
589
 
590
590
  json_str = json.dumps(display_template, indent=2)
591
591
  # Limit display to first 50 lines for readability
@@ -784,11 +784,10 @@ class ConfigureCommand(BaseCommand):
784
784
  config_dir.mkdir(parents=True, exist_ok=True)
785
785
  custom_path = config_dir / f"{agent.name}.json"
786
786
 
787
- if custom_path.exists():
788
- if not Confirm.ask(
789
- "[yellow]Custom template already exists. Overwrite?[/yellow]"
790
- ):
791
- return
787
+ if custom_path.exists() and not Confirm.ask(
788
+ "[yellow]Custom template already exists. Overwrite?[/yellow]"
789
+ ):
790
+ return
792
791
 
793
792
  # Save the template copy
794
793
  with open(custom_path, "w") as f:
@@ -1022,20 +1021,33 @@ class ConfigureCommand(BaseCommand):
1022
1021
  mpm_version = self.version_service.get_version()
1023
1022
  build_number = self.version_service.get_build_number()
1024
1023
 
1025
- # Try to get Claude Code version
1024
+ # Try to get Claude Code version using the installer's method
1026
1025
  claude_version = "Unknown"
1027
1026
  try:
1028
- import subprocess
1027
+ from ...hooks.claude_hooks.installer import HookInstaller
1029
1028
 
1030
- result = subprocess.run(
1031
- ["claude", "--version"],
1032
- capture_output=True,
1033
- text=True,
1034
- timeout=5,
1035
- check=False,
1036
- )
1037
- if result.returncode == 0:
1038
- claude_version = result.stdout.strip()
1029
+ installer = HookInstaller()
1030
+ detected_version = installer.get_claude_version()
1031
+ if detected_version:
1032
+ is_compatible, _ = installer.is_version_compatible()
1033
+ claude_version = f"{detected_version} (Claude Code)"
1034
+ if not is_compatible:
1035
+ claude_version += (
1036
+ f" - Monitoring requires {installer.MIN_CLAUDE_VERSION}+"
1037
+ )
1038
+ else:
1039
+ # Fallback to direct subprocess call
1040
+ import subprocess
1041
+
1042
+ result = subprocess.run(
1043
+ ["claude", "--version"],
1044
+ capture_output=True,
1045
+ text=True,
1046
+ timeout=5,
1047
+ check=False,
1048
+ )
1049
+ if result.returncode == 0:
1050
+ claude_version = result.stdout.strip()
1039
1051
  except:
1040
1052
  pass
1041
1053
 
@@ -1200,7 +1212,27 @@ Directory: {self.project_dir}
1200
1212
 
1201
1213
  installer = HookInstaller()
1202
1214
 
1203
- # Check current status first
1215
+ # Check Claude Code version compatibility first
1216
+ is_compatible, version_message = installer.is_version_compatible()
1217
+ self.console.print("[cyan]Checking Claude Code version...[/cyan]")
1218
+ self.console.print(version_message)
1219
+
1220
+ if not is_compatible:
1221
+ self.console.print(
1222
+ "\n[yellow]⚠ Hook monitoring is not available for your Claude Code version.[/yellow]"
1223
+ )
1224
+ self.console.print(
1225
+ "The dashboard and other features will work without real-time monitoring."
1226
+ )
1227
+ self.console.print(
1228
+ f"\n[dim]To enable monitoring, upgrade Claude Code to version {installer.MIN_CLAUDE_VERSION} or higher.[/dim]"
1229
+ )
1230
+ return CommandResult.success_result(
1231
+ "Version incompatible with hook monitoring",
1232
+ data={"compatible": False, "message": version_message},
1233
+ )
1234
+
1235
+ # Check current status
1204
1236
  status = installer.get_status()
1205
1237
  if status["installed"] and not force:
1206
1238
  self.console.print("[yellow]Hooks are already installed.[/yellow]")
@@ -1256,6 +1288,24 @@ Directory: {self.project_dir}
1256
1288
 
1257
1289
  self.console.print("[bold]Hook Installation Status[/bold]\n")
1258
1290
 
1291
+ # Show Claude Code version and compatibility
1292
+ if status.get("claude_version"):
1293
+ self.console.print(f"Claude Code Version: {status['claude_version']}")
1294
+ if status.get("version_compatible"):
1295
+ self.console.print(
1296
+ "[green]✓[/green] Version compatible with hook monitoring"
1297
+ )
1298
+ else:
1299
+ self.console.print(
1300
+ f"[yellow]⚠[/yellow] {status.get('version_message', 'Version incompatible')}"
1301
+ )
1302
+ self.console.print()
1303
+ else:
1304
+ self.console.print(
1305
+ "[yellow]Claude Code version could not be detected[/yellow]"
1306
+ )
1307
+ self.console.print()
1308
+
1259
1309
  if status["installed"]:
1260
1310
  self.console.print(
1261
1311
  f"[green]✓[/green] Hooks installed at: {status['hook_script']}"