claude-mpm 5.4.105__py3-none-any.whl → 5.5.0__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 (41) hide show
  1. claude_mpm/cli/commands/hook_errors.py +60 -60
  2. claude_mpm/commands/mpm-config.md +8 -0
  3. claude_mpm/commands/mpm-doctor.md +8 -0
  4. claude_mpm/commands/mpm-help.md +8 -0
  5. claude_mpm/commands/mpm-init.md +8 -0
  6. claude_mpm/commands/mpm-monitor.md +8 -0
  7. claude_mpm/commands/mpm-organize.md +8 -0
  8. claude_mpm/commands/mpm-postmortem.md +8 -0
  9. claude_mpm/commands/mpm-session-resume.md +8 -0
  10. claude_mpm/commands/mpm-status.md +8 -0
  11. claude_mpm/commands/mpm-ticket-view.md +8 -0
  12. claude_mpm/commands/mpm-version.md +8 -0
  13. claude_mpm/commands/mpm.md +8 -0
  14. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  15. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +0 -0
  16. claude_mpm/hooks/claude_hooks/event_handlers.py +0 -0
  17. claude_mpm/hooks/claude_hooks/hook_handler.py +10 -5
  18. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  19. claude_mpm/hooks/claude_hooks/installer.py +69 -5
  20. claude_mpm/hooks/claude_hooks/response_tracking.py +0 -0
  21. claude_mpm/scripts/start_activity_logging.py +0 -0
  22. claude_mpm/services/pm_skills_deployer.py +84 -6
  23. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  24. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  25. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  26. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  27. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  28. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  29. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  30. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  31. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  32. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  33. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  34. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  35. {claude_mpm-5.4.105.dist-info → claude_mpm-5.5.0.dist-info}/METADATA +3 -3
  36. {claude_mpm-5.4.105.dist-info → claude_mpm-5.5.0.dist-info}/RECORD +37 -25
  37. {claude_mpm-5.4.105.dist-info → claude_mpm-5.5.0.dist-info}/WHEEL +0 -0
  38. {claude_mpm-5.4.105.dist-info → claude_mpm-5.5.0.dist-info}/entry_points.txt +0 -0
  39. {claude_mpm-5.4.105.dist-info → claude_mpm-5.5.0.dist-info}/licenses/LICENSE +0 -0
  40. {claude_mpm-5.4.105.dist-info → claude_mpm-5.5.0.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  41. {claude_mpm-5.4.105.dist-info → claude_mpm-5.5.0.dist-info}/top_level.txt +0 -0
@@ -57,33 +57,33 @@ def list_errors(format, hook_type):
57
57
 
58
58
  if not errors:
59
59
  if hook_type:
60
- click.echo(f"No errors recorded for hook type: {hook_type}")
60
+ click.echo(f"No errors recorded for hook type: {hook_type}", err=True)
61
61
  else:
62
- click.echo("No errors recorded. Hook system is healthy! ✅")
62
+ click.echo("No errors recorded. Hook system is healthy! ✅", err=True)
63
63
  return
64
64
 
65
65
  if format == "json":
66
66
  # JSON output
67
- click.echo(json.dumps(errors, indent=2))
67
+ click.echo(json.dumps(errors, indent=2), err=True)
68
68
  else:
69
69
  # Table output
70
- click.echo("\n" + "=" * 80)
71
- click.echo("Hook Error Memory Report")
72
- click.echo("=" * 80)
70
+ click.echo("\n" + "=" * 80, err=True)
71
+ click.echo("Hook Error Memory Report", err=True)
72
+ click.echo("=" * 80, err=True)
73
73
 
74
74
  for key, data in errors.items():
75
- click.echo(f"\n🔴 Error: {data['type']}")
76
- click.echo(f" Hook Type: {data['hook_type']}")
77
- click.echo(f" Details: {data['details']}")
78
- click.echo(f" Match: {data['match']}")
79
- click.echo(f" Count: {data['count']} occurrences")
80
- click.echo(f" First Seen: {data['first_seen']}")
81
- click.echo(f" Last Seen: {data['last_seen']}")
75
+ click.echo(f"\n🔴 Error: {data['type']}", err=True)
76
+ click.echo(f" Hook Type: {data['hook_type']}", err=True)
77
+ click.echo(f" Details: {data['details']}", err=True)
78
+ click.echo(f" Match: {data['match']}", err=True)
79
+ click.echo(f" Count: {data['count']} occurrences", err=True)
80
+ click.echo(f" First Seen: {data['first_seen']}", err=True)
81
+ click.echo(f" Last Seen: {data['last_seen']}", err=True)
82
82
 
83
- click.echo("\n" + "=" * 80)
84
- click.echo(f"Total unique errors: {len(errors)}")
85
- click.echo(f"Memory file: {error_memory.memory_file}")
86
- click.echo("\nTo clear errors: claude-mpm hook-errors clear")
83
+ click.echo("\n" + "=" * 80, err=True)
84
+ click.echo(f"Total unique errors: {len(errors)}", err=True)
85
+ click.echo(f"Memory file: {error_memory.memory_file}", err=True)
86
+ click.echo("\nTo clear errors: claude-mpm hook-errors clear", err=True)
87
87
 
88
88
 
89
89
  @hook_errors_group.command(name="summary")
@@ -99,28 +99,28 @@ def show_summary():
99
99
  summary = error_memory.get_error_summary()
100
100
 
101
101
  if summary["total_errors"] == 0:
102
- click.echo("No errors recorded. Hook system is healthy! ✅")
102
+ click.echo("No errors recorded. Hook system is healthy! ✅", err=True)
103
103
  return
104
104
 
105
- click.echo("\n" + "=" * 80)
106
- click.echo("Hook Error Summary")
107
- click.echo("=" * 80)
108
- click.echo("\n📊 Statistics:")
109
- click.echo(f" Total Errors: {summary['total_errors']}")
110
- click.echo(f" Unique Errors: {summary['unique_errors']}")
105
+ click.echo("\n" + "=" * 80, err=True)
106
+ click.echo("Hook Error Summary", err=True)
107
+ click.echo("=" * 80, err=True)
108
+ click.echo("\n📊 Statistics:", err=True)
109
+ click.echo(f" Total Errors: {summary['total_errors']}", err=True)
110
+ click.echo(f" Unique Errors: {summary['unique_errors']}", err=True)
111
111
 
112
112
  if summary["errors_by_type"]:
113
- click.echo("\n🔍 Errors by Type:")
113
+ click.echo("\n🔍 Errors by Type:", err=True)
114
114
  for error_type, count in summary["errors_by_type"].items():
115
- click.echo(f" {error_type}: {count}")
115
+ click.echo(f" {error_type}: {count}", err=True)
116
116
 
117
117
  if summary["errors_by_hook"]:
118
- click.echo("\n🎣 Errors by Hook Type:")
118
+ click.echo("\n🎣 Errors by Hook Type:", err=True)
119
119
  for hook_type, count in summary["errors_by_hook"].items():
120
- click.echo(f" {hook_type}: {count}")
120
+ click.echo(f" {hook_type}: {count}", err=True)
121
121
 
122
- click.echo(f"\n📁 Memory File: {summary['memory_file']}")
123
- click.echo("\nFor detailed list: claude-mpm hook-errors list")
122
+ click.echo(f"\n📁 Memory File: {summary['memory_file']}", err=True)
123
+ click.echo("\nFor detailed list: claude-mpm hook-errors list", err=True)
124
124
 
125
125
 
126
126
  @hook_errors_group.command(name="clear")
@@ -158,21 +158,21 @@ def clear_errors(hook_type, yes):
158
158
  scope = "all hook types"
159
159
 
160
160
  if count == 0:
161
- click.echo(f"No errors to clear {scope}.")
161
+ click.echo(f"No errors to clear {scope}.", err=True)
162
162
  return
163
163
 
164
164
  # Confirm if not using -y flag
165
165
  if not yes:
166
166
  message = f"Clear {count} error(s) {scope}?"
167
167
  if not click.confirm(message):
168
- click.echo("Cancelled.")
168
+ click.echo("Cancelled.", err=True)
169
169
  return
170
170
 
171
171
  # Clear errors
172
172
  error_memory.clear_errors(hook_type)
173
173
 
174
- click.echo(f"✅ Cleared {count} error(s) {scope}.")
175
- click.echo("\nHooks will be retried on next execution.")
174
+ click.echo(f"✅ Cleared {count} error(s) {scope}.", err=True)
175
+ click.echo("\nHooks will be retried on next execution.", err=True)
176
176
 
177
177
 
178
178
  @hook_errors_group.command(name="diagnose")
@@ -201,19 +201,19 @@ def diagnose_errors(hook_type):
201
201
 
202
202
  if not errors:
203
203
  if hook_type:
204
- click.echo(f"No errors to diagnose for hook type: {hook_type}")
204
+ click.echo(f"No errors to diagnose for hook type: {hook_type}", err=True)
205
205
  else:
206
- click.echo("No errors to diagnose. Hook system is healthy! ✅")
206
+ click.echo("No errors to diagnose. Hook system is healthy! ✅", err=True)
207
207
  return
208
208
 
209
- click.echo("\n" + "=" * 80)
210
- click.echo("Hook Error Diagnostics")
211
- click.echo("=" * 80)
209
+ click.echo("\n" + "=" * 80, err=True)
210
+ click.echo("Hook Error Diagnostics", err=True)
211
+ click.echo("=" * 80, err=True)
212
212
 
213
213
  for key, data in errors.items():
214
- click.echo(f"\n🔴 Error: {data['type']}")
215
- click.echo(f" Hook: {data['hook_type']}")
216
- click.echo(f" Count: {data['count']} failures")
214
+ click.echo(f"\n🔴 Error: {data['type']}", err=True)
215
+ click.echo(f" Hook: {data['hook_type']}", err=True)
216
+ click.echo(f" Count: {data['count']} failures", err=True)
217
217
 
218
218
  # Generate and show fix suggestion
219
219
  error_info = {
@@ -223,13 +223,13 @@ def diagnose_errors(hook_type):
223
223
  }
224
224
  suggestion = error_memory.suggest_fix(error_info)
225
225
 
226
- click.echo("\n" + "-" * 80)
227
- click.echo(suggestion)
228
- click.echo("-" * 80)
226
+ click.echo("\n" + "-" * 80, err=True)
227
+ click.echo(suggestion, err=True)
228
+ click.echo("-" * 80, err=True)
229
229
 
230
- click.echo("\n" + "=" * 80)
231
- click.echo("After fixing issues, clear errors to retry:")
232
- click.echo(" claude-mpm hook-errors clear")
230
+ click.echo("\n" + "=" * 80, err=True)
231
+ click.echo("After fixing issues, clear errors to retry:", err=True)
232
+ click.echo(" claude-mpm hook-errors clear", err=True)
233
233
 
234
234
 
235
235
  @hook_errors_group.command(name="status")
@@ -244,27 +244,27 @@ def show_status():
244
244
  error_memory = get_hook_error_memory()
245
245
  summary = error_memory.get_error_summary()
246
246
 
247
- click.echo("\n📊 Hook Error Memory Status")
248
- click.echo("=" * 80)
247
+ click.echo("\n📊 Hook Error Memory Status", err=True)
248
+ click.echo("=" * 80, err=True)
249
249
 
250
250
  if summary["total_errors"] == 0:
251
- click.echo("✅ Status: Healthy (no errors recorded)")
251
+ click.echo("✅ Status: Healthy (no errors recorded)", err=True)
252
252
  else:
253
- click.echo(f"⚠️ Status: {summary['total_errors']} error(s) recorded")
254
- click.echo(f" Unique errors: {summary['unique_errors']}")
253
+ click.echo(f"⚠️ Status: {summary['total_errors']} error(s) recorded", err=True)
254
+ click.echo(f" Unique errors: {summary['unique_errors']}", err=True)
255
255
 
256
256
  # Show which hooks are affected
257
257
  if summary["errors_by_hook"]:
258
258
  affected_hooks = list(summary["errors_by_hook"].keys())
259
- click.echo(f" Affected hooks: {', '.join(affected_hooks)}")
259
+ click.echo(f" Affected hooks: {', '.join(affected_hooks)}", err=True)
260
260
 
261
- click.echo(f"\n📁 Memory file: {summary['memory_file']}")
262
- click.echo(f" Exists: {Path(summary['memory_file']).exists()}")
261
+ click.echo(f"\n📁 Memory file: {summary['memory_file']}", err=True)
262
+ click.echo(f" Exists: {Path(summary['memory_file']).exists()}", err=True)
263
263
 
264
- click.echo("\nCommands:")
265
- click.echo(" claude-mpm hook-errors list # View detailed errors")
266
- click.echo(" claude-mpm hook-errors diagnose # Get fix suggestions")
267
- click.echo(" claude-mpm hook-errors clear # Clear and retry")
264
+ click.echo("\nCommands:", err=True)
265
+ click.echo(" claude-mpm hook-errors list # View detailed errors", err=True)
266
+ click.echo(" claude-mpm hook-errors diagnose # Get fix suggestions", err=True)
267
+ click.echo(" claude-mpm hook-errors clear # Clear and retry", err=True)
268
268
 
269
269
 
270
270
  # Register the command group
@@ -5,7 +5,15 @@ aliases: [mpm-config]
5
5
  migration_target: /mpm/config
6
6
  category: config
7
7
  description: Manage Claude MPM configuration
8
+ deprecated: true
9
+ deprecated_in: "5.5.0"
10
+ replacement: "skill:mpm-config"
8
11
  ---
12
+
13
+ > **Deprecated:** This command file is deprecated in favor of the `mpm-config` skill.
14
+ > For Claude Code 2.1.3+, use the skill-based `/mpm-config` command instead.
15
+ > This file is kept for backward compatibility with Claude Code < 2.1.3.
16
+
9
17
  # /mpm-config
10
18
 
11
19
  Unified configuration management with auto-detection.
@@ -5,7 +5,15 @@ aliases: [mpm-doctor]
5
5
  migration_target: /mpm/system:doctor
6
6
  category: system
7
7
  description: Run diagnostic checks on Claude MPM installation
8
+ deprecated: true
9
+ deprecated_in: "5.5.0"
10
+ replacement: "skill:mpm-doctor"
8
11
  ---
12
+
13
+ > **Deprecated:** This command file is deprecated in favor of the `mpm-doctor` skill.
14
+ > For Claude Code 2.1.3+, use the skill-based `/mpm-doctor` command instead.
15
+ > This file is kept for backward compatibility with Claude Code < 2.1.3.
16
+
9
17
  # /mpm-doctor
10
18
 
11
19
  Run comprehensive diagnostics on Claude MPM installation.
@@ -5,7 +5,15 @@ aliases: [mpm-help]
5
5
  migration_target: /mpm/system:help
6
6
  category: system
7
7
  description: Display help for Claude MPM commands
8
+ deprecated: true
9
+ deprecated_in: "5.5.0"
10
+ replacement: "skill:mpm-help"
8
11
  ---
12
+
13
+ > **Deprecated:** This command file is deprecated in favor of the `mpm-help` skill.
14
+ > For Claude Code 2.1.3+, use the skill-based `/mpm-help` command instead.
15
+ > This file is kept for backward compatibility with Claude Code < 2.1.3.
16
+
9
17
  # /mpm-help
10
18
 
11
19
  Show help for MPM commands. Delegates to PM agent.
@@ -5,7 +5,15 @@ aliases: [mpm-init]
5
5
  migration_target: /mpm/system:init
6
6
  category: system
7
7
  description: Initialize or update project for Claude Code and MPM
8
+ deprecated: true
9
+ deprecated_in: "5.5.0"
10
+ replacement: "skill:mpm-init"
8
11
  ---
12
+
13
+ > **Deprecated:** This command file is deprecated in favor of the `mpm-init` skill.
14
+ > For Claude Code 2.1.3+, use the skill-based `/mpm-init` command instead.
15
+ > This file is kept for backward compatibility with Claude Code < 2.1.3.
16
+
9
17
  # /mpm-init
10
18
 
11
19
  Initialize or intelligently update project for Claude Code and Claude MPM.
@@ -5,7 +5,15 @@ aliases: [mpm-monitor]
5
5
  migration_target: /mpm/system:monitor
6
6
  category: system
7
7
  description: Control monitoring server and dashboard
8
+ deprecated: true
9
+ deprecated_in: "5.5.0"
10
+ replacement: "skill:mpm-monitor"
8
11
  ---
12
+
13
+ > **Deprecated:** This command file is deprecated in favor of the `mpm-monitor` skill.
14
+ > For Claude Code 2.1.3+, use the skill-based `/mpm-monitor` command instead.
15
+ > This file is kept for backward compatibility with Claude Code < 2.1.3.
16
+
9
17
  # /mpm-monitor
10
18
 
11
19
  Manage Socket.IO monitoring server for real-time dashboard.
@@ -5,7 +5,15 @@ aliases: [mpm-organize]
5
5
  migration_target: /mpm/system:organize
6
6
  category: system
7
7
  description: Organize project files with intelligent consolidation
8
+ deprecated: true
9
+ deprecated_in: "5.5.0"
10
+ replacement: "skill:mpm-organize"
8
11
  ---
12
+
13
+ > **Deprecated:** This command file is deprecated in favor of the `mpm-organize` skill.
14
+ > For Claude Code 2.1.3+, use the skill-based `/mpm-organize` command instead.
15
+ > This file is kept for backward compatibility with Claude Code < 2.1.3.
16
+
9
17
  # /mpm-organize
10
18
 
11
19
  Organize ALL project files with intelligent detection, consolidation, and pruning.
@@ -5,7 +5,15 @@ aliases: [mpm-postmortem]
5
5
  migration_target: /mpm/analysis:postmortem
6
6
  category: analysis
7
7
  description: Analyze session errors and suggest improvements
8
+ deprecated: true
9
+ deprecated_in: "5.5.0"
10
+ replacement: "skill:mpm-postmortem"
8
11
  ---
12
+
13
+ > **Deprecated:** This command file is deprecated in favor of the `mpm-postmortem` skill.
14
+ > For Claude Code 2.1.3+, use the skill-based `/mpm-postmortem` command instead.
15
+ > This file is kept for backward compatibility with Claude Code < 2.1.3.
16
+
9
17
  # /mpm-postmortem
10
18
 
11
19
  Analyze session errors and generate improvement suggestions.
@@ -5,7 +5,15 @@ aliases: [mpm-session-resume]
5
5
  migration_target: /mpm/session:resume
6
6
  category: session
7
7
  description: Load context from paused session
8
+ deprecated: true
9
+ deprecated_in: "5.5.0"
10
+ replacement: "skill:mpm-session-resume"
8
11
  ---
12
+
13
+ > **Deprecated:** This command file is deprecated in favor of the `mpm-session-resume` skill.
14
+ > For Claude Code 2.1.3+, use the skill-based `/mpm-session-resume` command instead.
15
+ > This file is kept for backward compatibility with Claude Code < 2.1.3.
16
+
9
17
  # /mpm-session-resume
10
18
 
11
19
  Load and display context from most recent paused session.
@@ -5,7 +5,15 @@ aliases: [mpm-status]
5
5
  migration_target: /mpm/system:status
6
6
  category: system
7
7
  description: Display Claude MPM system status
8
+ deprecated: true
9
+ deprecated_in: "5.5.0"
10
+ replacement: "skill:mpm-status"
8
11
  ---
12
+
13
+ > **Deprecated:** This command file is deprecated in favor of the `mpm-status` skill.
14
+ > For Claude Code 2.1.3+, use the skill-based `/mpm-status` command instead.
15
+ > This file is kept for backward compatibility with Claude Code < 2.1.3.
16
+
9
17
  # /mpm-status
10
18
 
11
19
  Show MPM system status. Delegates to PM agent.
@@ -5,7 +5,15 @@ aliases: [mpm-ticket-view]
5
5
  migration_target: /mpm/ticket:view
6
6
  category: tickets
7
7
  description: Orchestrate ticketing agent for project management workflows
8
+ deprecated: true
9
+ deprecated_in: "5.5.0"
10
+ replacement: "skill:mpm-ticket-view"
8
11
  ---
12
+
13
+ > **Deprecated:** This command file is deprecated in favor of the `mpm-ticket-view` skill.
14
+ > For Claude Code 2.1.3+, use the skill-based `/mpm-ticket-view` command instead.
15
+ > This file is kept for backward compatibility with Claude Code < 2.1.3.
16
+
9
17
  # /mpm-ticket
10
18
 
11
19
  High-level ticketing workflows delegating to ticketing agent.
@@ -5,7 +5,15 @@ aliases: [mpm-version]
5
5
  migration_target: /mpm/system:version
6
6
  category: system
7
7
  description: Show version information
8
+ deprecated: true
9
+ deprecated_in: "5.5.0"
10
+ replacement: "skill:mpm-version"
8
11
  ---
12
+
13
+ > **Deprecated:** This command file is deprecated in favor of the `mpm-version` skill.
14
+ > For Claude Code 2.1.3+, use the skill-based `/mpm-version` command instead.
15
+ > This file is kept for backward compatibility with Claude Code < 2.1.3.
16
+
9
17
  # /mpm-version
10
18
 
11
19
  Display version information for MPM, agents, and skills.
@@ -6,7 +6,15 @@ migration_target: /mpm
6
6
  category: system
7
7
  deprecated_aliases: []
8
8
  description: Access Claude MPM functionality and manage multi-agent orchestration
9
+ deprecated: true
10
+ deprecated_in: "5.5.0"
11
+ replacement: "skill:mpm"
9
12
  ---
13
+
14
+ > **Deprecated:** This command file is deprecated in favor of the `mpm` skill.
15
+ > For Claude Code 2.1.3+, use the skill-based `/mpm` command instead.
16
+ > This file is kept for backward compatibility with Claude Code < 2.1.3.
17
+
10
18
  # Claude MPM - Multi-Agent Project Manager
11
19
 
12
20
  Access Claude MPM functionality and manage your multi-agent orchestration.
File without changes
File without changes
@@ -111,6 +111,8 @@ WHY version checking:
111
111
  Security: Version checking prevents execution on incompatible environments.
112
112
  """
113
113
  MIN_CLAUDE_VERSION = "1.0.92"
114
+ # Minimum version for user-invocable skills support
115
+ MIN_SKILLS_VERSION = "2.1.3"
114
116
 
115
117
 
116
118
  def check_claude_version() -> Tuple[bool, Optional[str]]:
@@ -496,9 +498,12 @@ class ClaudeHookHandler:
496
498
  """
497
499
  if modified_input is not None:
498
500
  # Claude Code v2.0.30+ supports modifying PreToolUse tool inputs
499
- print(json.dumps({"action": "continue", "tool_input": modified_input}))
501
+ print(
502
+ json.dumps({"action": "continue", "tool_input": modified_input}),
503
+ flush=True,
504
+ )
500
505
  else:
501
- print(json.dumps({"action": "continue"}))
506
+ print(json.dumps({"action": "continue"}), flush=True)
502
507
 
503
508
  # Delegation methods for compatibility with event_handlers
504
509
  def _track_delegation(self, session_id: str, agent_type: str, request_data=None):
@@ -676,7 +681,7 @@ def main():
676
681
  f"Skipping hook processing due to version incompatibility ({version})",
677
682
  file=sys.stderr,
678
683
  )
