claude-mpm 4.1.8__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/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 +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 +77 -28
- claude_mpm/cli/commands/configure_tui.py +60 -60
- 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/debug_parser.py +319 -0
- 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 +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 -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 +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/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 +4 -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 +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.8.dist-info → claude_mpm-4.1.10.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/RECORD +102 -73
- claude_mpm/agents/schema/agent_schema.json +0 -314
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.8.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
|
|
@@ -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
|
)
|
|
@@ -576,16 +576,15 @@ class ConfigureCommand(BaseCommand):
|
|
|
576
576
|
display_template = template.copy()
|
|
577
577
|
if "instructions" in display_template and isinstance(
|
|
578
578
|
display_template["instructions"], dict
|
|
579
|
+
) and (
|
|
580
|
+
"custom_instructions" in display_template["instructions"]
|
|
581
|
+
and len(str(display_template["instructions"]["custom_instructions"]))
|
|
582
|
+
> 200
|
|
579
583
|
):
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
):
|
|
585
|
-
display_template["instructions"]["custom_instructions"] = (
|
|
586
|
-
display_template["instructions"]["custom_instructions"][:200]
|
|
587
|
-
+ "..."
|
|
588
|
-
)
|
|
584
|
+
display_template["instructions"]["custom_instructions"] = (
|
|
585
|
+
display_template["instructions"]["custom_instructions"][:200]
|
|
586
|
+
+ "..."
|
|
587
|
+
)
|
|
589
588
|
|
|
590
589
|
json_str = json.dumps(display_template, indent=2)
|
|
591
590
|
# Limit display to first 50 lines for readability
|
|
@@ -784,11 +783,10 @@ class ConfigureCommand(BaseCommand):
|
|
|
784
783
|
config_dir.mkdir(parents=True, exist_ok=True)
|
|
785
784
|
custom_path = config_dir / f"{agent.name}.json"
|
|
786
785
|
|
|
787
|
-
if custom_path.exists()
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
return
|
|
786
|
+
if custom_path.exists() and not Confirm.ask(
|
|
787
|
+
"[yellow]Custom template already exists. Overwrite?[/yellow]"
|
|
788
|
+
):
|
|
789
|
+
return
|
|
792
790
|
|
|
793
791
|
# Save the template copy
|
|
794
792
|
with open(custom_path, "w") as f:
|
|
@@ -1022,20 +1020,33 @@ class ConfigureCommand(BaseCommand):
|
|
|
1022
1020
|
mpm_version = self.version_service.get_version()
|
|
1023
1021
|
build_number = self.version_service.get_build_number()
|
|
1024
1022
|
|
|
1025
|
-
# Try to get Claude Code version
|
|
1023
|
+
# Try to get Claude Code version using the installer's method
|
|
1026
1024
|
claude_version = "Unknown"
|
|
1027
1025
|
try:
|
|
1028
|
-
import
|
|
1026
|
+
from ...hooks.claude_hooks.installer import HookInstaller
|
|
1029
1027
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1028
|
+
installer = HookInstaller()
|
|
1029
|
+
detected_version = installer.get_claude_version()
|
|
1030
|
+
if detected_version:
|
|
1031
|
+
is_compatible, _ = installer.is_version_compatible()
|
|
1032
|
+
claude_version = f"{detected_version} (Claude Code)"
|
|
1033
|
+
if not is_compatible:
|
|
1034
|
+
claude_version += (
|
|
1035
|
+
f" - Monitoring requires {installer.MIN_CLAUDE_VERSION}+"
|
|
1036
|
+
)
|
|
1037
|
+
else:
|
|
1038
|
+
# Fallback to direct subprocess call
|
|
1039
|
+
import subprocess
|
|
1040
|
+
|
|
1041
|
+
result = subprocess.run(
|
|
1042
|
+
["claude", "--version"],
|
|
1043
|
+
capture_output=True,
|
|
1044
|
+
text=True,
|
|
1045
|
+
timeout=5,
|
|
1046
|
+
check=False,
|
|
1047
|
+
)
|
|
1048
|
+
if result.returncode == 0:
|
|
1049
|
+
claude_version = result.stdout.strip()
|
|
1039
1050
|
except:
|
|
1040
1051
|
pass
|
|
1041
1052
|
|
|
@@ -1200,7 +1211,27 @@ Directory: {self.project_dir}
|
|
|
1200
1211
|
|
|
1201
1212
|
installer = HookInstaller()
|
|
1202
1213
|
|
|
1203
|
-
# Check
|
|
1214
|
+
# Check Claude Code version compatibility first
|
|
1215
|
+
is_compatible, version_message = installer.is_version_compatible()
|
|
1216
|
+
self.console.print("[cyan]Checking Claude Code version...[/cyan]")
|
|
1217
|
+
self.console.print(version_message)
|
|
1218
|
+
|
|
1219
|
+
if not is_compatible:
|
|
1220
|
+
self.console.print(
|
|
1221
|
+
"\n[yellow]⚠ Hook monitoring is not available for your Claude Code version.[/yellow]"
|
|
1222
|
+
)
|
|
1223
|
+
self.console.print(
|
|
1224
|
+
"The dashboard and other features will work without real-time monitoring."
|
|
1225
|
+
)
|
|
1226
|
+
self.console.print(
|
|
1227
|
+
f"\n[dim]To enable monitoring, upgrade Claude Code to version {installer.MIN_CLAUDE_VERSION} or higher.[/dim]"
|
|
1228
|
+
)
|
|
1229
|
+
return CommandResult.success_result(
|
|
1230
|
+
"Version incompatible with hook monitoring",
|
|
1231
|
+
data={"compatible": False, "message": version_message},
|
|
1232
|
+
)
|
|
1233
|
+
|
|
1234
|
+
# Check current status
|
|
1204
1235
|
status = installer.get_status()
|
|
1205
1236
|
if status["installed"] and not force:
|
|
1206
1237
|
self.console.print("[yellow]Hooks are already installed.[/yellow]")
|
|
@@ -1256,6 +1287,24 @@ Directory: {self.project_dir}
|
|
|
1256
1287
|
|
|
1257
1288
|
self.console.print("[bold]Hook Installation Status[/bold]\n")
|
|
1258
1289
|
|
|
1290
|
+
# Show Claude Code version and compatibility
|
|
1291
|
+
if status.get("claude_version"):
|
|
1292
|
+
self.console.print(f"Claude Code Version: {status['claude_version']}")
|
|
1293
|
+
if status.get("version_compatible"):
|
|
1294
|
+
self.console.print(
|
|
1295
|
+
"[green]✓[/green] Version compatible with hook monitoring"
|
|
1296
|
+
)
|
|
1297
|
+
else:
|
|
1298
|
+
self.console.print(
|
|
1299
|
+
f"[yellow]⚠[/yellow] {status.get('version_message', 'Version incompatible')}"
|
|
1300
|
+
)
|
|
1301
|
+
self.console.print()
|
|
1302
|
+
else:
|
|
1303
|
+
self.console.print(
|
|
1304
|
+
"[yellow]Claude Code version could not be detected[/yellow]"
|
|
1305
|
+
)
|
|
1306
|
+
self.console.print()
|
|
1307
|
+
|
|
1259
1308
|
if status["installed"]:
|
|
1260
1309
|
self.console.print(
|
|
1261
1310
|
f"[green]✓[/green] Hooks installed at: {status['hook_script']}"
|