claude-mpm 5.6.10__py3-none-any.whl → 5.6.33__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 (117) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/commands/commander.py +174 -4
  3. claude_mpm/cli/parsers/commander_parser.py +43 -10
  4. claude_mpm/cli/startup.py +140 -20
  5. claude_mpm/cli/startup_display.py +2 -1
  6. claude_mpm/commander/__init__.py +6 -0
  7. claude_mpm/commander/adapters/__init__.py +32 -3
  8. claude_mpm/commander/adapters/auggie.py +260 -0
  9. claude_mpm/commander/adapters/base.py +98 -1
  10. claude_mpm/commander/adapters/claude_code.py +32 -1
  11. claude_mpm/commander/adapters/codex.py +237 -0
  12. claude_mpm/commander/adapters/example_usage.py +310 -0
  13. claude_mpm/commander/adapters/mpm.py +389 -0
  14. claude_mpm/commander/adapters/registry.py +204 -0
  15. claude_mpm/commander/api/app.py +32 -16
  16. claude_mpm/commander/api/routes/messages.py +11 -11
  17. claude_mpm/commander/api/routes/projects.py +20 -20
  18. claude_mpm/commander/api/routes/sessions.py +19 -21
  19. claude_mpm/commander/api/routes/work.py +86 -50
  20. claude_mpm/commander/api/schemas.py +4 -0
  21. claude_mpm/commander/chat/cli.py +42 -3
  22. claude_mpm/commander/config.py +5 -3
  23. claude_mpm/commander/core/__init__.py +10 -0
  24. claude_mpm/commander/core/block_manager.py +325 -0
  25. claude_mpm/commander/core/response_manager.py +323 -0
  26. claude_mpm/commander/daemon.py +215 -10
  27. claude_mpm/commander/env_loader.py +59 -0
  28. claude_mpm/commander/frameworks/base.py +4 -1
  29. claude_mpm/commander/instance_manager.py +124 -11
  30. claude_mpm/commander/memory/__init__.py +45 -0
  31. claude_mpm/commander/memory/compression.py +347 -0
  32. claude_mpm/commander/memory/embeddings.py +230 -0
  33. claude_mpm/commander/memory/entities.py +310 -0
  34. claude_mpm/commander/memory/example_usage.py +290 -0
  35. claude_mpm/commander/memory/integration.py +325 -0
  36. claude_mpm/commander/memory/search.py +381 -0
  37. claude_mpm/commander/memory/store.py +657 -0
  38. claude_mpm/commander/registry.py +10 -4
  39. claude_mpm/commander/runtime/monitor.py +32 -2
  40. claude_mpm/commander/work/executor.py +38 -20
  41. claude_mpm/commander/workflow/event_handler.py +25 -3
  42. claude_mpm/core/claude_runner.py +152 -0
  43. claude_mpm/core/config.py +3 -3
  44. claude_mpm/core/config_constants.py +74 -9
  45. claude_mpm/core/constants.py +56 -12
  46. claude_mpm/core/interactive_session.py +5 -4
  47. claude_mpm/core/logging_utils.py +4 -2
  48. claude_mpm/core/network_config.py +148 -0
  49. claude_mpm/core/oneshot_session.py +7 -6
  50. claude_mpm/core/output_style_manager.py +37 -7
  51. claude_mpm/core/socketio_pool.py +13 -5
  52. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  53. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  54. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  55. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  56. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  57. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +1 -1
  58. claude_mpm/hooks/claude_hooks/event_handlers.py +284 -89
  59. claude_mpm/hooks/claude_hooks/hook_handler.py +81 -32
  60. claude_mpm/hooks/claude_hooks/installer.py +90 -28
  61. claude_mpm/hooks/claude_hooks/memory_integration.py +1 -1
  62. claude_mpm/hooks/claude_hooks/response_tracking.py +1 -1
  63. claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
  64. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  65. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  66. claude_mpm/hooks/claude_hooks/services/__pycache__/container.cpython-311.pyc +0 -0
  67. claude_mpm/hooks/claude_hooks/services/__pycache__/protocols.cpython-311.pyc +0 -0
  68. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  69. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  70. claude_mpm/hooks/claude_hooks/services/connection_manager.py +2 -2
  71. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +2 -2
  72. claude_mpm/hooks/claude_hooks/services/container.py +310 -0
  73. claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
  74. claude_mpm/hooks/claude_hooks/services/state_manager.py +2 -2
  75. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +2 -2
  76. claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
  77. claude_mpm/hooks/templates/pre_tool_use_template.py +6 -6
  78. claude_mpm/scripts/claude-hook-handler.sh +3 -3
  79. claude_mpm/services/command_deployment_service.py +44 -26
  80. claude_mpm/services/hook_installer_service.py +77 -8
  81. claude_mpm/services/pm_skills_deployer.py +3 -2
  82. claude_mpm/skills/__init__.py +2 -1
  83. claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
  84. claude_mpm/skills/registry.py +295 -90
  85. {claude_mpm-5.6.10.dist-info → claude_mpm-5.6.33.dist-info}/METADATA +5 -3
  86. {claude_mpm-5.6.10.dist-info → claude_mpm-5.6.33.dist-info}/RECORD +91 -94
  87. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-312.pyc +0 -0
  88. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  89. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-312.pyc +0 -0
  90. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-314.pyc +0 -0
  91. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-312.pyc +0 -0
  92. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-314.pyc +0 -0
  93. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-312.pyc +0 -0
  94. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-314.pyc +0 -0
  95. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  96. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-314.pyc +0 -0
  97. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-312.pyc +0 -0
  98. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-314.pyc +0 -0
  99. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-312.pyc +0 -0
  100. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-314.pyc +0 -0
  101. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-312.pyc +0 -0
  102. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-314.pyc +0 -0
  103. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-312.pyc +0 -0
  104. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-314.pyc +0 -0
  105. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-312.pyc +0 -0
  106. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-314.pyc +0 -0
  107. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-312.pyc +0 -0
  108. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-314.pyc +0 -0
  109. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-312.pyc +0 -0
  110. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-314.pyc +0 -0
  111. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-312.pyc +0 -0
  112. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-314.pyc +0 -0
  113. {claude_mpm-5.6.10.dist-info → claude_mpm-5.6.33.dist-info}/WHEEL +0 -0
  114. {claude_mpm-5.6.10.dist-info → claude_mpm-5.6.33.dist-info}/entry_points.txt +0 -0
  115. {claude_mpm-5.6.10.dist-info → claude_mpm-5.6.33.dist-info}/licenses/LICENSE +0 -0
  116. {claude_mpm-5.6.10.dist-info → claude_mpm-5.6.33.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  117. {claude_mpm-5.6.10.dist-info → claude_mpm-5.6.33.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,328 @@
1
+ """Protocol definitions for hook handler services.
2
+
3
+ This module defines Protocol classes for all hook handler services,
4
+ enabling type-safe dependency injection and easy testing through
5
+ protocol-based duck typing.
6
+
7
+ WHY Protocol-based DI:
8
+ - Enables loose coupling between components
9
+ - Allows easy mocking in tests without monkey-patching
10
+ - Provides clear interface contracts
11
+ - Supports static type checking with mypy
12
+ """
13
+
14
+ from typing import Any, Callable, Optional, Protocol, runtime_checkable
15
+
16
+
17
+ @runtime_checkable
18
+ class IStateManager(Protocol):
19
+ """Protocol for state management service.
20
+
21
+ Manages:
22
+ - Agent delegation tracking
23
+ - Git branch caching
24
+ - Session state management
25
+ - Cleanup of old entries
26
+ """
27
+
28
+ active_delegations: dict
29
+ delegation_history: Any # deque
30
+ delegation_requests: dict
31
+ pending_prompts: dict
32
+ events_processed: int
33
+
34
+ def track_delegation(
35
+ self, session_id: str, agent_type: str, request_data: Optional[dict] = None
36
+ ) -> None:
37
+ """Track a new agent delegation with optional request data."""
38
+ ...
39
+
40
+ def get_delegation_agent_type(self, session_id: str) -> str:
41
+ """Get the agent type for a session's active delegation."""
42
+ ...
43
+
44
+ def cleanup_old_entries(self) -> None:
45
+ """Clean up old entries to prevent memory growth."""
46
+ ...
47
+
48
+ def get_git_branch(self, working_dir: Optional[str] = None) -> str:
49
+ """Get git branch for the given directory with caching."""
50
+ ...
51
+
52
+ def find_matching_request(self, session_id: str) -> Optional[dict]:
53
+ """Find matching request data for a session, with fuzzy matching fallback."""
54
+ ...
55
+
56
+ def remove_request(self, session_id: str) -> None:
57
+ """Remove request data for a session."""
58
+ ...
59
+
60
+ def increment_events_processed(self) -> bool:
61
+ """Increment events processed counter and return True if cleanup is needed."""
62
+ ...
63
+
64
+
65
+ @runtime_checkable
66
+ class IConnectionManager(Protocol):
67
+ """Protocol for connection management service.
68
+
69
+ Handles:
70
+ - HTTP-based event emission to dashboard
71
+ - Event queuing and batching
72
+ - Connection state management
73
+ """
74
+
75
+ def emit_event(self, namespace: str, event: str, data: dict) -> None:
76
+ """Emit an event through HTTP to the dashboard."""
77
+ ...
78
+
79
+ def cleanup(self) -> None:
80
+ """Clean up any resources."""
81
+ ...
82
+
83
+
84
+ @runtime_checkable
85
+ class IDuplicateDetector(Protocol):
86
+ """Protocol for duplicate event detection service.
87
+
88
+ Detects:
89
+ - Duplicate events within a time window
90
+ - Rapid-fire events that should be deduplicated
91
+ """
92
+
93
+ def is_duplicate(self, event: dict) -> bool:
94
+ """Check if an event is a duplicate of a recent event."""
95
+ ...
96
+
97
+ def generate_event_key(self, event: dict) -> str:
98
+ """Generate a unique key for an event for deduplication."""
99
+ ...
100
+
101
+
102
+ @runtime_checkable
103
+ class IResponseTrackingManager(Protocol):
104
+ """Protocol for response tracking management.
105
+
106
+ Manages:
107
+ - Response tracking enablement
108
+ - Comprehensive response logging
109
+ - Agent response correlation
110
+ """
111
+
112
+ response_tracking_enabled: bool
113
+ response_tracker: Optional[Any]
114
+ track_all_interactions: bool
115
+ auto_pause_handler: Optional[Any]
116
+
117
+ def track_agent_response(
118
+ self,
119
+ session_id: str,
120
+ agent_type: str,
121
+ event: dict,
122
+ delegation_requests: dict,
123
+ ) -> None:
124
+ """Track an agent response for logging."""
125
+ ...
126
+
127
+ def track_stop_response(
128
+ self,
129
+ event: dict,
130
+ session_id: str,
131
+ metadata: dict,
132
+ pending_prompts: dict,
133
+ ) -> None:
134
+ """Track a stop event response."""
135
+ ...
136
+
137
+ def track_assistant_response(
138
+ self,
139
+ event: dict,
140
+ pending_prompts: dict,
141
+ ) -> None:
142
+ """Track an assistant response."""
143
+ ...
144
+
145
+
146
+ @runtime_checkable
147
+ class IMemoryHookManager(Protocol):
148
+ """Protocol for memory hook management.
149
+
150
+ Manages:
151
+ - Pre/post delegation memory hooks
152
+ - Memory field processing
153
+ """
154
+
155
+ def trigger_pre_delegation_hook(
156
+ self, agent_type: str, tool_input: dict, session_id: str
157
+ ) -> None:
158
+ """Trigger memory hooks before delegation."""
159
+ ...
160
+
161
+ def trigger_post_delegation_hook(
162
+ self, agent_type: str, event: dict, session_id: str
163
+ ) -> None:
164
+ """Trigger memory hooks after delegation."""
165
+ ...
166
+
167
+
168
+ @runtime_checkable
169
+ class ISubagentProcessor(Protocol):
170
+ """Protocol for subagent response processing.
171
+
172
+ Handles:
173
+ - SubagentStop event processing
174
+ - Structured response extraction
175
+ - Response tracking and correlation
176
+ """
177
+
178
+ def process_subagent_stop(self, event: dict) -> None:
179
+ """Handle subagent stop events."""
180
+ ...
181
+
182
+
183
+ @runtime_checkable
184
+ class IAutoPauseHandler(Protocol):
185
+ """Protocol for auto-pause functionality.
186
+
187
+ Manages:
188
+ - Session pause tracking
189
+ - Usage threshold monitoring
190
+ - Session state capture
191
+ """
192
+
193
+ def is_pause_active(self) -> bool:
194
+ """Check if auto-pause is currently active."""
195
+ ...
196
+
197
+ def on_user_message(self, message: str) -> None:
198
+ """Record a user message for auto-pause tracking."""
199
+ ...
200
+
201
+ def on_tool_call(self, tool_name: str, tool_input: dict) -> None:
202
+ """Record a tool call for auto-pause tracking."""
203
+ ...
204
+
205
+ def on_assistant_response(self, response: str) -> None:
206
+ """Record an assistant response for auto-pause tracking."""
207
+ ...
208
+
209
+ def on_usage_update(self, usage: dict) -> Optional[str]:
210
+ """Update usage metrics and return threshold crossed if any."""
211
+ ...
212
+
213
+ def emit_threshold_warning(self, threshold: str) -> str:
214
+ """Emit a warning for a crossed threshold."""
215
+ ...
216
+
217
+ def on_session_end(self) -> Optional[Any]:
218
+ """Finalize the current auto-pause session."""
219
+ ...
220
+
221
+
222
+ @runtime_checkable
223
+ class IEventHandlers(Protocol):
224
+ """Protocol for event handler collection.
225
+
226
+ Provides handlers for:
227
+ - UserPromptSubmit
228
+ - PreToolUse
229
+ - PostToolUse
230
+ - Notification
231
+ - Stop
232
+ - SubagentStop
233
+ - SubagentStart
234
+ - SessionStart
235
+ - AssistantResponse
236
+ """
237
+
238
+ def handle_user_prompt_fast(self, event: dict) -> None:
239
+ """Handle user prompt with comprehensive data capture."""
240
+ ...
241
+
242
+ def handle_pre_tool_fast(self, event: dict) -> Optional[dict]:
243
+ """Handle pre-tool use with comprehensive data capture."""
244
+ ...
245
+
246
+ def handle_post_tool_fast(self, event: dict) -> None:
247
+ """Handle post-tool use with comprehensive data capture."""
248
+ ...
249
+
250
+ def handle_notification_fast(self, event: dict) -> None:
251
+ """Handle notification events from Claude."""
252
+ ...
253
+
254
+ def handle_stop_fast(self, event: dict) -> None:
255
+ """Handle stop events when Claude processing stops."""
256
+ ...
257
+
258
+ def handle_subagent_stop_fast(self, event: dict) -> None:
259
+ """Handle subagent stop events."""
260
+ ...
261
+
262
+ def handle_subagent_start_fast(self, event: dict) -> None:
263
+ """Handle SubagentStart events."""
264
+ ...
265
+
266
+ def handle_session_start_fast(self, event: dict) -> None:
267
+ """Handle session start events."""
268
+ ...
269
+
270
+ def handle_assistant_response(self, event: dict) -> None:
271
+ """Handle assistant response events."""
272
+ ...
273
+
274
+
275
+ # Type alias for log function
276
+ LogFunction = Callable[[str], None]
277
+
278
+
279
+ @runtime_checkable
280
+ class ILogManager(Protocol):
281
+ """Protocol for log manager service (optional dependency).
282
+
283
+ Used for logging agent prompts and responses.
284
+ """
285
+
286
+ async def log_prompt(
287
+ self, source: str, content: str, metadata: dict
288
+ ) -> Optional[Any]:
289
+ """Log a prompt to the log manager."""
290
+ ...
291
+
292
+
293
+ @runtime_checkable
294
+ class IDelegationDetector(Protocol):
295
+ """Protocol for delegation pattern detector (optional dependency).
296
+
297
+ Used for detecting delegation anti-patterns in responses.
298
+ """
299
+
300
+ def detect_user_delegation(self, text: str) -> list[dict]:
301
+ """Detect delegation patterns in text."""
302
+ ...
303
+
304
+
305
+ @runtime_checkable
306
+ class IEventLog(Protocol):
307
+ """Protocol for event log service (optional dependency).
308
+
309
+ Used for logging PM violations and other events.
310
+ """
311
+
312
+ def append_event(
313
+ self,
314
+ event_type: str,
315
+ payload: dict,
316
+ status: str = "pending",
317
+ ) -> None:
318
+ """Append an event to the log."""
319
+ ...
320
+
321
+
322
+ @runtime_checkable
323
+ class IConfig(Protocol):
324
+ """Protocol for configuration service (optional dependency)."""
325
+
326
+ def get(self, key: str, default: Any = None) -> Any:
327
+ """Get a configuration value."""
328
+ ...
@@ -33,8 +33,8 @@ except ImportError:
33
33
  QUICK_TIMEOUT = 2.0
34
34
 
35
35
 
36
- # Debug mode is enabled by default for better visibility into hook processing
37
- DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
36
+ # Debug mode - disabled by default to prevent logging overhead in production
37
+ DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "false").lower() == "true"
38
38
 
39
39
 
40
40
  class StateManagerService:
@@ -22,8 +22,8 @@ except ImportError:
22
22
  pass # Silent fallback
23
23
 
24
24
 
25
- # Debug mode is enabled by default for better visibility into hook processing
26
- DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
25
+ # Debug mode - disabled by default to prevent logging overhead in production
26
+ DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "false").lower() == "true"
27
27
 
