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
@@ -1,5 +1,5 @@
1
1
  {
2
- "schema_version": "1.2.0",
2
+ "schema_version": "1.3.0",
3
3
  "agent_id": "research-agent",
4
4
  "agent_version": "4.4.0",
5
5
  "template_version": "2.3.0",
@@ -25,6 +25,7 @@ from .commands import ( # run_guarded_session is imported lazily to avoid loadi
25
25
  manage_agents,
26
26
  manage_config,
27
27
  manage_configure,
28
+ manage_debug,
28
29
  manage_mcp,
29
30
  manage_memory,
30
31
  manage_monitor,
@@ -33,6 +34,7 @@ from .commands import ( # run_guarded_session is imported lazily to avoid loadi
33
34
  run_session,
34
35
  show_info,
35
36
  )
37
+ from .commands.analyze_code import manage_analyze_code
36
38
  from .parser import create_parser, preprocess_args
37
39
  from .utils import ensure_directories, setup_logging
38
40
 
@@ -401,9 +403,11 @@ def _execute_command(command: str, args) -> int:
401
403
  CLICommands.CONFIG.value: manage_config,
402
404
  CLICommands.CONFIGURE.value: manage_configure,
403
405
  CLICommands.AGGREGATE.value: aggregate_command,
406
+ CLICommands.ANALYZE_CODE.value: manage_analyze_code,
404
407
  CLICommands.CLEANUP.value: cleanup_memory,
405
408
  CLICommands.MCP.value: manage_mcp,
406
409
  CLICommands.DOCTOR.value: run_doctor,
410
+ "debug": manage_debug, # Add debug command
407
411
  }
408
412
 
409
413
  # Execute command if found
@@ -8,9 +8,12 @@ separate modules for better maintainability and code organization.
8
8
  from .agent_manager import manage_agent_manager
9
9
  from .agents import manage_agents
10
10
  from .aggregate import aggregate_command
11
+ from .analyze import analyze_command
12
+ from .analyze_code import AnalyzeCodeCommand
11
13
  from .cleanup import cleanup_memory
12
14
  from .config import manage_config
13
15
  from .configure import manage_configure
16
+ from .debug import manage_debug
14
17
  from .doctor import run_doctor
15
18
  from .info import show_info
16
19
  from .mcp import manage_mcp
@@ -20,13 +23,16 @@ from .run import run_session
20
23
  from .tickets import list_tickets, manage_tickets
21
24
 
22
25
  __all__ = [
26
+ "AnalyzeCodeCommand",
23
27
  "aggregate_command",
28
+ "analyze_command",
24
29
  "cleanup_memory",
25
30
  "list_tickets",
26
31
  "manage_agent_manager",
27
32
  "manage_agents",
28
33
  "manage_config",
29
34
  "manage_configure",
35
+ "manage_debug",
30
36
  "manage_mcp",
31
37
  "manage_memory",
32
38
  "manage_monitor",
@@ -0,0 +1,547 @@
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, check=False, # 10 minute timeout
310
+ )
311
+
312
+ if result.returncode != 0:
313
+ self.logger.error(f"Claude execution failed: {result.stderr}")
314
+ # Return stdout even on error as it may contain partial results
315
+ return result.stdout if result.stdout else result.stderr
316
+
317
+ return result.stdout
318
+
319
+ finally:
320
+ # Clean up temp file
321
+ Path(prompt_file).unlink(missing_ok=True)
322
+
323
+ except subprocess.TimeoutExpired:
324
+ self.logger.error("Analysis timed out after 10 minutes")
325
+ return None
326
+ except Exception as e:
327
+ self.logger.error(f"Agent execution failed: {e}", exc_info=True)
328
+ return None
329
+
330
+ def _extract_mermaid_diagrams(self, response: str) -> List[Dict[str, str]]:
331
+ """Extract mermaid diagram blocks from response.
332
+
333
+ Args:
334
+ response: Agent response text
335
+
336
+ Returns:
337
+ List of diagram dictionaries with content and optional titles
338
+ """
339
+ diagrams = []
340
+
341
+ # Pattern to match mermaid code blocks
342
+ pattern = r"```mermaid\n(.*?)```"
343
+ matches = re.findall(pattern, response, re.DOTALL)
344
+
345
+ for i, match in enumerate(matches):
346
+ # Try to extract title from preceding line
347
+ title = f"diagram_{i+1}"
348
+
349
+ # Look for a title pattern before the diagram
350
+ title_pattern = r"(?:#+\s*)?([^\n]+)\n+```mermaid"
351
+ title_matches = re.findall(title_pattern, response)
352
+ if i < len(title_matches):
353
+ title = self._sanitize_filename(title_matches[i])
354
+
355
+ diagrams.append({"title": title, "content": match.strip(), "index": i + 1})
356
+
357
+ self.logger.info(f"Extracted {len(diagrams)} mermaid diagrams")
358
+ return diagrams
359
+
360
+ def _save_diagrams(self, diagrams: List[Dict[str, str]], args) -> List[Path]:
361
+ """Save mermaid diagrams to files.
362
+
363
+ Args:
364
+ diagrams: List of diagram dictionaries
365
+ args: Command arguments
366
+
367
+ Returns:
368
+ List of saved file paths
369
+ """
370
+ saved_files = []
371
+ diagram_dir = args.diagram_output or Path("./diagrams")
372
+ diagram_dir.mkdir(parents=True, exist_ok=True)
373
+
374
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
375
+
376
+ for diagram in diagrams:
377
+ filename = f"{timestamp}_{diagram['title']}.mermaid"
378
+ filepath = diagram_dir / filename
379
+
380
+ try:
381
+ with open(filepath, "w") as f:
382
+ # Write mermaid header comment
383
+ f.write("// Generated by Claude MPM Code Analyzer\n")
384
+ f.write(f"// Timestamp: {timestamp}\n")
385
+ f.write(f"// Target: {args.target}\n")
386
+ f.write(f"// Title: {diagram['title']}\n\n")
387
+ f.write(diagram["content"])
388
+
389
+ saved_files.append(filepath)
390
+ self.logger.debug(f"Saved diagram to {filepath}")
391
+
392
+ except Exception as e:
393
+ self.logger.error(f"Failed to save diagram {diagram['title']}: {e}")
394
+
395
+ return saved_files
396
+
397
+ def _sanitize_filename(self, title: str) -> str:
398
+ """Sanitize a string to be safe for filename.
399
+
400
+ Args:
401
+ title: Original title string
402
+
403
+ Returns:
404
+ Sanitized filename string
405
+ """
406
+ # Remove or replace unsafe characters
407
+ safe_chars = re.sub(r"[^\w\s-]", "", title)
408
+ safe_chars = re.sub(r"[-\s]+", "_", safe_chars)
409
+ return safe_chars[:50].strip("_").lower()
410
+
411
+ def _format_output(
412
+ self, result_data: Dict, format_type: str, diagrams: List[Dict]
413
+ ) -> str:
414
+ """Format output based on requested format.
415
+
416
+ Args:
417
+ result_data: Analysis results
418
+ format_type: Output format (text, json, markdown)
419
+ diagrams: List of extracted diagrams
420
+
421
+ Returns:
422
+ Formatted output string
423
+ """
424
+ if format_type == "json":
425
+ result_data["diagrams"] = diagrams
426
+ return json.dumps(result_data, indent=2)
427
+
428
+ if format_type == "markdown":
429
+ output = "# Code Analysis Report\n\n"
430
+ output += f"**Target:** `{result_data['target']}`\n"
431
+ output += f"**Timestamp:** {datetime.now().isoformat()}\n"
432
+
433
+ if result_data.get("session_id"):
434
+ output += f"**Session ID:** {result_data['session_id']}\n"
435
+
436
+ output += "\n## Analysis Results\n\n"
437
+ output += result_data.get("analysis", "No analysis results")
438
+
439
+ if diagrams:
440
+ output += f"\n## Generated Diagrams ({len(diagrams)})\n\n"
441
+ for diagram in diagrams:
442
+ output += f"### {diagram['title']}\n\n"
443
+ output += f"```mermaid\n{diagram['content']}\n```\n\n"
444
+
445
+ if result_data.get("saved_diagrams"):
446
+ output += "\n## Saved Files\n\n"
447
+ for filepath in result_data["saved_diagrams"]:
448
+ output += f"- `{filepath}`\n"
449
+
450
+ return output
451
+
452
+ # text format
453
+ output = f"Code Analysis Report\n{'='*50}\n\n"
454
+ output += f"Target: {result_data['target']}\n"
455
+
456
+ if diagrams:
457
+ output += f"\nšŸ“Š Extracted {len(diagrams)} mermaid diagrams:\n"
458
+ for diagram in diagrams:
459
+ output += f" • {diagram['title']}\n"
460
+
461
+ if result_data.get("saved_diagrams"):
462
+ output += "\nšŸ’¾ Saved diagrams to:\n"
463
+ for filepath in result_data["saved_diagrams"]:
464
+ output += f" • {filepath}\n"
465
+
466
+ output += f"\n{'-'*50}\nAnalysis Results:\n{'-'*50}\n"
467
+ output += result_data.get("analysis", "No analysis results")
468
+
469
+ return output
470
+
471
+ def _save_output(self, content: str, filepath: Path):
472
+ """Save output content to file.
473
+
474
+ Args:
475
+ content: Content to save
476
+ filepath: Target file path
477
+ """
478
+ try:
479
+ filepath.parent.mkdir(parents=True, exist_ok=True)
480
+ with open(filepath, "w") as f:
481
+ f.write(content)
482
+ self.logger.info(f"Saved output to {filepath}")
483
+ except Exception as e:
484
+ self.logger.error(f"Failed to save output: {e}")
485
+
486
+ def _create_analysis_session(self) -> str:
487
+ """Create a new analysis session.
488
+
489
+ Returns:
490
+ Session ID
491
+ """
492
+ session_data = {
493
+ "context": "code_analysis",
494
+ "created_at": datetime.now().isoformat(),
495
+ "type": "analysis",
496
+ }
497
+ session_id = self.session_manager.create_session(session_data)
498
+ self.logger.debug(f"Created analysis session: {session_id}")
499
+ return session_id
500
+
501
+
502
+ def analyze_command(args):
503
+ """Entry point for analyze command.
504
+
505
+ WHY: Provides a single entry point for code analysis with mermaid
506
+ diagram generation, helping users visualize and understand their codebase.
507
+
508
+ Args:
509
+ args: Parsed command-line arguments
510
+
511
+ Returns:
512
+ Exit code (0 for success, non-zero for errors)
513
+ """
514
+ command = AnalyzeCommand()
515
+ result = command.run(args)
516
+
517
+ if result.success:
518
+ if args.format == "json":
519
+ print(json.dumps(result.data, indent=2))
520
+ else:
521
+ print(result.message)
522
+ return 0
523
+ print(f"āŒ {result.message}", file=sys.stderr)
524
+ return 1
525
+
526
+
527
+ # Optional: Standalone execution for testing
528
+ if __name__ == "__main__":
529
+ import argparse
530
+
531
+ parser = argparse.ArgumentParser(description="Claude MPM Code Analyzer")
532
+ parser.add_argument("--target", type=Path, default=Path.cwd())
533
+ parser.add_argument("--mermaid", action="store_true")
534
+ parser.add_argument("--mermaid-types", nargs="+", default=["entry_points"])
535
+ parser.add_argument("--save-diagrams", action="store_true")
536
+ parser.add_argument("--diagram-output", type=Path)
537
+ parser.add_argument("--agent", default="code-analyzer")
538
+ parser.add_argument("--prompt", type=str)
539
+ parser.add_argument("--focus", nargs="+")
540
+ parser.add_argument("--session-id", type=str)
541
+ parser.add_argument("--no-session", action="store_true")
542
+ parser.add_argument("--format", default="text")
543
+ parser.add_argument("--output", type=Path)
544
+ parser.add_argument("--verbose", action="store_true")
545
+
546
+ args = parser.parse_args()
547
+ sys.exit(analyze_command(args))