claude-mpm 4.1.8__py3-none-any.whl → 4.1.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +26 -1
  3. claude_mpm/agents/agents_metadata.py +57 -0
  4. claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
  5. claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
  6. claude_mpm/agents/templates/agent-manager.json +263 -17
  7. claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
  8. claude_mpm/agents/templates/code_analyzer.json +18 -8
  9. claude_mpm/agents/templates/engineer.json +1 -1
  10. claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
  11. claude_mpm/agents/templates/qa.json +1 -1
  12. claude_mpm/agents/templates/research.json +1 -1
  13. claude_mpm/cli/__init__.py +15 -0
  14. claude_mpm/cli/commands/__init__.py +6 -0
  15. claude_mpm/cli/commands/analyze.py +548 -0
  16. claude_mpm/cli/commands/analyze_code.py +524 -0
  17. claude_mpm/cli/commands/configure.py +78 -28
  18. claude_mpm/cli/commands/configure_tui.py +62 -60
  19. claude_mpm/cli/commands/dashboard.py +288 -0
  20. claude_mpm/cli/commands/debug.py +1386 -0
  21. claude_mpm/cli/commands/mpm_init.py +427 -0
  22. claude_mpm/cli/commands/mpm_init_handler.py +83 -0
  23. claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
  24. claude_mpm/cli/parsers/analyze_parser.py +135 -0
  25. claude_mpm/cli/parsers/base_parser.py +44 -0
  26. claude_mpm/cli/parsers/dashboard_parser.py +113 -0
  27. claude_mpm/cli/parsers/debug_parser.py +319 -0
  28. claude_mpm/cli/parsers/mpm_init_parser.py +122 -0
  29. claude_mpm/constants.py +13 -1
  30. claude_mpm/core/framework_loader.py +148 -6
  31. claude_mpm/core/log_manager.py +16 -13
  32. claude_mpm/core/logger.py +1 -1
  33. claude_mpm/core/unified_agent_registry.py +1 -1
  34. claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
  35. claude_mpm/dashboard/analysis_runner.py +455 -0
  36. claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
  37. claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
  38. claude_mpm/dashboard/static/built/components/code-tree.js +2 -0
  39. claude_mpm/dashboard/static/built/components/code-viewer.js +2 -0
  40. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  41. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
  42. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  43. claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
  44. claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
  45. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  46. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  47. claude_mpm/dashboard/static/css/activity.css +549 -0
  48. claude_mpm/dashboard/static/css/code-tree.css +1175 -0
  49. claude_mpm/dashboard/static/css/dashboard.css +245 -0
  50. claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
  51. claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
  52. claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
  53. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  54. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  55. claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
  56. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  57. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  58. claude_mpm/dashboard/static/js/components/activity-tree.js +1338 -0
  59. claude_mpm/dashboard/static/js/components/code-tree.js +2535 -0
  60. claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
  61. claude_mpm/dashboard/static/js/components/event-viewer.js +59 -9
  62. claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
  63. claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
  64. claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
  65. claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
  66. claude_mpm/dashboard/static/js/dashboard.js +51 -0
  67. claude_mpm/dashboard/static/js/socket-client.js +465 -29
  68. claude_mpm/dashboard/templates/index.html +182 -4
  69. claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
  70. claude_mpm/hooks/claude_hooks/installer.py +386 -113
  71. claude_mpm/scripts/claude-hook-handler.sh +161 -0
  72. claude_mpm/scripts/socketio_daemon.py +121 -8
  73. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
  74. claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
  75. claude_mpm/services/agents/memory/memory_format_service.py +1 -3
  76. claude_mpm/services/cli/agent_cleanup_service.py +1 -5
  77. claude_mpm/services/cli/agent_dependency_service.py +1 -1
  78. claude_mpm/services/cli/agent_validation_service.py +3 -4
  79. claude_mpm/services/cli/dashboard_launcher.py +2 -3
  80. claude_mpm/services/cli/startup_checker.py +0 -11
  81. claude_mpm/services/core/cache_manager.py +1 -3
  82. claude_mpm/services/core/path_resolver.py +1 -4
  83. claude_mpm/services/core/service_container.py +2 -2
  84. claude_mpm/services/diagnostics/checks/instructions_check.py +1 -2
  85. claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
  86. claude_mpm/services/infrastructure/monitoring.py +11 -11
  87. claude_mpm/services/project/architecture_analyzer.py +1 -1
  88. claude_mpm/services/project/dependency_analyzer.py +4 -4
  89. claude_mpm/services/project/language_analyzer.py +3 -3
  90. claude_mpm/services/project/metrics_collector.py +3 -6
  91. claude_mpm/services/socketio/event_normalizer.py +64 -0
  92. claude_mpm/services/socketio/handlers/__init__.py +2 -0
  93. claude_mpm/services/socketio/handlers/code_analysis.py +672 -0
  94. claude_mpm/services/socketio/handlers/registry.py +2 -0
  95. claude_mpm/services/socketio/server/connection_manager.py +6 -4
  96. claude_mpm/services/socketio/server/core.py +100 -11
  97. claude_mpm/services/socketio/server/main.py +8 -2
  98. claude_mpm/services/visualization/__init__.py +19 -0
  99. claude_mpm/services/visualization/mermaid_generator.py +938 -0
  100. claude_mpm/tools/__main__.py +208 -0
  101. claude_mpm/tools/code_tree_analyzer.py +1596 -0
  102. claude_mpm/tools/code_tree_builder.py +631 -0
  103. claude_mpm/tools/code_tree_events.py +416 -0
  104. claude_mpm/tools/socketio_debug.py +671 -0
  105. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/METADATA +2 -1
  106. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/RECORD +110 -74
  107. claude_mpm/agents/schema/agent_schema.json +0 -314
  108. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/WHEEL +0 -0
  109. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/entry_points.txt +0 -0
  110. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/licenses/LICENSE +0 -0
  111. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,122 @@
