claude-mpm 5.6.30__py3-none-any.whl → 5.6.32__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 CHANGED
@@ -1 +1 @@
1
- 5.6.30
1
+ 5.6.32
@@ -203,10 +203,14 @@ main "$@"
203
203
  import logging
204
204
 
205
205
  self.logger = logging.getLogger(__name__)
206
- self.claude_dir = Path.home() / ".claude"
206
+ # Use project-level paths, NEVER global ~/.claude/settings.json
207
+ # This ensures hooks are scoped to the current project only
208
+ self.project_root = Path.cwd()
209
+ self.claude_dir = self.project_root / ".claude"
207
210
  self.hooks_dir = self.claude_dir / "hooks" # Kept for backward compatibility
208
- # Use settings.json for hooks (Claude Code reads from this file)
209
- self.settings_file = self.claude_dir / "settings.json"
211
+ # Use settings.local.json for project-level hook settings
212
+ # Claude Code reads project-level settings from .claude/settings.local.json
213
+ self.settings_file = self.claude_dir / "settings.local.json"
210
214
  # There is no legacy settings file - this was a bug where both pointed to same file
211
215
  # Setting to None to disable cleanup that was deleting freshly installed hooks
212
216
  self.old_settings_file = None
@@ -601,40 +605,98 @@ main "$@"
601
605
  # Hook configuration for each event type
602
606
  hook_command = {"type": "command", "command": str(hook_script_path.absolute())}
603
607
 
608
+ def is_our_hook(cmd: dict) -> bool:
609
+ """Check if a hook command belongs to claude-mpm."""
610
+ if cmd.get("type") != "command":
611
+ return False
612
+ command = cmd.get("command", "")
613
+ return "claude-hook-handler.sh" in command or command.endswith(
614
+ "claude-mpm-hook.sh"
615
+ )
616
+
617
+ def merge_hooks_for_event(
618
+ existing_hooks: list, new_hook_command: dict, use_matcher: bool = True
619
+ ) -> list:
620
+ """Merge new hook command into existing hooks without duplication.
621
+
622
+ Args:
623
+ existing_hooks: Current hooks configuration for an event type
624
+ new_hook_command: The claude-mpm hook command to add
625
+ use_matcher: Whether to include matcher: "*" in the config
626
+
627
+ Returns:
628
+ Updated hooks list with our hook merged in
629
+ """
630
+ # Check if our hook already exists in any existing hook config
631
+ our_hook_exists = False
632
+
633
+ for hook_config in existing_hooks:
634
+ if "hooks" in hook_config and isinstance(hook_config["hooks"], list):
635
+ for hook in hook_config["hooks"]:
636
+ if is_our_hook(hook):
637
+ # Update existing hook command path (in case it changed)
638
+ hook["command"] = new_hook_command["command"]
639
+ our_hook_exists = True
640
+ break
641
+ if our_hook_exists:
642
+ break
643
+
644
+ if our_hook_exists:
645
+ # Our hook already exists, just return the updated list
646
+ return existing_hooks
647
+
648
+ # Our hook doesn't exist - need to add it
649
+ # Strategy: Add our hook to the first "*" matcher config, or create new config
650
+ added = False
651
+
652
+ for hook_config in existing_hooks:
653
+ # Check if this config has matcher: "*" (or no matcher for simple events)
654
+ matcher = hook_config.get("matcher")
655
+ if matcher == "*" or (not use_matcher and matcher is None):
656
+ # Add our hook to this config's hooks array
657
+ if "hooks" not in hook_config:
658
+ hook_config["hooks"] = []
659
+ hook_config["hooks"].append(new_hook_command)
660
+ added = True
661
+ break
662
+
663
+ if not added:
664
+ # No suitable config found, create a new one
665
+ if use_matcher:
666
+ new_config = {"matcher": "*", "hooks": [new_hook_command]}
667
+ else:
668
+ new_config = {"hooks": [new_hook_command]}
669
+ existing_hooks.append(new_config)
670
+
671
+ return existing_hooks
672
+
604
673
  # Tool-related events need a matcher string
605
674
  tool_events = ["PreToolUse", "PostToolUse"]
606
675
  for event_type in tool_events:
607
- settings["hooks"][event_type] = [
608
- {
609
- "matcher": "*", # String value to match all tools
610
- "hooks": [hook_command],
611
- }
612
- ]
676
+ existing = settings["hooks"].get(event_type, [])
677
+ settings["hooks"][event_type] = merge_hooks_for_event(
678
+ existing, hook_command, use_matcher=True
679
+ )
613
680
 
614
681
  # Simple events (no subtypes, no matcher needed)
615
682
  simple_events = ["Stop", "SubagentStop", "SubagentStart"]
616
683
  for event_type in simple_events:
617
- settings["hooks"][event_type] = [
618
- {
619
- "hooks": [hook_command],
620
- }
621
- ]
684
+ existing = settings["hooks"].get(event_type, [])
685
+ settings["hooks"][event_type] = merge_hooks_for_event(
686
+ existing, hook_command, use_matcher=False
687
+ )
622
688
 
623
689
  # SessionStart needs matcher for subtypes (startup, resume)
624
- settings["hooks"]["SessionStart"] = [
625
- {
626
- "matcher": "*", # Match all SessionStart subtypes
627
- "hooks": [hook_command],
628
- }
629
- ]
690
+ existing = settings["hooks"].get("SessionStart", [])
691
+ settings["hooks"]["SessionStart"] = merge_hooks_for_event(
692
+ existing, hook_command, use_matcher=True
693
+ )
630
694
 
631
695
  # UserPromptSubmit needs matcher for potential subtypes
632
- settings["hooks"]["UserPromptSubmit"] = [
633
- {
634
- "matcher": "*",
635
- "hooks": [hook_command],
636
- }
637
- ]
696
+ existing = settings["hooks"].get("UserPromptSubmit", [])
697
+ settings["hooks"]["UserPromptSubmit"] = merge_hooks_for_event(
698
+ existing, hook_command, use_matcher=True
699
+ )
638
700
 
639
701
  # Fix statusLine command to handle both output style schemas
640
702
  self._fix_status_line(settings)
@@ -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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-mpm
3
- Version: 5.6.30
3
+ Version: 5.6.32
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
@@ -1,5 +1,5 @@
1
1
  claude_mpm/BUILD_NUMBER,sha256=9JfxhnDtr-8l3kCP2U5TVXSErptHoga8m7XA8zqgGOc,4
2
- claude_mpm/VERSION,sha256=Zd5sPevfBIuZO6lI33LMT-USn8kaj3LwBIckv8XJalg,7
2
+ claude_mpm/VERSION,sha256=dAg_-LZ5hKzApBj_BHyWt6jpEC7yRMezwMk3MwtJm40,7
3
3
  claude_mpm/__init__.py,sha256=AGfh00BHKvLYD-UVFw7qbKtl7NMRIzRXOWw7vEuZ-h4,2214
4
4
  claude_mpm/__main__.py,sha256=Ro5UBWBoQaSAIoSqWAr7zkbLyvi4sSy28WShqAhKJG0,723
5
5
  claude_mpm/constants.py,sha256=pz3lTrZZR5HhV3eZzYtIbtBwWo7iM6pkBHP_ixxmI6Y,6827
@@ -432,7 +432,7 @@ claude_mpm/hooks/claude_hooks/correlation_manager.py,sha256=3n-RxzqE8egG4max_Ncp
432
432
  claude_mpm/hooks/claude_hooks/event_handlers.py,sha256=ztkKTr5xFAY-K5gxhsXFtR_4tir3Cjx2l4auSYbJErU,46745
433
433
  claude_mpm/hooks/claude_hooks/hook_handler.py,sha256=UbBypLK1xhm6NdBBLLjqlsjLGWg1GgW-XSfeR7gLAOc,28242
434
434
  claude_mpm/hooks/claude_hooks/hook_wrapper.sh,sha256=XYkdYtcM0nfnwYvMdyIFCasr80ry3uI5-fLYsLtDGw4,2214
435
- claude_mpm/hooks/claude_hooks/installer.py,sha256=fPYeeilFEVR1J7dWLn-x1jtgoZ65cmHtlVEXioKmCrs,34911
435
+ claude_mpm/hooks/claude_hooks/installer.py,sha256=SvIgxRMocQxnqNF3ZQfKH8zA-1b4lpiCA4vCI0vWOZI,38065
436
436
  claude_mpm/hooks/claude_hooks/memory_integration.py,sha256=YOMD4Ah003uMh7A454w8ngLmKw8RUAEIHpEj-FRk3TI,10759
437
437
  claude_mpm/hooks/claude_hooks/response_tracking.py,sha256=1KOGC19rYRNYbc1Tfe7FAP6AtvgOMSM5uEPxMi2N6-c,16323
