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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +26 -1
- 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/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 +15 -0
- claude_mpm/cli/commands/__init__.py +6 -0
- claude_mpm/cli/commands/analyze.py +548 -0
- claude_mpm/cli/commands/analyze_code.py +524 -0
- claude_mpm/cli/commands/configure.py +78 -28
- claude_mpm/cli/commands/configure_tui.py +62 -60
- claude_mpm/cli/commands/dashboard.py +288 -0
- claude_mpm/cli/commands/debug.py +1386 -0
- claude_mpm/cli/commands/mpm_init.py +427 -0
- claude_mpm/cli/commands/mpm_init_handler.py +83 -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 +44 -0
- claude_mpm/cli/parsers/dashboard_parser.py +113 -0
- claude_mpm/cli/parsers/debug_parser.py +319 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +122 -0
- claude_mpm/constants.py +13 -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 +455 -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/code-tree.js +2 -0
- claude_mpm/dashboard/static/built/components/code-viewer.js +2 -0
- 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 +1175 -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 +1338 -0
- claude_mpm/dashboard/static/js/components/code-tree.js +2535 -0
- claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
- claude_mpm/dashboard/static/js/components/event-viewer.js +59 -9
- 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 +51 -0
- claude_mpm/dashboard/static/js/socket-client.js +465 -29
- claude_mpm/dashboard/templates/index.html +182 -4
- claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
- claude_mpm/hooks/claude_hooks/installer.py +386 -113
- claude_mpm/scripts/claude-hook-handler.sh +161 -0
- claude_mpm/scripts/socketio_daemon.py +121 -8
- 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 -3
- claude_mpm/services/cli/agent_cleanup_service.py +1 -5
- 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 -11
- claude_mpm/services/core/cache_manager.py +1 -3
- 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 +1 -2
- 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/event_normalizer.py +64 -0
- claude_mpm/services/socketio/handlers/__init__.py +2 -0
- claude_mpm/services/socketio/handlers/code_analysis.py +672 -0
- claude_mpm/services/socketio/handlers/registry.py +2 -0
- claude_mpm/services/socketio/server/connection_manager.py +6 -4
- claude_mpm/services/socketio/server/core.py +100 -11
- claude_mpm/services/socketio/server/main.py +8 -2
- 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 +1596 -0
- claude_mpm/tools/code_tree_builder.py +631 -0
- claude_mpm/tools/code_tree_events.py +416 -0
- claude_mpm/tools/socketio_debug.py +671 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/METADATA +2 -1
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/RECORD +110 -74
- claude_mpm/agents/schema/agent_schema.json +0 -314
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
-
|
|
586
|
-
|
|
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
|
-
|
|
789
|
-
|
|
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
|
|
1027
|
+
from ...hooks.claude_hooks.installer import HookInstaller
|
|
1029
1028
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
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
|
|
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']}"
|