1
+ """
2
+ MPM-Init parser module for claude-mpm CLI.
3
+
4
+ WHY: This module handles the mpm-init command parser configuration,
5
+ providing a clean interface for initializing projects with optimal
6
+ Claude Code and Claude MPM standards.
7
+ """
8
+
9
+ import argparse
10
+ from typing import Any
11
+
12
+
13
+ def add_mpm_init_subparser(subparsers: Any) -> None:
14
+ """
15
+ Add the mpm-init subparser to the main parser.
16
+
17
+ WHY: The mpm-init command sets up projects for optimal use with
18
+ Claude Code and Claude MPM by delegating to the Agentic Coder Optimizer.
19
+
20
+ Args:
21
+ subparsers: The subparsers object to add the mpm-init command to
22
+ """
23
+ mpm_init_parser = subparsers.add_parser(
24
+ "mpm-init",
25
+ help="Initialize project for optimal Claude Code and Claude MPM usage",
26
+ description=(
27
+ "Initialize a project with comprehensive documentation, single-path workflows, "
28
+ "and optimized structure for AI agent understanding. Uses the Agentic Coder "
29
+ "Optimizer agent to establish clear standards and remove ambiguity."
30
+ ),
31
+ epilog=(
32
+ "Examples:\n"
33
+ " claude-mpm mpm-init # Initialize current directory\n"
34
+ " claude-mpm mpm-init --project-type web # Initialize as web project\n"
35
+ " claude-mpm mpm-init --framework react # Initialize with React framework\n"
36
+ " claude-mpm mpm-init /path/to/project --force # Force reinitialize project"
37
+ ),
38
+ formatter_class=argparse.RawDescriptionHelpFormatter
39
+ )
40
+
41
+ # Project configuration options
42
+ config_group = mpm_init_parser.add_argument_group("project configuration")
43
+ config_group.add_argument(
44
+ "--project-type",
45
+ choices=["web", "api", "cli", "library", "mobile", "desktop", "fullstack", "data", "ml"],
46
+ help="Type of project to initialize (auto-detected if not specified)"
47
+ )
48
+ config_group.add_argument(
49
+ "--framework",
50
+ type=str,
51
+ help="Specific framework to configure (e.g., react, vue, django, fastapi, express)"
52
+ )
53
+ config_group.add_argument(
54
+ "--language",
55
+ choices=["python", "javascript", "typescript", "go", "rust", "java", "cpp"],
56
+ help="Primary programming language (auto-detected if not specified)"
57
+ )
58
+
59
+ # Initialization options
60
+ init_group = mpm_init_parser.add_argument_group("initialization options")
61
+ init_group.add_argument(
62
+ "--force",
63
+ action="store_true",
64
+ help="Force reinitialization even if project is already configured"
65
+ )
66
+ init_group.add_argument(
67
+ "--minimal",
68
+ action="store_true",
69
+ help="Create minimal configuration (CLAUDE.md only, no additional setup)"
70
+ )
71
+ init_group.add_argument(
72
+ "--comprehensive",
73
+ action="store_true",
74
+ help="Create comprehensive setup including CI/CD, testing, and deployment configs"
75
+ )
76
+ init_group.add_argument(
77
+ "--use-venv",
78
+ action="store_true",
79
+ help="Use traditional Python venv instead of mamba/conda environment"
80
+ )
81
+
82
+ # Template options
83
+ template_group = mpm_init_parser.add_argument_group("template options")
84
+ template_group.add_argument(
85
+ "--template",
86
+ type=str,
87
+ help="Use a specific template from claude-mpm templates library"
88
+ )
89
+ template_group.add_argument(
90
+ "--list-templates",
91
+ action="store_true",
92
+ help="List available project templates"
93
+ )
94
+
95
+ # Output options
96
+ output_group = mpm_init_parser.add_argument_group("output options")
97
+ output_group.add_argument(
98
+ "--dry-run",
99
+ action="store_true",
100
+ help="Show what would be done without making changes"
101
+ )
102
+ output_group.add_argument(
103
+ "--json",
104
+ action="store_true",
105
+ help="Output results in JSON format"
106
+ )
107
+ output_group.add_argument(
108
+ "--verbose",
109
+ action="store_true",
110
+ help="Show detailed output during initialization"
111
+ )
112
+
113
+ # Path argument
114
+ mpm_init_parser.add_argument(
115
+ "project_path",
116
+ nargs="?",
117
+ default=".",
118
+ help="Path to project directory (default: current directory)"
119
+ )
120
+
121
+ # Set the command handler
122
+ mpm_init_parser.set_defaults(command="mpm-init")
claude_mpm/constants.py CHANGED
@@ -38,9 +38,12 @@ class CLICommands(str, Enum):
38
38
  CONFIG = "config"
