claude-mpm 5.1.9__py3-none-any.whl → 5.4.3__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (131) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/PM_INSTRUCTIONS.md +46 -0
  3. claude_mpm/agents/agent_loader.py +10 -17
  4. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  5. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  6. claude_mpm/cli/commands/configure.py +1046 -149
  7. claude_mpm/cli/commands/configure_agent_display.py +13 -6
  8. claude_mpm/cli/commands/mpm_init/core.py +158 -1
  9. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  10. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  11. claude_mpm/cli/commands/summarize.py +413 -0
  12. claude_mpm/cli/executor.py +8 -0
  13. claude_mpm/cli/parsers/base_parser.py +5 -0
  14. claude_mpm/cli/startup.py +60 -53
  15. claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
  16. claude_mpm/config/agent_sources.py +27 -0
  17. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  18. claude_mpm/core/socketio_pool.py +3 -3
  19. claude_mpm/core/unified_agent_registry.py +5 -15
  20. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  21. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
  22. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  23. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  24. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  25. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  26. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  27. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  28. claude_mpm/hooks/claude_hooks/event_handlers.py +35 -2
  29. claude_mpm/hooks/claude_hooks/hook_handler.py +4 -0
  30. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  31. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  32. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  33. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  34. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  35. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  36. claude_mpm/scripts/launch_monitor.py +93 -13
  37. claude_mpm/services/agents/agent_recommendation_service.py +279 -0
  38. claude_mpm/services/agents/deployment/agent_template_builder.py +3 -2
  39. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +322 -53
  40. claude_mpm/services/agents/git_source_manager.py +20 -0
  41. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  42. claude_mpm/services/agents/toolchain_detector.py +6 -5
  43. claude_mpm/services/analysis/__init__.py +11 -1
  44. claude_mpm/services/analysis/clone_detector.py +1030 -0
  45. claude_mpm/services/command_deployment_service.py +0 -2
  46. claude_mpm/services/event_bus/config.py +3 -1
  47. claude_mpm/services/monitor/daemon.py +9 -2
  48. claude_mpm/services/monitor/daemon_manager.py +39 -3
  49. claude_mpm/services/monitor/server.py +225 -19
  50. claude_mpm/services/socketio/event_normalizer.py +15 -1
  51. claude_mpm/services/socketio/server/core.py +160 -21
  52. claude_mpm/services/version_control/git_operations.py +103 -0
  53. claude_mpm/utils/agent_filters.py +17 -44
  54. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/METADATA +1 -77
  55. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/RECORD +59 -114
  56. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/entry_points.txt +0 -2
  57. claude_mpm/dashboard/analysis_runner.py +0 -455
  58. claude_mpm/dashboard/index.html +0 -13
  59. claude_mpm/dashboard/open_dashboard.py +0 -66
  60. claude_mpm/dashboard/static/css/activity.css +0 -1958
  61. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  62. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  63. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  64. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  65. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  66. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  67. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  68. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  69. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  70. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  71. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  72. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  73. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  74. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  75. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  76. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  77. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  78. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  79. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  80. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  81. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  82. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  83. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  84. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  85. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  86. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  87. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  88. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  89. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  90. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  91. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  92. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  93. claude_mpm/dashboard/templates/code_simple.html +0 -153
  94. claude_mpm/dashboard/templates/index.html +0 -606
  95. claude_mpm/dashboard/test_dashboard.html +0 -372
  96. claude_mpm/scripts/mcp_server.py +0 -75
  97. claude_mpm/scripts/mcp_wrapper.py +0 -39
  98. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  99. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  100. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  101. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  102. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  103. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  104. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  105. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  106. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  107. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  108. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  109. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  110. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  111. claude_mpm/services/mcp_gateway/main.py +0 -589
  112. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  113. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  114. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  115. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  116. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  117. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  118. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  119. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  120. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  121. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  122. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  123. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  124. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  125. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  126. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  127. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  128. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  129. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/WHEEL +0 -0
  130. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/licenses/LICENSE +0 -0
  131. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/top_level.txt +0 -0
@@ -32,6 +32,7 @@ except ImportError:
32
32
  # Import VersionService for dynamic version retrieval
33
33
  import contextlib
34
34
 
35
+ import claude_mpm
35
36
  from claude_mpm.services.version_service import VersionService
36
37
 
37
38
  from ....core.constants import SystemLimits, TimeoutConfig
@@ -271,7 +272,15 @@ class SocketIOServerCore:
271
272
  """Handle POST /api/events from hook handlers."""
272
273
  try:
273
274
  # Parse JSON payload
274
- event_data = await request.json()
275
+ payload = await request.json()
276
+
277
+ # Extract event data from payload (handles both direct and wrapped formats)
278
+ # ConnectionManagerService sends: {"namespace": "...", "event": "...", "data": {...}}
279
+ # Direct hook events may send data directly
280
+ if "data" in payload and isinstance(payload.get("data"), dict):
281
+ event_data = payload["data"]
282
+ else:
283
+ event_data = payload
275
284
 
276
285
  # Log receipt with more detail
277
286
  event_type = (
@@ -292,38 +301,96 @@ class SocketIOServerCore:
292
301
 
293
302
  normalizer = EventNormalizer()
294
303
 
304
+ # Map hook event names to dashboard subtypes
305
+ # Comprehensive mapping of all known Claude Code hook event types
306
+ subtype_map = {
307
+ # User interaction events
308
+ "UserPromptSubmit": "user_prompt_submit",
309
+ "UserPromptCancel": "user_prompt_cancel",
310
+ # Tool execution events
311
+ "PreToolUse": "pre_tool_use",
312
+ "PostToolUse": "post_tool_use",
313
+ "ToolStart": "tool_start",
314
+ "ToolUse": "tool_use",
315
+ # Assistant events
316
+ "AssistantResponse": "assistant_response",
317
+ # Session lifecycle events
318
+ "Start": "start",
319
+ "Stop": "stop",
320
+ "SessionStart": "session_start",
321
+ # Subagent events
322
+ "SubagentStart": "subagent_start",
323
+ "SubagentStop": "subagent_stop",
324
+ "SubagentEvent": "subagent_event",
325
+ # Task events
326
+ "Task": "task",
327
+ "TaskStart": "task_start",
328
+ "TaskComplete": "task_complete",
329
+ # File operation events
330
+ "FileWrite": "file_write",
331
+ "Write": "write",
332
+ # System events
333
+ "Notification": "notification",
334
+ }
335
+
336
+ # Helper function to convert PascalCase to snake_case
337
+ def to_snake_case(name: str) -> str:
338
+ """Convert PascalCase event names to snake_case.
339
+
340
+ Examples:
341
+ UserPromptSubmit → user_prompt_submit
342
+ PreToolUse → pre_tool_use
343
+ TaskComplete → task_complete
344
+ """
345
+ import re
346
+
347
+ return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()
348
+
349
+ # Get hook event name and map to subtype
350
+ hook_event_name = event_data.get("hook_event_name", "unknown")
351
+ subtype = subtype_map.get(
352
+ hook_event_name, to_snake_case(hook_event_name)
353
+ )
354
+
355
+ # Debug log for unmapped events to discover new event types
356
+ if (
357
+ hook_event_name not in subtype_map
358
+ and hook_event_name != "unknown"
359
+ ):
360
+ self.logger.debug(
361
+ f"Unmapped hook event: {hook_event_name} → {subtype}"
362
+ )
363
+
295
364
  # Create the format expected by normalizer
296
365
  raw_event = {
297
366
  "type": "hook",
298
- "subtype": event_data.get("hook_event_name", "unknown")
299
- .lower()
300
- .replace("submit", "")
301
- .replace("use", "_use"),
367
+ "subtype": subtype,
302
368
  "timestamp": event_data.get("timestamp"),
303
369
  "data": event_data.get("hook_input_data", {}),
304
370
  "source": "claude_hooks",
305
371
  "session_id": event_data.get("session_id"),
306
372
  }
307
373
 
308
- # Map hook event names to dashboard subtypes
309
- subtype_map = {
310
- "UserPromptSubmit": "user_prompt",
311
- "PreToolUse": "pre_tool",
312
- "PostToolUse": "post_tool",
313
- "Stop": "stop",
314
- "SubagentStop": "subagent_stop",
315
- "AssistantResponse": "assistant_response",
316
- }
317
- raw_event["subtype"] = subtype_map.get(
318
- event_data.get("hook_event_name"), "unknown"
319
- )
320
-
321
374
  normalized = normalizer.normalize(raw_event, source="hook")
322
375
  event_data = normalized.to_dict()
323
376
  self.logger.debug(
324
377
  f"Normalized event: type={event_data.get('type')}, subtype={event_data.get('subtype')}"
325
378
  )
326
379
 
380
+ # Publish to EventBus for cross-component communication
381
+ # WHY: This allows other parts of the system to react to hook events
382
+ # without coupling to Socket.IO directly
383
+ try:
384
+ from claude_mpm.services.event_bus import EventBus
385
+
386
+ event_bus = EventBus.get_instance()
387
+ event_type = f"hook.{event_data.get('subtype', 'unknown')}"
388
+ event_bus.publish(event_type, event_data)
389
+ self.logger.debug(f"Published to EventBus: {event_type}")
390
+ except Exception as e:
391
+ # Non-fatal: EventBus publication failure shouldn't break event flow
392
+ self.logger.warning(f"Failed to publish to EventBus: {e}")
393
+
327
394
  # Broadcast to all connected dashboard clients via SocketIO
328
395
  if self.sio:
329
396
  # CRITICAL: Use the main server's broadcaster for proper event handling
@@ -390,6 +457,47 @@ class SocketIOServerCore:
390
457
  self.app.router.add_post("/api/events", api_events_handler)
391
458
  self.logger.info("✅ HTTP API endpoint registered at /api/events")
392
459
 
460
+ # Add health check endpoint
461
+ async def health_handler(request):
462
+ """Handle GET /api/health for health checks."""
463
+ try:
464
+ # Get server status
465
+ uptime_seconds = 0
466
+ if self.stats.get("start_time"):
467
+ uptime_seconds = int(
468
+ (
469
+ datetime.now(timezone.utc) - self.stats["start_time"]
470
+ ).total_seconds()
471
+ )
472
+
473
+ health_data = {
474
+ "status": "healthy",
475
+ "service": "claude-mpm-socketio",
476
+ "timestamp": datetime.now(timezone.utc).isoformat(),
477
+ "uptime_seconds": uptime_seconds,
478
+ "connected_clients": len(self.connected_clients),
479
+ "total_events": self.stats.get("events_sent", 0),
480
+ "buffered_events": self.stats.get("events_buffered", 0),
481
+ }
482
+
483
+ return web.json_response(health_data)
484
+ except Exception as e:
485
+ self.logger.error(f"Error in health check: {e}")
486
+ return web.json_response(
487
+ {
488
+ "status": "unhealthy",
489
+ "service": "claude-mpm-socketio",
490
+ "error": str(e),
491
+ },
492
+ status=503,
493
+ )
494
+
495
+ self.app.router.add_get("/api/health", health_handler)
496
+ self.app.router.add_get("/health", health_handler) # Alias for convenience
497
+ self.logger.info(
498
+ "✅ Health check endpoints registered at /api/health and /health"
499
+ )
500
+
393
501
  # Add working directory endpoint
394
502
  async def working_directory_handler(request):
395
503
  """Handle GET /api/working-directory to provide current working directory."""
@@ -591,9 +699,9 @@ class SocketIOServerCore:
591
699
  self.app.router.add_get("/version.json", version_handler)
592
700
 
593
701
  # Serve static assets (CSS, JS) from the dashboard static directory
594
- dashboard_static_path = (
595
- get_project_root() / "src" / "claude_mpm" / "dashboard" / "static"
596
- )
702
+ # Use package-relative path (works for both dev and installed package)
703
+ package_root = Path(claude_mpm.__file__).parent
704
+ dashboard_static_path = package_root / "dashboard" / "static"
597
705
  if dashboard_static_path.exists():
598
706
  self.app.router.add_static(
599
707
  "/static/", dashboard_static_path, name="dashboard_static"
@@ -606,6 +714,37 @@ class SocketIOServerCore:
606
714
  f"⚠️ Static assets directory not found at: {dashboard_static_path}"
607
715
  )
608
716
 
717
+ # Serve Svelte dashboard build
718
+ svelte_build_path = (
719
+ package_root / "dashboard" / "static" / "svelte-build"
720
+ )
721
+ if svelte_build_path.exists():
722
+ # Serve Svelte dashboard at /svelte route
723
+ async def svelte_handler(request):
724
+ svelte_index = svelte_build_path / "index.html"
725
+ if svelte_index.exists():
726
+ self.logger.debug(
727
+ f"Serving Svelte dashboard from: {svelte_index}"
728
+ )
729
+ return web.FileResponse(svelte_index)
730
+ return web.Response(
731
+ text="Svelte dashboard not available", status=404
732
+ )
733
+
734
+ self.app.router.add_get("/svelte", svelte_handler)
735
+
736
+ # Serve Svelte app assets at /_app/ (needed for SvelteKit builds)
737
+ svelte_app_path = svelte_build_path / "_app"
738
+ if svelte_app_path.exists():
739
+ self.app.router.add_static(
740
+ "/_app/", svelte_app_path, name="svelte_app"
741
+ )
742
+ self.logger.info(
743
+ f"✅ Svelte dashboard available at /svelte (build: {svelte_build_path})"
744
+ )
745
+ else:
746
+ self.logger.debug(f"Svelte build not found at: {svelte_build_path}")
747
+
609
748
  else:
610
749
  self.logger.warning("⚠️ No dashboard found, serving fallback response")
611
750
 
@@ -18,6 +18,11 @@ from dataclasses import dataclass, field
18
18
  from datetime import datetime, timezone
19
19
  from typing import Any, Dict, List, Optional
20
20
 
21
+ # Privileged users who can push directly to main branch
22
+ # All other users must use feature branches and PRs
23
+ PRIVILEGED_GIT_USERS = ["bobmatnyc@users.noreply.github.com"]
24
+ PROTECTED_BRANCHES = ["main", "master"]
25
+
21
26
 
22
27
  @dataclass
23
28
  class GitBranchInfo:
@@ -101,6 +106,94 @@ class GitOperationsManager:
101
106
  if not self._is_git_repository():
102
107
  raise GitOperationError(f"Not a Git repository: {project_root}")
103
108
 
109
+ def _get_current_git_user(self) -> str:
110
+ """
111
+ Get the current Git user email.
112
+
113
+ Returns:
114
+ Git user email configured in repository or globally
115
+
116
+ Raises:
117
+ GitOperationError: If git user.email is not configured
118
+ """
119
+ try:
120
+ result = self._run_git_command(["config", "user.email"])
121
+ email = result.stdout.strip()
122
+ if not email:
123
+ raise GitOperationError(
124
+ "Git user.email is not configured. "
125
+ "Please configure it with: git config user.email 'your@email.com'"
126
+ )
127
+ return email
128
+ except GitOperationError as e:
129
+ raise GitOperationError(
130
+ "Git user.email is not configured. "
131
+ "Please configure it with: git config user.email 'your@email.com'"
132
+ ) from e
133
+
134
+ def _is_privileged_user(self) -> bool:
135
+ """
136
+ Check if the current Git user is privileged to push to protected branches.
137
+
138
+ Returns:
139
+ True if user email is in PRIVILEGED_GIT_USERS, False otherwise
140
+ """
141
+ try:
142
+ current_user = self._get_current_git_user()
143
+ return current_user in PRIVILEGED_GIT_USERS
144
+ except GitOperationError:
145
+ # If we can't determine user, assume not privileged
146
+ return False
147
+
148
+ def _enforce_branch_protection(
149
+ self, target_branch: str, operation: str
150
+ ) -> Optional[GitOperationResult]:
151
+ """
152
+ Enforce branch protection rules for protected branches.
153
+
154
+ Args:
155
+ target_branch: Branch being operated on
156
+ operation: Operation being performed (e.g., "push", "merge")
157
+
158
+ Returns:
159
+ GitOperationResult with error if protection violated, None if allowed
160
+ """
161
+ # Check if target branch is protected
162
+ if target_branch not in PROTECTED_BRANCHES:
163
+ return None
164
+
165
+ # Check if user is privileged
166
+ if self._is_privileged_user():
167
+ return None
168
+
169
+ # Get current user for error message
170
+ try:
171
+ current_user = self._get_current_git_user()
172
+ except GitOperationError:
173
+ current_user = "unknown"
174
+
175
+ # Build helpful error message
176
+ error_message = (
177
+ f"Direct {operation} to '{target_branch}' branch is restricted.\n"
178
+ f"Only {', '.join(PRIVILEGED_GIT_USERS)} can {operation} directly to protected branches.\n"
179
+ f"Current user: {current_user}\n\n"
180
+ f"Please use the feature branch workflow:\n"
181
+ f" 1. git checkout -b feature/your-feature-name\n"
182
+ f" 2. Make your changes and commit\n"
183
+ f" 3. git push -u origin feature/your-feature-name\n"
184
+ f" 4. Create a Pull Request on GitHub for review"
185
+ )
186
+
187
+ return GitOperationResult(
188
+ success=False,
189
+ operation=f"{operation}_branch_protection",
190
+ message=f"Branch protection: {operation} to '{target_branch}' denied",
191
+ error=error_message,
192
+ branch_before=self.get_current_branch(),
193
+ branch_after=self.get_current_branch(),
194
+ execution_time=0.0,
195
+ )
196
+
104
197
  def _is_git_repository(self) -> bool:
105
198
  """Check if the directory is a Git repository."""
106
199
  return self.git_dir.exists() and self.git_dir.is_dir()
@@ -503,6 +596,11 @@ class GitOperationsManager:
503
596
  start_time = datetime.now(timezone.utc)
504
597
  current_branch = self.get_current_branch()
505
598
 
599
+ # Enforce branch protection for target branch
600
+ protection_result = self._enforce_branch_protection(target_branch, "merge")
601
+ if protection_result:
602
+ return protection_result
603
+
506
604
  try:
507
605
  # Switch to target branch
508
606
  if current_branch != target_branch:
@@ -659,6 +757,11 @@ class GitOperationsManager:
659
757
  if not branch_name:
660
758
  branch_name = current_branch
661
759
 
760
+ # Enforce branch protection
761
+ protection_result = self._enforce_branch_protection(branch_name, "push")
762
+ if protection_result:
763
+ return protection_result
764
+
662
765
  try:
663
766
  # Build push command
664
767
  push_args = ["push"]
@@ -4,10 +4,14 @@ Agent filtering utilities for claude-mpm.
4
4
  WHY: This module provides centralized filtering logic to remove non-deployable
5
5
  agents (BASE_AGENT) and already-deployed agents from user-facing displays.
6
6
 
7
+ ARCHITECTURE:
8
+ - SOURCE: ~/.claude-mpm/cache/remote-agents/ (git repository cache)
9
+ - DEPLOYMENT: .claude/agents/ (project-level deployment location)
10
+
7
11
  DESIGN DECISIONS:
8
12
  - BASE_AGENT is a build tool, not a deployable agent - filter everywhere
9
- - Deployed agent detection supports both new (.claude-mpm/agents/) and
10
- legacy (.claude/agents/)
13
+ - Deployed agent detection checks .claude/agents/ for all deployed agents
14
+ - Supports both virtual (.mpm_deployment_state) and physical (.md files) detection
11
15
  - Case-insensitive BASE_AGENT detection for robustness
12
16
  - Pure functions for easy testing and reuse
13
17
 
@@ -102,10 +106,11 @@ def get_deployed_agent_ids(project_dir: Optional[Path] = None) -> Set[str]:
102
106
 
103
107
  Design Rationale:
104
108
  - Primary detection: Virtual deployment state (.mpm_deployment_state)
105
- - Fallback detection: Physical .md files (.claude-mpm/agents/, .claude/agents/)
109
+ - Fallback detection: Physical .md files in .claude/agents/
106
110
  - Returns leaf names for consistent comparison with agent_id formats
107
111
  - Combines both detection methods for complete coverage
108
112
  - Graceful error handling for malformed or missing state files
113
+ - Only checks project-level deployment (simplified architecture)
109
114
 
110
115
  Related:
111
116
  - Fixes checkbox interface showing all agents as "○ [Available]" instead of "● [Installed]"
@@ -115,24 +120,17 @@ def get_deployed_agent_ids(project_dir: Optional[Path] = None) -> Set[str]:
115
120
  deployed = set()
116
121
 
117
122
  # Track if project_dir was explicitly provided
118
- explicit_project_dir = project_dir is not None
119
123
 
120
124
  if project_dir is None:
121
125
  project_dir = Path.cwd()
122
126
 
123
127
  # NEW: Check virtual deployment state (primary method)
124
128
  # This is the current deployment model used by Claude Code
129
+ # Only checking project-level deployment in simplified architecture
125
130
  deployment_state_paths = [
126
131
  project_dir / ".claude" / "agents" / ".mpm_deployment_state",
127
132
  ]
128
133
 
129
- # Only check user-level state if using default project directory
130
- # This prevents test isolation issues when explicit project_dir is provided
131
- if not explicit_project_dir:
132
- deployment_state_paths.append(
133
- Path.home() / ".claude" / "agents" / ".mpm_deployment_state"
134
- )
135
-
136
134
  for state_path in deployment_state_paths:
137
135
  if state_path.exists():
138
136
  try:
@@ -162,42 +160,17 @@ def get_deployed_agent_ids(project_dir: Optional[Path] = None) -> Set[str]:
162
160
  continue
163
161
 
164
162
  # EXISTING: Check physical .md files (fallback for backward compatibility)
165
- # Check new architecture
166
- new_agents_dir = project_dir / ".claude-mpm" / "agents"
167
- if new_agents_dir.exists():
168
- for file in new_agents_dir.glob("*.md"):
169
- if file.stem not in {"BASE-AGENT", ".DS_Store"}:
170
- deployed.add(file.stem)
171
-
172
- # Check legacy architecture
173
- legacy_agents_dir = project_dir / ".claude" / "agents"
174
- if legacy_agents_dir.exists():
175
- for file in legacy_agents_dir.glob("*.md"):
163
+ # Check project deployment location (.claude/agents/)
164
+ agents_dir = project_dir / ".claude" / "agents"
165
+ if agents_dir.exists():
166
+ for file in agents_dir.glob("*.md"):
176
167
  if file.stem not in {"BASE-AGENT", ".DS_Store"}:
177
168
  deployed.add(file.stem)
178
169
 
179
- # Check .claude/templates/ directory (where agents are actually deployed)
180
- templates_dir = project_dir / ".claude" / "templates"
181
- if templates_dir.exists():
182
- for file in templates_dir.glob("*.md"):
183
- if file.stem not in {
184
- "BASE-AGENT",
185
- ".DS_Store",
186
- "README",
187
- "circuit-breakers",
188
- }:
189
- # Skip template/example files
190
- if not any(x in file.stem for x in ["example", "template", "pm-"]):
191
- deployed.add(file.stem)
192
-
193
- # Check user-level directory only if using default project directory
194
- # This prevents test isolation issues when explicit project_dir is provided
195
- if not explicit_project_dir:
196
- user_agents_dir = Path.home() / ".claude" / "agents"
197
- if user_agents_dir.exists():
198
- for file in user_agents_dir.glob("*.md"):
199
- if file.stem not in {"BASE-AGENT", ".DS_Store"}:
200
- deployed.add(file.stem)
170
+ # NOTE: .claude/templates/ contains PM instruction templates, NOT deployed agents
171
+ # It should NOT be checked here. Agents are deployed to:
172
+ # - .mpm_deployment_state (virtual deployment)
173
+ # - .claude/agents/*.md (project deployment)
201
174
 
202
175
  return deployed
203
176
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-mpm
3
- Version: 5.1.9
3
+ Version: 5.4.3
4
4
  Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
5
5
  Author-email: Bob Matsuoka <bob@matsuoka.com>
6
6
  Maintainer: Claude MPM Team
@@ -80,80 +80,6 @@ Requires-Dist: aiohttp-cors<0.8.0,>=0.7.0; extra == "monitor"
80
80
  Requires-Dist: python-engineio>=4.8.0; extra == "monitor"
81
81
  Requires-Dist: aiofiles>=23.0.0; extra == "monitor"
82
82
  Requires-Dist: websockets>=12.0; extra == "monitor"
83
- Provides-Extra: agents
84
- Requires-Dist: alembic>=1.13.0; extra == "agents"
85
- Requires-Dist: astroid>=3.0.0; extra == "agents"
86
- Requires-Dist: axe-selenium-python>=2.1.0; extra == "agents"
87
- Requires-Dist: bandit>=1.7.5; extra == "agents"
88
- Requires-Dist: beautifulsoup4>=4.12.0; extra == "agents"
89
- Requires-Dist: black>=24.0.0; extra == "agents"
90
- Requires-Dist: click>=8.1.0; extra == "agents"
91
- Requires-Dist: commitizen>=3.13.0; extra == "agents"
92
- Requires-Dist: coverage>=7.0.0; extra == "agents"
93
- Requires-Dist: csvkit>=1.3.0; extra == "agents"
94
- Requires-Dist: dask>=2023.12.0; extra == "agents"
95
- Requires-Dist: detect-secrets>=1.4.0; extra == "agents"
96
- Requires-Dist: diagrams>=0.23.0; extra == "agents"
97
- Requires-Dist: docstring-parser>=0.15.0; extra == "agents"
98
- Requires-Dist: faker>=20.0.0; extra == "agents"
99
- Requires-Dist: flake8>=7.0.0; extra == "agents"
100
- Requires-Dist: gitlint>=0.19.0; extra == "agents"
101
- Requires-Dist: gitpython>=3.1.40; extra == "agents"
102
- Requires-Dist: google-auth>=2.0.0; extra == "agents"
103
- Requires-Dist: google-cloud-core>=2.0.0; extra == "agents"
104
- Requires-Dist: hypothesis>=6.98.0; extra == "agents"
105
- Requires-Dist: isort>=5.13.0; extra == "agents"
106
- Requires-Dist: jinja2>=3.1.0; extra == "agents"
107
- Requires-Dist: jsonschema>=4.19.0; extra == "agents"
108
- Requires-Dist: libcst>=1.1.0; extra == "agents"
109
- Requires-Dist: lizard>=1.17.0; extra == "agents"
110
- Requires-Dist: lxml>=4.9.0; extra == "agents"
111
- Requires-Dist: mermaid-py>=0.2.0; extra == "agents"
112
- Requires-Dist: mkdocs>=1.5.0; extra == "agents"
113
- Requires-Dist: mutmut>=2.4.0; extra == "agents"
114
- Requires-Dist: mypy>=1.8.0; extra == "agents"
115
- Requires-Dist: numpy>=1.24.0; extra == "agents"
116
- Requires-Dist: openpyxl>=3.1.0; extra == "agents"
117
- Requires-Dist: pandas>=2.1.0; extra == "agents"
118
- Requires-Dist: pathlib; extra == "agents"
119
- Requires-Dist: pillow>=9.0.0; extra == "agents"
120
- Requires-Dist: playwright>=1.40.0; extra == "agents"
121
- Requires-Dist: polars>=0.19.0; extra == "agents"
122
- Requires-Dist: pre-commit>=3.5.0; extra == "agents"
123
- Requires-Dist: prometheus-client>=0.19.0; extra == "agents"
124
- Requires-Dist: psycopg2-binary>=2.9.0; extra == "agents"
125
- Requires-Dist: pyarrow>=14.0.0; extra == "agents"
126
- Requires-Dist: pydantic>=2.6.0; extra == "agents"
127
- Requires-Dist: pydriller>=2.5.0; extra == "agents"
128
- Requires-Dist: pygments>=2.17.0; extra == "agents"
129
- Requires-Dist: pyjwt>=2.8.0; extra == "agents"
130
- Requires-Dist: pylint>=3.0.0; extra == "agents"
131
- Requires-Dist: pymongo>=4.5.0; extra == "agents"
132
- Requires-Dist: pymysql>=1.1.0; extra == "agents"
133
- Requires-Dist: pytest>=8.0.0; extra == "agents"
134
- Requires-Dist: pytest-asyncio>=0.23.0; extra == "agents"
135
- Requires-Dist: pytest-benchmark>=4.0.0; extra == "agents"
136
- Requires-Dist: pytest-cov>=4.1.0; extra == "agents"
137
- Requires-Dist: python-dateutil>=2.8.0; extra == "agents"
138
- Requires-Dist: pyyaml>=6.0; extra == "agents"
139
- Requires-Dist: radon>=6.0.0; extra == "agents"
140
- Requires-Dist: redis>=5.0.0; extra == "agents"
141
- Requires-Dist: requests>=2.31.0; extra == "agents"
142
- Requires-Dist: rich>=13.0.0; extra == "agents"
143
- Requires-Dist: rope>=1.11.0; extra == "agents"
144
- Requires-Dist: safety>=3.0.0; extra == "agents"
145
- Requires-Dist: semantic-version>=2.10.0; extra == "agents"
146
- Requires-Dist: semgrep>=1.45.0; extra == "agents"
147
- Requires-Dist: sphinx>=7.2.0; extra == "agents"
148
- Requires-Dist: sqlalchemy>=2.0.0; extra == "agents"
149
- Requires-Dist: sqlparse>=0.4.4; extra == "agents"
150
- Requires-Dist: tabulate>=0.9.0; extra == "agents"
151
- Requires-Dist: tree-sitter>=0.21.0; extra == "agents"
152
- Requires-Dist: vaex>=4.17.0; extra == "agents"
153
- Requires-Dist: validators>=0.20.0; extra == "agents"
154
- Requires-Dist: xlrd>=2.0.0; extra == "agents"
155
- Requires-Dist: xlsxwriter>=3.1.0; extra == "agents"
156
- Requires-Dist: xlwt>=1.3.0; extra == "agents"
157
83
  Provides-Extra: data-processing
158
84
  Requires-Dist: pandas>=2.1.0; extra == "data-processing"
159
85
  Requires-Dist: openpyxl>=3.1.0; extra == "data-processing"
@@ -174,8 +100,6 @@ Requires-Dist: pymongo>=4.5.0; extra == "data-processing"
174
100
  Requires-Dist: redis>=5.0.0; extra == "data-processing"
175
101
  Requires-Dist: beautifulsoup4>=4.12.0; extra == "data-processing"
176
102
  Requires-Dist: jsonschema>=4.19.0; extra == "data-processing"
177
- Provides-Extra: agents-load-testing
178
- Requires-Dist: locust>=2.15.0; extra == "agents-load-testing"
179
103
  Dynamic: license-file
180
104
 
181
105
  # Claude MPM - Multi-Agent Project Manager