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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +46 -0
- claude_mpm/agents/agent_loader.py +10 -17
- claude_mpm/agents/templates/circuit-breakers.md +138 -1
- claude_mpm/cli/commands/agent_state_manager.py +8 -17
- claude_mpm/cli/commands/configure.py +1046 -149
- claude_mpm/cli/commands/configure_agent_display.py +13 -6
- claude_mpm/cli/commands/mpm_init/core.py +158 -1
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +8 -0
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/startup.py +60 -53
- claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/unified_agent_registry.py +5 -15
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +35 -2
- claude_mpm/hooks/claude_hooks/hook_handler.py +4 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/services/agents/agent_recommendation_service.py +279 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +3 -2
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +322 -53
- claude_mpm/services/agents/git_source_manager.py +20 -0
- claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
- claude_mpm/services/agents/toolchain_detector.py +6 -5
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/command_deployment_service.py +0 -2
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/monitor/daemon.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +39 -3
- claude_mpm/services/monitor/server.py +225 -19
- claude_mpm/services/socketio/event_normalizer.py +15 -1
- claude_mpm/services/socketio/server/core.py +160 -21
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/utils/agent_filters.py +17 -44
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/METADATA +1 -77
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/RECORD +59 -114
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/entry_points.txt +0 -2
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/WHEEL +0 -0
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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":
|
|
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
|
-
|
|
595
|
-
|
|
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
|
|
10
|
-
|
|
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
|
|
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
|
|
166
|
-
|
|
167
|
-
if
|
|
168
|
-
for file in
|
|
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
|
-
#
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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.
|
|
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
|