claude-mpm 5.6.4__py3-none-any.whl → 5.6.30__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.
Files changed (103) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/PM_INSTRUCTIONS.md +8 -3
  3. claude_mpm/cli/commands/commander.py +174 -4
  4. claude_mpm/cli/commands/skill_source.py +51 -2
  5. claude_mpm/cli/commands/skills.py +5 -3
  6. claude_mpm/cli/parsers/commander_parser.py +43 -10
  7. claude_mpm/cli/parsers/skill_source_parser.py +4 -0
  8. claude_mpm/cli/parsers/skills_parser.py +5 -0
  9. claude_mpm/cli/startup.py +140 -20
  10. claude_mpm/cli/startup_display.py +2 -1
  11. claude_mpm/commander/__init__.py +6 -0
  12. claude_mpm/commander/adapters/__init__.py +32 -3
  13. claude_mpm/commander/adapters/auggie.py +260 -0
  14. claude_mpm/commander/adapters/base.py +98 -1
  15. claude_mpm/commander/adapters/claude_code.py +32 -1
  16. claude_mpm/commander/adapters/codex.py +237 -0
  17. claude_mpm/commander/adapters/example_usage.py +310 -0
  18. claude_mpm/commander/adapters/mpm.py +389 -0
  19. claude_mpm/commander/adapters/registry.py +204 -0
  20. claude_mpm/commander/api/app.py +32 -16
  21. claude_mpm/commander/api/errors.py +21 -0
  22. claude_mpm/commander/api/routes/messages.py +11 -11
  23. claude_mpm/commander/api/routes/projects.py +20 -20
  24. claude_mpm/commander/api/routes/sessions.py +37 -26
  25. claude_mpm/commander/api/routes/work.py +86 -50
  26. claude_mpm/commander/api/schemas.py +4 -0
  27. claude_mpm/commander/chat/cli.py +42 -3
  28. claude_mpm/commander/config.py +5 -3
  29. claude_mpm/commander/core/__init__.py +10 -0
  30. claude_mpm/commander/core/block_manager.py +325 -0
  31. claude_mpm/commander/core/response_manager.py +323 -0
  32. claude_mpm/commander/daemon.py +215 -10
  33. claude_mpm/commander/env_loader.py +59 -0
  34. claude_mpm/commander/frameworks/base.py +4 -1
  35. claude_mpm/commander/instance_manager.py +124 -11
  36. claude_mpm/commander/memory/__init__.py +45 -0
  37. claude_mpm/commander/memory/compression.py +347 -0
  38. claude_mpm/commander/memory/embeddings.py +230 -0
  39. claude_mpm/commander/memory/entities.py +310 -0
  40. claude_mpm/commander/memory/example_usage.py +290 -0
  41. claude_mpm/commander/memory/integration.py +325 -0
  42. claude_mpm/commander/memory/search.py +381 -0
  43. claude_mpm/commander/memory/store.py +657 -0
  44. claude_mpm/commander/registry.py +10 -4
  45. claude_mpm/commander/runtime/monitor.py +32 -2
  46. claude_mpm/commander/work/executor.py +38 -20
  47. claude_mpm/commander/workflow/event_handler.py +25 -3
  48. claude_mpm/config/skill_sources.py +16 -0
  49. claude_mpm/core/claude_runner.py +152 -0
  50. claude_mpm/core/config.py +30 -22
  51. claude_mpm/core/config_constants.py +74 -9
  52. claude_mpm/core/constants.py +56 -12
  53. claude_mpm/core/interactive_session.py +5 -4
  54. claude_mpm/core/logging_utils.py +4 -2
  55. claude_mpm/core/network_config.py +148 -0
  56. claude_mpm/core/oneshot_session.py +7 -6
  57. claude_mpm/core/output_style_manager.py +37 -7
  58. claude_mpm/core/socketio_pool.py +13 -5
  59. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  60. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  61. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  62. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  63. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  64. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  65. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  66. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  67. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +30 -31
  68. claude_mpm/hooks/claude_hooks/event_handlers.py +22 -0
  69. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -6
  70. claude_mpm/hooks/claude_hooks/installer.py +43 -2
  71. claude_mpm/hooks/claude_hooks/memory_integration.py +31 -22
  72. claude_mpm/hooks/claude_hooks/response_tracking.py +40 -59
  73. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  74. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  75. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  76. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  77. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  78. claude_mpm/hooks/claude_hooks/services/connection_manager.py +25 -30
  79. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +24 -28
  80. claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
  81. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +49 -75
  82. claude_mpm/hooks/session_resume_hook.py +22 -18
  83. claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
  84. claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
  85. claude_mpm/scripts/claude-hook-handler.sh +8 -8
  86. claude_mpm/services/agents/agent_selection_service.py +2 -2
  87. claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
  88. claude_mpm/services/command_deployment_service.py +44 -26
  89. claude_mpm/services/pm_skills_deployer.py +3 -2
  90. claude_mpm/services/skills/git_skill_source_manager.py +79 -8
  91. claude_mpm/services/skills/selective_skill_deployer.py +28 -0
  92. claude_mpm/services/skills/skill_discovery_service.py +17 -1
  93. claude_mpm/services/skills_deployer.py +31 -5
  94. claude_mpm/skills/__init__.py +2 -1
  95. claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
  96. claude_mpm/skills/registry.py +295 -90
  97. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/METADATA +5 -3
  98. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/RECORD +103 -71
  99. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/WHEEL +0 -0
  100. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/entry_points.txt +0 -0
  101. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/licenses/LICENSE +0 -0
  102. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  103. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/top_level.txt +0 -0
