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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +26 -1
- claude_mpm/agents/OUTPUT_STYLE.md +73 -0
- claude_mpm/agents/agents_metadata.py +57 -0
- claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
- claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
- claude_mpm/agents/templates/agent-manager.json +263 -17
- claude_mpm/agents/templates/agent-manager.md +248 -10
- claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
- claude_mpm/agents/templates/code_analyzer.json +18 -8
- claude_mpm/agents/templates/engineer.json +1 -1
- claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +1 -1
- claude_mpm/cli/__init__.py +4 -0
- claude_mpm/cli/commands/__init__.py +6 -0
- claude_mpm/cli/commands/analyze.py +547 -0
- claude_mpm/cli/commands/analyze_code.py +524 -0
- claude_mpm/cli/commands/configure.py +223 -25
- claude_mpm/cli/commands/configure_tui.py +65 -61
- claude_mpm/cli/commands/debug.py +1387 -0
- claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
- claude_mpm/cli/parsers/analyze_parser.py +135 -0
- claude_mpm/cli/parsers/base_parser.py +29 -0
- claude_mpm/cli/parsers/configure_parser.py +23 -0
- claude_mpm/cli/parsers/debug_parser.py +319 -0
- claude_mpm/config/socketio_config.py +21 -21
- claude_mpm/constants.py +3 -1
- claude_mpm/core/framework_loader.py +148 -6
- claude_mpm/core/log_manager.py +16 -13
- claude_mpm/core/logger.py +1 -1
- claude_mpm/core/unified_agent_registry.py +1 -1
- claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
- claude_mpm/dashboard/analysis_runner.py +428 -0
- claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
- claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
- claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
- claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/css/activity.css +549 -0
- claude_mpm/dashboard/static/css/code-tree.css +846 -0
- claude_mpm/dashboard/static/css/dashboard.css +245 -0
- claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
- claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
- claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/activity-tree.js +1139 -0
- claude_mpm/dashboard/static/js/components/code-tree.js +1357 -0
- claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
- claude_mpm/dashboard/static/js/components/event-viewer.js +11 -0
- claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
- claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
- claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
- claude_mpm/dashboard/static/js/dashboard.js +39 -0
- claude_mpm/dashboard/static/js/socket-client.js +414 -20
- claude_mpm/dashboard/templates/index.html +184 -4
- claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
- claude_mpm/hooks/claude_hooks/installer.py +728 -0
- claude_mpm/scripts/claude-hook-handler.sh +161 -0
- claude_mpm/scripts/socketio_daemon.py +121 -8
- claude_mpm/services/agents/deployment/agent_config_provider.py +127 -27
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
- claude_mpm/services/agents/memory/memory_format_service.py +1 -5
- claude_mpm/services/cli/agent_cleanup_service.py +1 -2
- claude_mpm/services/cli/agent_dependency_service.py +1 -1
- claude_mpm/services/cli/agent_validation_service.py +3 -4
- claude_mpm/services/cli/dashboard_launcher.py +2 -3
- claude_mpm/services/cli/startup_checker.py +0 -10
- claude_mpm/services/core/cache_manager.py +1 -2
- claude_mpm/services/core/path_resolver.py +1 -4
- claude_mpm/services/core/service_container.py +2 -2
- claude_mpm/services/diagnostics/checks/instructions_check.py +2 -5
- claude_mpm/services/event_bus/direct_relay.py +98 -20
- claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
- claude_mpm/services/infrastructure/monitoring.py +11 -11
- claude_mpm/services/project/architecture_analyzer.py +1 -1
- claude_mpm/services/project/dependency_analyzer.py +4 -4
- claude_mpm/services/project/language_analyzer.py +3 -3
- claude_mpm/services/project/metrics_collector.py +3 -6
- claude_mpm/services/socketio/handlers/__init__.py +2 -0
- claude_mpm/services/socketio/handlers/code_analysis.py +170 -0
- claude_mpm/services/socketio/handlers/registry.py +2 -0
- claude_mpm/services/socketio/server/connection_manager.py +95 -65
- claude_mpm/services/socketio/server/core.py +125 -17
- claude_mpm/services/socketio/server/main.py +44 -5
- claude_mpm/services/visualization/__init__.py +19 -0
- claude_mpm/services/visualization/mermaid_generator.py +938 -0
- claude_mpm/tools/__main__.py +208 -0
- claude_mpm/tools/code_tree_analyzer.py +778 -0
- claude_mpm/tools/code_tree_builder.py +632 -0
- claude_mpm/tools/code_tree_events.py +318 -0
- claude_mpm/tools/socketio_debug.py +671 -0
- {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/RECORD +108 -77
- claude_mpm/agents/schema/agent_schema.json +0 -314
- {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/licenses/LICENSE +0 -0
- {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
|