claude-mpm 5.5.0__py3-none-any.whl → 5.6.2__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/cli/commands/commander.py +46 -0
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/executor.py +9 -0
- claude_mpm/cli/parsers/base_parser.py +17 -0
- claude_mpm/cli/parsers/commander_parser.py +83 -0
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +72 -0
- claude_mpm/commander/adapters/__init__.py +31 -0
- claude_mpm/commander/adapters/base.py +191 -0
- claude_mpm/commander/adapters/claude_code.py +361 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +105 -0
- claude_mpm/commander/api/errors.py +112 -0
- claude_mpm/commander/api/routes/__init__.py +8 -0
- claude_mpm/commander/api/routes/events.py +184 -0
- claude_mpm/commander/api/routes/inbox.py +171 -0
- claude_mpm/commander/api/routes/messages.py +148 -0
- claude_mpm/commander/api/routes/projects.py +271 -0
- claude_mpm/commander/api/routes/sessions.py +215 -0
- claude_mpm/commander/api/routes/work.py +260 -0
- claude_mpm/commander/api/schemas.py +182 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +107 -0
- claude_mpm/commander/chat/commands.py +96 -0
- claude_mpm/commander/chat/repl.py +310 -0
- claude_mpm/commander/config.py +49 -0
- claude_mpm/commander/config_loader.py +115 -0
- claude_mpm/commander/daemon.py +398 -0
- claude_mpm/commander/events/__init__.py +26 -0
- claude_mpm/commander/events/manager.py +332 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +143 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +62 -0
- claude_mpm/commander/inbox/__init__.py +16 -0
- claude_mpm/commander/inbox/dedup.py +128 -0
- claude_mpm/commander/inbox/inbox.py +224 -0
- claude_mpm/commander/inbox/models.py +70 -0
- claude_mpm/commander/instance_manager.py +337 -0
- claude_mpm/commander/llm/__init__.py +6 -0
- claude_mpm/commander/llm/openrouter_client.py +167 -0
- claude_mpm/commander/llm/summarizer.py +70 -0
- claude_mpm/commander/models/__init__.py +18 -0
- claude_mpm/commander/models/events.py +121 -0
- claude_mpm/commander/models/project.py +162 -0
- claude_mpm/commander/models/work.py +214 -0
- claude_mpm/commander/parsing/__init__.py +20 -0
- claude_mpm/commander/parsing/extractor.py +132 -0
- claude_mpm/commander/parsing/output_parser.py +270 -0
- claude_mpm/commander/parsing/patterns.py +100 -0
- claude_mpm/commander/persistence/__init__.py +11 -0
- claude_mpm/commander/persistence/event_store.py +274 -0
- claude_mpm/commander/persistence/state_store.py +309 -0
- claude_mpm/commander/persistence/work_store.py +164 -0
- claude_mpm/commander/polling/__init__.py +13 -0
- claude_mpm/commander/polling/event_detector.py +104 -0
- claude_mpm/commander/polling/output_buffer.py +49 -0
- claude_mpm/commander/polling/output_poller.py +153 -0
- claude_mpm/commander/project_session.py +268 -0
- claude_mpm/commander/proxy/__init__.py +12 -0
- claude_mpm/commander/proxy/formatter.py +89 -0
- claude_mpm/commander/proxy/output_handler.py +191 -0
- claude_mpm/commander/proxy/relay.py +155 -0
- claude_mpm/commander/registry.py +404 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +316 -0
- claude_mpm/commander/session/__init__.py +6 -0
- claude_mpm/commander/session/context.py +81 -0
- claude_mpm/commander/session/manager.py +59 -0
- claude_mpm/commander/tmux_orchestrator.py +361 -0
- claude_mpm/commander/web/__init__.py +1 -0
- claude_mpm/commander/work/__init__.py +30 -0
- claude_mpm/commander/work/executor.py +189 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +219 -0
- claude_mpm/commander/workflow/notifier.py +146 -0
- claude_mpm/config/agent_presets.py +2 -1
- claude_mpm/core/logger.py +1 -1
- claude_mpm/core/logging_utils.py +35 -13
- claude_mpm/core/unified_config.py +3 -2
- claude_mpm/core/unified_paths.py +68 -80
- claude_mpm/hooks/claude_hooks/hook_handler.py +67 -80
- claude_mpm/hooks/claude_hooks/installer.py +6 -3
- claude_mpm/hooks/claude_hooks/memory_integration.py +22 -11
- claude_mpm/services/skills/git_skill_source_manager.py +51 -2
- {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/METADATA +13 -1
- {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/RECORD +97 -37
- 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__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/WHEEL +0 -0
- {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/top_level.txt +0 -0
|
@@ -62,14 +62,31 @@ except ImportError:
|
|
|
62
62
|
"""
|
|
63
63
|
Debug mode configuration for hook processing.
|
|
64
64
|
|
|
65
|
-
WHY
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
WHY disabled by default: Production users should see clean output without debug noise.
|
|
66
|
+
Hook errors appear less confusing when debug output is minimal.
|
|
67
|
+
Development and debugging can enable via CLAUDE_MPM_HOOK_DEBUG=true.
|
|
68
68
|
|
|
69
69
|
Performance Impact: Debug logging adds ~5-10% overhead but provides crucial
|
|
70
|
-
visibility into event flow, timing, and error conditions.
|
|
70
|
+
visibility into event flow, timing, and error conditions when enabled.
|
|
71
71
|
"""
|
|
72
|
-
DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "
|
|
72
|
+
DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "false").lower() == "true"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _log(message: str) -> None:
|
|
76
|
+
"""Log message to file if DEBUG enabled. Never write to stderr.
|
|
77
|
+
|
|
78
|
+
WHY: Claude Code interprets ANY stderr output as a hook error.
|
|
79
|
+
Writing to stderr causes confusing "hook error" messages even for debug logs.
|
|
80
|
+
|
|
81
|
+
This helper ensures all debug output goes to a log file instead.
|
|
82
|
+
"""
|
|
83
|
+
if DEBUG:
|
|
84
|
+
try:
|
|
85
|
+
with open("/tmp/claude-mpm-hook.log", "a") as f: # nosec B108
|
|
86
|
+
f.write(f"[{datetime.now(timezone.utc).isoformat()}] {message}\n")
|
|
87
|
+
except Exception: # nosec B110 - intentional silent failure
|
|
88
|
+
pass # Never disrupt hook execution
|
|
89
|
+
|
|
73
90
|
|
|
74
91
|
"""
|
|
75
92
|
Conditional imports with graceful fallbacks for testing and modularity.
|
|
@@ -188,22 +205,17 @@ def check_claude_version() -> Tuple[bool, Optional[str]]:
|
|
|
188
205
|
req_part = required[i] if i < len(required) else 0
|
|
189
206
|
|
|
190
207
|
if curr_part < req_part:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
file=sys.stderr,
|
|
196
|
-
)
|
|
208
|
+
_log(
|
|
209
|
+
f"⚠️ Claude Code {version} does not support matcher-based hooks "
|
|
210
|
+
f"(requires {MIN_CLAUDE_VERSION}+). Hook monitoring disabled."
|
|
211
|
+
)
|
|
197
212
|
return False, version
|
|
198
213
|
if curr_part > req_part:
|
|
199
214
|
return True, version
|
|
200
215
|
|
|
201
216
|
return True, version
|
|
202
217
|
except Exception as e:
|
|
203
|
-
|
|
204
|
-
print(
|
|
205
|
-
f"Warning: Could not detect Claude Code version: {e}", file=sys.stderr
|
|
206
|
-
)
|
|
218
|
+
_log(f"Warning: Could not detect Claude Code version: {e}")
|
|
207
219
|
|
|
208
220
|
return False, None
|
|
209
221
|
|
|
@@ -244,8 +256,7 @@ class ClaudeHookHandler:
|
|
|
244
256
|
)
|
|
245
257
|
except Exception as e:
|
|
246
258
|
self.auto_pause_handler = None
|
|
247
|
-
|
|
248
|
-
print(f"Auto-pause initialization failed: {e}", file=sys.stderr)
|
|
259
|
+
_log(f"Auto-pause initialization failed: {e}")
|
|
249
260
|
|
|
250
261
|
# Backward compatibility properties for tests
|
|
251
262
|
# Note: HTTP-based connection manager doesn't use connection_pool
|
|
@@ -278,8 +289,7 @@ class ClaudeHookHandler:
|
|
|
278
289
|
def timeout_handler(signum, frame):
|
|
279
290
|
"""Handle timeout by forcing exit."""
|
|
280
291
|
nonlocal _continue_sent
|
|
281
|
-
|
|
282
|
-
print(f"Hook handler timeout (pid: {os.getpid()})", file=sys.stderr)
|
|
292
|
+
_log(f"Hook handler timeout (pid: {os.getpid()})")
|
|
283
293
|
if not _continue_sent:
|
|
284
294
|
self._continue_execution()
|
|
285
295
|
_continue_sent = True
|
|
@@ -300,11 +310,9 @@ class ClaudeHookHandler:
|
|
|
300
310
|
|
|
301
311
|
# Check for duplicate events (same event within 100ms)
|
|
302
312
|
if self.duplicate_detector.is_duplicate(event):
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
file=sys.stderr,
|
|
307
|
-
)
|
|
313
|
+
_log(
|
|
314
|
+
f"[{datetime.now(timezone.utc).isoformat()}] Skipping duplicate event: {event.get('hook_event_name', 'unknown')} (PID: {os.getpid()})"
|
|
315
|
+
)
|
|
308
316
|
# Still need to output continue for this invocation
|
|
309
317
|
if not _continue_sent:
|
|
310
318
|
self._continue_execution()
|
|
@@ -312,12 +320,10 @@ class ClaudeHookHandler:
|
|
|
312
320
|
return
|
|
313
321
|
|
|
314
322
|
# Debug: Log that we're processing an event
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
file=sys.stderr,
|
|
320
|
-
)
|
|
323
|
+
hook_type = event.get("hook_event_name", "unknown")
|
|
324
|
+
_log(
|
|
325
|
+
f"\n[{datetime.now(timezone.utc).isoformat()}] Processing hook event: {hook_type} (PID: {os.getpid()})"
|
|
326
|
+
)
|
|
321
327
|
|
|
322
328
|
# Perform periodic cleanup if needed
|
|
323
329
|
if self.state_manager.increment_events_processed():
|
|
@@ -326,11 +332,9 @@ class ClaudeHookHandler:
|
|
|
326
332
|
from .correlation_manager import CorrelationManager
|
|
327
333
|
|
|
328
334
|
CorrelationManager.cleanup_old()
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
file=sys.stderr,
|
|
333
|
-
)
|
|
335
|
+
_log(
|
|
336
|
+
f"🧹 Performed cleanup after {self.state_manager.events_processed} events"
|
|
337
|
+
)
|
|
334
338
|
|
|
335
339
|
# Route event to appropriate handler
|
|
336
340
|
# Handlers can optionally return modified input for PreToolUse events
|
|
@@ -370,8 +374,7 @@ class ClaudeHookHandler:
|
|
|
370
374
|
ready, _, _ = select.select([sys.stdin], [], [], 1.0)
|
|
371
375
|
if not ready:
|
|
372
376
|
# No data available within timeout
|
|
373
|
-
|
|
374
|
-
print("No hook event data received within timeout", file=sys.stderr)
|
|
377
|
+
_log("No hook event data received within timeout")
|
|
375
378
|
return None
|
|
376
379
|
|
|
377
380
|
# Data is available, read it
|
|
@@ -382,21 +385,16 @@ class ClaudeHookHandler:
|
|
|
382
385
|
|
|
383
386
|
parsed = json.loads(event_data)
|
|
384
387
|
# Debug: Log the actual event format we receive
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
for key in ["hook_event_name", "event", "type", "event_type"]:
|
|
390
|
-
if key in parsed:
|
|
391
|
-
print(f" {key} = '{parsed[key]}'", file=sys.stderr)
|
|
388
|
+
_log(f"Received event with keys: {list(parsed.keys())}")
|
|
389
|
+
for key in ["hook_event_name", "event", "type", "event_type"]:
|
|
390
|
+
if key in parsed:
|
|
391
|
+
_log(f" {key} = '{parsed[key]}'")
|
|
392
392
|
return parsed
|
|
393
393
|
except (json.JSONDecodeError, ValueError) as e:
|
|
394
|
-
|
|
395
|
-
print(f"Failed to parse hook event: {e}", file=sys.stderr)
|
|
394
|
+
_log(f"Failed to parse hook event: {e}")
|
|
396
395
|
return None
|
|
397
396
|
except Exception as e:
|
|
398
|
-
|
|
399
|
-
print(f"Error reading hook event: {e}", file=sys.stderr)
|
|
397
|
+
_log(f"Error reading hook event: {e}")
|
|
400
398
|
return None
|
|
401
399
|
|
|
402
400
|
def _route_event(self, event: dict) -> Optional[dict]:
|
|
@@ -425,9 +423,9 @@ class ClaudeHookHandler:
|
|
|
425
423
|
)
|
|
426
424
|
|
|
427
425
|
# Log the actual event structure for debugging
|
|
428
|
-
if
|
|
429
|
-
|
|
430
|
-
|
|
426
|
+
if hook_type == "unknown":
|
|
427
|
+
_log(f"Unknown event format, keys: {list(event.keys())}")
|
|
428
|
+
_log(f"Event sample: {str(event)[:200]}")
|
|
431
429
|
|
|
432
430
|
# Map event types to handlers
|
|
433
431
|
event_handlers = {
|
|
@@ -463,8 +461,7 @@ class ClaudeHookHandler:
|
|
|
463
461
|
except Exception as e:
|
|
464
462
|
error_message = str(e)
|
|
465
463
|
return_value = None
|
|
466
|
-
|
|
467
|
-
print(f"Error handling {hook_type}: {e}", file=sys.stderr)
|
|
464
|
+
_log(f"Error handling {hook_type}: {e}")
|
|
468
465
|
finally:
|
|
469
466
|
# Calculate duration
|
|
470
467
|
duration_ms = int((time.time() - start_time) * 1000)
|
|
@@ -589,11 +586,9 @@ class ClaudeHookHandler:
|
|
|
589
586
|
# This uses the existing event infrastructure
|
|
590
587
|
self._emit_socketio_event("", "hook_execution", hook_data)
|
|
591
588
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
file=sys.stderr,
|
|
596
|
-
)
|
|
589
|
+
_log(
|
|
590
|
+
f"📊 Hook execution event: {hook_type} - {duration_ms}ms - {'✅' if success else '❌'}"
|
|
591
|
+
)
|
|
597
592
|
|
|
598
593
|
def _generate_hook_summary(self, hook_type: str, event: dict, success: bool) -> str:
|
|
599
594
|
"""Generate a human-readable summary of what the hook did.
|
|
@@ -676,22 +671,15 @@ def main():
|
|
|
676
671
|
if not is_compatible:
|
|
677
672
|
# Version incompatible - just continue without processing
|
|
678
673
|
# This prevents errors on older Claude Code versions
|
|
679
|
-
if
|
|
680
|
-
|
|
681
|
-
f"Skipping hook processing due to version incompatibility ({version})",
|
|
682
|
-
file=sys.stderr,
|
|
683
|
-
)
|
|
674
|
+
if version:
|
|
675
|
+
_log(f"Skipping hook processing due to version incompatibility ({version})")
|
|
684
676
|
print(json.dumps({"action": "continue"}), flush=True)
|
|
685
677
|
sys.exit(0)
|
|
686
678
|
|
|
687
679
|
def cleanup_handler(signum=None, frame=None):
|
|
688
680
|
"""Cleanup handler for signals and exit."""
|
|
689
681
|
nonlocal _continue_printed
|
|
690
|
-
|
|
691
|
-
print(
|
|
692
|
-
f"Hook handler cleanup (pid: {os.getpid()}, signal: {signum})",
|
|
693
|
-
file=sys.stderr,
|
|
694
|
-
)
|
|
682
|
+
_log(f"Hook handler cleanup (pid: {os.getpid()}, signal: {signum})")
|
|
695
683
|
# Only output continue if we haven't already (i.e., if interrupted by signal)
|
|
696
684
|
if signum is not None and not _continue_printed:
|
|
697
685
|
print(json.dumps({"action": "continue"}), flush=True)
|
|
@@ -708,15 +696,10 @@ def main():
|
|
|
708
696
|
with _handler_lock:
|
|
709
697
|
if _global_handler is None:
|
|
710
698
|
_global_handler = ClaudeHookHandler()
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
)
|
|
716
|
-
elif DEBUG:
|
|
717
|
-
print(
|
|
718
|
-
f"♻️ Reusing existing ClaudeHookHandler singleton (pid: {os.getpid()})",
|
|
719
|
-
file=sys.stderr,
|
|
699
|
+
_log(f"✅ Created new ClaudeHookHandler singleton (pid: {os.getpid()})")
|
|
700
|
+
else:
|
|
701
|
+
_log(
|
|
702
|
+
f"♻️ Reusing existing ClaudeHookHandler singleton (pid: {os.getpid()})"
|
|
720
703
|
)
|
|
721
704
|
|
|
722
705
|
handler = _global_handler
|
|
@@ -735,10 +718,14 @@ def main():
|
|
|
735
718
|
print(json.dumps({"action": "continue"}), flush=True)
|
|
736
719
|
_continue_printed = True
|
|
737
720
|
# Log error for debugging
|
|
738
|
-
|
|
739
|
-
print(f"Hook handler error: {e}", file=sys.stderr)
|
|
721
|
+
_log(f"Hook handler error: {e}")
|
|
740
722
|
sys.exit(0) # Exit cleanly even on error
|
|
741
723
|
|
|
742
724
|
|
|
743
725
|
if __name__ == "__main__":
|
|
744
|
-
|
|
726
|
+
try:
|
|
727
|
+
main()
|
|
728
|
+
except Exception:
|
|
729
|
+
# Catastrophic failure (import error, etc.) - always output valid JSON
|
|
730
|
+
print(json.dumps({"action": "continue"}), flush=True)
|
|
731
|
+
sys.exit(0)
|
|
@@ -14,8 +14,6 @@ import subprocess # nosec B404 - Safe: only uses hardcoded 'claude' CLI command
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
from typing import Dict, List, Optional, Tuple
|
|
16
16
|
|
|
17
|
-
from ...core.logger import get_logger
|
|
18
|
-
|
|
19
17
|
|
|
20
18
|
class HookInstaller:
|
|
21
19
|
"""Manages installation and configuration of Claude MPM hooks."""
|
|
@@ -199,7 +197,12 @@ main "$@"
|
|
|
199
197
|
|
|
200
198
|
def __init__(self):
|
|
201
199
|
"""Initialize the hook installer."""
|
|
202
|
-
|
|
200
|
+
# Use __name__ directly to avoid double prefix
|
|
201
|
+
# __name__ is already 'claude_mpm.hooks.claude_hooks.installer'
|
|
202
|
+
# get_logger() adds 'claude_mpm.' prefix, causing duplicate
|
|
203
|
+
import logging
|
|
204
|
+
|
|
205
|
+
self.logger = logging.getLogger(__name__)
|
|
203
206
|
self.claude_dir = Path.home() / ".claude"
|
|
204
207
|
self.hooks_dir = self.claude_dir / "hooks" # Kept for backward compatibility
|
|
205
208
|
# Use settings.json for hooks (Claude Code reads from this file)
|
|
@@ -11,15 +11,23 @@ import sys
|
|
|
11
11
|
|
|
12
12
|
# Install-type-aware logging configuration BEFORE kuzu-memory imports
|
|
13
13
|
# This overrides kuzu-memory's WARNING-level basicConfig (fixes 1M-445)
|
|
14
|
-
# but respects production install silence
|
|
14
|
+
# but respects production install silence AND startup suppression
|
|
15
15
|
try:
|
|
16
16
|
from claude_mpm.core.unified_paths import DeploymentContext, PathContext
|
|
17
17
|
|
|
18
18
|
context = PathContext.detect_deployment_context()
|
|
19
19
|
|
|
20
|
+
# CRITICAL: Check if root logger is already suppressed (CRITICAL+1 from startup.py)
|
|
21
|
+
# If so, don't call basicConfig as it will reset the level to INFO
|
|
22
|
+
root_logger = logging.getLogger()
|
|
23
|
+
is_suppressed = root_logger.level > logging.CRITICAL # CRITICAL+1 = 51
|
|
24
|
+
|
|
20
25
|
# Only configure verbose logging for development/editable installs
|
|
21
|
-
#
|
|
22
|
-
if context in (
|
|
26
|
+
# AND if logging isn't already suppressed by startup.py
|
|
27
|
+
if not is_suppressed and context in (
|
|
28
|
+
DeploymentContext.DEVELOPMENT,
|
|
29
|
+
DeploymentContext.EDITABLE_INSTALL,
|
|
30
|
+
):
|
|
23
31
|
logging.basicConfig(
|
|
24
32
|
level=logging.INFO,
|
|
25
33
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
@@ -27,14 +35,17 @@ try:
|
|
|
27
35
|
stream=sys.stderr,
|
|
28
36
|
)
|
|
29
37
|
except ImportError:
|
|
30
|
-
# Fallback: if unified_paths not available,
|
|
31
|
-
|
|
32
|
-
logging.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
# Fallback: if unified_paths not available, check suppression before configuring
|
|
39
|
+
root_logger = logging.getLogger()
|
|
40
|
+
is_suppressed = root_logger.level > logging.CRITICAL
|
|
41
|
+
|
|
42
|
+
if not is_suppressed:
|
|
43
|
+
logging.basicConfig(
|
|
44
|
+
level=logging.INFO,
|
|
45
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
46
|
+
force=True,
|
|
47
|
+
stream=sys.stderr,
|
|
48
|
+
)
|
|
38
49
|
from datetime import datetime, timezone
|
|
39
50
|
from typing import Optional
|
|
40
51
|
|
|
@@ -682,7 +682,7 @@ class GitSkillSourceManager:
|
|
|
682
682
|
try:
|
|
683
683
|
with open(etag_cache_file, encoding="utf-8") as f:
|
|
684
684
|
etag_cache = json.load(f)
|
|
685
|
-
except Exception:
|
|
685
|
+
except Exception: # nosec B110 - intentional: proceed without cache on read failure
|
|
686
686
|
pass
|
|
687
687
|
|
|
688
688
|
cached_etag = etag_cache.get(str(local_path))
|
|
@@ -1163,6 +1163,10 @@ class GitSkillSourceManager:
|
|
|
1163
1163
|
) -> List[str]:
|
|
1164
1164
|
"""Remove skills from target directory that aren't in the filtered skill list.
|
|
1165
1165
|
|
|
1166
|
+
CRITICAL: Only removes MPM-managed skills (those in our cache). Custom user skills
|
|
1167
|
+
are preserved. This prevents accidental deletion of user-created skills that were
|
|
1168
|
+
never part of MPM's skill repository.
|
|
1169
|
+
|
|
1166
1170
|
Uses fuzzy matching to handle both exact deployment names and short skill names.
|
|
1167
1171
|
For example:
|
|
1168
1172
|
- "toolchains-python-frameworks-flask" (deployed dir) matches "flask" (filter)
|
|
@@ -1213,6 +1217,40 @@ class GitSkillSourceManager:
|
|
|
1213
1217
|
|
|
1214
1218
|
return False
|
|
1215
1219
|
|
|
1220
|
+
def is_mpm_managed_skill(skill_dir_name: str) -> bool:
|
|
1221
|
+
"""Check if skill is managed by MPM (exists in our cache).
|
|
1222
|
+
|
|
1223
|
+
Custom user skills (not in cache) are NEVER deleted, even if not in filter.
|
|
1224
|
+
Only MPM-managed skills (in cache but not in filter) are candidates for removal.
|
|
1225
|
+
|
|
1226
|
+
Args:
|
|
1227
|
+
skill_dir_name: Name of deployed skill directory
|
|
1228
|
+
|
|
1229
|
+
Returns:
|
|
1230
|
+
True if skill exists in MPM cache (MPM-managed), False if custom user skill
|
|
1231
|
+
"""
|
|
1232
|
+
# Check all configured skill sources for this skill
|
|
1233
|
+
for source in self.config.get_enabled_sources():
|
|
1234
|
+
cache_path = self._get_source_cache_path(source)
|
|
1235
|
+
if not cache_path.exists():
|
|
1236
|
+
continue
|
|
1237
|
+
|
|
1238
|
+
# Check if this skill directory exists anywhere in the cache
|
|
1239
|
+
# Use glob to find matching directories recursively
|
|
1240
|
+
matches = list(cache_path.rglob(f"*{skill_dir_name}*"))
|
|
1241
|
+
if matches:
|
|
1242
|
+
# Found in cache - this is MPM-managed
|
|
1243
|
+
self.logger.debug(
|
|
1244
|
+
f"Skill '{skill_dir_name}' found in cache at {matches[0]} - MPM-managed"
|
|
1245
|
+
)
|
|
1246
|
+
return True
|
|
1247
|
+
|
|
1248
|
+
# Not found in any cache - this is a custom user skill
|
|
1249
|
+
self.logger.debug(
|
|
1250
|
+
f"Skill '{skill_dir_name}' not found in cache - custom user skill, preserving"
|
|
1251
|
+
)
|
|
1252
|
+
return False
|
|
1253
|
+
|
|
1216
1254
|
# Check each directory in target_dir
|
|
1217
1255
|
if not target_dir.exists():
|
|
1218
1256
|
return removed_skills
|
|
@@ -1229,6 +1267,15 @@ class GitSkillSourceManager:
|
|
|
1229
1267
|
|
|
1230
1268
|
# Check if this skill directory should be kept (fuzzy matching)
|
|
1231
1269
|
if not should_keep_skill(item.name):
|
|
1270
|
+
# CRITICAL: Check if this is an MPM-managed skill before deletion
|
|
1271
|
+
if not is_mpm_managed_skill(item.name):
|
|
1272
|
+
# This is a custom user skill - NEVER delete
|
|
1273
|
+
self.logger.debug(
|
|
1274
|
+
f"Preserving custom user skill (not in MPM cache): {item.name}"
|
|
1275
|
+
)
|
|
1276
|
+
continue
|
|
1277
|
+
|
|
1278
|
+
# It's MPM-managed but not in filter - safe to remove
|
|
1232
1279
|
try:
|
|
1233
1280
|
# Security: Validate path is within target_dir
|
|
1234
1281
|
if not self._validate_safe_path(target_dir, item):
|
|
@@ -1244,7 +1291,9 @@ class GitSkillSourceManager:
|
|
|
1244
1291
|
shutil.rmtree(item)
|
|
1245
1292
|
|
|
1246
1293
|
removed_skills.append(item.name)
|
|
1247
|
-
self.logger.info(
|
|
1294
|
+
self.logger.info(
|
|
1295
|
+
f"Removed orphaned MPM-managed skill: {item.name}"
|
|
1296
|
+
)
|
|
1248
1297
|
|
|
1249
1298
|
except Exception as e:
|
|
1250
1299
|
self.logger.warning(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-mpm
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.6.2
|
|
4
4
|
Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
|
|
5
5
|
Author-email: Bob Matsuoka <bob@matsuoka.com>
|
|
6
6
|
Maintainer: Claude MPM Team
|
|
@@ -49,6 +49,8 @@ Requires-Dist: rich>=13.0.0
|
|
|
49
49
|
Requires-Dist: questionary>=2.0.0
|
|
50
50
|
Requires-Dist: pyee>=13.0.0
|
|
51
51
|
Requires-Dist: pathspec>=0.11.0
|
|
52
|
+
Requires-Dist: fastapi>=0.100.0
|
|
53
|
+
Requires-Dist: uvicorn>=0.20.0
|
|
52
54
|
Provides-Extra: mcp
|
|
53
55
|
Requires-Dist: mcp>=0.1.0; extra == "mcp"
|
|
54
56
|
Requires-Dist: mcp-vector-search>=0.1.0; extra == "mcp"
|
|
@@ -233,6 +235,16 @@ ls ~/.claude/agents/ # Should show 47+ agents
|
|
|
233
235
|
|
|
234
236
|
[→ Learn more: Developer Use Cases](docs/usecases/developers.md#semantic-code-search)
|
|
235
237
|
|
|
238
|
+
### 🧪 MPM Commander (ALPHA)
|
|
239
|
+
- **Multi-Project Orchestration** with autonomous AI coordination across codebases
|
|
240
|
+
- **Tmux Integration** for isolated project environments and session management
|
|
241
|
+
- **Event-Driven Architecture** with inbox system for cross-project communication
|
|
242
|
+
- **LLM-Powered Decisions** via OpenRouter for autonomous work queue processing
|
|
243
|
+
- **Real-Time Monitoring** with state tracking (IDLE, WORKING, BLOCKED, PAUSED, ERROR)
|
|
244
|
+
- ⚠️ **Experimental** - API and CLI interface subject to change
|
|
245
|
+
|
|
246
|
+
[→ Commander Documentation](docs/commander/usage-guide.md)
|
|
247
|
+
|
|
236
248
|
### 🔌 Advanced Integration
|
|
237
249
|
- **MCP Integration** with full Model Context Protocol support
|
|
238
250
|
- **Real-Time Monitoring** via `--monitor` flag and web dashboard
|