claude-mpm 5.4.89__py3-none-any.whl → 5.4.91__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 (32) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +67 -779
  4. claude_mpm/cli/commands/mpm_init/core.py +2 -2
  5. claude_mpm/cli/startup.py +51 -14
  6. claude_mpm/cli/startup_display.py +72 -5
  7. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  8. claude_mpm/hooks/claude_hooks/event_handlers.py +17 -0
  9. claude_mpm/init.py +1 -1
  10. claude_mpm/services/pm_skills_deployer.py +177 -83
  11. claude_mpm/services/socketio/handlers/hook.py +14 -7
  12. claude_mpm/services/socketio/server/main.py +12 -4
  13. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  14. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  15. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  16. claude_mpm/skills/bundled/pm/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
  17. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  18. claude_mpm/skills/skill_manager.py +4 -4
  19. claude_mpm-5.4.91.dist-info/METADATA +377 -0
  20. {claude_mpm-5.4.89.dist-info → claude_mpm-5.4.91.dist-info}/RECORD +31 -27
  21. claude_mpm-5.4.89.dist-info/METADATA +0 -1023
  22. /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
  23. /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
  24. /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
  25. /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
  26. /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
  27. /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
  28. {claude_mpm-5.4.89.dist-info → claude_mpm-5.4.91.dist-info}/WHEEL +0 -0
  29. {claude_mpm-5.4.89.dist-info → claude_mpm-5.4.91.dist-info}/entry_points.txt +0 -0
  30. {claude_mpm-5.4.89.dist-info → claude_mpm-5.4.91.dist-info}/licenses/LICENSE +0 -0
  31. {claude_mpm-5.4.89.dist-info → claude_mpm-5.4.91.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  32. {claude_mpm-5.4.89.dist-info → claude_mpm-5.4.91.dist-info}/top_level.txt +0 -0
@@ -688,9 +688,9 @@ class MPMInitCommand:
688
688
  return len(text) // 4
689
689
 
690
690
  def _deploy_pm_skills(self) -> None:
691
- """Deploy PM skills templates to project .claude-mpm directory.
691
+ """Deploy PM skills templates to project .claude directory.
692
692
 
693
- Copies PM skills from bundled templates to .claude-mpm/skills/pm/
693
+ Copies PM skills from bundled templates to .claude/skills/
694
694
  with version tracking and checksum validation.
695
695
  """
696
696
  try:
claude_mpm/cli/startup.py CHANGED
@@ -1139,33 +1139,70 @@ def show_skill_summary():
1139
1139
 
1140
1140
 
1141
1141
  def verify_and_show_pm_skills():
1142
- """Verify PM skills and display status.
1142
+ """Verify PM skills and display status with enhanced validation.
1143
1143
 
1144
- WHY: PM skills are essential for PM agent operation.
1145
- Shows deployment status and auto-deploys if missing.
1144
+ WHY: PM skills are CRITICAL for PM agent operation. PM must KNOW if
1145
+ framework knowledge is unavailable at startup. Enhanced validation
1146
+ checks all required skills exist, are not corrupted, and auto-repairs
1147
+ if needed.
1148
+
1149
+ Shows deployment status:
1150
+ - "✓ PM skills: 8/8 verified" if all required skills are valid
1151
+ - "⚠ PM skills: 2 missing, auto-repairing..." if issues detected
1152
+ - Non-blocking but visible warning if auto-repair fails
1146
1153
  """
1147
1154
  try:
1148
1155
  from pathlib import Path
1149
1156
 
1150
- from ..services.pm_skills_deployer import PMSkillsDeployerService
1157
+ from ..services.pm_skills_deployer import (
1158
+ REQUIRED_PM_SKILLS,
1159
+ PMSkillsDeployerService,
1160
+ )
1151
1161
 
1152
1162
  deployer = PMSkillsDeployerService()
1153
1163
  project_dir = Path.cwd()
1154
1164
 
1155
- result = deployer.verify_pm_skills(project_dir)
1165
+ # Verify with auto-repair enabled
1166
+ result = deployer.verify_pm_skills(project_dir, auto_repair=True)
1156
1167
 
1157
1168
  if result.verified:
1158
- # Show verified status
1159
- print(f"✓ PM skills: {result.skill_count} verified", flush=True)
1169
+ # Show verified status with count
1170
+ total_required = len(REQUIRED_PM_SKILLS)
1171
+ print(
1172
+ f"✓ PM skills: {total_required}/{total_required} verified", flush=True
1173
+ )
1160
1174
  else:
1161
- # Auto-deploy if missing
1162
- print("Deploying PM skills...", end="", flush=True)
1163
- deploy_result = deployer.deploy_pm_skills(project_dir)
1164
- if deploy_result.success:
1165
- total = len(deploy_result.deployed) + len(deploy_result.skipped)
1166
- print(f"\r✓ PM skills: {total} deployed" + " " * 20, flush=True)
1175
+ # Show warning with details
1176
+ missing_count = len(result.missing_skills)
1177
+ corrupted_count = len(result.corrupted_skills)
1178
+
1179
+ # Build status message
1180
+ issues = []
1181
+ if missing_count > 0:
1182
+ issues.append(f"{missing_count} missing")
1183
+ if corrupted_count > 0:
1184
+ issues.append(f"{corrupted_count} corrupted")
1185
+
1186
+ status = ", ".join(issues)
1187
+
1188
+ # Check if auto-repair was attempted
1189
+ if "Auto-repaired" in result.message:
1190
+ # Auto-repair succeeded
1191
+ total_required = len(REQUIRED_PM_SKILLS)
1192
+ print(
1193
+ f"✓ PM skills: {total_required}/{total_required} verified (auto-repaired)",
1194
+ flush=True,
1195
+ )
1167
1196
  else:
1168
- print("\r⚠ PM skills: deployment failed" + " " * 20, flush=True)
1197
+ # Auto-repair failed or not attempted
1198
+ print(f"⚠ PM skills: {status}", flush=True)
1199
+
1200
+ # Log warnings for debugging
1201
+ from ..core.logger import get_logger
1202
+
1203
+ logger = get_logger("cli")
1204
+ for warning in result.warnings:
1205
+ logger.warning(f"PM skills: {warning}")
1169
1206
 
1170
1207
  except ImportError:
1171
1208
  # PM skills deployer not available - skip silently
@@ -7,7 +7,7 @@ Shows welcome message, version info, ASCII art, and what's new section.
7
7
  import os
8
8
  import re
9
9
  import shutil
10
- import subprocess
10
+ import subprocess # nosec B404 - required for git operations
11
11
  from pathlib import Path
12
12
  from typing import List
13
13
 
@@ -65,8 +65,8 @@ def _get_recent_commits(max_commits: int = 3) -> List[str]:
65
65
  if not is_git_repository("."):
66
66
  return []
67
67
 
68
- # Run git log with custom format
69
- result = subprocess.run(
68
+ # Run git log with custom format (safe - no user input)
69
+ result = subprocess.run( # nosec B603 B607
70
70
  ["git", "log", "--format=%h • %ar • %s", f"-{max_commits}"],
71
71
  capture_output=True,
72
72
  text=True,
@@ -216,6 +216,59 @@ def _get_cwd_display(max_width: int = 40) -> str:
216
216
  return "..." + cwd[-(max_width - 3) :]
217
217
 
218
218
 
219
+ def _count_mpm_skills() -> int:
220
+ """
221
+ Count user-level MPM skills from ~/.claude/skills/.
222
+
223
+ Returns:
224
+ Number of skill directories with SKILL.md files
225
+ """
226
+ try:
227
+ user_skills_dir = Path.home() / ".claude" / "skills"
228
+ if not user_skills_dir.exists():
229
+ return 0
230
+
231
+ # Count directories with SKILL.md (skill directories)
232
+ skill_count = 0
233
+ for item in user_skills_dir.iterdir():
234
+ if item.is_dir():
235
+ skill_file = item / "SKILL.md"
236
+ if skill_file.exists():
237
+ skill_count += 1
238
+ # Also count standalone .md files (legacy format)
239
+ elif item.is_file() and item.suffix == ".md" and item.name != "README.md":
240
+ skill_count += 1
241
+
242
+ return skill_count
243
+ except Exception:
244
+ # Silent failure - return 0 if any error
245
+ return 0
246
+
247
+
248
+ def _count_deployed_agents() -> int:
249
+ """
250
+ Count deployed agents from .claude/agents/.
251
+
252
+ Returns:
253
+ Number of deployed agent files
254
+ """
255
+ try:
256
+ deploy_target = Path.cwd() / ".claude" / "agents"
257
+ if not deploy_target.exists():
258
+ return 0
259
+
260
+ # Count .md files, excluding README and other docs
261
+ agent_files = [
262
+ f
263
+ for f in deploy_target.glob("*.md")
264
+ if not f.name.startswith(("README", "INSTRUCTIONS", "."))
265
+ ]
266
+ return len(agent_files)
267
+ except Exception:
268
+ # Silent failure - return 0 if any error
269
+ return 0
270
+
271
+
219
272
  def _format_two_column_line(
220
273
  left: str, right: str, left_panel_width: int, right_panel_width: int
221
274
  ) -> str:
@@ -402,11 +455,25 @@ def display_startup_banner(version: str, logging_level: str) -> None:
402
455
  )
403
456
  )
404
457
 
405
- # Line 10: Model info | separator
458
+ # Line 10: Model info with counts | separator
406
459
  separator = "─" * right_panel_width
460
+ agent_count = _count_deployed_agents()
461
+ skill_count = _count_mpm_skills()
462
+
463
+ # Format: "Sonnet 4.5 · 44 agents, 19 skills"
464
+ if agent_count > 0 or skill_count > 0:
465
+ counts_text = []
466
+ if agent_count > 0:
467
+ counts_text.append(f"{agent_count} agent{'s' if agent_count != 1 else ''}")
468
+ if skill_count > 0:
469
+ counts_text.append(f"{skill_count} skill{'s' if skill_count != 1 else ''}")
470
+ model_info = f"Sonnet 4.5 · {', '.join(counts_text)}"
471
+ else:
472
+ model_info = "Sonnet 4.5 · Claude MPM"
473
+
407
474
  lines.append(
408
475
  _format_two_column_line(
409
- "Sonnet 4.5 · Claude MPM", separator, left_panel_width, right_panel_width
476
+ model_info, separator, left_panel_width, right_panel_width
410
477
  )
411
478
  )
412
479
 
@@ -200,6 +200,23 @@ class EventHandlers:
200
200
 
201
201
  self.hook_handler._emit_socketio_event("", "pre_tool", pre_tool_data)
202
202
 
203
+ # Handle TodoWrite specially - emit dedicated todo_updated event
204
+ # WHY: Frontend expects todo_updated events for dashboard display
205
+ # The broadcaster.todo_updated() method exists but was never called
206
+ if tool_name == "TodoWrite" and tool_params.get("todos"):
207
+ todo_data = {
208
+ "todos": tool_params["todos"],
209
+ "total_count": len(tool_params["todos"]),
210
+ "session_id": session_id,
211
+ "timestamp": timestamp,
212
+ }
213
+ self.hook_handler._emit_socketio_event("", "todo_updated", todo_data)
214
+ if DEBUG:
215
+ print(
216
+ f" - Emitted todo_updated event with {len(tool_params['todos'])} todos for session {session_id[:8]}...",
217
+ file=sys.stderr,
218
+ )
219
+
203
220
  def _handle_task_delegation(
204
221
  self, tool_input: dict, pre_tool_data: dict, session_id: str
205
222
  ):
claude_mpm/init.py CHANGED
@@ -216,7 +216,7 @@ class ProjectInitializer:
216
216
  if deploy_result.deployed:
217
217
  print(
218
218
  f"✓ Deployed {len(deploy_result.deployed)} PM skill(s) "
219
- f"to .claude-mpm/skills/pm/"
219
+ f"to .claude/skills/"
220
220
  )
221
221
  else:
222
222
  self.logger.warning(