39
39
  CONFIGURE = "configure"
40
40
  AGGREGATE = "aggregate"
41
+ ANALYZE = "analyze"
42
+ ANALYZE_CODE = "analyze-code"
41
43
  CLEANUP = "cleanup-memory"
42
44
  MCP = "mcp"
43
45
  DOCTOR = "doctor"
46
+ DASHBOARD = "dashboard"
44
47
 
45
48
  def with_prefix(self, prefix: CLIPrefix = CLIPrefix.MPM) -> str:
46
49
  """Get command with prefix."""
@@ -97,6 +100,15 @@ class MonitorCommands(str, Enum):
97
100
  PORT = "port"
98
101
 
99
102
 
103
+ class DashboardCommands(str, Enum):
104
+ """Dashboard subcommand constants."""
105
+
106
+ START = "start"
107
+ STOP = "stop"
108
+ STATUS = "status"
109
+ OPEN = "open"
110
+
111
+
100
112
  class ConfigCommands(str, Enum):
101
113
  """Config subcommand constants."""
102
114
 
@@ -217,7 +229,7 @@ class Paths(str, Enum):
217
229
 
218
230
  CLAUDE_AGENTS_DIR = ".claude/agents"
219
231
  CLAUDE_CONFIG_DIR = ".claude"
220
- MPM_LOG_DIR = ".claude-mpm/logs"
232
+ MPM_LOG_DIR = "logs/mpm"
221
233
  MPM_SESSION_DIR = ".claude-mpm/session"
222
234
  MPM_PROMPTS_DIR = ".claude-mpm/prompts"
223
235
 
@@ -1,6 +1,10 @@
1
1
  """Framework loader for Claude MPM."""
2
2
 
3
+ import getpass
4
+ import locale
3
5
  import logging
6
+ import os
7
+ import platform
4
8
  import time
5
9
  from datetime import datetime
6
10
  from pathlib import Path
@@ -995,11 +999,8 @@ class FrameworkLoader:
995
999
  # Add dynamic agent capabilities section
996
1000
  instructions += self._generate_agent_capabilities_section()
997
1001
 
998
- # Add current date for temporal awareness
999
- instructions += f"\n\n## Temporal Context\n**Today's Date**: {datetime.now().strftime('%Y-%m-%d')}\n"
1000
- instructions += (
1001
- "Apply date awareness to all time-sensitive tasks and decisions.\n"
1002
- )
1002
+ # Add enhanced temporal and user context for better awareness
1003
+ instructions += self._generate_temporal_user_context()
1003
1004
 
1004
1005
  # Add BASE_PM.md framework requirements AFTER INSTRUCTIONS.md
1005
1006
  if self.framework_content.get("base_pm_instructions"):
@@ -1322,6 +1323,147 @@ Extract tickets from these patterns:
1322
1323
  self._agent_capabilities_cache_time = current_time
1323
1324
  return result
1324
1325
 
