claude-mpm 5.4.65__py3-none-any.whl → 5.4.96__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (174) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md +405 -0
  3. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +66 -241
  4. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +107 -1928
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +82 -686
  6. claude_mpm/cli/__init__.py +5 -1
  7. claude_mpm/cli/commands/agents.py +2 -4
  8. claude_mpm/cli/commands/agents_reconcile.py +197 -0
  9. claude_mpm/cli/commands/autotodos.py +526 -0
  10. claude_mpm/cli/commands/configure.py +620 -21
  11. claude_mpm/cli/commands/monitor.py +2 -2
  12. claude_mpm/cli/commands/mpm_init/core.py +2 -2
  13. claude_mpm/cli/commands/skills.py +166 -14
  14. claude_mpm/cli/executor.py +89 -0
  15. claude_mpm/cli/interactive/__init__.py +10 -0
  16. claude_mpm/cli/interactive/agent_wizard.py +30 -50
  17. claude_mpm/cli/interactive/questionary_styles.py +65 -0
  18. claude_mpm/cli/interactive/skill_selector.py +481 -0
  19. claude_mpm/cli/parsers/base_parser.py +59 -1
  20. claude_mpm/cli/startup.py +184 -358
  21. claude_mpm/cli/startup_display.py +72 -5
  22. claude_mpm/cli/startup_logging.py +2 -2
  23. claude_mpm/commands/mpm-session-resume.md +1 -1
  24. claude_mpm/constants.py +1 -0
  25. claude_mpm/core/claude_runner.py +2 -2
  26. claude_mpm/core/hook_manager.py +51 -3
  27. claude_mpm/core/interactive_session.py +7 -7
  28. claude_mpm/core/output_style_manager.py +21 -13
  29. claude_mpm/core/unified_config.py +50 -8
  30. claude_mpm/core/unified_paths.py +30 -13
  31. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  32. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  33. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
  34. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
  35. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
  36. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
  37. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
  38. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
  39. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
  40. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
  41. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
  42. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
  43. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
  44. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
  45. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
  46. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  47. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  48. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
  49. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
  50. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
  51. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
  52. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
  53. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
  54. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
  55. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
  56. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  57. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
  58. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
  59. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
  60. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
  61. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
  62. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
  63. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
  64. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
  65. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
  66. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
  67. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
  68. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
  69. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
  70. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
  71. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
  72. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
  73. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
  74. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
  75. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
  76. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
  77. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
  78. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
  79. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  80. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
  81. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
  82. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
  83. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
  84. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  85. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
  86. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
  87. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  88. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  89. claude_mpm/dashboard/static/svelte-build/index.html +9 -9
  90. claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
  91. claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
  92. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  93. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  94. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  95. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  96. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +486 -0
  97. claude_mpm/hooks/claude_hooks/event_handlers.py +216 -11
  98. claude_mpm/hooks/claude_hooks/hook_handler.py +28 -4
  99. claude_mpm/hooks/claude_hooks/response_tracking.py +3 -1
  100. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  101. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  102. claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
  103. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +30 -6
  104. claude_mpm/hooks/session_resume_hook.py +85 -1
  105. claude_mpm/init.py +1 -1
  106. claude_mpm/services/agents/cache_git_manager.py +1 -1
  107. claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
  108. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
  109. claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
  110. claude_mpm/services/agents/startup_sync.py +5 -2
  111. claude_mpm/services/cli/__init__.py +3 -0
  112. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  113. claude_mpm/services/cli/session_resume_helper.py +10 -2
  114. claude_mpm/services/delegation_detector.py +175 -0
  115. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  116. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  117. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  118. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  119. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  120. claude_mpm/services/diagnostics/models.py +14 -1
  121. claude_mpm/services/event_log.py +317 -0
  122. claude_mpm/services/infrastructure/__init__.py +4 -0
  123. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  124. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  125. claude_mpm/services/monitor/daemon_manager.py +15 -4
  126. claude_mpm/services/monitor/management/lifecycle.py +8 -2
  127. claude_mpm/services/monitor/server.py +106 -16
  128. claude_mpm/services/pm_skills_deployer.py +177 -83
  129. claude_mpm/services/skills/git_skill_source_manager.py +5 -1
  130. claude_mpm/services/skills/selective_skill_deployer.py +114 -26
  131. claude_mpm/services/socketio/handlers/hook.py +14 -7
  132. claude_mpm/services/socketio/server/main.py +12 -4
  133. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  134. claude_mpm/skills/bundled/pm/mpm-bug-reporting/SKILL.md +248 -0
  135. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  136. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  137. claude_mpm/skills/bundled/pm/mpm-teaching-mode/SKILL.md +657 -0
  138. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  139. claude_mpm/skills/skill_manager.py +4 -4
  140. claude_mpm/utils/agent_dependency_loader.py +4 -2
  141. claude_mpm/utils/robust_installer.py +10 -6
  142. claude_mpm-5.4.96.dist-info/METADATA +377 -0
  143. {claude_mpm-5.4.65.dist-info → claude_mpm-5.4.96.dist-info}/RECORD +153 -142
  144. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
  145. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
  146. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
  147. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
  148. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
  149. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
  150. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
  151. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
  152. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
  153. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-312.pyc +0 -0
  154. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-312.pyc +0 -0
  155. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-312.pyc +0 -0
  156. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-312.pyc +0 -0
  157. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-312.pyc +0 -0
  158. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-312.pyc +0 -0
  159. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-312.pyc +0 -0
  160. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-312.pyc +0 -0
  161. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-312.pyc +0 -0
  162. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-312.pyc +0 -0
  163. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-312.pyc +0 -0
  164. claude_mpm-5.4.65.dist-info/METADATA +0 -999
  165. /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
  166. /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
  167. /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
  168. /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
  169. /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
  170. {claude_mpm-5.4.65.dist-info → claude_mpm-5.4.96.dist-info}/WHEEL +0 -0
  171. {claude_mpm-5.4.65.dist-info → claude_mpm-5.4.96.dist-info}/entry_points.txt +0 -0
  172. {claude_mpm-5.4.65.dist-info → claude_mpm-5.4.96.dist-info}/licenses/LICENSE +0 -0
  173. {claude_mpm-5.4.65.dist-info → claude_mpm-5.4.96.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  174. {claude_mpm-5.4.65.dist-info → claude_mpm-5.4.96.dist-info}/top_level.txt +0 -0
@@ -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
 
@@ -672,7 +672,7 @@ async def trigger_vector_search_indexing(project_root: Optional[Path] = None) ->
672
672
  # Using installed binary
673
673
  cmd = [vector_search_path, "index", str(project_root)]
674
674
 
675
- logger.info(
675
+ logger.debug(
676
676
  "MCP Vector Search: Starting background indexing for improved code search"
677
677
  )
678
678
 
@@ -761,7 +761,7 @@ def _start_vector_search_subprocess(project_root: Optional[Path] = None) -> None
761
761
  else:
762
762
  cmd = [vector_search_path, "index", str(project_root)]
763
763
 
764
- logger.info(
764
+ logger.debug(
765
765
  "MCP Vector Search: Starting background indexing for improved code search"
766
766
  )
767
767
 
@@ -21,7 +21,7 @@ Load and display context from most recent paused session.
21
21
  - Git context and recent commits
22
22
  - Next recommended actions
23
23
 
24
- **Session location:** `.claude-mpm/sessions/session-*.json`
24
+ **Session location:** `.claude-mpm/sessions/session-*.md`
25
25
 
26
26
  **Token usage:** ~20-40k tokens (10-20% of context budget)
27
27
 
claude_mpm/constants.py CHANGED
@@ -169,6 +169,7 @@ class SkillsCommands(str, Enum):
169
169
  INFO = "info"
170
170
  CONFIG = "config"
171
171
  CONFIGURE = "configure" # Interactive skills selection (like agents configure)
172
+ SELECT = "select" # Interactive topic-grouped skill selector
172
173
  # GitHub deployment commands
173
174
  DEPLOY_FROM_GITHUB = "deploy-github"
174
175
  LIST_AVAILABLE = "list-available"
@@ -730,7 +730,7 @@ Use these agents to delegate specialized work via the Task tool.
730
730
  import json
731
731
 
732
732
  settings = json.loads(settings_file.read_text())
733
- if settings.get("activeOutputStyle") == "claude-mpm":
733
+ if settings.get("activeOutputStyle") == "Claude MPM":
734
734
  # Already active, check if file exists
735
735
  output_style_file = (
736
736
  Path.home() / ".claude" / "output-styles" / "claude-mpm.md"
@@ -740,7 +740,7 @@ Use these agents to delegate specialized work via the Task tool.
740
740
  "Output style 'Claude MPM' already deployed and active"
741
741
  )
742
742
  return
743
- except Exception:
743
+ except Exception: # nosec B110
744
744
  pass # Continue with deployment if we can't read settings
745
745
 
746
746
  # Read the OUTPUT_STYLE.md content if it exists
@@ -16,13 +16,15 @@ import contextlib
16
16
  import json
17
17
  import os
18
18
  import queue
19
- import subprocess
19
+ import subprocess # nosec B404
20
20
  import threading
21
21
  import uuid
22
22
  from datetime import datetime, timezone
23
23
  from typing import Any, Dict, Optional
24
24
 
25
25
  from ..core.logger import get_logger
26
+ from ..services.event_bus.event_bus import EventBus
27
+ from ..services.event_log import get_event_log
26
28
  from .hook_error_memory import get_hook_error_memory
27
29
  from .hook_performance_config import get_hook_performance_config
28
30
  from .unified_paths import get_package_root
@@ -46,6 +48,10 @@ class HookManager:
46
48
  # Initialize error memory for tracking and preventing repeated errors
47
49
  self.error_memory = get_hook_error_memory()
48
50
 
51
+ # Initialize event log and event bus for event-driven architecture
52
+ self.event_log = get_event_log()
53
+ self.event_bus = EventBus.get_instance()
54
+
49
55
  # Initialize background hook processing for async execution
50
56
  self.performance_config = get_hook_performance_config()
51
57
  queue_config = self.performance_config.get_queue_config()
@@ -100,6 +106,45 @@ class HookManager:
100
106
  self.background_thread.start()
101
107
  self.logger.debug("Started background hook processor thread")
102
108
 
109
+ def _publish_error_event(
110
+ self, hook_type: str, error_info: Dict[str, str], suggestion: str
111
+ ):
112
+ """Publish hook error event to event log and event bus.
113
+
114
+ WHY publish events:
115
+ - Decouple error detection from error handling
116
+ - Enable autotodos CLI to read from persistent event log
117
+ - Support real-time notifications via event bus
118
+ - Maintain audit trail of all hook errors
119
+
120
+ Args:
121
+ hook_type: Type of hook that failed
122
+ error_info: Error information from error detection
123
+ suggestion: Fix suggestion from error memory
124
+ """
125
+ try:
126
+ # Prepare event payload
127
+ payload = {
128
+ "error_type": error_info["type"],
129
+ "hook_type": hook_type,
130
+ "details": error_info.get("details", ""),
131
+ "full_message": error_info.get("match", ""),
132
+ "suggested_fix": suggestion,
133
+ "source": "hook_manager",
134
+ }
135
+
136
+ # Publish to event log (persistent storage)
137
+ self.event_log.append_event(
138
+ event_type="autotodo.error", payload=payload, status="pending"
139
+ )
140
+
141
+ # Publish to event bus (real-time listeners)
142
+ self.event_bus.publish("autotodo.error", payload)
143
+
144
+ except Exception as e:
145
+ # Don't let event publishing break hook processing
146
+ self.logger.debug(f"Failed to publish error event: {e}")
147
+
103
148
  def _execute_hook_sync(self, hook_data: Dict[str, Any]):
104
149
  """Execute a single hook synchronously in the background thread with error detection.
105
150
 
@@ -141,7 +186,7 @@ class HookManager:
141
186
  env["CLAUDE_MPM_HOOK_DEBUG"] = "true"
142
187
 
143
188
  # Execute with timeout in background thread
144
- result = subprocess.run(
189
+ result = subprocess.run( # nosec B603 B607
145
190
  ["python", str(self.hook_handler_path)],
146
191
  input=event_json,
147
192
  text=True,
@@ -157,7 +202,7 @@ class HookManager:
157
202
  )
158
203
 
159
204
  if error_info:
160
- # Record the error
205
+ # Record the error in memory (for skipping repeated failures)
161
206
  self.error_memory.record_error(error_info, hook_type)
162
207
 
163
208
  # Get fix suggestion
@@ -165,6 +210,9 @@ class HookManager:
165
210
 
166
211
  # Log error with suggestion
167
212
  self.logger.warning(f"Hook {hook_type} error detected:\n{suggestion}")
213
+
214
+ # Publish event to event log for autotodos processing
215
+ self._publish_error_event(hook_type, error_info, suggestion)
168
216
  elif result.returncode != 0:
169
217
  # Non-zero return without detected pattern
170
218
  self.logger.debug(f"Hook {hook_type} returned code {result.returncode}")
@@ -12,7 +12,7 @@ of InteractiveSession without circular dependency issues.
12
12
 
13
13
  import contextlib
14
14
  import os
15
- import subprocess
15
+ import subprocess # nosec B404
16
16
  import uuid
17
17
  from pathlib import Path
18
18
  from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple
@@ -359,19 +359,19 @@ class InteractiveSession:
359
359
  osm = self.runner.framework_loader.output_style_manager
360
360
  if osm:
361
361
  if osm.claude_version and osm.supports_output_styles():
362
- # Check if claude-mpm style is active
362
+ # Check if Claude MPM style is active
363
363
  settings_file = osm.settings_file
364
364
  if settings_file.exists():
365
365
  import json
366
366
 
367
367
  settings = json.loads(settings_file.read_text())
368
368
  active_style = settings.get("activeOutputStyle")
369
- if active_style == "claude-mpm":
370
- return "Output Style: claude-mpm ✅"
369
+ if active_style in ("Claude MPM", "Claude MPM Teacher"):
370
+ return f"Output Style: {active_style} ✅"
371
371
  return f"Output Style: {active_style or 'none'}"
372
372
  return "Output Style: Available"
373
373
  return "Output Style: Injected (legacy)"
374
- except Exception:
374
+ except Exception: # nosec B110
375
375
  pass
376
376
  return None
377
377
 
@@ -582,7 +582,7 @@ class InteractiveSession:
582
582
  )
583
583
 
584
584
  # This will not return if successful
585
- os.execvpe(cmd[0], cmd, env)
585
+ os.execvpe(cmd[0], cmd, env) # nosec B606
586
586
  return False # Only reached on failure
587
587
 
588
588
  def _launch_subprocess_mode(self, cmd: list, env: dict) -> bool:
@@ -641,7 +641,7 @@ class InteractiveSession:
641
641
  cmd = environment["command"]
642
642
  env = environment["environment"]
643
643
 
644
- result = subprocess.run(
644
+ result = subprocess.run( # nosec B603
645
645
  cmd, stdin=None, stdout=None, stderr=None, env=env, check=False
646
646
  )
647
647
 
@@ -13,7 +13,7 @@ Users can change it if they want, and the system will respect their choice.
13
13
 
14
14
  import json
15
15
  import re
16
- import subprocess
16
+ import subprocess # nosec B404
17
17
  from pathlib import Path
18
18
  from typing import Any, Dict, Literal, Optional, TypedDict, cast
19
19
 
@@ -27,7 +27,7 @@ _CACHED_CLAUDE_VERSION: Optional[str] = None
27
27
  _VERSION_DETECTED: bool = False
28
28
 
29
29
  # Output style types
30
- OutputStyleType = Literal["professional", "teaching"]
30
+ OutputStyleType = Literal["professional", "teaching", "founders"]
31
31
 
32
32
 
33
33
  class StyleConfig(TypedDict):
@@ -41,9 +41,10 @@ class StyleConfig(TypedDict):
41
41
  class OutputStyleManager:
42
42
  """Manages output style deployment and version-based handling.
43
43
 
44
- Supports two output styles:
44
+ Supports three output styles:
45
45
  - professional: Default Claude MPM style (claude-mpm.md)
46
- - teaching: Adaptive teaching mode (claude-mpm-teach.md)
46
+ - teaching: Adaptive teaching mode (claude-mpm-teacher.md)
47
+ - founders: Non-technical mode for startup founders (claude-mpm-founders.md)
47
48
  """
48
49
 
49
50
  def __init__(self) -> None:
@@ -51,8 +52,8 @@ class OutputStyleManager:
51
52
  self.logger = get_logger("output_style_manager") # type: ignore[misc]
52
53
  self.claude_version = self._detect_claude_version()
53
54
 
54
- # Deploy to ~/.claude/styles/ directory (NOT output-styles/)
55
- self.output_style_dir = Path.home() / ".claude" / "styles"
55
+ # Deploy to ~/.claude/output-styles/ directory (official Claude Code location)
56
+ self.output_style_dir = Path.home() / ".claude" / "output-styles"
56
57
  self.settings_file = Path.home() / ".claude" / "settings.json"
57
58
 
58
59
  # Style definitions
@@ -62,14 +63,21 @@ class OutputStyleManager:
62
63
  / "agents"
63
64
  / "CLAUDE_MPM_OUTPUT_STYLE.md",
64
65
  target=self.output_style_dir / "claude-mpm.md",
65
- name="claude-mpm",
66
+ name="Claude MPM",
66
67
  ),
67
68
  "teaching": StyleConfig(
68
69
  source=Path(__file__).parent.parent
69
70
  / "agents"
70
71
  / "CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md",
71
- target=self.output_style_dir / "claude-mpm-teach.md",
72
- name="claude-mpm-teach",
72
+ target=self.output_style_dir / "claude-mpm-teacher.md",
73
+ name="Claude MPM Teacher",
74
+ ),
75
+ "founders": StyleConfig(
76
+ source=Path(__file__).parent.parent
77
+ / "agents"
78
+ / "CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md",
79
+ target=self.output_style_dir / "claude-mpm-founders.md",
80
+ name="Claude MPM Founders",
73
81
  ),
74
82
  }
75
83
 
@@ -93,7 +101,7 @@ class OutputStyleManager:
93
101
 
94
102
  try:
95
103
  # Run claude --version command
96
- result = subprocess.run(
104
+ result = subprocess.run( # nosec B603 B607
97
105
  ["claude", "--version"],
98
106
  capture_output=True,
99
107
  text=True,
@@ -300,12 +308,12 @@ class OutputStyleManager:
300
308
  self.logger.error(f"Failed to deploy {style} style: {e}")
301
309
  return False
302
310
 
303
- def _activate_output_style(self, style_name: str = "claude-mpm") -> bool:
311
+ def _activate_output_style(self, style_name: str = "Claude MPM") -> bool:
304
312
  """
305
313
  Update Claude Code settings to activate a specific output style.
306
314
 
307
315
  Args:
308
- style_name: Name of the style to activate (e.g., "claude-mpm", "claude-mpm-teach")
316
+ style_name: Name of the style to activate (e.g., "Claude MPM", "Claude MPM Teacher")
309
317
 
310
318
  Returns:
311
319
  True if activated successfully, False otherwise
@@ -443,7 +451,7 @@ class OutputStyleManager:
443
451
 
444
452
  # Activate the default style if requested
445
453
  if activate_default and results.get("professional", False):
446
- self._activate_output_style("claude-mpm")
454
+ self._activate_output_style("Claude MPM")
447
455
 
448
456
  return results
449
457
 
@@ -16,7 +16,7 @@ Design Principles:
16
16
  from pathlib import Path
17
17
  from typing import Any, Dict, List, Optional, Union
18
18
 
19
- from pydantic import BaseModel, Field, validator
19
+ from pydantic import BaseModel, Field, field_validator
20
20
  from pydantic_settings import BaseSettings
21
21
 
22
22
  from .exceptions import ConfigurationError
@@ -54,8 +54,9 @@ class LoggingConfig(BaseModel):
54
54
  default=True, description="Enable console logging"
55
55
  )
56
56
 
57
- @validator("level")
58
- def validate_log_level(self, v):
57
+ @field_validator("level")
58
+ @classmethod
59
+ def validate_log_level(cls, v):
59
60
  valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
60
61
  if v.upper() not in valid_levels:
61
62
  raise ValueError(f"Invalid log level. Must be one of: {valid_levels}")
@@ -65,8 +66,32 @@ class LoggingConfig(BaseModel):
65
66
  class AgentConfig(BaseModel):
66
67
  """Agent system configuration."""
67
68
 
69
+ # Explicit deployment lists (simplified model)
70
+ enabled: List[str] = Field(
71
+ default_factory=list,
72
+ description="Explicit list of agent IDs to deploy (empty = use auto_discover)",
73
+ )
74
+
75
+ # Required agents that are always deployed
76
+ required: List[str] = Field(
77
+ default_factory=lambda: [
78
+ "research",
79
+ "mpm-skills-manager",
80
+ "mpm-agent-manager",
81
+ "memory-manager-agent", # Specific agent ID (not generic "memory-manager")
82
+ "ticketing",
83
+ ],
84
+ description="Agents that are always deployed (core system agents)",
85
+ )
86
+
87
+ include_universal: bool = Field(
88
+ default=True,
89
+ description="Auto-include all agents with 'universal' toolchain/category",
90
+ )
91
+
68
92
  auto_discover: bool = Field(
69
- default=True, description="Enable automatic agent discovery"
93
+ default=False,
94
+ description="Enable automatic agent discovery (deprecated, use enabled list)",
70
95
  )
71
96
  precedence: List[str] = Field(
72
97
  default=["project", "user", "system"], description="Agent precedence order"
@@ -239,6 +264,21 @@ class DocumentationConfig(BaseModel):
239
264
  )
240
265
 
241
266
 
267
+ class SkillConfig(BaseModel):
268
+ """Skill system configuration."""
269
+
270
+ # Explicit deployment lists (simplified model)
271
+ enabled: List[str] = Field(
272
+ default_factory=list,
273
+ description="Explicit list of skill IDs to deploy (includes agent dependencies)",
274
+ )
275
+
276
+ auto_detect_dependencies: bool = Field(
277
+ default=True,
278
+ description="Automatically include skills required by enabled agents",
279
+ )
280
+
281
+
242
282
  class UnifiedConfig(BaseSettings):
243
283
  """
244
284
  Unified configuration model for Claude MPM.
@@ -258,6 +298,7 @@ class UnifiedConfig(BaseSettings):
258
298
  network: NetworkConfig = Field(default_factory=NetworkConfig)
259
299
  logging: LoggingConfig = Field(default_factory=LoggingConfig)
260
300
  agents: AgentConfig = Field(default_factory=AgentConfig)
301
+ skills: SkillConfig = Field(default_factory=SkillConfig)
261
302
  memory: MemoryConfig = Field(default_factory=MemoryConfig)
262
303
  security: SecurityConfig = Field(default_factory=SecurityConfig)
263
304
  performance: PerformanceConfig = Field(default_factory=PerformanceConfig)
@@ -287,8 +328,9 @@ class UnifiedConfig(BaseSettings):
287
328
  validate_assignment = True
288
329
  extra = "allow" # Allow extra fields for backward compatibility
289
330
 
290
- @validator("environment")
291
- def validate_environment(self, v):
331
+ @field_validator("environment")
332
+ @classmethod
333
+ def validate_environment(cls, v):
292
334
  valid_envs = ["development", "testing", "production"]
293
335
  if v not in valid_envs:
294
336
  raise ValueError(f"Invalid environment. Must be one of: {valid_envs}")
@@ -554,12 +596,12 @@ class ConfigurationService:
554
596
  import yaml
555
597
 
556
598
  with file_path.open("w") as f:
557
- yaml.dump(self._config.dict(), f, default_flow_style=False)
599
+ yaml.dump(self._config.model_dump(), f, default_flow_style=False)
558
600
  elif format.lower() == "json":
559
601
  import json
560
602
 
561
603
  with file_path.open("w") as f:
562
- json.dump(self._config.dict(), f, indent=2)
604
+ json.dump(self._config.model_dump(), f, indent=2)
563
605
  else:
564
606
  raise ConfigurationError(f"Unsupported export format: {format}")
565
607
 
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env python3
2
1
  """
3
2
  Unified Path Management System for Claude MPM
4
3
  ==============================================
@@ -36,6 +35,24 @@ from claude_mpm.core.logging_utils import get_logger
36
35
  logger = get_logger(__name__)
37
36
 
38
37
 
38
+ def _safe_cwd() -> Path:
39
+ """Safely get the current working directory.
40
+
41
+ If the current directory no longer exists (deleted/moved), fall back to home directory.
42
+ This prevents FileNotFoundError when Path.cwd() is called from a deleted directory.
43
+
44
+ Returns:
45
+ Path: Current working directory, or home directory if cwd doesn't exist
46
+ """
47
+ try:
48
+ return Path.cwd()
49
+ except (FileNotFoundError, OSError) as e:
50
+ logger.debug(
51
+ f"Current directory doesn't exist ({e}), falling back to home directory"
52
+ )
53
+ return Path.home()
54
+
55
+
39
56
  class PathType(Enum):
40
57
  """Enumeration of different path types for categorization."""
41
58
 
@@ -95,7 +112,7 @@ class PathContext:
95
112
 
96
113
  # Additional check: If we're running from within a claude-mpm development directory
97
114
  # This handles the case where pipx claude-mpm is invoked from within the dev directory
98
- cwd = Path.cwd()
115
+ cwd = _safe_cwd()
99
116
  current = cwd
100
117
  for _ in range(5): # Check up to 5 levels up from current directory
101
118
  if (current / "pyproject.toml").exists() and (
@@ -114,7 +131,7 @@ class PathContext:
114
131
  # Verify this is a development setup by checking for key files
115
132
  if (current / "scripts" / "claude-mpm").exists():
116
133
  return True
117
- except Exception:
134
+ except Exception: # nosec B110
118
135
  pass
119
136
  if current == current.parent:
120
137
  break
@@ -140,7 +157,7 @@ class PathContext:
140
157
  f"Found editable install via .pth file: {pth_file}"
141
158
  )
142
159
  return True
143
- except Exception:
160
+ except Exception: # nosec B112
144
161
  continue
145
162
 
146
163
  # Check for egg-link files
@@ -156,7 +173,7 @@ class PathContext:
156
173
  f"Found editable install via egg-link: {egg_link}"
157
174
  )
158
175
  return True
159
- except Exception:
176
+ except Exception: # nosec B112
160
177
  continue
161
178
  except ImportError:
162
179
  pass
@@ -186,7 +203,7 @@ class PathContext:
186
203
 
187
204
  # Check if current working directory is a claude-mpm development project
188
205
  # This handles the case where pipx claude-mpm is run from within the dev directory
189
- cwd = Path.cwd()
206
+ cwd = _safe_cwd()
190
207
  current = cwd
191
208
  for _ in range(5): # Check up to 5 levels up from current directory
192
209
  if (current / "pyproject.toml").exists() and (
@@ -206,7 +223,7 @@ class PathContext:
206
223
  "Using development mode for local source preference"
207
224
  )
208
225
  return DeploymentContext.DEVELOPMENT
209
- except Exception:
226
+ except Exception: # nosec B110
210
227
  pass
211
228
  if current == current.parent:
212
229
  break
@@ -228,7 +245,7 @@ class PathContext:
228
245
  if "pipx" in str(module_path):
229
246
  # Running via pipx but from within a development directory
230
247
  # Use development mode to prefer local source over pipx installation
231
- cwd = Path.cwd()
248
+ cwd = _safe_cwd()
232
249
  current = cwd
233
250
  for _ in range(5):
234
251
  if (current / "src" / "claude_mpm").exists() and (
@@ -346,7 +363,7 @@ class UnifiedPathManager:
346
363
  ):
347
364
  # For development mode, first check if we're running from within a dev directory
348
365
  # This handles the case where pipx is invoked from a development directory
349
- cwd = Path.cwd()
366
+ cwd = _safe_cwd()
350
367
  current = cwd
351
368
  for _ in range(5):
352
369
  if (current / "src" / "claude_mpm").exists() and (
@@ -360,7 +377,7 @@ class UnifiedPathManager:
360
377
  f"Found framework root via cwd at {current}"
361
378
  )
362
379
  return current
363
- except Exception:
380
+ except Exception: # nosec B110
364
381
  pass
365
382
  if current == current.parent:
366
383
  break
@@ -403,7 +420,7 @@ class UnifiedPathManager:
403
420
  @lru_cache(maxsize=1)
404
421
  def project_root(self) -> Path:
405
422
  """Get the current project root directory."""
406
- current = Path.cwd()
423
+ current = _safe_cwd()
407
424
  while current != current.parent:
408
425
  for marker in self._project_markers:
409
426
  if (current / marker).exists():
@@ -413,7 +430,7 @@ class UnifiedPathManager:
413
430
 
414
431
  # Fallback to current directory
415
432
  logger.warning("Could not find project root, using current directory")
416
- return Path.cwd()
433
+ return _safe_cwd()
417
434
 
418
435
  @property
419
436
  def package_root(self) -> Path:
@@ -562,7 +579,7 @@ class UnifiedPathManager:
562
579
  self, filename: str, start_path: Optional[Path] = None
563
580
  ) -> Optional[Path]:
564
581
  """Search for a file by traversing up the directory tree."""
565
- current = start_path or Path.cwd()
582
+ current = start_path or _safe_cwd()
566
583
 
567
584
  while current != current.parent:
568
585
  candidate = current / filename