438
438
  claude_mpm/hooks/claude_hooks/tool_analysis.py,sha256=3_o2PP9D7wEMwLriCtIBOw0cj2fSZfepN7lI4P1meSQ,7862
@@ -483,7 +483,7 @@ claude_mpm/services/delegation_detector.py,sha256=ZpElqjhTbuEeeTjTMUsl-G1lHMJ9m1
483
483
  claude_mpm/services/event_aggregator.py,sha256=V_5Wln1RzozLMDZawIPl5gSjIN5KHniNPaaSP11Lihc,20251
484
484
  claude_mpm/services/event_log.py,sha256=pMKc8p2lXRoRi_cvVxaU1uNuUCNrGc0EXqBv8TeueyI,10043
485
485
  claude_mpm/services/exceptions.py,sha256=5lVZETr_6-xk0ItH7BTfYUiX5RlckS1e8ah_UalYG9c,26475
486
- claude_mpm/services/hook_installer_service.py,sha256=x3H3bFVlmhK4Ue1K279f44lwMEw3W1p3zoETGfjIH_w,19708
486
+ claude_mpm/services/hook_installer_service.py,sha256=AFN5hXfj0oz-pzPNaPRUOAqWFNF9lIZE3i2vNTduYCE,22916
487
487
  claude_mpm/services/hook_service.py,sha256=I6JILbackBsdvrDNQ9TeGSB7XNqozNRP26T4E9_ROtU,15693
488
488
  claude_mpm/services/mcp_config_manager.py,sha256=pQcu5g3lHo082TMX0RDgnfspnvW8huiKysCnFGOD54A,57792
489
489
  claude_mpm/services/mcp_service_verifier.py,sha256=C2DhHY9R4j91WQbe1XSpfYeHjxHg2UyiJ1VQsBgjnlc,25808
@@ -1102,10 +1102,10 @@ claude_mpm/utils/subprocess_utils.py,sha256=D0izRT8anjiUb_JG72zlJR_JAw1cDkb7kalN
1102
1102
  claude_mpm/validation/__init__.py,sha256=YZhwE3mhit-lslvRLuwfX82xJ_k4haZeKmh4IWaVwtk,156
1103
1103
  claude_mpm/validation/agent_validator.py,sha256=GprtAvu80VyMXcKGsK_VhYiXWA6BjKHv7O6HKx0AB9w,20917
1104
1104
  claude_mpm/validation/frontmatter_validator.py,sha256=YpJlYNNYcV8u6hIOi3_jaRsDnzhbcQpjCBE6eyBKaFY,7076
1105
- claude_mpm-5.6.30.dist-info/licenses/LICENSE,sha256=ca3y_Rk4aPrbF6f62z8Ht5MJM9OAvbGlHvEDcj9vUQ4,3867
1106
- claude_mpm-5.6.30.dist-info/licenses/LICENSE-FAQ.md,sha256=TxfEkXVCK98RzDOer09puc7JVCP_q_bN4dHtZKHCMcM,5104
1107
- claude_mpm-5.6.30.dist-info/METADATA,sha256=LcJRUOd074L5e7vTIqooI5O9nZ2b9NQDb9bELWDLzzc,15245
1108
- claude_mpm-5.6.30.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1109
- claude_mpm-5.6.30.dist-info/entry_points.txt,sha256=n-Uk4vwHPpuvu-g_I7-GHORzTnN_m6iyOsoLveKKD0E,228
1110
- claude_mpm-5.6.30.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
1111
- claude_mpm-5.6.30.dist-info/RECORD,,
1105
+ claude_mpm-5.6.32.dist-info/licenses/LICENSE,sha256=ca3y_Rk4aPrbF6f62z8Ht5MJM9OAvbGlHvEDcj9vUQ4,3867
1106
+ claude_mpm-5.6.32.dist-info/licenses/LICENSE-FAQ.md,sha256=TxfEkXVCK98RzDOer09puc7JVCP_q_bN4dHtZKHCMcM,5104
1107
+ claude_mpm-5.6.32.dist-info/METADATA,sha256=hRiW5VmkSFqGVVORVStb3BZ5YCGSa2LFW50eFGy3jSY,15245
1108
+ claude_mpm-5.6.32.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1109
+ claude_mpm-5.6.32.dist-info/entry_points.txt,sha256=n-Uk4vwHPpuvu-g_I7-GHORzTnN_m6iyOsoLveKKD0E,228
1110
+ claude_mpm-5.6.32.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
1111
+ claude_mpm-5.6.32.dist-info/RECORD,,