1326
+ def _generate_temporal_user_context(self) -> str:
1327
+ """Generate enhanced temporal and user context for better PM awareness.
1328
+
1329
+ Returns:
1330
+ str: Formatted context string with datetime, user, and system information
1331
+ """
1332
+ context_lines = ["\n\n## Temporal & User Context\n"]
1333
+
1334
+ try:
1335
+ # Get current datetime with timezone awareness
1336
+ now = datetime.now()
1337
+
1338
+ # Try to get timezone info - fallback to UTC offset if timezone name not available
1339
+ try:
1340
+ import time as time_module
1341
+
1342
+ if hasattr(time_module, "tzname"):
1343
+ tz_name = time_module.tzname[time_module.daylight]
1344
+ tz_offset = time_module.strftime("%z")
1345
+ if tz_offset:
1346
+ # Format UTC offset properly (e.g., -0800 to -08:00)
1347
+ tz_offset = (
1348
+ f"{tz_offset[:3]}:{tz_offset[3:]}"
1349
+ if len(tz_offset) >= 4
1350
+ else tz_offset
1351
+ )
1352
+ tz_info = f"{tz_name} (UTC{tz_offset})"
1353
+ else:
1354
+ tz_info = tz_name
1355
+ else:
1356
+ tz_info = "Local Time"
1357
+ except Exception:
1358
+ tz_info = "Local Time"
1359
+
1360
+ # Format datetime components
1361
+ date_str = now.strftime("%Y-%m-%d")
1362
+ time_str = now.strftime("%H:%M:%S")
1363
+ day_name = now.strftime("%A")
1364
+
1365
+ context_lines.append(
1366
+ f"**Current DateTime**: {date_str} {time_str} {tz_info}\n"
1367
+ )
1368
+ context_lines.append(f"**Day**: {day_name}\n")
1369
+
1370
+ except Exception as e:
1371
+ # Fallback to basic date if enhanced datetime fails
1372
+ self.logger.debug(f"Error generating enhanced datetime context: {e}")
1373
+ context_lines.append(
1374
+ f"**Today's Date**: {datetime.now().strftime('%Y-%m-%d')}\n"
1375
+ )
1376
+
1377
+ try:
1378
+ # Get user information with safe fallbacks
1379
+ username = None
1380
+
1381
+ # Try multiple methods to get username
1382
+ methods = [
1383
+ lambda: os.environ.get("USER"),
1384
+ lambda: os.environ.get("USERNAME"), # Windows fallback
1385
+ lambda: getpass.getuser(),
1386
+ ]
1387
+
1388
+ for method in methods:
1389
+ try:
1390
+ username = method()
1391
+ if username:
1392
+ break
1393
+ except Exception:
1394
+ continue
1395
+
1396
+ if username:
1397
+ context_lines.append(f"**User**: {username}\n")
1398
+
1399
+ # Add home directory if available
1400
+ try:
1401
+ home_dir = os.path.expanduser("~")
1402
+ if home_dir and home_dir != "~":
1403
+ context_lines.append(f"**Home Directory**: {home_dir}\n")
1404
+ except Exception:
1405
+ pass
1406
+
1407
+ except Exception as e:
1408
+ # User detection is optional, don't fail
1409
+ self.logger.debug(f"Could not detect user information: {e}")
1410
+
1411
+ try:
1412
+ # Get system information
1413
+ system_info = platform.system()
1414
+ if system_info:
1415
+ # Enhance system name for common platforms
1416
+ system_names = {
1417
+ "Darwin": "Darwin (macOS)",
1418
+ "Linux": "Linux",
1419
+ "Windows": "Windows",
1420
+ }
1421
+ system_display = system_names.get(system_info, system_info)
1422
+ context_lines.append(f"**System**: {system_display}\n")
1423
+
1424
+ # Add platform version if available
1425
+ try:
1426
+ platform_version = platform.release()
1427
+ if platform_version:
1428
+ context_lines.append(
1429
+ f"**System Version**: {platform_version}\n"
1430
+ )
1431
+ except Exception:
1432
+ pass
1433
+
1434
+ except Exception as e:
1435
+ # System info is optional
1436
+ self.logger.debug(f"Could not detect system information: {e}")
1437
+
1438
+ try:
1439
+ # Add current working directory
1440
+ cwd = os.getcwd()
1441
+ if cwd:
1442
+ context_lines.append(f"**Working Directory**: {cwd}\n")
1443
+ except Exception:
1444
+ pass
1445
+
1446
+ try:
1447
+ # Add locale information if available
1448
+ current_locale = locale.getlocale()
1449
+ if current_locale and current_locale[0]:
1450
+ context_lines.append(f"**Locale**: {current_locale[0]}\n")
1451
+ except Exception:
1452
+ # Locale is optional
1453
+ pass
1454
+
1455
+ # Add instruction for applying context
1456
+ context_lines.append(
1457
+ "\nApply temporal and user awareness to all tasks, "
1458
+ "decisions, and interactions.\n"
1459
+ )
1460
+ context_lines.append(
1461
+ "Use this context for personalized responses and "
1462
+ "time-sensitive operations.\n"
1463
+ )
1464
+
1465
+ return "".join(context_lines)
1466
+
1325
1467
  def _parse_agent_metadata(self, agent_file: Path) -> Optional[Dict[str, Any]]:
1326
1468
  """Parse agent metadata from deployed agent file.
1327
1469
  Uses caching based on file path and modification time.
@@ -1333,7 +1475,7 @@ Extract tickets from these patterns:
1333
1475
  # Check cache based on file path and modification time
1334
1476
  cache_key = str(agent_file)
1335
1477
  file_mtime = agent_file.stat().st_mtime
1336
- current_time = time.time()
1478
+ time.time()
1337
1479
 
1338
1480
  # Try to get from cache first
1339
1481
  cached_result = self._cache_manager.get_agent_metadata(cache_key)
@@ -101,9 +101,7 @@ class LogManager:
101
101
  }
102
102
 
103
103
  # Base directories
104
- self.base_log_dir = Path(
105
- logging_config.get("base_directory", ".claude-mpm/logs")
106
- )
104
+ self.base_log_dir = Path(logging_config.get("base_directory", "logs"))
107
105
  if not self.base_log_dir.is_absolute():
108
106
  self.base_log_dir = Path.cwd() / self.base_log_dir
109
107
 
@@ -352,27 +350,32 @@ class LogManager:
352
350
  """
353
351
  One-time migration to move existing MPM logs to new subdirectory.
354
352
 
355
- Moves mpm_*.log files from .claude-mpm/logs/ to .claude-mpm/logs/mpm/
353
+ Moves mpm_*.log files from .claude-mpm/logs/ to logs/mpm/
356
354
  """
357
355
  try:
358
- old_location = self.base_log_dir
356
+ # Check both old possible locations
357
+ old_locations = [
358
+ Path.cwd() / ".claude-mpm" / "logs", # Old default location
359
+ self.base_log_dir, # Current base location (logs/)
360
+ ]
359
361
  new_location = self.base_log_dir / "mpm"
360
362
 
361
- # Only proceed if old location exists and has MPM logs
362
- if not old_location.exists():
363
- return
364
-
365
- # Find all MPM log files in the old location
366
- mpm_logs = list(old_location.glob("mpm_*.log"))
363
+ # Collect all MPM logs from all old locations
364
+ all_mpm_logs = []
365
+ for old_location in old_locations:
366
+ if old_location.exists() and old_location != new_location:
367
+ # Find all MPM log files in this location
368
+ mpm_logs = list(old_location.glob("mpm_*.log"))
369
+ all_mpm_logs.extend(mpm_logs)
367
370
 
368
- if not mpm_logs:
371
+ if not all_mpm_logs:
369
372
  return # No logs to migrate
370
373
 
371
374
  # Ensure new directory exists
372
375
  new_location.mkdir(parents=True, exist_ok=True)
373
376
 
374
377
  migrated_count = 0
375
- for log_file in mpm_logs:
378
+ for log_file in all_mpm_logs:
376
379
  try:
377
380
  # Move file to new location
378
381
  new_path = new_location / log_file.name
claude_mpm/core/logger.py CHANGED
@@ -232,7 +232,7 @@ def setup_logging(
232
232
  if log_dir is None:
233
233
  # Use deployment root for logs to keep everything centralized
234
234
  deployment_root = get_project_root()
235
- log_dir = deployment_root / ".claude-mpm" / "logs"
235
+ log_dir = deployment_root / "logs" / "mpm"
236
236
 
237
237
  log_dir.mkdir(parents=True, exist_ok=True)
238
238
 
@@ -455,7 +455,7 @@ class UnifiedAgentRegistry:
455
455
  """Apply tier precedence rules to resolve conflicts."""
456
456
  # Group agents by their actual name (without tier suffix)
457
457
  agent_groups = {}
458
- for registry_key, metadata in self.registry.items():
458
+ for _registry_key, metadata in self.registry.items():
459
459
  # Extract the actual agent name (registry_key is "name_tier")
460
460
  agent_name = metadata.name # Use the actual name from metadata
461
461
  if agent_name not in agent_groups: