claude-mpm 5.0.2__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 (184) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +1218 -905
  4. claude_mpm/agents/agent_loader.py +10 -17
  5. claude_mpm/agents/base_agent_loader.py +10 -35
  6. claude_mpm/agents/frontmatter_validator.py +68 -0
  7. claude_mpm/agents/templates/circuit-breakers.md +431 -45
  8. claude_mpm/cli/__init__.py +0 -1
  9. claude_mpm/cli/commands/__init__.py +2 -0
  10. claude_mpm/cli/commands/agent_state_manager.py +67 -23
  11. claude_mpm/cli/commands/agents.py +446 -25
  12. claude_mpm/cli/commands/auto_configure.py +535 -233
  13. claude_mpm/cli/commands/configure.py +1500 -147
  14. claude_mpm/cli/commands/configure_agent_display.py +13 -6
  15. claude_mpm/cli/commands/mpm_init/core.py +158 -1
  16. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  17. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  18. claude_mpm/cli/commands/postmortem.py +401 -0
  19. claude_mpm/cli/commands/run.py +1 -39
  20. claude_mpm/cli/commands/skills.py +322 -19
  21. claude_mpm/cli/commands/summarize.py +413 -0
  22. claude_mpm/cli/executor.py +8 -0
  23. claude_mpm/cli/interactive/agent_wizard.py +302 -195
  24. claude_mpm/cli/parsers/agents_parser.py +137 -0
  25. claude_mpm/cli/parsers/auto_configure_parser.py +13 -0
  26. claude_mpm/cli/parsers/base_parser.py +9 -0
  27. claude_mpm/cli/parsers/skills_parser.py +7 -0
  28. claude_mpm/cli/startup.py +133 -85
  29. claude_mpm/commands/mpm-agents-auto-configure.md +2 -2
  30. claude_mpm/commands/mpm-agents-list.md +2 -2
  31. claude_mpm/commands/mpm-config-view.md +2 -2
  32. claude_mpm/commands/mpm-help.md +3 -0
  33. claude_mpm/commands/{mpm-ticket-organize.md โ†’ mpm-organize.md} +4 -5
  34. claude_mpm/commands/mpm-postmortem.md +123 -0
  35. claude_mpm/commands/mpm-session-resume.md +2 -2
  36. claude_mpm/commands/mpm-ticket-view.md +2 -2
  37. claude_mpm/config/agent_presets.py +312 -82
  38. claude_mpm/config/agent_sources.py +27 -0
  39. claude_mpm/config/skill_presets.py +392 -0
  40. claude_mpm/constants.py +1 -0
  41. claude_mpm/core/claude_runner.py +2 -25
  42. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  43. claude_mpm/core/framework/loaders/file_loader.py +54 -101
  44. claude_mpm/core/interactive_session.py +19 -5
  45. claude_mpm/core/oneshot_session.py +16 -4
  46. claude_mpm/core/output_style_manager.py +173 -43
  47. claude_mpm/core/protocols/__init__.py +23 -0
  48. claude_mpm/core/protocols/runner_protocol.py +103 -0
  49. claude_mpm/core/protocols/session_protocol.py +131 -0
  50. claude_mpm/core/shared/singleton_manager.py +11 -4
  51. claude_mpm/core/socketio_pool.py +3 -3
  52. claude_mpm/core/system_context.py +38 -0
  53. claude_mpm/core/unified_agent_registry.py +134 -16
  54. claude_mpm/core/unified_config.py +22 -0
  55. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  56. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
  57. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  58. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  59. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  60. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  61. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  62. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  63. claude_mpm/hooks/claude_hooks/event_handlers.py +35 -2
  64. claude_mpm/hooks/claude_hooks/hook_handler.py +4 -0
  65. claude_mpm/hooks/claude_hooks/memory_integration.py +12 -1
  66. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  67. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  68. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  69. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  70. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  71. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  72. claude_mpm/models/agent_definition.py +7 -0
  73. claude_mpm/scripts/launch_monitor.py +93 -13
  74. claude_mpm/services/agents/agent_recommendation_service.py +279 -0
  75. claude_mpm/services/agents/cache_git_manager.py +621 -0
  76. claude_mpm/services/agents/deployment/agent_template_builder.py +3 -2
  77. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +110 -3
  78. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +518 -55
  79. claude_mpm/services/agents/git_source_manager.py +20 -0
  80. claude_mpm/services/agents/sources/git_source_sync_service.py +45 -6
  81. claude_mpm/services/agents/toolchain_detector.py +6 -5
  82. claude_mpm/services/analysis/__init__.py +35 -0
  83. claude_mpm/services/analysis/clone_detector.py +1030 -0
  84. claude_mpm/services/analysis/postmortem_reporter.py +474 -0
  85. claude_mpm/services/analysis/postmortem_service.py +765 -0
  86. claude_mpm/services/command_deployment_service.py +106 -5
  87. claude_mpm/services/core/base.py +7 -2
  88. claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
  89. claude_mpm/services/event_bus/config.py +3 -1
  90. claude_mpm/services/git/git_operations_service.py +8 -8
  91. claude_mpm/services/mcp_config_manager.py +75 -145
  92. claude_mpm/services/mcp_service_verifier.py +6 -3
  93. claude_mpm/services/monitor/daemon.py +37 -10
  94. claude_mpm/services/monitor/daemon_manager.py +134 -21
  95. claude_mpm/services/monitor/server.py +225 -19
  96. claude_mpm/services/project/project_organizer.py +4 -0
  97. claude_mpm/services/runner_configuration_service.py +16 -3
  98. claude_mpm/services/session_management_service.py +16 -4
  99. claude_mpm/services/socketio/event_normalizer.py +15 -1
  100. claude_mpm/services/socketio/server/core.py +160 -21
  101. claude_mpm/services/version_control/git_operations.py +103 -0
  102. claude_mpm/utils/agent_filters.py +261 -0
  103. claude_mpm/utils/gitignore.py +3 -0
  104. claude_mpm/utils/migration.py +372 -0
  105. claude_mpm/utils/progress.py +5 -1
  106. {claude_mpm-5.0.2.dist-info โ†’ claude_mpm-5.4.3.dist-info}/METADATA +69 -84
  107. {claude_mpm-5.0.2.dist-info โ†’ claude_mpm-5.4.3.dist-info}/RECORD +112 -153
  108. {claude_mpm-5.0.2.dist-info โ†’ claude_mpm-5.4.3.dist-info}/entry_points.txt +0 -2
  109. claude_mpm/dashboard/analysis_runner.py +0 -455
  110. claude_mpm/dashboard/index.html +0 -13
  111. claude_mpm/dashboard/open_dashboard.py +0 -66
  112. claude_mpm/dashboard/static/css/activity.css +0 -1958
  113. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  114. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  115. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  116. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  117. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  118. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  119. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  120. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  121. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  122. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  123. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  124. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  125. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  126. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  127. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  128. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  129. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  130. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  131. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  132. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  133. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  134. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  135. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  136. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  137. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  138. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  139. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  140. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  141. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  142. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  143. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  144. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  145. claude_mpm/dashboard/templates/code_simple.html +0 -153
  146. claude_mpm/dashboard/templates/index.html +0 -606
  147. claude_mpm/dashboard/test_dashboard.html +0 -372
  148. claude_mpm/scripts/mcp_server.py +0 -75
  149. claude_mpm/scripts/mcp_wrapper.py +0 -39
  150. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  151. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  152. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  153. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  154. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  155. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  156. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  157. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  158. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  159. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  160. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -971
  161. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  162. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  163. claude_mpm/services/mcp_gateway/main.py +0 -589
  164. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  165. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  166. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  167. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  168. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  169. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  170. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  171. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  172. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  173. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  174. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  175. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  176. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  177. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  178. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  179. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  180. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  181. /claude_mpm/agents/{OUTPUT_STYLE.md โ†’ CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  182. {claude_mpm-5.0.2.dist-info โ†’ claude_mpm-5.4.3.dist-info}/WHEEL +0 -0
  183. {claude_mpm-5.0.2.dist-info โ†’ claude_mpm-5.4.3.dist-info}/licenses/LICENSE +0 -0
  184. {claude_mpm-5.0.2.dist-info โ†’ claude_mpm-5.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,131 @@
1
+ """Protocol definitions for session management dependencies.
2
+
3
+ These protocols use Python's typing.Protocol for structural subtyping,
4
+ allowing dependency injection without circular imports.
5
+ """
6
+
7
+ from typing import Any, Dict, Optional, Protocol, Tuple
8
+
9
+
10
+ class InteractiveSessionProtocol(Protocol):
11
+ """Protocol for interactive session orchestration.
12
+
13
+ This protocol defines the interface that SessionManagementService
14
+ needs from InteractiveSession without requiring a full import.
15
+ """
16
+
17
+ def initialize_interactive_session(self) -> Tuple[bool, Optional[str]]:
18
+ """Initialize the interactive session environment.
19
+
20
+ Returns:
21
+ Tuple of (success, error_message)
22
+ """
23
+ ...
24
+
25
+ def setup_interactive_environment(self) -> Tuple[bool, Dict[str, Any]]:
26
+ """Set up the interactive environment including agents and commands.
27
+
28
+ Returns:
29
+ Tuple of (success, environment_dict)
30
+ """
31
+ ...
32
+
33
+ def handle_interactive_input(self, environment: Dict[str, Any]) -> bool:
34
+ """Handle the interactive input/output loop.
35
+
36
+ Args:
37
+ environment: Dictionary with command, env vars, and session info
38
+
39
+ Returns:
40
+ True if successful, False otherwise
41
+ """
42
+ ...
43
+
44
+ def cleanup_interactive_session(self) -> None:
45
+ """Clean up resources after interactive session ends."""
46
+ ...
47
+
48
+
49
+ class OneshotSessionProtocol(Protocol):
50
+ """Protocol for oneshot session orchestration.
51
+
52
+ This protocol defines the interface that SessionManagementService
53
+ needs from OneshotSession without requiring a full import.
54
+ """
55
+
56
+ def initialize_session(self, prompt: str) -> Tuple[bool, Optional[str]]:
57
+ """Initialize the oneshot session.
58
+
59
+ Args:
60
+ prompt: The command or prompt to execute
61
+
62
+ Returns:
63
+ Tuple of (success, error_message)
64
+ """
65
+ ...
66
+
67
+ def deploy_agents(self) -> bool:
68
+ """Deploy agents for the session.
69
+
70
+ Returns:
71
+ True if successful, False otherwise
72
+ """
73
+ ...
74
+
75
+ def setup_infrastructure(self) -> Dict[str, Any]:
76
+ """Set up session infrastructure.
77
+
78
+ Returns:
79
+ Dictionary with infrastructure configuration
80
+ """
81
+ ...
82
+
83
+ def execute_command(
84
+ self, prompt: str, context: Optional[str], infrastructure: Dict[str, Any]
85
+ ) -> Tuple[bool, Optional[str]]:
86
+ """Execute the command with given context and infrastructure.
87
+
88
+ Args:
89
+ prompt: Command to execute
90
+ context: Optional context
91
+ infrastructure: Infrastructure configuration
92
+
93
+ Returns:
94
+ Tuple of (success, response)
95
+ """
96
+ ...
97
+
98
+ def cleanup_session(self) -> None:
99
+ """Clean up session resources."""
100
+ ...
101
+
102
+
103
+ class SessionManagementProtocol(Protocol):
104
+ """Protocol for session management service.
105
+
106
+ This protocol defines the interface that ClaudeRunner needs from
107
+ SessionManagementService without requiring a full import.
108
+ """
109
+
110
+ def run_interactive_session(self, initial_context: Optional[str] = None) -> bool:
111
+ """Run Claude in interactive mode.
112
+
113
+ Args:
114
+ initial_context: Optional initial context to pass to Claude
115
+
116
+ Returns:
117
+ True if successful, False otherwise
118
+ """
119
+ ...
120
+
121
+ def run_oneshot_session(self, prompt: str, context: Optional[str] = None) -> bool:
122
+ """Run Claude with a single prompt.
123
+
124
+ Args:
125
+ prompt: The command or prompt to execute
126
+ context: Optional context to prepend to the prompt
127
+
128
+ Returns:
129
+ True if successful, False otherwise
130
+ """
131
+ ...
@@ -16,11 +16,14 @@ class SingletonManager:
16
16
 
17
17
  Reduces duplication by providing thread-safe singleton patterns
18
18
  that can be used across different classes.
19
+
20
+ Uses RLock (reentrant locks) to support recursive calls from
21
+ SingletonMixin.__new__ and @singleton decorator patterns.
19
22
  """
20
23
 
21
24
  _instances: Dict[Type, Any] = {}
22
- _locks: Dict[Type, threading.Lock] = {}
23
- _global_lock = threading.Lock()
25
+ _locks: Dict[Type, threading.RLock] = {}
26
+ _global_lock = threading.RLock()
24
27
 
25
28
  @classmethod
26
29
  def get_instance(
@@ -42,7 +45,7 @@ class SingletonManager:
42
45
  if singleton_class not in cls._locks:
43
46
  with cls._global_lock:
44
47
  if singleton_class not in cls._locks:
45
- cls._locks[singleton_class] = threading.Lock()
48
+ cls._locks[singleton_class] = threading.RLock()
46
49
 
47
50
  # Get instance with class-specific lock
48
51
  with cls._locks[singleton_class]:
@@ -50,9 +53,13 @@ class SingletonManager:
50
53
  logger = get_logger("singleton_manager")
51
54
  logger.debug(f"Creating singleton instance: {singleton_class.__name__}")
52
55
 
53
- instance = singleton_class(*args, **kwargs)
56
+ # Use object.__new__ to bypass SingletonMixin.__new__ and avoid recursion
57
+ instance = object.__new__(singleton_class)
54
58
  cls._instances[singleton_class] = instance
55
59
 
60
+ # Now call __init__ explicitly with the stored instance
61
+ instance.__init__(*args, **kwargs)
62
+
56
63
  return instance
57
64
 
58
65
  return cls._instances[singleton_class]
@@ -55,8 +55,8 @@ class CircuitState(Enum):
55
55
  class ConnectionStats:
56
56
  """Connection statistics for monitoring."""
57
57
 
58
- created_at: datetime = field(default_factory=datetime.now)
59
- last_used: datetime = field(default_factory=datetime.now)
58
+ created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
59
+ last_used: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
60
60
  events_sent: int = 0
61
61
  errors: int = 0
62
62
  consecutive_errors: int = 0
@@ -70,7 +70,7 @@ class BatchEvent:
70
70
  namespace: str
71
71
  event: str
72
72
  data: Dict[str, Any]
73
- timestamp: datetime = field(default_factory=datetime.now)
73
+ timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
74
74
 
75
75
 
76
76
  class CircuitBreaker:
@@ -0,0 +1,38 @@
1
+ """System context utilities for Claude runner.
2
+
3
+ This module provides shared context creation functions that can be used
4
+ across different modules without circular dependencies.
5
+ """
6
+
7
+
8
+ def create_simple_context() -> str:
9
+ """Create basic context for Claude.
10
+
11
+ This function is extracted to avoid circular imports between
12
+ claude_runner.py and interactive_session.py.
13
+
14
+ Returns:
15
+ Basic system context string for Claude
16
+ """
17
+ return """You are Claude Code running in Claude MPM (Multi-Agent Project Manager).
18
+
19
+ You have access to native subagents via the Task tool with subagent_type parameter:
20
+ - engineer: For coding, implementation, and technical tasks
21
+ - qa: For testing, validation, and quality assurance
22
+ - documentation: For docs, guides, and explanations
23
+ - research: For investigation and analysis
24
+ - security: For security-related tasks
25
+ - ops: For deployment and infrastructure
26
+ - version-control: For git and version management
27
+ - data-engineer: For data processing and APIs
28
+
29
+ Use these agents by calling: Task(description="task description", subagent_type="agent_name")
30
+
31
+ IMPORTANT: The Task tool accepts both naming formats:
32
+ - Capitalized format: "Research", "Engineer", "QA", "Version Control", "Data Engineer"
33
+ - Lowercase format: "research", "engineer", "qa", "version-control", "data-engineer"
34
+
35
+ Both formats work correctly. When you see capitalized names (matching TodoWrite prefixes),
36
+ automatically normalize them to lowercase-hyphenated format for the Task tool.
37
+
38
+ Work efficiently and delegate appropriately to subagents when needed."""
@@ -84,6 +84,12 @@ class AgentMetadata:
84
84
  version: str = "1.0.0"
85
85
  author: str = ""
86
86
  tags: List[str] = None
87
+ # NEW: Collection-based identification fields
88
+ collection_id: Optional[str] = None # Format: owner/repo-name
89
+ source_path: Optional[str] = None # Relative path in repo
90
+ canonical_id: Optional[str] = (
91
+ None # Format: collection_id:agent_id or legacy:filename
92
+ )
87
93
 
88
94
  def __post_init__(self):
89
95
  """Initialize default values for mutable fields."""
@@ -168,26 +174,16 @@ class UnifiedAgentRegistry:
168
174
  if project_path.exists():
169
175
  self.discovery_paths.append(project_path)
170
176
 
171
- # Also check for local JSON templates in .claude-mpm/agents/
172
- local_project_path = self.path_manager.project_root / ".claude-mpm" / "agents"
173
- if (
174
- local_project_path.exists()
175
- and local_project_path not in self.discovery_paths
176
- ):
177
- self.discovery_paths.append(local_project_path)
178
- logger.debug(f"Added local project templates path: {local_project_path}")
177
+ # NOTE: .claude-mpm/agents/ is deprecated in the simplified architecture
178
+ # Source agents come from ~/.claude-mpm/cache/remote-agents/
179
+ # Deployed agents go to .claude/agents/
179
180
 
180
- # User-level agents
181
+ # User-level agents (deprecated in simplified architecture)
182
+ # Keeping for backward compatibility but not actively used
181
183
  user_path = self.path_manager.get_user_agents_dir()
182
184
  if user_path.exists():
183
185
  self.discovery_paths.append(user_path)
184
186
 
185
- # Also check for user JSON templates in ~/.claude-mpm/agents/
186
- local_user_path = Path.home() / ".claude-mpm" / "agents"
187
- if local_user_path.exists() and local_user_path not in self.discovery_paths:
188
- self.discovery_paths.append(local_user_path)
189
- logger.debug(f"Added local user templates path: {local_user_path}")
190
-
191
187
  # System-level agents (includes templates as a subdirectory)
192
188
  system_path = self.path_manager.get_system_agents_dir()
193
189
  if system_path.exists():
@@ -690,6 +686,111 @@ class UnifiedAgentRegistry:
690
686
  """Get all memory-aware agents."""
691
687
  return self.list_agents(agent_type=AgentType.MEMORY_AWARE)
692
688
 
689
+ def get_agents_by_collection(self, collection_id: str) -> List[AgentMetadata]:
690
+ """Get all agents from a specific collection.
691
+
692
+ NEW: Enables collection-based agent selection.
693
+
694
+ Args:
695
+ collection_id: Collection identifier (e.g., "bobmatnyc/claude-mpm-agents")
696
+
697
+ Returns:
698
+ List of agents from the specified collection
699
+
700
+ Example:
701
+ >>> registry = get_agent_registry()
702
+ >>> agents = registry.get_agents_by_collection("bobmatnyc/claude-mpm-agents")
703
+ >>> len(agents)
704
+ 45
705
+ """
706
+ if not self.registry:
707
+ self.discover_agents()
708
+
709
+ collection_agents = [
710
+ agent
711
+ for agent in self.registry.values()
712
+ if agent.collection_id == collection_id
713
+ ]
714
+
715
+ return sorted(collection_agents, key=lambda a: a.name)
716
+
717
+ def list_collections(self) -> List[Dict[str, Any]]:
718
+ """List all available collections with agent counts.
719
+
720
+ NEW: Provides overview of available collections.
721
+
722
+ Returns:
723
+ List of collection info dictionaries with:
724
+ - collection_id: Collection identifier
725
+ - agent_count: Number of agents in collection
726
+ - agents: List of agent names in collection
727
+
728
+ Example:
729
+ >>> registry = get_agent_registry()
730
+ >>> collections = registry.list_collections()
731
+ >>> collections
732
+ [
733
+ {
734
+ "collection_id": "bobmatnyc/claude-mpm-agents",
735
+ "agent_count": 45,
736
+ "agents": ["pm", "engineer", "qa", ...]
737
+ }
738
+ ]
739
+ """
740
+ if not self.registry:
741
+ self.discover_agents()
742
+
743
+ # Group agents by collection_id
744
+ collections_map: Dict[str, List[str]] = {}
745
+
746
+ for agent in self.registry.values():
747
+ if not agent.collection_id:
748
+ # Skip agents without collection (legacy or local)
749
+ continue
750
+
751
+ if agent.collection_id not in collections_map:
752
+ collections_map[agent.collection_id] = []
753
+
754
+ collections_map[agent.collection_id].append(agent.name)
755
+
756
+ # Convert to list format
757
+ collections = [
758
+ {
759
+ "collection_id": coll_id,
760
+ "agent_count": len(agent_names),
761
+ "agents": sorted(agent_names),
762
+ }
763
+ for coll_id, agent_names in collections_map.items()
764
+ ]
765
+
766
+ return sorted(collections, key=lambda c: c["collection_id"])
767
+
768
+ def get_agent_by_canonical_id(self, canonical_id: str) -> Optional[AgentMetadata]:
769
+ """Get agent by canonical ID (primary matching key).
770
+
771
+ NEW: Primary matching method using canonical_id.
772
+
773
+ Args:
774
+ canonical_id: Canonical identifier (e.g., "bobmatnyc/claude-mpm-agents:pm")
775
+
776
+ Returns:
777
+ AgentMetadata if found, None otherwise
778
+
779
+ Example:
780
+ >>> registry = get_agent_registry()
781
+ >>> agent = registry.get_agent_by_canonical_id("bobmatnyc/claude-mpm-agents:pm")
782
+ >>> agent.name
783
+ 'Project Manager Agent'
784
+ """
785
+ if not self.registry:
786
+ self.discover_agents()
787
+
788
+ for agent in self.registry.values():
789
+ if agent.canonical_id == canonical_id:
790
+ return agent
791
+
792
+ return None
793
+
693
794
  def add_discovery_path(self, path: Union[str, Path]) -> None:
694
795
  """Add a new path for agent discovery."""
695
796
  path = Path(path)
@@ -809,6 +910,21 @@ def get_registry_stats() -> Dict[str, Any]:
809
910
  return get_agent_registry().get_registry_stats()
810
911
 
811
912
 
913
+ def get_agents_by_collection(collection_id: str) -> List[AgentMetadata]:
914
+ """Get all agents from a specific collection."""
915
+ return get_agent_registry().get_agents_by_collection(collection_id)
916
+
917
+
918
+ def list_collections() -> List[Dict[str, Any]]:
919
+ """List all available collections."""
920
+ return get_agent_registry().list_collections()
921
+
922
+
923
+ def get_agent_by_canonical_id(canonical_id: str) -> Optional[AgentMetadata]:
924
+ """Get agent by canonical ID."""
925
+ return get_agent_registry().get_agent_by_canonical_id(canonical_id)
926
+
927
+
812
928
  # Legacy function names for backward compatibility
813
929
  def listAgents() -> List[str]:
814
930
  """Legacy function: Get list of agent names."""
@@ -838,14 +954,16 @@ __all__ = [
838
954
  "discover_agents",
839
955
  "discover_agents_sync",
840
956
  "get_agent",
957
+ "get_agent_by_canonical_id",
841
958
  "get_agent_names",
842
959
  "get_agent_registry",
960
+ "get_agents_by_collection",
843
961
  "get_core_agents",
844
962
  "get_project_agents",
845
963
  "get_registry_stats",
846
964
  "get_specialized_agents",
847
- # Legacy compatibility
848
965
  "listAgents",
849
966
  "list_agents",
850
967
  "list_agents_all",
968
+ "list_collections",
851
969
  ]
@@ -218,6 +218,27 @@ class DevelopmentConfig(BaseModel):
218
218
  )
219
219
 
220
220
 
221
+ class DocumentationConfig(BaseModel):
222
+ """Documentation routing and management configuration."""
223
+
224
+ docs_path: str = Field(
225
+ default="docs/research/",
226
+ description="Default path for session documentation (relative to project root)",
227
+ )
228
+ attach_to_tickets: bool = Field(
229
+ default=True,
230
+ description="Attach work products to tickets when ticket context exists",
231
+ )
232
+ backup_locally: bool = Field(
233
+ default=True,
234
+ description="Always create local backup copies of documentation",
235
+ )
236
+ enable_ticket_detection: bool = Field(
237
+ default=True,
238
+ description="Enable automatic ticket context detection from user messages",
239
+ )
240
+
241
+
221
242
  class UnifiedConfig(BaseSettings):
222
243
  """
223
244
  Unified configuration model for Claude MPM.
@@ -242,6 +263,7 @@ class UnifiedConfig(BaseSettings):
242
263
  performance: PerformanceConfig = Field(default_factory=PerformanceConfig)
243
264
  sessions: SessionConfig = Field(default_factory=SessionConfig)
244
265
  development: DevelopmentConfig = Field(default_factory=DevelopmentConfig)
266
+ documentation: DocumentationConfig = Field(default_factory=DocumentationConfig)
245
267
 
246
268
  # Path configuration
247
269
  base_path: Optional[Path] = Field(
@@ -0,0 +1,60 @@
1
+ """Cross-process correlation storage using .claude-mpm directory."""
2
+
3
+ import json
4
+ import time
5
+ from pathlib import Path
6
+
7
+
8
+ def get_correlation_dir() -> Path:
9
+ """Get correlation directory in project's .claude-mpm folder."""
10
+ # Use CWD's .claude-mpm directory (where hooks run from)
11
+ cwd = Path.cwd()
12
+ return cwd / ".claude-mpm" / "correlations"
13
+
14
+
15
+ TTL_SECONDS = 3600 # 1 hour
16
+
17
+
18
+ class CorrelationManager:
19
+ """Manages correlation IDs across separate hook processes."""
20
+
21
+ @staticmethod
22
+ def store(session_id: str, tool_call_id: str, tool_name: str) -> None:
23
+ """Store correlation data for later retrieval by post_tool."""
24
+ correlation_dir = get_correlation_dir()
25
+ correlation_dir.mkdir(parents=True, exist_ok=True)
26
+ filepath = correlation_dir / f"correlation_{session_id}.json"
27
+ data = {
28
+ "tool_call_id": tool_call_id,
29
+ "tool_name": tool_name,
30
+ "timestamp": time.time(),
31
+ }
32
+ filepath.write_text(json.dumps(data))
33
+
34
+ @staticmethod
35
+ def retrieve(session_id: str) -> str | None:
36
+ """Retrieve and delete correlation data from temp file."""
37
+ correlation_dir = get_correlation_dir()
38
+ filepath = correlation_dir / f"correlation_{session_id}.json"
39
+ if not filepath.exists():
40
+ return None
41
+ try:
42
+ data = json.loads(filepath.read_text())
43
+ filepath.unlink() # Delete after reading
44
+ return data.get("tool_call_id")
45
+ except (json.JSONDecodeError, OSError):
46
+ return None
47
+
48
+ @staticmethod
49
+ def cleanup_old() -> None:
50
+ """Remove correlation files older than TTL."""
51
+ correlation_dir = get_correlation_dir()
52
+ if not correlation_dir.exists():
53
+ return
54
+ now = time.time()
55
+ for filepath in correlation_dir.glob("correlation_*.json"):
56
+ try:
57
+ if now - filepath.stat().st_mtime > TTL_SECONDS:
58
+ filepath.unlink()
59
+ except OSError:
60
+ pass
@@ -9,6 +9,7 @@ import os
9
9
  import re
10
10
  import subprocess
11
11
  import sys
12
+ import uuid
12
13
  from datetime import datetime, timezone
13
14
  from pathlib import Path
14
15
  from typing import Optional
@@ -134,6 +135,9 @@ class EventHandlers:
134
135
  tool_name = event.get("tool_name", "")
135
136
  tool_input = event.get("tool_input", {})
136
137
 
138
+ # Generate unique tool call ID for correlation with post_tool event
139
+ tool_call_id = str(uuid.uuid4())
140
+
137
141
  # Extract key parameters based on tool type
138
142
  tool_params = extract_tool_parameters(tool_name, tool_input)
139
143
 
@@ -144,6 +148,8 @@ class EventHandlers:
144
148
  working_dir = event.get("cwd", "")
145
149
  git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
146
150
 
151
+ timestamp = datetime.now(timezone.utc).isoformat()
152
+
147
153
  pre_tool_data = {
148
154
  "tool_name": tool_name,
149
155
  "operation_type": operation_type,
@@ -151,15 +157,27 @@ class EventHandlers:
151
157
  "session_id": event.get("session_id", ""),
152
158
  "working_directory": working_dir,
153
159
  "git_branch": git_branch,
154
- "timestamp": datetime.now(timezone.utc).isoformat(),
160
+ "timestamp": timestamp,
155
161
  "parameter_count": len(tool_input) if isinstance(tool_input, dict) else 0,
156
162
  "is_file_operation": tool_name
157
163
  in ["Write", "Edit", "MultiEdit", "Read", "LS", "Glob"],
158
164
  "is_execution": tool_name in ["Bash", "NotebookEdit"],
159
165
  "is_delegation": tool_name == "Task",
160
166
  "security_risk": assess_security_risk(tool_name, tool_input),
167
+ "correlation_id": tool_call_id, # Add correlation_id for pre/post correlation
161
168
  }
162
169
 
170
+ # Store tool_call_id using CorrelationManager for cross-process retrieval
171
+ if session_id:
172
+ from .correlation_manager import CorrelationManager
173
+
174
+ CorrelationManager.store(session_id, tool_call_id, tool_name)
175
+ if DEBUG:
176
+ print(
177
+ f" - Generated tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}...",
178
+ file=sys.stderr,
179
+ )
180
+
163
181
  # Add delegation-specific data if this is a Task tool
164
182
  if tool_name == "Task" and isinstance(tool_input, dict):
165
183
  self._handle_task_delegation(tool_input, pre_tool_data, session_id)
@@ -375,6 +393,7 @@ class EventHandlers:
375
393
  """
376
394
  tool_name = event.get("tool_name", "")
377
395
  exit_code = event.get("exit_code", 0)
396
+ session_id = event.get("session_id", "")
378
397
 
379
398
  # Extract result data
380
399
  result_data = extract_tool_results(event)
@@ -386,6 +405,16 @@ class EventHandlers:
386
405
  working_dir = event.get("cwd", "")
387
406
  git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
388
407
 
408
+ # Retrieve tool_call_id using CorrelationManager for cross-process correlation
409
+ from .correlation_manager import CorrelationManager
410
+
411
+ tool_call_id = CorrelationManager.retrieve(session_id) if session_id else None
412
+ if DEBUG and tool_call_id:
413
+ print(
414
+ f" - Retrieved tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}...",
415
+ file=sys.stderr,
416
+ )
417
+
389
418
  post_tool_data = {
390
419
  "tool_name": tool_name,
391
420
  "exit_code": exit_code,
@@ -399,7 +428,7 @@ class EventHandlers:
399
428
  ),
400
429
  "duration_ms": duration,
401
430
  "result_summary": result_data,
402
- "session_id": event.get("session_id", ""),
431
+ "session_id": session_id,
403
432
  "working_directory": working_dir,
404
433
  "git_branch": git_branch,
405
434
  "timestamp": datetime.now(timezone.utc).isoformat(),
@@ -412,6 +441,10 @@ class EventHandlers:
412
441
  ),
413
442
  }
414
443
 
444
+ # Add correlation_id if available for correlation with pre_tool
445
+ if tool_call_id:
446
+ post_tool_data["correlation_id"] = tool_call_id
447
+
415
448
  # Handle Task delegation completion for memory hooks and response tracking
416
449
  if tool_name == "Task":
417
450
  session_id = event.get("session_id", "")
@@ -304,6 +304,10 @@ class ClaudeHookHandler:
304
304
  # Perform periodic cleanup if needed
305
305
  if self.state_manager.increment_events_processed():
306
306
  self.state_manager.cleanup_old_entries()
307
+ # Also cleanup old correlation files
308
+ from .correlation_manager import CorrelationManager
309
+
310
+ CorrelationManager.cleanup_old()
307
311
  if DEBUG:
308
312
  print(
309
313
  f"๐Ÿงน Performed cleanup after {self.state_manager.events_processed} events",
@@ -5,8 +5,19 @@ This module provides utilities for integrating with the memory system,
5
5
  including pre and post delegation hooks.
6
6
  """
7
7
 
8
- import os
8
+ import logging
9
9
  import sys
10
+
11
+ # Reconfigure logging to INFO level BEFORE kuzu-memory imports
12
+ # This overrides kuzu-memory's WARNING-level basicConfig (fixes 1M-445)
13
+ logging.basicConfig(
14
+ level=logging.INFO,
15
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
16
+ force=True, # Python 3.8+ - reconfigures root logger
17
+ stream=sys.stderr,
18
+ )
19
+
20
+ import os
10
21
  from datetime import datetime, timezone
11
22
  from typing import Optional
12
23