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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +8 -3
- claude_mpm/cli/commands/commander.py +174 -4
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +5 -3
- claude_mpm/cli/parsers/commander_parser.py +43 -10
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +5 -0
- claude_mpm/cli/startup.py +140 -20
- claude_mpm/cli/startup_display.py +2 -1
- claude_mpm/commander/__init__.py +6 -0
- claude_mpm/commander/adapters/__init__.py +32 -3
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +98 -1
- claude_mpm/commander/adapters/claude_code.py +32 -1
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/example_usage.py +310 -0
- claude_mpm/commander/adapters/mpm.py +389 -0
- claude_mpm/commander/adapters/registry.py +204 -0
- claude_mpm/commander/api/app.py +32 -16
- claude_mpm/commander/api/errors.py +21 -0
- claude_mpm/commander/api/routes/messages.py +11 -11
- claude_mpm/commander/api/routes/projects.py +20 -20
- claude_mpm/commander/api/routes/sessions.py +37 -26
- claude_mpm/commander/api/routes/work.py +86 -50
- claude_mpm/commander/api/schemas.py +4 -0
- claude_mpm/commander/chat/cli.py +42 -3
- claude_mpm/commander/config.py +5 -3
- claude_mpm/commander/core/__init__.py +10 -0
- claude_mpm/commander/core/block_manager.py +325 -0
- claude_mpm/commander/core/response_manager.py +323 -0
- claude_mpm/commander/daemon.py +215 -10
- claude_mpm/commander/env_loader.py +59 -0
- claude_mpm/commander/frameworks/base.py +4 -1
- claude_mpm/commander/instance_manager.py +124 -11
- claude_mpm/commander/memory/__init__.py +45 -0
- claude_mpm/commander/memory/compression.py +347 -0
- claude_mpm/commander/memory/embeddings.py +230 -0
- claude_mpm/commander/memory/entities.py +310 -0
- claude_mpm/commander/memory/example_usage.py +290 -0
- claude_mpm/commander/memory/integration.py +325 -0
- claude_mpm/commander/memory/search.py +381 -0
- claude_mpm/commander/memory/store.py +657 -0
- claude_mpm/commander/registry.py +10 -4
- claude_mpm/commander/runtime/monitor.py +32 -2
- claude_mpm/commander/work/executor.py +38 -20
- claude_mpm/commander/workflow/event_handler.py +25 -3
- claude_mpm/config/skill_sources.py +16 -0
- claude_mpm/core/claude_runner.py +152 -0
- claude_mpm/core/config.py +30 -22
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/interactive_session.py +5 -4
- claude_mpm/core/logging_utils.py +4 -2
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/output_style_manager.py +37 -7
- claude_mpm/core/socketio_pool.py +13 -5
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +30 -31
- claude_mpm/hooks/claude_hooks/event_handlers.py +22 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +6 -6
- claude_mpm/hooks/claude_hooks/installer.py +43 -2
- claude_mpm/hooks/claude_hooks/memory_integration.py +31 -22
- claude_mpm/hooks/claude_hooks/response_tracking.py +40 -59
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +25 -30
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +24 -28
- claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +49 -75
- claude_mpm/hooks/session_resume_hook.py +22 -18
- claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
- claude_mpm/scripts/claude-hook-handler.sh +8 -8
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
- claude_mpm/services/command_deployment_service.py +44 -26
- claude_mpm/services/pm_skills_deployer.py +3 -2
- claude_mpm/services/skills/git_skill_source_manager.py +79 -8
- claude_mpm/services/skills/selective_skill_deployer.py +28 -0
- claude_mpm/services/skills/skill_discovery_service.py +17 -1
- claude_mpm/services/skills_deployer.py +31 -5
- claude_mpm/skills/__init__.py +2 -1
- claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
- claude_mpm/skills/registry.py +295 -90
- {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/METADATA +5 -3
- {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/RECORD +103 -71
- {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/WHEEL +0 -0
- {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {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
|
-
#
|
|
18
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
64
|
+
_log(" - Stored sessions (first 16 chars):")
|
|
61
65
|
for sid in all_sessions[:10]: # Show up to 10
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
f" -
|
|
227
|
-
file=sys.stderr,
|
|
216
|
+
_log(
|
|
217
|
+
f" - response_tracker exists: {self.response_tracking_manager.response_tracker is not None}"
|
|
228
218
|
)
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
)
|
|
101
|
-
|
|
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({"
|
|
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({"
|
|
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
|
-
"
|
|
63
|
-
"
|
|
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({"
|
|
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({"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
43
|
-
"
|
|
42
|
+
"continue": false,
|
|
43
|
+
"stopReason": "Reason for blocking"
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
Or to continue without modification:
|
|
47
47
|
{
|
|
48
|
-
"
|
|
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
|
|
75
|
+
"""Log debug message using _log helper."""
|
|
68
76
|
if self.debug:
|
|
69
|
-
|
|
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 = {"
|
|
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 = {"
|
|
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
|
|
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 '{"
|
|
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 '{"
|
|
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
|
|
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 '{"
|
|
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
|
|
42
|
+
from claude_mpm.services.agents.single_tier_deployment_service import (
|
|
43
43
|
SingleTierDeploymentService,
|
|
44
44
|
)
|
|
45
|
-
from
|
|
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
|
|
34
|
-
from
|
|
35
|
-
from
|
|
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
|
|
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
|
|
1
|
+
"""Service for managing MPM slash commands in user's Claude configuration.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
24
|
+
"""Service for managing MPM slash commands (cleanup only - deployment deprecated)."""
|
|
21
25
|
|
|
22
|
-
# Deprecated commands that
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
#
|
|
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
|
|
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
|
-
#
|
|
439
|
-
|
|
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
|
|
393
|
-
|
|
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
|
|