28
28
 
29
29
  class SubagentResponseProcessor:
@@ -38,7 +38,7 @@ def main():
38
38
  # Read event from stdin
39
39
  event_data = sys.stdin.read()
40
40
  if not event_data.strip():
41
- print(json.dumps({"action": "continue"}))
41
+ print(json.dumps({"continue": True}))
42
42
  return
43
43
 
44
44
  event = json.loads(event_data)
@@ -49,7 +49,7 @@ def main():
49
49
  if tool_name == "Grep" and "-n" not in tool_input:
50
50
  modified_input = tool_input.copy()
51
51
  modified_input["-n"] = True
52
- print(json.dumps({"action": "continue", "tool_input": modified_input}))
52
+ print(json.dumps({"continue": True, "tool_input": modified_input}))
53
53
  return
54
54
 
55
55
  # Example: Block operations on .env files
@@ -59,19 +59,19 @@ def main():
59
59
  print(
60
60
  json.dumps(
61
61
  {
62
- "action": "block",
63
- "message": "Access to .env file blocked for security",
62
+ "continue": False,
63
+ "stopReason": "Access to .env file blocked for security",
64
64
  }
65
65
  )
66
66
  )
67
67
  return
68
68
 
69
69
  # Default: continue without modification
70
- print(json.dumps({"action": "continue"}))
70
+ print(json.dumps({"continue": True}))
71
71
 