@@ -10,12 +10,20 @@ This service handles:
10
10
  import json
11
11
  import os
12
12
  import re
13
- import sys
14
13
  from datetime import datetime, timezone
15
14
  from typing import Optional, Tuple
16
15
 
17
- # Debug mode is enabled by default for better visibility into hook processing
18
- DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
16
+ # Try to import _log from hook_handler, fall back to no-op
17
+ try:
18
+ from claude_mpm.hooks.claude_hooks.hook_handler import _log
19
+ except ImportError:
20
+
21
+ def _log(msg: str) -> None:
22
+ pass # Silent fallback
23
+
24
+
25
+ # Debug mode - disabled by default to prevent logging overhead in production
26
+ DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "false").lower() == "true"
19
27
 
20
28
 
21
29
  class SubagentResponseProcessor:
@@ -45,26 +53,21 @@ class SubagentResponseProcessor:
45
53
  # Enhanced debug logging for session correlation
46
54
  session_id = event.get("session_id", "")
47
55
  if DEBUG:
48
- print(
49
- f" - session_id: {session_id[:16] if session_id else 'None'}...",
50
- file=sys.stderr,
51
- )
52
- print(f" - event keys: {list(event.keys())}", file=sys.stderr)
53
- print(
54
- f" - delegation_requests size: {len(self.state_manager.delegation_requests)}",
55
- file=sys.stderr,
56
+ _log(f" - session_id: {session_id[:16] if session_id else 'None'}...")
57
+ _log(f" - event keys: {list(event.keys())}")
58
+ _log(
59
+ f" - delegation_requests size: {len(self.state_manager.delegation_requests)}"
56
60
  )
57
61
  # Show all stored session IDs for comparison
58
62
  all_sessions = list(self.state_manager.delegation_requests.keys())
59
63
  if all_sessions:
60
- print(" - Stored sessions (first 16 chars):", file=sys.stderr)
64
+ _log(" - Stored sessions (first 16 chars):")
61
65
  for sid in all_sessions[:10]: # Show up to 10
62
- print(
63
- f" - {sid[:16]}... (agent: {self.state_manager.delegation_requests[sid].get('agent_type', 'unknown')})",
64
- file=sys.stderr,
66
+ _log(
67
+ f" - {sid[:16]}... (agent: {self.state_manager.delegation_requests[sid].get('agent_type', 'unknown')})"
65
68
  )
66
69
  else:
67
- print(" - No stored sessions in delegation_requests!", file=sys.stderr)
70
+ _log(" - No stored sessions in delegation_requests!")
68
71
 
69
72
  # Get agent type and other basic info
70
73
  agent_type, agent_id, reason, agent_type_inferred = self._extract_basic_info(
@@ -73,9 +76,8 @@ class SubagentResponseProcessor:
73
76
 
74
77
  # Always log SubagentStop events for debugging
75
78
  if DEBUG or agent_type != "unknown":
76
- print(
77
- f"Hook handler: Processing SubagentStop - agent: '{agent_type}', session: '{session_id}', reason: '{reason}'",
78
- file=sys.stderr,
79
+ _log(
80
+ f"Hook handler: Processing SubagentStop - agent: '{agent_type}', session: '{session_id}', reason: '{reason}'"
79
81
  )
80
82
 
81
83
  # Get working directory and git branch
@@ -115,9 +117,8 @@ class SubagentResponseProcessor:
115
117
 
116
118
  # Debug log the processed data
117
119
  if DEBUG:
118
- print(
119
- f"SubagentStop processed data: agent_type='{agent_type}', session_id='{session_id}'",
120
- file=sys.stderr,
120
+ _log(
121
+ f"SubagentStop processed data: agent_type='{agent_type}', session_id='{session_id}'"
121
122
  )
122
123
 
123
124
  # Emit to default namespace (consistent with subagent_start)
@@ -163,10 +164,7 @@ class SubagentResponseProcessor:
163
164
  agent_type = "pm"
164
165
  agent_type_inferred = True
165
166
  if DEBUG:
166
- print(
167
- " - Inferred agent_type='pm' (no explicit type found)",
168
- file=sys.stderr,
169
- )
167
+ _log(" - Inferred agent_type='pm' (no explicit type found)")
170
168
 
171
169
  return agent_type, agent_id, reason, agent_type_inferred
172
170
 
@@ -182,17 +180,15 @@ class SubagentResponseProcessor:
182
180
  if json_match:
183
181
  structured_response = json.loads(json_match.group(1))
184
182
  if DEBUG:
185
- print(
186
- f"Extracted structured response from {agent_type} agent in SubagentStop",
187
- file=sys.stderr,
183
+ _log(
184
+ f"Extracted structured response from {agent_type} agent in SubagentStop"
188
185
  )
189
186
 
190
187
  # Log if MEMORIES field is present
191
188
  if structured_response.get("MEMORIES") and DEBUG:
192
189
  memories_count = len(structured_response["MEMORIES"])
193
- print(
194
- f"Agent {agent_type} returned MEMORIES field with {memories_count} items",
195
- file=sys.stderr,
190
+ _log(
191
+ f"Agent {agent_type} returned MEMORIES field with {memories_count} items"
196
192
  )
197
193
 
198
194
  return structured_response
@@ -214,20 +210,15 @@ class SubagentResponseProcessor:
214
210
  ):
215
211
  """Track the agent response if response tracking is enabled."""
216
212
  if DEBUG:
217
- print(
218
- f" - response_tracking_enabled: {self.response_tracking_manager.response_tracking_enabled}",
219
- file=sys.stderr,
220
- )
221
- print(
222
- f" - response_tracker exists: {self.response_tracking_manager.response_tracker is not None}",
223
- file=sys.stderr,
213
+ _log(
214
+ f" - response_tracking_enabled: {self.response_tracking_manager.response_tracking_enabled}"
224
215
  )
225
- print(
226
- f" - session_id: {session_id[:16] if session_id else 'None'}...",
227
- file=sys.stderr,
216
+ _log(
217
+ f" - response_tracker exists: {self.response_tracking_manager.response_tracker is not None}"
228
218
  )
229
- print(f" - agent_type: {agent_type}", file=sys.stderr)
230
- print(f" - reason: {reason}", file=sys.stderr)
219
+ _log(f" - session_id: {session_id[:16] if session_id else 'None'}...")
220
+ _log(f" - agent_type: {agent_type}")
221
+ _log(f" - reason: {reason}")
231
222
 
232
223
  if (
233
224
  self.response_tracking_manager.response_tracking_enabled
@@ -238,27 +229,16 @@ class SubagentResponseProcessor:
238
229
  request_info = self.state_manager.find_matching_request(session_id)
239
230
 
240
231
  if DEBUG:
241
- print(
242
- f" - request_info present: {bool(request_info)}",
243
- file=sys.stderr,
244
- )
232
+ _log(f" - request_info present: {bool(request_info)}")
245
233
  if request_info:
246
- print(
247
- " - Found request data for response tracking",
248
- file=sys.stderr,
249
- )
250
- print(
251
- f" - stored agent_type: {request_info.get('agent_type')}",
252
- file=sys.stderr,
253
- )
254
- print(
255
- f" - request keys: {list(request_info.get('request', {}).keys())}",
256
- file=sys.stderr,
234
+ _log(" - ✅ Found request data for response tracking")
235
+ _log(f" - stored agent_type: {request_info.get('agent_type')}")
236
+ _log(
237
+ f" - request keys: {list(request_info.get('request', {}).keys())}"
257
238
  )
258
239
  else:
259
- print(
260
- f" - ❌ No request data found for session {session_id[:16]}...",
261
- file=sys.stderr,
240
+ _log(
241
+ f" - ❌ No request data found for session {session_id[:16]}..."
262
242
  )
263
243
 
264
244
  if request_info:
@@ -310,9 +290,8 @@ class SubagentResponseProcessor:
310
290
  # Check for MEMORIES field and process if present
311
291
  if structured_response.get("MEMORIES") and DEBUG:
312
292
  memories = structured_response["MEMORIES"]
313
- print(
314
- f"Found MEMORIES field in {agent_type} response with {len(memories)} items",
315
- file=sys.stderr,
293
+ _log(
294
+ f"Found MEMORIES field in {agent_type} response with {len(memories)} items"
316
295
  )
317
296
  # The memory will be processed by extract_and_update_memory
318
297
  # which is called by the memory hook service
@@ -329,26 +308,21 @@ class SubagentResponseProcessor:
329
308
  )
330
309
 
331
310
  if file_path and DEBUG:
332
- print(
333
- f"✅ Tracked {agent_type} agent response on SubagentStop: {file_path.name}",
334
- file=sys.stderr,
311
+ _log(
312
+ f"✅ Tracked {agent_type} agent response on SubagentStop: {file_path.name}"
335
313
  )
336
314
 
337
315
  # Clean up the request data
338
316
  self.state_manager.remove_request(session_id)
339
317
 
340
318
  elif DEBUG:
341
- print(
342
- f"No request data for SubagentStop session {session_id[:8]}..., agent: {agent_type}",
343
- file=sys.stderr,
319
+ _log(
320
+ f"No request data for SubagentStop session {session_id[:8]}..., agent: {agent_type}"
344
321
  )
345
322
 
346
323
  except Exception as e:
347
324
  if DEBUG:
348
- print(
349
- f"❌ Failed to track response on SubagentStop: {e}",
350
- file=sys.stderr,
351
- )
325
+ _log(f"❌ Failed to track response on SubagentStop: {e}")
352
326
 
353
327
  def _build_subagent_stop_data(
354
328
  self,
@@ -12,13 +12,21 @@ DESIGN DECISIONS:
12
12
  """
13
13
 
14
14
  import json
15
- import sys
16
15
  from pathlib import Path
17
16
  from typing import Any, Dict, Optional
18
17
 
19
18
  from claude_mpm.core.logger import get_logger
20
19
  from claude_mpm.services.cli.session_resume_helper import SessionResumeHelper
21
20
 
21
+ # Try to import _log from hook_handler, fall back to no-op
22
+ try:
23
+ from claude_mpm.hooks.claude_hooks.hook_handler import _log
24
+ except ImportError:
25
+
26
+ def _log(msg: str) -> None:
27
+ pass # Silent fallback
28
+
29
+
22
30
  logger = get_logger(__name__)
23
31
 
24
32
 
@@ -86,23 +94,19 @@ class SessionResumeStartupHook:
86
94
  Args:
87
95
  pause_info: Pause session metadata from check_for_active_pause()
88
96
  """
89
- print("\n" + "=" * 60, file=sys.stderr)
90
- print("⚠️ ACTIVE AUTO-PAUSE SESSION DETECTED", file=sys.stderr)
91
- print("=" * 60, file=sys.stderr)
92
- print(f"Session ID: {pause_info['session_id']}", file=sys.stderr)
93
- print(f"Started at: {pause_info['started_at']}", file=sys.stderr)
94
- print(
95
- f"Context at pause: {pause_info['context_at_start']:.1%}", file=sys.stderr
96
- )
97
- print(f"Actions recorded: {pause_info['action_count']}", file=sys.stderr)
98
- print(
99
- "\nThis session was auto-paused due to high context usage.", file=sys.stderr
100
- )
101
- print("Options:", file=sys.stderr)
102
- print(" 1. Continue (actions will be appended)", file=sys.stderr)
103
- print(" 2. Use /mpm-init pause --finalize to create snapshot", file=sys.stderr)
104
- print(" 3. Use /mpm-init pause --discard to abandon", file=sys.stderr)
105
- print("=" * 60 + "\n", file=sys.stderr)
97
+ _log("=" * 60)
98
+ _log("⚠️ ACTIVE AUTO-PAUSE SESSION DETECTED")
99
+ _log("=" * 60)
100
+ _log(f"Session ID: {pause_info['session_id']}")
101
+ _log(f"Started at: {pause_info['started_at']}")
102
+ _log(f"Context at pause: {pause_info['context_at_start']:.1%}")
103
+ _log(f"Actions recorded: {pause_info['action_count']}")
104
+ _log("\nThis session was auto-paused due to high context usage.")
105
+ _log("Options:")
106
+ _log(" 1. Continue (actions will be appended)")
107
+ _log(" 2. Use /mpm-init pause --finalize to create snapshot")
108
+ _log(" 3. Use /mpm-init pause --discard to abandon")
109
+ _log("=" * 60 + "\n")
106
110
 
107
111
  def on_pm_startup(self) -> Optional[Dict[str, Any]]:
108
112
  """Execute on PM startup to check for paused sessions.
@@ -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
 
@@ -55,6 +55,14 @@ import sys
55
55
  from pathlib import Path
56
56
  from typing import Any, Dict, Optional
57
57
 
58
+ # Try to import _log from hook_handler, fall back to no-op
59
+ try:
60
+ from claude_mpm.hooks.claude_hooks.hook_handler import _log
61
+ except ImportError:
62
+
63
+ def _log(msg: str) -> None:
64
+ pass # Silent fallback
65
+
58
66
 
59
67
  class PreToolUseHook:
60
68
  """Base class for PreToolUse hooks with input modification support."""
@@ -64,9 +72,9 @@ class PreToolUseHook:
64
72
  self.debug = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "false").lower() == "true"
65
73
 
66
74
  def log_debug(self, message: str) -> None:
67
- """Log debug message to stderr."""
75
+ """Log debug message using _log helper."""
68
76
  if self.debug:
69
- print(f"[PreToolUse Hook] {message}", file=sys.stderr)
77
+ _log(f"[PreToolUse Hook] {message}")
70
78
 
71
79
  def read_event(self) -> Optional[Dict[str, Any]]:
72
80
  """Read and parse the hook event from stdin."""
@@ -86,14 +94,14 @@ class PreToolUseHook:
86
94
  self, modified_input: Optional[Dict[str, Any]] = None
87
95
  ) -> None:
88
96
  """Continue execution with optional modified input."""
89
- response = {"action": "continue"}
97
+ response = {"continue": True}
90
98
  if modified_input is not None:
91
99
  response["tool_input"] = modified_input
92
100
  print(json.dumps(response))
93
101
 
94
102
  def block_execution(self, message: str) -> None:
95
103
  """Block execution with a message."""
96
- response = {"action": "block", "message": message}
104
+ response = {"continue": False, "stopReason": message}
97
105
  print(json.dumps(response))
98
106
 
99
107
  def modify_input(
@@ -125,7 +125,7 @@ find_python_command() {
125
125
  # 1. Check for UV project first (uv.lock or pyproject.toml with uv)
126
126
  if [ -f "$CLAUDE_MPM_ROOT/uv.lock" ]; then
127
127
  if command -v uv &> /dev/null; then
128
- echo "uv run python"
128
+ echo "uv run --directory \"$CLAUDE_MPM_ROOT\" python"
129
129
  return
130
130
  fi
131
131
  fi
@@ -219,16 +219,16 @@ log_debug() {
219
219
 
220
220
  # Test Python works and module exists
221
221
  # Handle UV's multi-word command specially
222
- if [[ "$PYTHON_CMD" == "uv run python" ]]; then
223
- if ! uv run python -c "import claude_mpm" 2>/dev/null; then
222
+ if [[ "$PYTHON_CMD" == "uv run"* ]]; then
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
@@ -237,8 +237,8 @@ fi
237
237
  # Use exec to replace the shell process with Python
238
238
  # Handle UV's multi-word command specially
239
239
  # Suppress RuntimeWarning to prevent stderr output (which causes hook errors)
240
- if [[ "$PYTHON_CMD" == "uv run python" ]]; then
241
- exec uv run python -W ignore::RuntimeWarning -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/claude-mpm-hook-error.log
240
+ if [[ "$PYTHON_CMD" == "uv run"* ]]; then
241
+ exec uv run --directory "$CLAUDE_MPM_ROOT" python -W ignore::RuntimeWarning -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/claude-mpm-hook-error.log
242
242
  else
243
243
  exec "$PYTHON_CMD" -W ignore::RuntimeWarning -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/claude-mpm-hook-error.log
244
244
  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
@@ -39,10 +39,10 @@ import logging
39
39
  from pathlib import Path
40
40
  from typing import Any, Dict, List, Optional, Set, Tuple
41
41
 
42
- from src.claude_mpm.services.agents.single_tier_deployment_service import (
42
+ from claude_mpm.services.agents.single_tier_deployment_service import (
43
43
  SingleTierDeploymentService,
44
44
  )
45
- from src.claude_mpm.services.agents.toolchain_detector import ToolchainDetector
45
+ from claude_mpm.services.agents.toolchain_detector import ToolchainDetector
46
46
 
47
47
  logger = logging.getLogger(__name__)
48
48
 
@@ -30,12 +30,12 @@ from datetime import datetime, timezone
30
30
  from pathlib import Path
31
31
  from typing import Any, Dict, List, Optional
32
32
 
33
- from src.claude_mpm.config.agent_sources import AgentSourceConfiguration
34
- from src.claude_mpm.models.git_repository import GitRepository
35
- from src.claude_mpm.services.agents.deployment.remote_agent_discovery_service import (
33
+ from claude_mpm.config.agent_sources import AgentSourceConfiguration
34
+ from claude_mpm.models.git_repository import GitRepository
35
+ from claude_mpm.services.agents.deployment.remote_agent_discovery_service import (
36
36
  RemoteAgentDiscoveryService,
37
37
  )
38
- from src.claude_mpm.services.agents.git_source_manager import GitSourceManager
38
+ from claude_mpm.services.agents.git_source_manager import GitSourceManager
39
39
 
40
40
  logger = logging.getLogger(__name__)
41
41
 
@@ -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.
@@ -389,8 +389,9 @@ class PMSkillsDeployerService(LoggerMixin):
389
389
  if not skill_dir.is_dir() or skill_dir.name.startswith("."):
390
390
  continue
391
391
 
392
- # Only process mpm-* skills (framework management)
393
- if not skill_dir.name.startswith("mpm-"):
392
+ # Only process mpm* skills (framework management)
393
+ # Note: Includes both 'mpm' (core skill) and 'mpm-*' (other PM skills)
394
+ if not skill_dir.name.startswith("mpm"):
394
395
  self.logger.debug(f"Skipping non-mpm skill: {skill_dir.name}")
395
396
  continue
396
397