679
- print(json.dumps({"action": "continue"}))
684
+ print(json.dumps({"action": "continue"}), flush=True)
680
685
  sys.exit(0)
681
686
 
682
687
  def cleanup_handler(signum=None, frame=None):
@@ -689,7 +694,7 @@ def main():
689
694
  )
690
695
  # Only output continue if we haven't already (i.e., if interrupted by signal)
691
696
  if signum is not None and not _continue_printed:
692
- print(json.dumps({"action": "continue"}))
697
+ print(json.dumps({"action": "continue"}), flush=True)
693
698
  _continue_printed = True
694
699
  sys.exit(0)
695
700
 
@@ -727,7 +732,7 @@ def main():
727
732
  except Exception as e:
728
733
  # Only output continue if not already printed
729
734
  if not _continue_printed:
730
- print(json.dumps({"action": "continue"}))
735
+ print(json.dumps({"action": "continue"}), flush=True)
731
736
  _continue_printed = True
732
737
  # Log error for debugging
733
738
  if DEBUG:
@@ -48,15 +48,10 @@ echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] PYTHONPATH: $PYTHONPATH" >> /tmp/hook
48
48
  echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Running: $PYTHON_CMD -m claude_mpm.hooks.claude_hooks.hook_handler" >> /tmp/hook-wrapper.log