72
72
  except Exception:
73
73
  # Always continue on error to avoid blocking Claude
74
- print(json.dumps({"action": "continue"}))
74
+ print(json.dumps({"continue": True}))
75
75
 
76
76
 
77
77
  if __name__ == "__main__":
@@ -29,7 +29,7 @@ Input Format (stdin):
29
29
 
30
30
  Output Format (stdout):
31
31
  {
32
- "action": "continue",
32
+ "continue": true,
33
33
  "tool_input": {
34
34
  "file_path": "/path/to/file.py",
35
35
  "old_string": "foo",
@@ -39,13 +39,13 @@ Output Format (stdout):
39
39
 
40
40
  Or to block execution:
41
41
  {
42
- "action": "block",
43
- "message": "Reason for blocking"
42
+ "continue": false,
43
+ "stopReason": "Reason for blocking"
44
44
  }
45
45
 
46
46
  Or to continue without modification:
47
47
  {
48
- "action": "continue"
48
+ "continue": true
49
49
  }
50
50
  """
51
51
 
@@ -94,14 +94,14 @@ class PreToolUseHook:
94
94
  self, modified_input: Optional[Dict[str, Any]] = None
95
95
  ) -> None:
96
96
  """Continue execution with optional modified input."""
97
- response = {"action": "continue"}
97
+ response = {"continue": True}
98
98
  if modified_input is not None:
99
99
  response["tool_input"] = modified_input
100
100
  print(json.dumps(response))
101
101
 
102
102
  def block_execution(self, message: str) -> None:
103
103
  """Block execution with a message."""
104
- response = {"action": "block", "message": message}
104
+ response = {"continue": False, "stopReason": message}
105
105
  print(json.dumps(response))
106
106
 
107
107
  def modify_input(
@@ -222,13 +222,13 @@ log_debug() {
222
222
  if [[ "$PYTHON_CMD" == "uv run"* ]]; then
223
223
  if ! uv run --directory "$CLAUDE_MPM_ROOT" python -c "import claude_mpm" 2>/dev/null; then
224
224
  log_debug "claude_mpm module not available, continuing without hook"
225
- echo '{"action": "continue"}'
225
+ echo '{"continue": true}'
226
226
  exit 0
227
227
  fi
228
228
  else
229
229
  if ! $PYTHON_CMD -c "import claude_mpm" 2>/dev/null; then
230
230
  log_debug "claude_mpm module not available, continuing without hook"
231
- echo '{"action": "continue"}'
231
+ echo '{"continue": true}'
232
232
  exit 0
233
233
  fi
234
234
  fi
@@ -250,5 +250,5 @@ if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
250
250
  echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Error: $(cat /tmp/claude-mpm-hook-error.log 2>/dev/null | head -5)" >> /tmp/claude-mpm-hook.log
251
251
  fi
252
252
  # Return continue action to prevent blocking Claude Code
253
- echo '{"action": "continue"}'
253
+ echo '{"continue": true}'
254
254
  exit 0
@@ -1,10 +1,14 @@
1
- """Service for deploying MPM slash commands to user's Claude configuration.
1
+ """Service for managing MPM slash commands in user's Claude configuration.
2
2
 
3
- This service handles:
4
- 1. Copying command markdown files from source to user's ~/.claude/commands directory
5
- 2. Creating the commands directory if it doesn't exist
6
- 3. Overwriting existing commands to ensure they're up-to-date
7
- 4. Parsing and validating YAML frontmatter for namespace metadata (Phase 1 - 1M-400)
3
+ DEPRECATED: User-level commands in ~/.claude/commands/ are deprecated.
4
+ Project-level skills (.claude/skills/) are now the only source for commands.
5
+
6
+ This service now only handles:
7
+ 1. Cleanup of deprecated commands from previous versions
8
+ 2. Cleanup of stale commands that no longer exist in source
9
+ 3. Parsing and validating YAML frontmatter (for internal use)
10
+
11
+ New command deployment is intentionally disabled - see deploy_commands_on_startup().
8
12
  """
9
13
 
10
14
  from pathlib import Path
@@ -17,20 +21,34 @@ from claude_mpm.core.logger import get_logger
17
21
 
18
22
 
19
23
  class CommandDeploymentService(BaseService):
20
- """Service for deploying MPM slash commands."""
24
+ """Service for managing MPM slash commands (cleanup only - deployment deprecated)."""
21
25
 
22
- # Deprecated commands that have been replaced (cleanup on startup)
26
+ # Deprecated commands that should be removed from ~/.claude/commands/
27
+ # ALL user-level commands are now deprecated - project-level skills are the only source
23
28
  DEPRECATED_COMMANDS = [
29
+ # Legacy deprecated commands (historical)
24
30
  "mpm-agents.md", # Replaced by mpm-agents-list.md
25
31
  "mpm-auto-configure.md", # Replaced by mpm-agents-auto-configure.md
26
32
  "mpm-config-view.md", # Replaced by mpm-config.md
27
33
  "mpm-resume.md", # Replaced by mpm-session-resume.md
28
34
  "mpm-ticket.md", # Replaced by mpm-ticket-view.md
29
- # Removed - consolidated into /mpm-configure
30
35
  "mpm-agents-list.md", # Consolidated into /mpm-configure
31
36
  "mpm-agents-detect.md", # Consolidated into /mpm-configure
32
37
  "mpm-agents-auto-configure.md", # Consolidated into /mpm-configure
33
38
  "mpm-agents-recommend.md", # Consolidated into /mpm-configure
39
+ # ALL user-level commands are now deprecated (use project-level skills)
40
+ "mpm.md",
41
+ "mpm-config.md",
42
+ "mpm-doctor.md",
43
+ "mpm-help.md",
44
+ "mpm-init.md",
45
+ "mpm-monitor.md",
46
+ "mpm-organize.md",
47
+ "mpm-postmortem.md",
48
+ "mpm-session-resume.md",
49
+ "mpm-status.md",
50
+ "mpm-ticket-view.md",
51
+ "mpm-version.md",
34
52
  ]
35
53
 
36
54
  def __init__(self):
@@ -414,33 +432,33 @@ class CommandDeploymentService(BaseService):
414
432
  def deploy_commands_on_startup(force: bool = False) -> None:
415
433
  """Convenience function to deploy commands during startup.
416
434
 
417
- This function:
418
- 1. Removes deprecated commands that have been replaced
419
- 2. Removes stale commands that no longer exist in source
420
- 3. Deploys current command files
435
+ DEPRECATED: User-level commands in ~/.claude/commands/ are deprecated.
436
+ Project-level skills should be the only source for commands.
437
+
438
+ This function now only cleans up any existing deprecated/stale commands
439
+ without deploying new ones.
421
440
 
422
441
  Args:
423
- force: Force deployment even if files exist
442
+ force: Force deployment even if files exist (ignored - deployment disabled)
424
443
  """
425
- service = CommandDeploymentService()
426
444
  logger = get_logger("startup")
445
+ logger.debug(
446
+ "User-level command deployment is deprecated - "
447
+ "project-level skills are the only command source"
448
+ )
427
449
 
428
- # Clean up deprecated commands FIRST (known old commands)
450
+ # Still clean up any lingering deprecated/stale commands from previous versions
451
+ service = CommandDeploymentService()
452
+
453
+ # Clean up deprecated commands
429
454
  deprecated_count = service.remove_deprecated_commands()
430
455
  if deprecated_count > 0:
431
456
  logger.info(f"Cleaned up {deprecated_count} deprecated command(s)")
432
457
 
433
- # Clean up stale commands SECOND (deployed but not in source anymore)
458
+ # Clean up stale commands
434
459
  stale_count = service.remove_stale_commands()
435
460
  if stale_count > 0:
436
461
  logger.info(f"Cleaned up {stale_count} stale command(s)")
437
462
 
438
- # Deploy current commands LAST
439
- result = service.deploy_commands(force=force)
440
-
441
- if result["deployed"]:
442
- logger.info(f"MPM commands deployed: {', '.join(result['deployed'])}")
443
-
444
- if result["errors"]:
445
- for error in result["errors"]:
446
- logger.warning(f"Command deployment issue: {error}")
463
+ # NOTE: Deployment of new commands is intentionally disabled.
464
+ # Project-level skills (.claude/skills/) are the only source for commands.
@@ -20,8 +20,13 @@ class HookInstallerService:
20
20
  def __init__(self):
21
21
  """Initialize the hook installer service."""
22
22
  self.logger = get_logger(__name__)
23
- self.claude_dir = Path.home() / ".claude"
24
- self.settings_file = self.claude_dir / "settings.json"
23
+ # Use project-level paths, NEVER global ~/.claude/settings.json
24
+ # This ensures hooks are scoped to the current project only
25
+ self.project_root = Path.cwd()
26
+ self.claude_dir = self.project_root / ".claude"
27
+ # Use settings.local.json for project-level hook settings
28
+ # Claude Code reads project-level settings from .claude/settings.local.json
29
+ self.settings_file = self.claude_dir / "settings.local.json"
25
30
 
26
31
  def is_hooks_configured(self) -> bool:
27
32
  """Check if hooks are configured in Claude settings.
@@ -299,16 +304,77 @@ class HookInstallerService:
299
304
  self.logger.debug("Creating new Claude settings")
300
305
 
301
306
  # Configure hooks
302
- hook_config = {
303
- "matcher": "*",
304
- "hooks": [{"type": "command", "command": hook_script_path}],
305
- }
307
+ new_hook_command = {"type": "command", "command": hook_script_path}
306
308
 
307
309
  # Update settings
308
310
  if "hooks" not in settings:
309
311
  settings["hooks"] = {}
310
312
 
311
- # Add hooks for all event types
313
+ def is_our_hook(cmd: Dict[str, Any]) -> bool:
314
+ """Check if a hook command belongs to claude-mpm."""
315
+ if cmd.get("type") != "command":
316
+ return False
317
+ command = cmd.get("command", "")
318
+ return (
319
+ "hook_wrapper.sh" in command
320
+ or "claude-hook-handler.sh" in command
321
+ or "claude-mpm" in command
322
+ )
323
+
324
+ def merge_hooks_for_event(
325
+ existing_hooks: list, hook_command: Dict[str, Any]
326
+ ) -> list:
327
+ """Merge new hook command into existing hooks without duplication.
328
+
329
+ Args:
330
+ existing_hooks: Current hooks configuration for an event type
331
+ hook_command: The claude-mpm hook command to add
332
+
333
+ Returns:
334
+ Updated hooks list with our hook merged in
335
+ """
336
+ # Check if our hook already exists in any existing hook config
337
+ our_hook_exists = False
338
+
339
+ for hook_config in existing_hooks:
340
+ if "hooks" in hook_config and isinstance(
341
+ hook_config["hooks"], list
342
+ ):
343
+ for hook in hook_config["hooks"]:
344
+ if is_our_hook(hook):
345
+ # Update existing hook command path (in case it changed)
346
+ hook["command"] = hook_command["command"]
347
+ our_hook_exists = True
348
+ break
349
+ if our_hook_exists:
350
+ break
351
+
352
+ if our_hook_exists:
353
+ # Our hook already exists, just return the updated list
354
+ return existing_hooks
355
+
356
+ # Our hook doesn't exist - need to add it
357
+ # Strategy: Add our hook to the first "*" matcher config, or create new
358
+ added = False
359
+
360
+ for hook_config in existing_hooks:
361
+ # Check if this config has matcher: "*"
362
+ if hook_config.get("matcher") == "*":
363
+ # Add our hook to this config's hooks array
364
+ if "hooks" not in hook_config:
365
+ hook_config["hooks"] = []
366
+ hook_config["hooks"].append(hook_command)
367
+ added = True
368
+ break
369
+
370
+ if not added:
371
+ # No suitable config found, create a new one
372
+ new_config = {"matcher": "*", "hooks": [hook_command]}
373
+ existing_hooks.append(new_config)
374
+
375
+ return existing_hooks
376
+
377
+ # Add hooks for all event types - MERGE instead of overwrite
312
378
  for event_type in [
313
379
  "UserPromptSubmit",
314
380
  "PreToolUse",
@@ -316,7 +382,10 @@ class HookInstallerService:
316
382
  "Stop",
317
383
  "SubagentStop",
318
384
  ]:
319
- settings["hooks"][event_type] = [hook_config]
385
+ existing = settings["hooks"].get(event_type, [])
386
+ settings["hooks"][event_type] = merge_hooks_for_event(
387
+ existing, new_hook_command
388
+ )
320
389
 
321
390
  # Write settings
322
391
  with self.settings_file.open("w") as f: