claude-mpm 5.4.22__py3-none-any.whl → 5.4.48__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/BASE_AGENT.md +164 -0
- claude_mpm/agents/BASE_ENGINEER.md +658 -0
- claude_mpm/agents/MEMORY.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +739 -1052
- claude_mpm/agents/WORKFLOW.md +5 -254
- claude_mpm/agents/agent_loader.py +1 -1
- claude_mpm/agents/base_agent.json +31 -0
- claude_mpm/agents/frontmatter_validator.py +2 -2
- claude_mpm/cli/commands/agent_state_manager.py +10 -10
- claude_mpm/cli/commands/agents.py +9 -9
- claude_mpm/cli/commands/auto_configure.py +4 -4
- claude_mpm/cli/commands/configure.py +1 -1
- claude_mpm/cli/commands/configure_agent_display.py +10 -0
- claude_mpm/cli/commands/mpm_init/core.py +65 -0
- claude_mpm/cli/commands/postmortem.py +1 -1
- claude_mpm/cli/commands/profile.py +277 -0
- claude_mpm/cli/commands/skills.py +14 -18
- claude_mpm/cli/executor.py +10 -0
- claude_mpm/cli/interactive/agent_wizard.py +2 -2
- claude_mpm/cli/parsers/base_parser.py +7 -0
- claude_mpm/cli/parsers/profile_parser.py +148 -0
- claude_mpm/cli/parsers/skills_parser.py +0 -6
- claude_mpm/cli/startup.py +346 -75
- claude_mpm/commands/mpm-config.md +13 -250
- claude_mpm/commands/mpm-doctor.md +9 -22
- claude_mpm/commands/mpm-help.md +5 -206
- claude_mpm/commands/mpm-init.md +81 -507
- claude_mpm/commands/mpm-monitor.md +15 -402
- claude_mpm/commands/mpm-organize.md +61 -441
- claude_mpm/commands/mpm-postmortem.md +6 -108
- claude_mpm/commands/mpm-session-resume.md +12 -363
- claude_mpm/commands/mpm-status.md +5 -69
- claude_mpm/commands/mpm-ticket-view.md +52 -495
- claude_mpm/commands/mpm-version.md +5 -107
- claude_mpm/core/config.py +2 -4
- claude_mpm/core/framework/loaders/agent_loader.py +1 -1
- claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
- claude_mpm/core/optimized_startup.py +59 -0
- claude_mpm/core/shared/config_loader.py +1 -1
- claude_mpm/core/unified_agent_registry.py +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DjhvlsAc.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/N4qtv3Hx.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uj46x2Wr.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.CAGBuiOw.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
- claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
- claude_mpm/dashboard/static/svelte-build/index.html +36 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +149 -1
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +26 -6
- claude_mpm/hooks/kuzu_memory_hook.py +5 -5
- claude_mpm/init.py +63 -0
- claude_mpm/models/git_repository.py +3 -3
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_builder.py +3 -3
- claude_mpm/services/agents/cache_git_manager.py +6 -6
- claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
- claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -2
- claude_mpm/services/agents/deployment/agent_format_converter.py +23 -13
- claude_mpm/services/agents/deployment/agent_template_builder.py +29 -19
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
- claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
- claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +169 -26
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +98 -75
- claude_mpm/services/agents/git_source_manager.py +19 -4
- claude_mpm/services/agents/recommender.py +5 -3
- claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
- claude_mpm/services/agents/sources/git_source_sync_service.py +112 -6
- claude_mpm/services/agents/startup_sync.py +22 -2
- claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
- claude_mpm/services/git/git_operations_service.py +8 -8
- claude_mpm/services/monitor/management/lifecycle.py +8 -1
- claude_mpm/services/monitor/server.py +473 -3
- claude_mpm/services/pm_skills_deployer.py +711 -0
- claude_mpm/services/profile_manager.py +331 -0
- claude_mpm/services/skills/git_skill_source_manager.py +101 -3
- claude_mpm/services/skills_deployer.py +4 -3
- claude_mpm/services/socketio/dashboard_server.py +1 -0
- claude_mpm/services/socketio/event_normalizer.py +37 -6
- claude_mpm/services/socketio/server/core.py +262 -123
- claude_mpm/skills/skill_manager.py +92 -3
- claude_mpm/utils/agent_dependency_loader.py +14 -2
- claude_mpm/utils/agent_filters.py +1 -1
- claude_mpm/utils/migration.py +4 -4
- claude_mpm/utils/robust_installer.py +47 -3
- {claude_mpm-5.4.22.dist-info → claude_mpm-5.4.48.dist-info}/METADATA +7 -4
- {claude_mpm-5.4.22.dist-info → claude_mpm-5.4.48.dist-info}/RECORD +118 -79
- {claude_mpm-5.4.22.dist-info → claude_mpm-5.4.48.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.22.dist-info → claude_mpm-5.4.48.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.22.dist-info → claude_mpm-5.4.48.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.22.dist-info → claude_mpm-5.4.48.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.22.dist-info → claude_mpm-5.4.48.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
7
|
+
|
|
8
|
+
<link rel="modulepreload" href="/_app/immutable/entry/start.DzuEhzqh.js">
|
|
9
|
+
<link rel="modulepreload" href="/_app/immutable/chunks/CIXEwuWe.js">
|
|
10
|
+
<link rel="modulepreload" href="/_app/immutable/chunks/DjhvlsAc.js">
|
|
11
|
+
<link rel="modulepreload" href="/_app/immutable/entry/app.DTL5mJO-.js">
|
|
12
|
+
<link rel="modulepreload" href="/_app/immutable/chunks/DMkZpdF2.js">
|
|
13
|
+
<link rel="modulepreload" href="/_app/immutable/chunks/CWc5urbQ.js">
|
|
14
|
+
<link rel="modulepreload" href="/_app/immutable/chunks/BgChzWQ1.js">
|
|
15
|
+
</head>
|
|
16
|
+
<body data-sveltekit-preload-data="hover">
|
|
17
|
+
<div style="display: contents">
|
|
18
|
+
<script>
|
|
19
|
+
{
|
|
20
|
+
__sveltekit_16ujgvz = {
|
|
21
|
+
base: ""
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const element = document.currentScript.parentElement;
|
|
25
|
+
|
|
26
|
+
Promise.all([
|
|
27
|
+
import("/_app/immutable/entry/start.DzuEhzqh.js"),
|
|
28
|
+
import("/_app/immutable/entry/app.DTL5mJO-.js")
|
|
29
|
+
]).then(([kit, app]) => {
|
|
30
|
+
kit.start(app, element);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
</div>
|
|
35
|
+
</body>
|
|
36
|
+
</html>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -394,6 +394,8 @@ class ClaudeHookHandler:
|
|
|
394
394
|
Returns:
|
|
395
395
|
Modified input for PreToolUse events (v2.0.30+), None otherwise
|
|
396
396
|
"""
|
|
397
|
+
import time
|
|
398
|
+
|
|
397
399
|
# Try multiple field names for compatibility
|
|
398
400
|
hook_type = (
|
|
399
401
|
event.get("hook_event_name")
|
|
@@ -425,15 +427,40 @@ class ClaudeHookHandler:
|
|
|
425
427
|
# Call appropriate handler if exists
|
|
426
428
|
handler = event_handlers.get(hook_type)
|
|
427
429
|
if handler:
|
|
430
|
+
# Track execution timing for hook emission
|
|
431
|
+
start_time = time.time()
|
|
432
|
+
success = False
|
|
433
|
+
error_message = None
|
|
434
|
+
result = None
|
|
435
|
+
|
|
428
436
|
try:
|
|
429
437
|
# Handlers can optionally return modified input
|
|
430
438
|
result = handler(event)
|
|
439
|
+
success = True
|
|
431
440
|
# Only PreToolUse handlers should return modified input
|
|
432
441
|
if hook_type == "PreToolUse" and result is not None:
|
|
433
|
-
|
|
442
|
+
return_value = result
|
|
443
|
+
else:
|
|
444
|
+
return_value = None
|
|
434
445
|
except Exception as e:
|
|
446
|
+
error_message = str(e)
|
|
447
|
+
return_value = None
|
|
435
448
|
if DEBUG:
|
|
436
449
|
print(f"Error handling {hook_type}: {e}", file=sys.stderr)
|
|
450
|
+
finally:
|
|
451
|
+
# Calculate duration
|
|
452
|
+
duration_ms = int((time.time() - start_time) * 1000)
|
|
453
|
+
|
|
454
|
+
# Emit hook execution event
|
|
455
|
+
self._emit_hook_execution_event(
|
|
456
|
+
hook_type=hook_type,
|
|
457
|
+
event=event,
|
|
458
|
+
success=success,
|
|
459
|
+
duration_ms=duration_ms,
|
|
460
|
+
error_message=error_message,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
return return_value
|
|
437
464
|
|
|
438
465
|
return None
|
|
439
466
|
|
|
@@ -478,6 +505,127 @@ class ClaudeHookHandler:
|
|
|
478
505
|
"""Generate event key through duplicate detector (backward compatibility)."""
|
|
479
506
|
return self.duplicate_detector.generate_event_key(event)
|
|
480
507
|
|
|
508
|
+
def _emit_hook_execution_event(
|
|
509
|
+
self,
|
|
510
|
+
hook_type: str,
|
|
511
|
+
event: dict,
|
|
512
|
+
success: bool,
|
|
513
|
+
duration_ms: int,
|
|
514
|
+
error_message: Optional[str] = None,
|
|
515
|
+
):
|
|
516
|
+
"""Emit a structured JSON event for hook execution.
|
|
517
|
+
|
|
518
|
+
This emits a normalized event following the claude_event schema to provide
|
|
519
|
+
visibility into hook processing, timing, and success/failure status.
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
hook_type: The type of hook that executed (e.g., "UserPromptSubmit", "PreToolUse")
|
|
523
|
+
event: The original hook event data
|
|
524
|
+
success: Whether the hook executed successfully
|
|
525
|
+
duration_ms: How long the hook took to execute in milliseconds
|
|
526
|
+
error_message: Optional error message if the hook failed
|
|
527
|
+
"""
|
|
528
|
+
# Generate a human-readable summary based on hook type
|
|
529
|
+
summary = self._generate_hook_summary(hook_type, event, success)
|
|
530
|
+
|
|
531
|
+
# Extract common fields
|
|
532
|
+
session_id = event.get("session_id", "")
|
|
533
|
+
working_dir = event.get("cwd", "")
|
|
534
|
+
|
|
535
|
+
# Build hook execution data
|
|
536
|
+
hook_data = {
|
|
537
|
+
"hook_name": hook_type,
|
|
538
|
+
"hook_type": hook_type,
|
|
539
|
+
"session_id": session_id,
|
|
540
|
+
"working_directory": working_dir,
|
|
541
|
+
"success": success,
|
|
542
|
+
"duration_ms": duration_ms,
|
|
543
|
+
"result_summary": summary,
|
|
544
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
# Add error information if present
|
|
548
|
+
if error_message:
|
|
549
|
+
hook_data["error_message"] = error_message
|
|
550
|
+
|
|
551
|
+
# Add hook-specific context
|
|
552
|
+
if hook_type == "PreToolUse":
|
|
553
|
+
hook_data["tool_name"] = event.get("tool_name", "")
|
|
554
|
+
elif hook_type == "PostToolUse":
|
|
555
|
+
hook_data["tool_name"] = event.get("tool_name", "")
|
|
556
|
+
hook_data["exit_code"] = event.get("exit_code", 0)
|
|
557
|
+
elif hook_type == "UserPromptSubmit":
|
|
558
|
+
prompt = event.get("prompt", "")
|
|
559
|
+
hook_data["prompt_preview"] = prompt[:100] if len(prompt) > 100 else prompt
|
|
560
|
+
hook_data["prompt_length"] = len(prompt)
|
|
561
|
+
elif hook_type == "SubagentStop":
|
|
562
|
+
hook_data["agent_type"] = event.get("agent_type", "unknown")
|
|
563
|
+
hook_data["reason"] = event.get("reason", "unknown")
|
|
564
|
+
|
|
565
|
+
# Emit through connection manager with proper structure
|
|
566
|
+
# This uses the existing event infrastructure
|
|
567
|
+
self._emit_socketio_event("", "hook_execution", hook_data)
|
|
568
|
+
|
|
569
|
+
if DEBUG:
|
|
570
|
+
print(
|
|
571
|
+
f"📊 Hook execution event: {hook_type} - {duration_ms}ms - {'✅' if success else '❌'}",
|
|
572
|
+
file=sys.stderr,
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
def _generate_hook_summary(self, hook_type: str, event: dict, success: bool) -> str:
|
|
576
|
+
"""Generate a human-readable summary of what the hook did.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
hook_type: The type of hook
|
|
580
|
+
event: The hook event data
|
|
581
|
+
success: Whether the hook executed successfully
|
|
582
|
+
|
|
583
|
+
Returns:
|
|
584
|
+
A brief description of what happened
|
|
585
|
+
"""
|
|
586
|
+
if not success:
|
|
587
|
+
return f"Hook {hook_type} failed during processing"
|
|
588
|
+
|
|
589
|
+
# Generate hook-specific summaries
|
|
590
|
+
if hook_type == "UserPromptSubmit":
|
|
591
|
+
prompt = event.get("prompt", "")
|
|
592
|
+
if prompt.startswith("/"):
|
|
593
|
+
return f"Processed command: {prompt.split()[0]}"
|
|
594
|
+
return f"Processed user prompt ({len(prompt)} chars)"
|
|
595
|
+
|
|
596
|
+
if hook_type == "PreToolUse":
|
|
597
|
+
tool_name = event.get("tool_name", "unknown")
|
|
598
|
+
return f"Pre-processing tool call: {tool_name}"
|
|
599
|
+
|
|
600
|
+
if hook_type == "PostToolUse":
|
|
601
|
+
tool_name = event.get("tool_name", "unknown")
|
|
602
|
+
exit_code = event.get("exit_code", 0)
|
|
603
|
+
status = "success" if exit_code == 0 else "failed"
|
|
604
|
+
return f"Completed tool call: {tool_name} ({status})"
|
|
605
|
+
|
|
606
|
+
if hook_type == "SubagentStop":
|
|
607
|
+
agent_type = event.get("agent_type", "unknown")
|
|
608
|
+
reason = event.get("reason", "unknown")
|
|
609
|
+
return f"Subagent {agent_type} stopped: {reason}"
|
|
610
|
+
|
|
611
|
+
if hook_type == "SessionStart":
|
|
612
|
+
return "New session started"
|
|
613
|
+
|
|
614
|
+
if hook_type == "Stop":
|
|
615
|
+
reason = event.get("reason", "unknown")
|
|
616
|
+
return f"Session stopped: {reason}"
|
|
617
|
+
|
|
618
|
+
if hook_type == "Notification":
|
|
619
|
+
notification_type = event.get("notification_type", "unknown")
|
|
620
|
+
return f"Notification received: {notification_type}"
|
|
621
|
+
|
|
622
|
+
if hook_type == "AssistantResponse":
|
|
623
|
+
response_len = len(event.get("response", ""))
|
|
624
|
+
return f"Assistant response generated ({response_len} chars)"
|
|
625
|
+
|
|
626
|
+
# Default summary
|
|
627
|
+
return f"Hook {hook_type} processed successfully"
|
|
628
|
+
|
|
481
629
|
def __del__(self):
|
|
482
630
|
"""Cleanup on handler destruction."""
|
|
483
631
|
# Clean up connection manager if it exists
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -58,7 +58,7 @@ except ImportError:
|
|
|
58
58
|
(),
|
|
59
59
|
{
|
|
60
60
|
"to_dict": lambda: {
|
|
61
|
-
"event": "
|
|
61
|
+
"event": "mpm_event",
|
|
62
62
|
"type": event_data.get("type", "unknown"),
|
|
63
63
|
"subtype": event_data.get("subtype", "generic"),
|
|
64
64
|
"timestamp": event_data.get(
|
|
@@ -119,13 +119,33 @@ class ConnectionManagerService:
|
|
|
119
119
|
tool_call_id = data.get("tool_call_id")
|
|
120
120
|
|
|
121
121
|
# Create event data for normalization
|
|
122
|
+
# Extract session_id (try both camelCase and snake_case)
|
|
123
|
+
session_id = data.get("session_id") or data.get("sessionId")
|
|
124
|
+
|
|
125
|
+
# Extract working directory for project identification
|
|
126
|
+
# Try multiple field names for maximum compatibility
|
|
127
|
+
cwd = (
|
|
128
|
+
data.get("cwd")
|
|
129
|
+
or data.get("working_directory")
|
|
130
|
+
or data.get("workingDirectory")
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# For hook_execution events, extract the actual hook type from data
|
|
134
|
+
# Otherwise use "hook" as the type
|
|
135
|
+
if event == "hook_execution":
|
|
136
|
+
hook_type = data.get("hook_type", "unknown")
|
|
137
|
+
event_type = hook_type
|
|
138
|
+
else:
|
|
139
|
+
event_type = "hook"
|
|
140
|
+
|
|
122
141
|
raw_event = {
|
|
123
|
-
"type": "hook"
|
|
124
|
-
"subtype": event, # e.g., "user_prompt", "pre_tool", "subagent_stop"
|
|
142
|
+
"type": event_type, # Use actual hook type for hook_execution, "hook" otherwise
|
|
143
|
+
"subtype": event, # e.g., "user_prompt", "pre_tool", "subagent_stop", "execution"
|
|
125
144
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
126
145
|
"data": data,
|
|
127
|
-
"source": "
|
|
128
|
-
"session_id":
|
|
146
|
+
"source": "mpm_hook", # Identify the source as mpm_hook
|
|
147
|
+
"session_id": session_id, # Include session if available (supports both naming conventions)
|
|
148
|
+
"cwd": cwd, # Add working directory at top level for easy frontend access
|
|
129
149
|
"correlation_id": tool_call_id, # Set from tool_call_id for event correlation
|
|
130
150
|
}
|
|
131
151
|
|
|
@@ -154,7 +174,7 @@ class ConnectionManagerService:
|
|
|
154
174
|
if self.connection_pool:
|
|
155
175
|
try:
|
|
156
176
|
# Emit to Socket.IO server directly
|
|
157
|
-
self.connection_pool.emit("
|
|
177
|
+
self.connection_pool.emit("mpm_event", claude_event_data)
|
|
158
178
|
if DEBUG:
|
|
159
179
|
print(f"✅ Emitted via connection pool: {event}", file=sys.stderr)
|
|
160
180
|
return # Success - no need for fallback
|
|
@@ -13,9 +13,9 @@ for structured memory storage with semantic search capabilities.
|
|
|
13
13
|
DESIGN DECISIONS:
|
|
14
14
|
- Priority 10 for early execution to enrich prompts before other hooks
|
|
15
15
|
- Uses subprocess to call kuzu-memory directly for maximum compatibility
|
|
16
|
-
- Graceful degradation if kuzu-memory is not
|
|
16
|
+
- Graceful degradation if kuzu-memory is not installed
|
|
17
17
|
- Automatic extraction and storage of important information
|
|
18
|
-
- kuzu-memory
|
|
18
|
+
- kuzu-memory is an OPTIONAL dependency (install with: pip install claude-mpm[memory])
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
import json
|
|
@@ -51,9 +51,9 @@ class KuzuMemoryHook(SubmitHook):
|
|
|
51
51
|
self.enabled = self.kuzu_memory_cmd is not None
|
|
52
52
|
|
|
53
53
|
if not self.enabled:
|
|
54
|
-
logger.
|
|
55
|
-
"Kuzu-memory not found
|
|
56
|
-
"
|
|
54
|
+
logger.debug(
|
|
55
|
+
"Kuzu-memory not found. Graph-based memory disabled. "
|
|
56
|
+
"To enable: pip install claude-mpm[memory] (requires cmake)"
|
|
57
57
|
)
|
|
58
58
|
else:
|
|
59
59
|
logger.info(f"Kuzu-memory integration enabled: {self.kuzu_memory_cmd}")
|
claude_mpm/init.py
CHANGED
|
@@ -163,6 +163,9 @@ class ProjectInitializer:
|
|
|
163
163
|
f"✓ Found {agent_count} project agent(s) in .claude-mpm/agents/"
|
|
164
164
|
)
|
|
165
165
|
|
|
166
|
+
# Verify and deploy PM skills (non-blocking)
|
|
167
|
+
self._verify_and_deploy_pm_skills(project_root, is_mcp_mode)
|
|
168
|
+
|
|
166
169
|
return True
|
|
167
170
|
|
|
168
171
|
except Exception as e:
|
|
@@ -170,6 +173,66 @@ class ProjectInitializer:
|
|
|
170
173
|
print(f"✗ Failed to create .claude-mpm/ directory: {e}")
|
|
171
174
|
return False
|
|
172
175
|
|
|
176
|
+
def _verify_and_deploy_pm_skills(
|
|
177
|
+
self, project_root: Path, is_mcp_mode: bool = False
|
|
178
|
+
) -> None:
|
|
179
|
+
"""Verify PM skills are deployed and auto-deploy if missing.
|
|
180
|
+
|
|
181
|
+
Non-blocking operation that gracefully handles errors.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
project_root: Project root directory
|
|
185
|
+
is_mcp_mode: Whether running in MCP mode (suppress console output)
|
|
186
|
+
"""
|
|
187
|
+
try:
|
|
188
|
+
from claude_mpm.services.pm_skills_deployer import PMSkillsDeployerService
|
|
189
|
+
|
|
190
|
+
deployer = PMSkillsDeployerService()
|
|
191
|
+
result = deployer.verify_pm_skills(project_root)
|
|
192
|
+
|
|
193
|
+
if not result.verified:
|
|
194
|
+
# Log warnings
|
|
195
|
+
for warning in result.warnings:
|
|
196
|
+
self.logger.warning(warning)
|
|
197
|
+
|
|
198
|
+
# Auto-deploy PM skills
|
|
199
|
+
self.logger.info("Auto-deploying PM skills...")
|
|
200
|
+
deploy_result = deployer.deploy_pm_skills(project_root)
|
|
201
|
+
|
|
202
|
+
if deploy_result.success:
|
|
203
|
+
self.logger.info(
|
|
204
|
+
f"PM skills deployed: {len(deploy_result.deployed)} deployed, "
|
|
205
|
+
f"{len(deploy_result.skipped)} skipped"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Print to console if not in MCP mode
|
|
209
|
+
if not is_mcp_mode:
|
|
210
|
+
if deploy_result.deployed:
|
|
211
|
+
print(
|
|
212
|
+
f"✓ Deployed {len(deploy_result.deployed)} PM skill(s) "
|
|
213
|
+
f"to .claude-mpm/skills/pm/"
|
|
214
|
+
)
|
|
215
|
+
else:
|
|
216
|
+
self.logger.warning(
|
|
217
|
+
f"PM skills deployment had errors: {len(deploy_result.errors)}"
|
|
218
|
+
)
|
|
219
|
+
if not is_mcp_mode and deploy_result.errors:
|
|
220
|
+
print(f"⚠ PM skills deployment had {len(deploy_result.errors)} error(s)")
|
|
221
|
+
else:
|
|
222
|
+
# Skills verified successfully
|
|
223
|
+
registry = deployer._load_registry(project_root)
|
|
224
|
+
skill_count = len(registry.get("skills", []))
|
|
225
|
+
self.logger.debug(f"PM skills verified: {skill_count} skills")
|
|
226
|
+
|
|
227
|
+
if not is_mcp_mode and skill_count > 0:
|
|
228
|
+
print(f"✓ Verified {skill_count} PM skill(s)")
|
|
229
|
+
|
|
230
|
+
except ImportError:
|
|
231
|
+
self.logger.debug("PM skills deployer not available")
|
|
232
|
+
except Exception as e:
|
|
233
|
+
self.logger.warning(f"PM skills verification failed: {e}")
|
|
234
|
+
# Don't print to console - this is a non-critical failure
|
|
235
|
+
|
|
173
236
|
def _migrate_project_agents(self):
|
|
174
237
|
"""Migrate agents from old subdirectory structure to direct agents directory.
|
|
175
238
|
|
|
@@ -34,7 +34,7 @@ class GitRepository:
|
|
|
34
34
|
def cache_path(self) -> Path:
|
|
35
35
|
"""Return cache directory path for this repository.
|
|
36
36
|
|
|
37
|
-
Cache structure: ~/.claude-mpm/cache/
|
|
37
|
+
Cache structure: ~/.claude-mpm/cache/agents/{owner}/{repo}/{subdirectory}/
|
|
38
38
|
|
|
39
39
|
Returns:
|
|
40
40
|
Absolute path to cache directory for this repository
|
|
@@ -45,10 +45,10 @@ class GitRepository:
|
|
|
45
45
|
... subdirectory="agents"
|
|
46
46
|
... )
|
|
47
47
|
>>> repo.cache_path
|
|
48
|
-
Path('/Users/user/.claude-mpm/cache/
|
|
48
|
+
Path('/Users/user/.claude-mpm/cache/agents/bobmatnyc/claude-mpm-agents/agents')
|
|
49
49
|
"""
|
|
50
50
|
home = Path.home()
|
|
51
|
-
base_cache = home / ".claude-mpm" / "cache" / "
|
|
51
|
+
base_cache = home / ".claude-mpm" / "cache" / "agents"
|
|
52
52
|
|
|
53
53
|
# Extract owner and repo from URL
|
|
54
54
|
owner, repo = self._parse_github_url(self.url)
|
|
File without changes
|
|
@@ -206,8 +206,8 @@ class AgentBuilderService:
|
|
|
206
206
|
"""
|
|
207
207
|
errors = []
|
|
208
208
|
|
|
209
|
-
# Required fields
|
|
210
|
-
required_fields = ["id", "name", "prompt"
|
|
209
|
+
# Required fields (model is optional - defaults to sonnet if not specified)
|
|
210
|
+
required_fields = ["id", "name", "prompt"]
|
|
211
211
|
for field in required_fields:
|
|
212
212
|
if field not in config:
|
|
213
213
|
errors.append(f"Missing required field: {field}")
|
|
@@ -219,7 +219,7 @@ class AgentBuilderService:
|
|
|
219
219
|
except AgentDeploymentError as e:
|
|
220
220
|
errors.append(str(e))
|
|
221
221
|
|
|
222
|
-
# Validate model
|
|
222
|
+
# Validate model (only if present)
|
|
223
223
|
if "model" in config:
|
|
224
224
|
try:
|
|
225
225
|
self._validate_model(config["model"])
|
|
@@ -29,7 +29,7 @@ Error Handling:
|
|
|
29
29
|
|
|
30
30
|
Example:
|
|
31
31
|
>>> from pathlib import Path
|
|
32
|
-
>>> manager = CacheGitManager(Path.home() / ".claude-mpm/cache/
|
|
32
|
+
>>> manager = CacheGitManager(Path.home() / ".claude-mpm/cache/agents")
|
|
33
33
|
>>> if manager.is_git_repo():
|
|
34
34
|
... status = manager.get_status()
|
|
35
35
|
... print(f"Branch: {status['branch']}, Uncommitted: {len(status['uncommitted'])}")
|
|
@@ -76,7 +76,7 @@ class CacheGitManager:
|
|
|
76
76
|
timeout: Git command timeout in seconds (default: 30)
|
|
77
77
|
|
|
78
78
|
Example:
|
|
79
|
-
>>> cache_dir = Path.home() / ".claude-mpm/cache/
|
|
79
|
+
>>> cache_dir = Path.home() / ".claude-mpm/cache/agents"
|
|
80
80
|
>>> manager = CacheGitManager(cache_dir)
|
|
81
81
|
"""
|
|
82
82
|
self.cache_path = Path(cache_path)
|
|
@@ -105,12 +105,12 @@ class CacheGitManager:
|
|
|
105
105
|
|
|
106
106
|
Example:
|
|
107
107
|
>>> # Case 1: cache_path inside repo (searches upward)
|
|
108
|
-
>>> # cache_path: ~/.claude-mpm/cache/
|
|
109
|
-
>>> # Found at: ~/.claude-mpm/cache/
|
|
108
|
+
>>> # cache_path: ~/.claude-mpm/cache/agents/bobmatnyc/claude-mpm-agents/agents
|
|
109
|
+
>>> # Found at: ~/.claude-mpm/cache/agents/bobmatnyc/claude-mpm-agents
|
|
110
110
|
|
|
111
111
|
>>> # Case 2: repo nested in cache_path (searches downward)
|
|
112
|
-
>>> # cache_path: ~/.claude-mpm/cache/
|
|
113
|
-
>>> # Found at: ~/.claude-mpm/cache/
|
|
112
|
+
>>> # cache_path: ~/.claude-mpm/cache/agents
|
|
113
|
+
>>> # Found at: ~/.claude-mpm/cache/agents/bobmatnyc/claude-mpm-agents
|
|
114
114
|
"""
|
|
115
115
|
# Strategy 1: Search upward (cache_path is inside repo)
|
|
116
116
|
current = self.cache_path
|
|
@@ -876,13 +876,13 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
|
|
|
876
876
|
user_agents_dir = potential_user_dir
|
|
877
877
|
self.logger.info(f"Found user agents at: {user_agents_dir}")
|
|
878
878
|
|
|
879
|
-
# Check for
|
|
880
|
-
|
|
879
|
+
# Check for agents cache (from Git sources)
|
|
880
|
+
agents_cache_dir = None
|
|
881
881
|
cache_dir = user_home / ".claude-mpm" / "cache"
|
|
882
|
-
|
|
883
|
-
if
|
|
884
|
-
|
|
885
|
-
self.logger.info(f"Found
|
|
882
|
+
potential_cache_dir = cache_dir / "agents"
|
|
883
|
+
if potential_cache_dir.exists():
|
|
884
|
+
agents_cache_dir = potential_cache_dir
|
|
885
|
+
self.logger.info(f"Found agents cache at: {agents_cache_dir}")
|
|
886
886
|
|
|
887
887
|
# Get agents with version comparison and cleanup (4-tier discovery)
|
|
888
888
|
agents_to_deploy, agent_sources, cleanup_results = (
|
|
@@ -890,7 +890,7 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
|
|
|
890
890
|
system_templates_dir=system_templates_dir,
|
|
891
891
|
project_agents_dir=project_agents_dir,
|
|
892
892
|
user_agents_dir=user_agents_dir,
|
|
893
|
-
|
|
893
|
+
agents_cache_dir=agents_cache_dir, # NEW: 4th tier
|
|
894
894
|
working_directory=self.working_directory,
|
|
895
895
|
excluded_agents=excluded_agents,
|
|
896
896
|
config=config,
|
|
@@ -898,6 +898,9 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
|
|
|
898
898
|
)
|
|
899
899
|
)
|
|
900
900
|
|
|
901
|
+
# Keep track of all enabled agents before filtering (for cleanup)
|
|
902
|
+
all_enabled_agents = agents_to_deploy.copy()
|
|
903
|
+
|
|
901
904
|
# Compare with deployed versions if agents directory exists
|
|
902
905
|
if agents_dir.exists():
|
|
903
906
|
comparison_results = self.multi_source_service.compare_deployed_versions(
|
|
@@ -954,6 +957,25 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
|
|
|
954
957
|
f"All {len(comparison_results.get('up_to_date', []))} agents are up to date"
|
|
955
958
|
)
|
|
956
959
|
|
|
960
|
+
# Cleanup excluded agents (remove agents not in deployment list)
|
|
961
|
+
# CRITICAL: Use all_enabled_agents (before filtering for updates) to preserve up-to-date agents
|
|
962
|
+
# Bug fix (1M-XXX): Previously used filtered agents_to_deploy which could be empty,
|
|
963
|
+
# causing all agents to be removed when everything was up-to-date
|
|
964
|
+
exclusion_cleanup_results = self.multi_source_service.cleanup_excluded_agents(
|
|
965
|
+
deployed_agents_dir=agents_dir,
|
|
966
|
+
agents_to_deploy=all_enabled_agents,
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
# Add exclusion cleanup results to main cleanup results
|
|
970
|
+
if exclusion_cleanup_results.get("removed"):
|
|
971
|
+
cleanup_results.setdefault("excluded_removed", []).extend(
|
|
972
|
+
exclusion_cleanup_results["removed"]
|
|
973
|
+
)
|
|
974
|
+
self.logger.info(
|
|
975
|
+
f"Removed {len(exclusion_cleanup_results['removed'])} excluded agents: "
|
|
976
|
+
f"{', '.join(exclusion_cleanup_results['removed'])}"
|
|
977
|
+
)
|
|
978
|
+
|
|
957
979
|
# Convert to list of Path objects
|
|
958
980
|
template_files = list(agents_to_deploy.values())
|
|
959
981
|
|
|
@@ -248,7 +248,7 @@ class AgentDiscoveryService:
|
|
|
248
248
|
return agent_info
|
|
249
249
|
|
|
250
250
|
except yaml.YAMLError as e:
|
|
251
|
-
self.logger.
|
|
251
|
+
self.logger.warning(f"Invalid YAML frontmatter in {template_file.name}: {e}")
|
|
252
252
|
return None
|
|
253
253
|
except Exception as e:
|
|
254
254
|
self.logger.error(
|
|
@@ -431,7 +431,7 @@ class AgentDiscoveryService:
|
|
|
431
431
|
return True
|
|
432
432
|
|
|
433
433
|
except yaml.YAMLError:
|
|
434
|
-
self.logger.
|
|
434
|
+
self.logger.warning(
|
|
435
435
|
f"Invalid YAML frontmatter in template: {template_file.name}"
|
|
436
436
|
)
|
|
437
437
|
return False
|
|
@@ -137,8 +137,8 @@ class AgentFormatConverter:
|
|
|
137
137
|
else:
|
|
138
138
|
pass
|
|
139
139
|
|
|
140
|
-
# Extract additional fields
|
|
141
|
-
model = self.extract_yaml_field(yaml_content, "model")
|
|
140
|
+
# Extract additional fields - model is optional (Claude Code uses conversation model if not set)
|
|
141
|
+
model = self.extract_yaml_field(yaml_content, "model") # None if not specified
|
|
142
142
|
author = (
|
|
143
143
|
self.extract_yaml_field(yaml_content, "author")
|
|
144
144
|
or "claude-mpm@anthropic.com"
|
|
@@ -147,7 +147,7 @@ class AgentFormatConverter:
|
|
|
147
147
|
# Extract instructions from YAML content
|
|
148
148
|
instructions = self._extract_instructions_from_yaml(yaml_content, agent_name)
|
|
149
149
|
|
|
150
|
-
# Map model names to Claude Code format
|
|
150
|
+
# Map model names to Claude Code format (only if model is specified)
|
|
151
151
|
model_map = {
|
|
152
152
|
"claude-3-5-sonnet-20241022": "sonnet",
|
|
153
153
|
"claude-3-5-sonnet": "sonnet",
|
|
@@ -159,7 +159,8 @@ class AgentFormatConverter:
|
|
|
159
159
|
"opus": "opus",
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
# Only map model if it's not None (preserve None for agents without model field)
|
|
163
|
+
mapped_model = model_map.get(model, model) if model is not None else None
|
|
163
164
|
|
|
164
165
|
# Create multiline description with example (Claude Code format)
|
|
165
166
|
multiline_description = f"""{description}
|
|
@@ -172,16 +173,25 @@ assistant: "I'll use the {name} agent to provide specialized assistance."
|
|
|
172
173
|
|
|
173
174
|
# Build new YAML frontmatter - Claude Code compatible format
|
|
174
175
|
# NOTE: Removed tags field and other non-essential fields for Claude Code compatibility
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
author: "{author}"
|
|
182
|
-
---
|
|
176
|
+
frontmatter_lines = [
|
|
177
|
+
"---",
|
|
178
|
+
f"name: {name}",
|
|
179
|
+
"description: |",
|
|
180
|
+
f" {self._indent_text(multiline_description, 2)}",
|
|
181
|
+
]
|
|
183
182
|
|
|
184
|
-
|
|
183
|
+
# Only include model field if explicitly set in source
|
|
184
|
+
if mapped_model is not None:
|
|
185
|
+
frontmatter_lines.append(f"model: {mapped_model}")
|
|
186
|
+
|
|
187
|
+
frontmatter_lines.extend([
|
|
188
|
+
f'version: "{version}"',
|
|
189
|
+
f'author: "{author}"',
|
|
190
|
+
"---",
|
|
191
|
+
"",
|
|
192
|
+
])
|
|
193
|
+
|
|
194
|
+
new_frontmatter = "\n".join(frontmatter_lines)
|
|
185
195
|
|
|
186
196
|
return new_frontmatter + instructions
|
|
187
197
|
|