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,548 @@
1
+ """
2
+ Code analysis command implementation for claude-mpm.
3
+
4
+ WHY: This module provides code analysis capabilities with mermaid diagram
5
+ generation, allowing users to visualize and understand their codebase
6
+ architecture through automated analysis.
7
+
8
+ DESIGN DECISIONS:
9
+ - Use async for better performance with multiple diagram generation
10
+ - Extract mermaid blocks from agent responses automatically
11
+ - Save diagrams with timestamps for versioning
12
+ - Support multiple diagram types in single run
13
+ - Integrate with existing session management
14
+ """
15
+
16
+ import asyncio
17
+ import json
18
+ import os
19
+ import re
20
+ import subprocess
21
+ import sys
22
+ from datetime import datetime
23
+ from pathlib import Path
24
+ from typing import Dict, List, Optional
25
+
26
+ from ...core.logging_config import get_logger
27
+ from ...services.cli.session_manager import SessionManager
28
+ from ..shared import BaseCommand, CommandResult
29
+
30
+
31
+ class AnalyzeCommand(BaseCommand):
32
+ """Analyze command for code analysis with mermaid generation."""
33
+
34
+ def __init__(self):
35
+ super().__init__("analyze")
36
+ self.logger = get_logger(__name__)
37
+ self.session_manager = SessionManager()
38
+
39
+ def validate_args(self, args) -> Optional[str]:
40
+ """Validate command arguments.
41
+
42
+ Args:
43
+ args: Command arguments
44
+
45
+ Returns:
46
+ Error message if validation fails, None otherwise
47
+ """
48
+ # Validate target exists
49
+ if not args.target.exists():
50
+ return f"Target path does not exist: {args.target}"
51
+
52
+ # Validate diagram output directory if saving
53
+ if args.save_diagrams:
54
+ diagram_dir = args.diagram_output or Path("./diagrams")
55
+ if not diagram_dir.exists():
56
+ try:
57
+ diagram_dir.mkdir(parents=True, exist_ok=True)
58
+ except Exception as e:
59
+ return f"Cannot create diagram output directory: {e}"
60
+
61
+ return None
62
+
63
+ def run(self, args) -> CommandResult:
64
+ """Execute the analyze command.
65
+
66
+ Args:
67
+ args: Command arguments
68
+
69
+ Returns:
70
+ CommandResult with analysis results
71
+ """
72
+ try:
73
+ # Run async analysis
74
+ return asyncio.run(self._run_analysis(args))
75
+ except KeyboardInterrupt:
76
+ return CommandResult.error_result("Analysis interrupted by user")
77
+ except Exception as e:
78
+ self.logger.error(f"Analysis failed: {e}", exc_info=True)
79
+ return CommandResult.error_result(f"Analysis failed: {e}")
80
+
81
+ async def _run_analysis(self, args) -> CommandResult:
82
+ """Run the actual analysis asynchronously.
83
+
84
+ Args:
85
+ args: Command arguments
86
+
87
+ Returns:
88
+ CommandResult with analysis results
89
+ """
90
+ # Build analysis prompt
91
+ prompt = self._build_analysis_prompt(args)
92
+
93
+ # Setup session if needed
94
+ session_id = None
95
+ if not args.no_session:
96
+ session_id = args.session_id or self._create_analysis_session()
97
+
98
+ # Execute analysis via agent
99
+ self.logger.info(f"Starting code analysis of {args.target}")
100
+ response = await self._execute_agent_analysis(
101
+ agent=args.agent, prompt=prompt, session_id=session_id, args=args
102
+ )
103
+
104
+ if not response:
105
+ return CommandResult.error_result("No response from analysis agent")
106
+
107
+ # Extract mermaid diagrams if enabled
108
+ diagrams = []
109
+ if args.mermaid:
110
+ diagrams = self._extract_mermaid_diagrams(response)
111
+
112
+ if args.save_diagrams:
113
+ saved_files = self._save_diagrams(diagrams, args)
114
+ self.logger.info(f"Saved {len(saved_files)} diagrams")
115
+
116
+ # Format and return results
117
+ result_data = {
118
+ "target": str(args.target),
119
+ "analysis": response,
120
+ "diagrams_found": len(diagrams),
121
+ "session_id": session_id,
122
+ }
123
+
124
+ if args.save_diagrams and diagrams:
125
+ result_data["saved_diagrams"] = [str(f) for f in saved_files]
126
+
127
+ # Handle output format
128
+ output = self._format_output(result_data, args.format, diagrams)
129
+
130
+ # Save to file if requested
131
+ if args.output:
132
+ self._save_output(output, args.output)
133
+
134
+ return CommandResult.success_result(
135
+ message=output if args.format == "text" else "Analysis completed",
136
+ data=result_data,
137
+ )
138
+
139
+ def _build_analysis_prompt(self, args) -> str:
140
+ """Build the analysis prompt based on arguments.
141
+
142
+ Args:
143
+ args: Command arguments
144
+
145
+ Returns:
146
+ Formatted prompt string
147
+ """
148
+ prompt_parts = []
149
+
150
+ # Base analysis request
151
+ prompt_parts.append(f"Analyze the code at: {args.target}")
152
+
153
+ # Add custom prompt if provided
154
+ if args.prompt:
155
+ prompt_parts.append(f"\n{args.prompt}")
156
+
157
+ # Add focus areas
158
+ if args.focus:
159
+ focus_list = args.focus if isinstance(args.focus, list) else [args.focus]
160
+ focus_str = ", ".join(focus_list)
161
+ prompt_parts.append(f"\nFocus on: {focus_str}")
162
+
163
+ # Add mermaid diagram requests
164
+ if args.mermaid:
165
+ types_list = (
166
+ args.mermaid_types
167
+ if isinstance(args.mermaid_types, list)
168
+ else [args.mermaid_types]
169
+ )
170
+ diagram_types = ", ".join(types_list)
171
+ prompt_parts.append(
172
+ f"\nGenerate mermaid diagrams for: {diagram_types}\n"
173
+ "Ensure each diagram is in a separate ```mermaid code block."
174
+ )
175
+
176
+ # Add specific instructions per diagram type
177
+ diagram_instructions = self._get_diagram_instructions(args.mermaid_types)
178
+ if diagram_instructions:
179
+ prompt_parts.append(diagram_instructions)
180
+
181
+ return "\n".join(prompt_parts)
182
+
183
+ def _get_diagram_instructions(self, diagram_types: List[str]) -> str:
184
+ """Get specific instructions for requested diagram types.
185
+
186
+ Args:
187
+ diagram_types: List of diagram types
188
+
189
+ Returns:
190
+ Formatted instructions string
191
+ """
192
+ instructions = []
193
+
194
+ type_instructions = {
195
+ "entry_points": "Identify and map all entry points in the codebase",
196
+ "class_diagram": "Create UML class diagrams showing relationships",
197
+ "sequence": "Show sequence diagrams for key workflows",
198
+ "flowchart": "Create flowcharts for main processes",
199
+ "state": "Show state diagrams for stateful components",
200
+ "entity_relationship": "Map database entities and relationships",
201
+ "component": "Show high-level component architecture",
202
+ "dependency_graph": "Map module and package dependencies",
203
+ "call_graph": "Show function/method call relationships",
204
+ "architecture": "Create overall system architecture diagram",
205
+ }
206
+
207
+ for dtype in diagram_types:
208
+ if dtype in type_instructions:
209
+ instructions.append(f"- {type_instructions[dtype]}")
210
+
211
+ if instructions:
212
+ return "\nDiagram requirements:\n" + "\n".join(instructions)
213
+ return ""
214
+
215
+ async def _execute_agent_analysis(
216
+ self, agent: str, prompt: str, session_id: Optional[str], args
217
+ ) -> Optional[str]:
218
+ """Execute analysis using the specified agent.
219
+
220
+ Args:
221
+ agent: Agent ID to use
222
+ prompt: Analysis prompt
223
+ session_id: Session ID if using sessions
224
+ args: Command arguments
225
+
226
+ Returns:
227
+ Agent response text or None if failed
228
+ """
229
+ try:
230
+ # Import required modules
231
+ from ...core.claude_runner import ClaudeRunner
232
+ from ...services.agents.deployment.agent_deployment import (
233
+ AgentDeploymentService,
234
+ )
235
+
236
+ # Deploy the analysis agent if not already deployed
237
+ deployment_service = AgentDeploymentService()
238
+ deployment_result = deployment_service.deploy_agent(agent, force=False)
239
+
240
+ if not deployment_result["success"]:
241
+ self.logger.error(
242
+ f"Failed to deploy agent {agent}: {deployment_result.get('error')}"
243
+ )
244
+ return None
245
+
246
+ # Create a temporary file with the prompt
247
+ import tempfile
248
+
249
+ with tempfile.NamedTemporaryFile(
250
+ mode="w", suffix=".txt", delete=False
251
+ ) as f:
252
+ f.write(prompt)
253
+ prompt_file = f.name
254
+
255
+ try:
256
+ # Build Claude args for analysis
257
+ claude_args = []
258
+
259
+ # Add input file
260
+ claude_args.extend(["--input", prompt_file])
261
+
262
+ # Add session if specified
263
+ if session_id:
264
+ claude_args.extend(["--session", session_id])
265
+
266
+ # Set working directory
267
+ claude_args.extend(["--cwd", str(args.target)])
268
+
269
+ # Disable hooks for cleaner output capture
270
+ no_hooks = True
271
+
272
+ # Initialize and run Claude runner
273
+ ClaudeRunner(
274
+ enable_tickets=False,
275
+ launch_method="subprocess",
276
+ claude_args=claude_args,
277
+ )
278
+
279
+ # Set up environment
280
+ env = os.environ.copy()
281
+ env["CLAUDE_MPM_AGENT"] = agent
282
+
283
+ # Build the full command
284
+ scripts_dir = (
285
+ Path(__file__).parent.parent.parent.parent.parent / "scripts"
286
+ )
287
+ claude_mpm_script = scripts_dir / "claude-mpm"
288
+
289
+ cmd = []
290
+ if claude_mpm_script.exists():
291
+ cmd = [str(claude_mpm_script)]
292
+ else:
293
+ # Fallback to using module execution
294
+ cmd = [sys.executable, "-m", "claude_mpm"]
295
+
296
+ # Add subcommand and options
297
+ cmd.extend(["run", "--no-tickets", "--input", prompt_file])
298
+
299
+ if no_hooks:
300
+ cmd.append("--no-hooks")
301
+
302
+ # Execute the command
303
+ result = subprocess.run(
304
+ cmd,
305
+ capture_output=True,
306
+ text=True,
307
+ cwd=str(args.target),
308
+ env=env,
309
+ timeout=600,
310
+ check=False, # 10 minute timeout
311
+ )
312
+
313
+ if result.returncode != 0:
314
+ self.logger.error(f"Claude execution failed: {result.stderr}")
315
+ # Return stdout even on error as it may contain partial results
316
+ return result.stdout if result.stdout else result.stderr
317
+
318
+ return result.stdout
319
+
320
+ finally:
321
+ # Clean up temp file
322
+ Path(prompt_file).unlink(missing_ok=True)
323
+
324
+ except subprocess.TimeoutExpired:
325
+ self.logger.error("Analysis timed out after 10 minutes")
326
+ return None
327
+ except Exception as e:
328
+ self.logger.error(f"Agent execution failed: {e}", exc_info=True)
329
+ return None
330
+
331
+ def _extract_mermaid_diagrams(self, response: str) -> List[Dict[str, str]]:
332
+ """Extract mermaid diagram blocks from response.
333
+
334
+ Args:
335
+ response: Agent response text
336
+
337
+ Returns:
338
+ List of diagram dictionaries with content and optional titles
339
+ """
340
+ diagrams = []
341
+
342
+ # Pattern to match mermaid code blocks
343
+ pattern = r"```mermaid\n(.*?)```"
344
+ matches = re.findall(pattern, response, re.DOTALL)
345
+
346
+ for i, match in enumerate(matches):
347
+ # Try to extract title from preceding line
348
+ title = f"diagram_{i+1}"
349
+
350
+ # Look for a title pattern before the diagram
351
+ title_pattern = r"(?:#+\s*)?([^\n]+)\n+```mermaid"
352
+ title_matches = re.findall(title_pattern, response)
353
+ if i < len(title_matches):
354
+ title = self._sanitize_filename(title_matches[i])
355
+
356
+ diagrams.append({"title": title, "content": match.strip(), "index": i + 1})
357
+
358
+ self.logger.info(f"Extracted {len(diagrams)} mermaid diagrams")
359
+ return diagrams
360
+
361
+ def _save_diagrams(self, diagrams: List[Dict[str, str]], args) -> List[Path]:
362
+ """Save mermaid diagrams to files.
363
+
364
+ Args:
365
+ diagrams: List of diagram dictionaries
366
+ args: Command arguments
367
+
368
+ Returns:
369
+ List of saved file paths
370
+ """
371
+ saved_files = []
372
+ diagram_dir = args.diagram_output or Path("./diagrams")
373
+ diagram_dir.mkdir(parents=True, exist_ok=True)
374
+
375
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
376
+
377
+ for diagram in diagrams:
378
+ filename = f"{timestamp}_{diagram['title']}.mermaid"
379
+ filepath = diagram_dir / filename
380
+
381
+ try:
382
+ with open(filepath, "w") as f:
383
+ # Write mermaid header comment
384
+ f.write("// Generated by Claude MPM Code Analyzer\n")
385
+ f.write(f"// Timestamp: {timestamp}\n")
386
+ f.write(f"// Target: {args.target}\n")
387
+ f.write(f"// Title: {diagram['title']}\n\n")
388
+ f.write(diagram["content"])
389
+
390
+ saved_files.append(filepath)
391
+ self.logger.debug(f"Saved diagram to {filepath}")
392
+
393
+ except Exception as e:
394
+ self.logger.error(f"Failed to save diagram {diagram['title']}: {e}")
395
+
396
+ return saved_files
397
+
398
+ def _sanitize_filename(self, title: str) -> str:
399
+ """Sanitize a string to be safe for filename.
400
+
401
+ Args:
402
+ title: Original title string
403
+
404
+ Returns:
405
+ Sanitized filename string
406
+ """
407
+ # Remove or replace unsafe characters
408
+ safe_chars = re.sub(r"[^\w\s-]", "", title)
409
+ safe_chars = re.sub(r"[-\s]+", "_", safe_chars)
410
+ return safe_chars[:50].strip("_").lower()
411
+
412
+ def _format_output(
413
+ self, result_data: Dict, format_type: str, diagrams: List[Dict]
414
+ ) -> str:
415
+ """Format output based on requested format.
416
+
417
+ Args:
418
+ result_data: Analysis results
419
+ format_type: Output format (text, json, markdown)
420
+ diagrams: List of extracted diagrams
421
+
422
+ Returns:
423
+ Formatted output string
424
+ """
425
+ if format_type == "json":
426
+ result_data["diagrams"] = diagrams
427
+ return json.dumps(result_data, indent=2)
428
+
429
+ if format_type == "markdown":
430
+ output = "# Code Analysis Report\n\n"
431
+ output += f"**Target:** `{result_data['target']}`\n"
432
+ output += f"**Timestamp:** {datetime.now().isoformat()}\n"
433
+
434
+ if result_data.get("session_id"):
435
+ output += f"**Session ID:** {result_data['session_id']}\n"
436
+
437
+ output += "\n## Analysis Results\n\n"
438
+ output += result_data.get("analysis", "No analysis results")
439
+
440
+ if diagrams:
441
+ output += f"\n## Generated Diagrams ({len(diagrams)})\n\n"
442
+ for diagram in diagrams:
443
+ output += f"### {diagram['title']}\n\n"
444
+ output += f"```mermaid\n{diagram['content']}\n```\n\n"
445
+
446
+ if result_data.get("saved_diagrams"):
447
+ output += "\n## Saved Files\n\n"
448
+ for filepath in result_data["saved_diagrams"]:
449
+ output += f"- `{filepath}`\n"
450
+
451
+ return output
452
+
453
+ # text format
454
+ output = f"Code Analysis Report\n{'='*50}\n\n"
455
+ output += f"Target: {result_data['target']}\n"
456
+
457
+ if diagrams:
458
+ output += f"\nšŸ“Š Extracted {len(diagrams)} mermaid diagrams:\n"
459
+ for diagram in diagrams:
460
+ output += f" • {diagram['title']}\n"
461
+
462
+ if result_data.get("saved_diagrams"):
463
+ output += "\nšŸ’¾ Saved diagrams to:\n"
464
+ for filepath in result_data["saved_diagrams"]:
465
+ output += f" • {filepath}\n"
466
+
467
+ output += f"\n{'-'*50}\nAnalysis Results:\n{'-'*50}\n"
468
+ output += result_data.get("analysis", "No analysis results")
469
+
470
+ return output
471
+
472
+ def _save_output(self, content: str, filepath: Path):
473
+ """Save output content to file.
474
+
475
+ Args:
476
+ content: Content to save
477
+ filepath: Target file path
478
+ """
479
+ try:
480
+ filepath.parent.mkdir(parents=True, exist_ok=True)
481
+ with open(filepath, "w") as f:
482
+ f.write(content)
483
+ self.logger.info(f"Saved output to {filepath}")
484
+ except Exception as e:
485
+ self.logger.error(f"Failed to save output: {e}")
486
+
487
+ def _create_analysis_session(self) -> str:
488
+ """Create a new analysis session.
489
+
490
+ Returns:
491
+ Session ID
492
+ """
493
+ session_data = {
494
+ "context": "code_analysis",
495
+ "created_at": datetime.now().isoformat(),
496
+ "type": "analysis",
497
+ }
498
+ session_id = self.session_manager.create_session(session_data)
499
+ self.logger.debug(f"Created analysis session: {session_id}")
500
+ return session_id
501
+
502
+
503
+ def analyze_command(args):
504
+ """Entry point for analyze command.
505
+
506
+ WHY: Provides a single entry point for code analysis with mermaid
507
+ diagram generation, helping users visualize and understand their codebase.
508
+
509
+ Args:
510
+ args: Parsed command-line arguments
511
+
512
+ Returns:
513
+ Exit code (0 for success, non-zero for errors)
514
+ """
515
+ command = AnalyzeCommand()
516
+ result = command.run(args)
517
+
518
+ if result.success:
519
+ if args.format == "json":
520
+ print(json.dumps(result.data, indent=2))
521
+ else:
522
+ print(result.message)
523
+ return 0
524
+ print(f"āŒ {result.message}", file=sys.stderr)
525
+ return 1
526
+
527
+
528
+ # Optional: Standalone execution for testing
529
+ if __name__ == "__main__":
530
+ import argparse
531
+
532
+ parser = argparse.ArgumentParser(description="Claude MPM Code Analyzer")
533
+ parser.add_argument("--target", type=Path, default=Path.cwd())
534
+ parser.add_argument("--mermaid", action="store_true")
535
+ parser.add_argument("--mermaid-types", nargs="+", default=["entry_points"])
536
+ parser.add_argument("--save-diagrams", action="store_true")
537
+ parser.add_argument("--diagram-output", type=Path)
538
+ parser.add_argument("--agent", default="code-analyzer")
539
+ parser.add_argument("--prompt", type=str)
540
+ parser.add_argument("--focus", nargs="+")
541
+ parser.add_argument("--session-id", type=str)
542
+ parser.add_argument("--no-session", action="store_true")
543
+ parser.add_argument("--format", default="text")
544
+ parser.add_argument("--output", type=Path)
545
+ parser.add_argument("--verbose", action="store_true")
546
+
547
+ args = parser.parse_args()
548
+ sys.exit(analyze_command(args))