49
49
  echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] SOCKETIO_PORT: $CLAUDE_MPM_SOCKETIO_PORT" >> /tmp/hook-wrapper.log
50
50
 
51
- # Run the Python hook handler as a module with error handling
52
- # Use exec to replace the shell process, but wrap in error handling
53
- if ! "$PYTHON_CMD" -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/hook-error.log; then
54
- # If the Python handler fails, always return continue to not block Claude
55
- echo '{"action": "continue"}'
56
- # Log the error for debugging
57
- echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Hook handler failed, see /tmp/hook-error.log" >> /tmp/hook-wrapper.log
58
- exit 0
59
- fi
51
+ # Run the Python hook handler as a module
52
+ # Python handler is responsible for ALL stdout output (including error fallback)
53
+ # Redirect stderr to log file for debugging
54
+ "$PYTHON_CMD" -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/hook-error.log
60
55
 
61
- # Success - Python handler already printed continue, just exit
62
- exit 0
56
+ # Exit with Python's exit code (should always be 0)
57
+ exit $?
@@ -10,7 +10,7 @@ import os
10
10
  import re
11
11
  import shutil
12
12
  import stat
13
- import subprocess
13
+ import subprocess # nosec B404 - Safe: only uses hardcoded 'claude' CLI command, no user input
14
14
  from pathlib import Path
15
15
  from typing import Dict, List, Optional, Tuple
16
16
 
@@ -194,6 +194,8 @@ main "$@"
194
194
  MIN_CLAUDE_VERSION = "1.0.92"
195
195
  # Minimum version for PreToolUse input modification support
196
196
  MIN_PRETOOL_MODIFY_VERSION = "2.0.30"
197
+ # Minimum version for user-invocable skills support
198
+ MIN_SKILLS_VERSION = "2.1.3"
197
199
 
198
200
  def __init__(self):
199
201
  """Initialize the hook installer."""
@@ -220,7 +222,7 @@ main "$@"
220
222
 
221
223
  try:
222
224
  # Run claude --version command
223
- result = subprocess.run(
225
+ result = subprocess.run( # nosec B607 B603 - Safe: hardcoded command, no user input
224
226
  ["claude", "--version"],
225
227
  capture_output=True,
226
228
  text=True,
@@ -331,6 +333,53 @@ main "$@"
331
333
 
332
334
  return True
333
335
 
336
+ def _version_meets_minimum(self, version: str, min_version: str) -> bool:
337
+ """Check if a version meets minimum requirements.
338
+
339
+ Args:
340
+ version: Current version string (e.g., "2.1.3")
341
+ min_version: Minimum required version string (e.g., "2.1.3")
342
+
343
+ Returns:
344
+ True if version meets or exceeds minimum, False otherwise
345
+ """
346
+
347
+ def parse_version(v: str) -> List[int]:
348
+ """Parse semantic version string to list of integers."""
349
+ try:
350
+ return [int(x) for x in v.split(".")]
351
+ except (ValueError, AttributeError):
352
+ return [0]
353
+
354
+ current = parse_version(version)
355
+ required = parse_version(min_version)
356
+
357
+ # Compare versions
358
+ for i in range(max(len(current), len(required))):
359
+ curr_part = current[i] if i < len(current) else 0
360
+ req_part = required[i] if i < len(required) else 0
361
+
362
+ if curr_part < req_part:
363
+ return False
364
+ if curr_part > req_part:
365
+ return True
366
+
367
+ return True
368
+
369
+ def supports_user_invocable_skills(self) -> bool:
370
+ """Check if Claude Code version supports user-invocable skills.
371
+
372
+ User-invocable skills were added in Claude Code v2.1.3.
373
+ This feature allows users to invoke skills via slash commands.
374
+
375
+ Returns:
376
+ True if version supports user-invocable skills, False otherwise
377
+ """
378
+ version = self.get_claude_version()
379
+ if not version:
380
+ return False
381
+ return self._version_meets_minimum(version, self.MIN_SKILLS_VERSION)
382
+
334
383
  def get_hook_script_path(self) -> Path:
335
384
  """Get the path to the hook handler script based on installation method.
336
385
 
@@ -556,7 +605,22 @@ main "$@"
556
605
  self._cleanup_old_settings()
557
606
 
558
607
  def _install_commands(self) -> None:
559
- """Install custom commands for Claude Code."""
608
+ """Install custom commands for Claude Code.
609
+
610
+ For Claude Code >= 2.1.3, commands are deployed as skills via PMSkillsDeployerService.
611
+ This method provides backward compatibility for older versions.
612
+ """
613
+ # Check if skills-based commands are supported
614
+ if self.supports_user_invocable_skills():
615
+ self.logger.info(
616
+ "Claude Code >= 2.1.3 detected. Commands deployed as skills - "
617
+ "skipping legacy command installation."
618
+ )
619
+ return
620
+
621
+ # Legacy installation for older Claude Code versions
622
+ self.logger.info("Installing legacy commands for Claude Code < 2.1.3")
623
+
560
624
  # Find commands directory using proper resource resolution
561
625
  try:
562
626
  from ...core.unified_paths import get_package_resource_path
@@ -782,7 +846,7 @@ main "$@"
782
846
  if "hooks" in settings:
783
847
  status["configured_events"] = list(settings["hooks"].keys())
784
848
  configured_in_local = True
785
- except Exception:
849
+ except Exception: # nosec B110 - Intentional: ignore errors reading settings file
786
850
  pass
787
851
 
788
852
  # Also check old settings file
@@ -796,7 +860,7 @@ main "$@"
796
860
  status["warning"] = (
797
861
  "Hooks found in settings.local.json but Claude Code reads from settings.json"
798
862
  )
799
- except Exception:
863
+ except Exception: # nosec B110 - Intentional: ignore errors reading old settings file
800
864
  pass
801
865
 
802
866
  status["settings_location"] = (
File without changes
File without changes