claude-mpm 5.0.2__py3-none-any.whl → 5.4.3__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 (184) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +1218 -905
  4. claude_mpm/agents/agent_loader.py +10 -17
  5. claude_mpm/agents/base_agent_loader.py +10 -35
  6. claude_mpm/agents/frontmatter_validator.py +68 -0
  7. claude_mpm/agents/templates/circuit-breakers.md +431 -45
  8. claude_mpm/cli/__init__.py +0 -1
  9. claude_mpm/cli/commands/__init__.py +2 -0
  10. claude_mpm/cli/commands/agent_state_manager.py +67 -23
  11. claude_mpm/cli/commands/agents.py +446 -25
  12. claude_mpm/cli/commands/auto_configure.py +535 -233
  13. claude_mpm/cli/commands/configure.py +1500 -147
  14. claude_mpm/cli/commands/configure_agent_display.py +13 -6
  15. claude_mpm/cli/commands/mpm_init/core.py +158 -1
  16. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  17. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  18. claude_mpm/cli/commands/postmortem.py +401 -0
  19. claude_mpm/cli/commands/run.py +1 -39
  20. claude_mpm/cli/commands/skills.py +322 -19
  21. claude_mpm/cli/commands/summarize.py +413 -0
  22. claude_mpm/cli/executor.py +8 -0
  23. claude_mpm/cli/interactive/agent_wizard.py +302 -195
  24. claude_mpm/cli/parsers/agents_parser.py +137 -0
  25. claude_mpm/cli/parsers/auto_configure_parser.py +13 -0
  26. claude_mpm/cli/parsers/base_parser.py +9 -0
  27. claude_mpm/cli/parsers/skills_parser.py +7 -0
  28. claude_mpm/cli/startup.py +133 -85
  29. claude_mpm/commands/mpm-agents-auto-configure.md +2 -2
  30. claude_mpm/commands/mpm-agents-list.md +2 -2
  31. claude_mpm/commands/mpm-config-view.md +2 -2
  32. claude_mpm/commands/mpm-help.md +3 -0
  33. claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
  34. claude_mpm/commands/mpm-postmortem.md +123 -0
  35. claude_mpm/commands/mpm-session-resume.md +2 -2
  36. claude_mpm/commands/mpm-ticket-view.md +2 -2
  37. claude_mpm/config/agent_presets.py +312 -82
  38. claude_mpm/config/agent_sources.py +27 -0
  39. claude_mpm/config/skill_presets.py +392 -0
  40. claude_mpm/constants.py +1 -0
  41. claude_mpm/core/claude_runner.py +2 -25
  42. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  43. claude_mpm/core/framework/loaders/file_loader.py +54 -101
  44. claude_mpm/core/interactive_session.py +19 -5
  45. claude_mpm/core/oneshot_session.py +16 -4
  46. claude_mpm/core/output_style_manager.py +173 -43
  47. claude_mpm/core/protocols/__init__.py +23 -0
  48. claude_mpm/core/protocols/runner_protocol.py +103 -0
  49. claude_mpm/core/protocols/session_protocol.py +131 -0
  50. claude_mpm/core/shared/singleton_manager.py +11 -4
  51. claude_mpm/core/socketio_pool.py +3 -3
  52. claude_mpm/core/system_context.py +38 -0
  53. claude_mpm/core/unified_agent_registry.py +134 -16
  54. claude_mpm/core/unified_config.py +22 -0
  55. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  56. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
  57. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  58. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  59. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  60. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  61. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  62. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  63. claude_mpm/hooks/claude_hooks/event_handlers.py +35 -2
  64. claude_mpm/hooks/claude_hooks/hook_handler.py +4 -0
  65. claude_mpm/hooks/claude_hooks/memory_integration.py +12 -1
  66. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  67. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  68. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  69. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  70. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  71. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  72. claude_mpm/models/agent_definition.py +7 -0
  73. claude_mpm/scripts/launch_monitor.py +93 -13
  74. claude_mpm/services/agents/agent_recommendation_service.py +279 -0
  75. claude_mpm/services/agents/cache_git_manager.py +621 -0
  76. claude_mpm/services/agents/deployment/agent_template_builder.py +3 -2
  77. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +110 -3
  78. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +518 -55
  79. claude_mpm/services/agents/git_source_manager.py +20 -0
  80. claude_mpm/services/agents/sources/git_source_sync_service.py +45 -6
  81. claude_mpm/services/agents/toolchain_detector.py +6 -5
  82. claude_mpm/services/analysis/__init__.py +35 -0
  83. claude_mpm/services/analysis/clone_detector.py +1030 -0
  84. claude_mpm/services/analysis/postmortem_reporter.py +474 -0
  85. claude_mpm/services/analysis/postmortem_service.py +765 -0
  86. claude_mpm/services/command_deployment_service.py +106 -5
  87. claude_mpm/services/core/base.py +7 -2
  88. claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
  89. claude_mpm/services/event_bus/config.py +3 -1
  90. claude_mpm/services/git/git_operations_service.py +8 -8
  91. claude_mpm/services/mcp_config_manager.py +75 -145
  92. claude_mpm/services/mcp_service_verifier.py +6 -3
  93. claude_mpm/services/monitor/daemon.py +37 -10
  94. claude_mpm/services/monitor/daemon_manager.py +134 -21
  95. claude_mpm/services/monitor/server.py +225 -19
  96. claude_mpm/services/project/project_organizer.py +4 -0
  97. claude_mpm/services/runner_configuration_service.py +16 -3
  98. claude_mpm/services/session_management_service.py +16 -4
  99. claude_mpm/services/socketio/event_normalizer.py +15 -1
  100. claude_mpm/services/socketio/server/core.py +160 -21
  101. claude_mpm/services/version_control/git_operations.py +103 -0
  102. claude_mpm/utils/agent_filters.py +261 -0
  103. claude_mpm/utils/gitignore.py +3 -0
  104. claude_mpm/utils/migration.py +372 -0
  105. claude_mpm/utils/progress.py +5 -1
  106. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/METADATA +69 -84
  107. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/RECORD +112 -153
  108. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/entry_points.txt +0 -2
  109. claude_mpm/dashboard/analysis_runner.py +0 -455
  110. claude_mpm/dashboard/index.html +0 -13
  111. claude_mpm/dashboard/open_dashboard.py +0 -66
  112. claude_mpm/dashboard/static/css/activity.css +0 -1958
  113. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  114. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  115. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  116. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  117. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  118. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  119. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  120. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  121. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  122. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  123. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  124. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  125. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  126. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  127. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  128. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  129. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  130. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  131. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  132. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  133. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  134. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  135. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  136. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  137. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  138. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  139. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  140. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  141. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  142. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  143. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  144. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  145. claude_mpm/dashboard/templates/code_simple.html +0 -153
  146. claude_mpm/dashboard/templates/index.html +0 -606
  147. claude_mpm/dashboard/test_dashboard.html +0 -372
  148. claude_mpm/scripts/mcp_server.py +0 -75
  149. claude_mpm/scripts/mcp_wrapper.py +0 -39
  150. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  151. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  152. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  153. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  154. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  155. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  156. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  157. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  158. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  159. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  160. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -971
  161. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  162. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  163. claude_mpm/services/mcp_gateway/main.py +0 -589
  164. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  165. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  166. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  167. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  168. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  169. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  170. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  171. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  172. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  173. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  174. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  175. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  176. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  177. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  178. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  179. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  180. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  181. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  182. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/WHEEL +0 -0
  183. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/licenses/LICENSE +0 -0
  184. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,12 @@
2
2
 
3
3
  This module provides the InteractiveSession class that manages Claude's interactive mode
4
4
  with proper separation of concerns and reduced complexity.
5
+
6
+ DEPENDENCY INJECTION:
7
+ This module uses protocol-based dependency injection to break circular imports.
8
+ Instead of importing ClaudeRunner directly, it uses ClaudeRunnerProtocol which
9
+ defines the interface it needs. This allows ClaudeRunner to create instances
10
+ of InteractiveSession without circular dependency issues.
5
11
  """
6
12
 
7
13
  import contextlib
@@ -9,11 +15,18 @@ import os
9
15
  import subprocess
10
16
  import uuid
11
17
  from pathlib import Path
12
- from typing import Any, Dict, Optional, Tuple
18
+ from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple
13
19
 
14
20
  from claude_mpm.core.enums import ServiceState
15
21
  from claude_mpm.core.logger import get_logger
16
22
 
23
+ # Protocol imports for type checking without circular dependencies
24
+ if TYPE_CHECKING:
25
+ from claude_mpm.core.protocols import ClaudeRunnerProtocol
26
+ else:
27
+ # At runtime, accept any object with matching interface
28
+ ClaudeRunnerProtocol = Any
29
+
17
30
 
18
31
  class InteractiveSession:
19
32
  """
@@ -28,13 +41,14 @@ class InteractiveSession:
28
41
  and makes testing easier while preserving all original functionality.
29
42
  """
30
43
 
31
- def __init__(self, runner):
44
+ def __init__(self, runner: "ClaudeRunnerProtocol"):
32
45
  """Initialize interactive session handler.
33
46
 
34
47
  Args:
35
- runner: ClaudeRunner instance with all necessary services
48
+ runner: ClaudeRunner instance (or any object matching ClaudeRunnerProtocol)
49
+ with all necessary services
36
50
  """
37
- self.runner = runner
51
+ self.runner: ClaudeRunnerProtocol = runner
38
52
  self.logger = get_logger("interactive_session")
39
53
  self.session_id = None
40
54
  self.original_cwd = Path.cwd()
@@ -397,7 +411,7 @@ class InteractiveSession:
397
411
  self.logger.info("✓ Native agents mode: Using --agents CLI flag")
398
412
 
399
413
  # Add system instructions with file-based caching
400
- from claude_mpm.core.claude_runner import create_simple_context
414
+ from claude_mpm.core.system_context import create_simple_context
401
415
  from claude_mpm.services.instructions.instruction_cache_service import (
402
416
  InstructionCacheService,
403
417
  )
@@ -2,6 +2,11 @@
2
2
 
3
3
  This module encapsulates the logic for running one-time Claude commands,
4
4
  breaking down the monolithic run_oneshot method into focused, testable components.
5
+
6
+ DEPENDENCY INJECTION:
7
+ This module uses protocol-based dependency injection to break circular imports.
8
+ Instead of importing ClaudeRunner directly, it uses ClaudeRunnerProtocol which
9
+ defines the interface it needs.
5
10
  """
6
11
 
7
12
  import contextlib
@@ -11,11 +16,18 @@ import tempfile
11
16
  import time
12
17
  import uuid
13
18
  from pathlib import Path
14
- from typing import Any, Dict, Optional, Tuple
19
+ from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple
15
20
 
16
21
  from claude_mpm.core.enums import OperationResult, ServiceState
17
22
  from claude_mpm.core.logger import get_logger
18
23
 
24
+ # Protocol imports for type checking without circular dependencies
25
+ if TYPE_CHECKING:
26
+ from claude_mpm.core.protocols import ClaudeRunnerProtocol
27
+ else:
28
+ # At runtime, accept any object with matching interface
29
+ ClaudeRunnerProtocol = Any
30
+
19
31
 
20
32
  class OneshotSession:
21
33
  """Manages a single oneshot Claude execution session.
@@ -27,13 +39,13 @@ class OneshotSession:
27
39
  complexity < 10 and lines < 80, making the code easier to test and modify.
28
40
  """
29
41
 
30
- def __init__(self, runner):
42
+ def __init__(self, runner: "ClaudeRunnerProtocol"):
31
43
  """Initialize the oneshot session with a reference to the runner.
32
44
 
33
45
  Args:
34
- runner: The ClaudeRunner instance that owns this session
46
+ runner: The ClaudeRunner instance (or any object matching ClaudeRunnerProtocol)
35
47
  """
36
- self.runner = runner
48
+ self.runner: ClaudeRunnerProtocol = runner
37
49
  self.logger = get_logger("oneshot_session")
38
50
  self.start_time = None
39
51
  self.session_id = None
@@ -5,6 +5,7 @@ This module handles:
5
5
  2. Output style extraction from framework instructions
6
6
  3. One-time deployment to Claude Code >= 1.0.83 at startup
7
7
  4. Fallback injection for older versions
8
+ 5. Support for multiple output styles (professional and teaching modes)
8
9
 
9
10
  The output style is set once at startup and not monitored or enforced after that.
10
11
  Users can change it if they want, and the system will respect their choice.
@@ -14,7 +15,7 @@ import json
14
15
  import re
15
16
  import subprocess
16
17
  from pathlib import Path
17
- from typing import Dict, Optional
18
+ from typing import Any, Dict, Literal, Optional, TypedDict, cast
18
19
 
19
20
  from ..utils.imports import safe_import
20
21
 
@@ -25,22 +26,56 @@ get_logger = safe_import("claude_mpm.core.logger", "core.logger", ["get_logger"]
25
26
  _CACHED_CLAUDE_VERSION: Optional[str] = None
26
27
  _VERSION_DETECTED: bool = False
27
28
 
29
+ # Output style types
30
+ OutputStyleType = Literal["professional", "teaching"]
31
+
32
+
33
+ class StyleConfig(TypedDict):
34
+ """Configuration for an output style."""
35
+
36
+ source: Path
37
+ target: Path
38
+ name: str
39
+
28
40
 
29
41
  class OutputStyleManager:
30
- """Manages output style deployment and version-based handling."""
42
+ """Manages output style deployment and version-based handling.
43
+
44
+ Supports two output styles:
45
+ - professional: Default Claude MPM style (claude-mpm.md)
46
+ - teaching: Adaptive teaching mode (claude-mpm-teach.md)
47
+ """
31
48
 
32
- def __init__(self):
49
+ def __init__(self) -> None:
33
50
  """Initialize the output style manager."""
34
- self.logger = get_logger("output_style_manager")
51
+ self.logger = get_logger("output_style_manager") # type: ignore[misc]
35
52
  self.claude_version = self._detect_claude_version()
36
- self.output_style_dir = Path.home() / ".claude" / "output-styles"
37
- self.output_style_path = self.output_style_dir / "claude-mpm.md"
53
+
54
+ # Deploy to ~/.claude/styles/ directory (NOT output-styles/)
55
+ self.output_style_dir = Path.home() / ".claude" / "styles"
38
56
  self.settings_file = Path.home() / ".claude" / "settings.json"
39
57
 
40
- # Cache the output style content path
41
- self.mpm_output_style_path = (
42
- Path(__file__).parent.parent / "agents" / "OUTPUT_STYLE.md"
43
- )
58
+ # Style definitions
59
+ self.styles: Dict[str, StyleConfig] = {
60
+ "professional": StyleConfig(
61
+ source=Path(__file__).parent.parent
62
+ / "agents"
63
+ / "CLAUDE_MPM_OUTPUT_STYLE.md",
64
+ target=self.output_style_dir / "claude-mpm.md",
65
+ name="claude-mpm",
66
+ ),
67
+ "teaching": StyleConfig(
68
+ source=Path(__file__).parent.parent
69
+ / "agents"
70
+ / "CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md",
71
+ target=self.output_style_dir / "claude-mpm-teach.md",
72
+ name="claude-mpm-teach",
73
+ ),
74
+ }
75
+
76
+ # Default style path (for backward compatibility)
77
+ self.output_style_path = self.styles["professional"]["target"]
78
+ self.mpm_output_style_path = self.styles["professional"]["source"]
44
79
 
45
80
  def _detect_claude_version(self) -> Optional[str]:
46
81
  """
@@ -158,56 +193,77 @@ class OutputStyleManager:
158
193
  """
159
194
  return not self.supports_output_styles()
160
195
 
161
- def extract_output_style_content(self, framework_loader=None) -> str:
196
+ def extract_output_style_content(
197
+ self, framework_loader: Any = None, style: OutputStyleType = "professional"
198
+ ) -> str:
162
199
  """
163
- Read output style content from OUTPUT_STYLE.md.
200
+ Read output style content from style source file.
164
201
 
165
202
  Args:
166
203
  framework_loader: Optional framework loader (kept for compatibility, not used)
204
+ style: Style type to extract ("professional" or "teaching")
167
205
 
168
206
  Returns:
169
207
  Complete output style content from file
170
208
  """
171
- # Always read from the complete OUTPUT_STYLE.md file
172
- if self.mpm_output_style_path.exists():
173
- content = self.mpm_output_style_path.read_text()
174
- self.logger.info(f"Read OUTPUT_STYLE.md directly ({len(content)} chars)")
209
+ style_config = self.styles[style]
210
+ source_path = style_config["source"]
211
+
212
+ if source_path.exists():
213
+ content = source_path.read_text()
214
+ self.logger.info(
215
+ f"Read {style} style from {source_path.name} ({len(content)} chars)"
216
+ )
175
217
  return content
218
+
176
219
  # Fallback error
177
- error_msg = f"OUTPUT_STYLE.md not found at {self.mpm_output_style_path}"
220
+ error_msg = f"{style} style not found at {source_path}"
178
221
  self.logger.error(error_msg)
179
222
  raise FileNotFoundError(error_msg)
180
223
 
181
- def save_output_style(self, content: str) -> Path:
224
+ def save_output_style(
225
+ self, content: str, style: OutputStyleType = "professional"
226
+ ) -> Path:
182
227
  """
183
- Save output style content to OUTPUT_STYLE.md.
228
+ Save output style content to source file.
184
229
 
185
230
  Args:
186
231
  content: The formatted output style content
232
+ style: Style type to save ("professional" or "teaching")
187
233
 
188
234
  Returns:
189
235
  Path to the saved file
190
236
  """
191
237
  try:
238
+ style_config = self.styles[style]
239
+ source_path = style_config["source"]
240
+
192
241
  # Ensure the parent directory exists
193
- self.mpm_output_style_path.parent.mkdir(parents=True, exist_ok=True)
242
+ source_path.parent.mkdir(parents=True, exist_ok=True)
194
243
 
195
244
  # Write the content
196
- self.mpm_output_style_path.write_text(content, encoding="utf-8")
197
- self.logger.info(f"Saved output style to {self.mpm_output_style_path}")
245
+ source_path.write_text(content, encoding="utf-8")
246
+ self.logger.info(f"Saved {style} style to {source_path}")
198
247
 
199
- return self.mpm_output_style_path
248
+ return source_path
200
249
  except Exception as e:
201
- self.logger.error(f"Failed to save output style: {e}")
250
+ self.logger.error(f"Failed to save {style} style: {e}")
202
251
  raise
203
252
 
204
- def deploy_output_style(self, content: str) -> bool:
253
+ def deploy_output_style(
254
+ self,
255
+ content: Optional[str] = None,
256
+ style: OutputStyleType = "professional",
257
+ activate: bool = True,
258
+ ) -> bool:
205
259
  """
206
260
  Deploy output style to Claude Code if version >= 1.0.83.
207
- Deploys the style file and activates it once.
261
+ Deploys the style file and optionally activates it.
208
262
 
209
263
  Args:
210
- content: The output style content to deploy
264
+ content: The output style content to deploy (if None, reads from source)
265
+ style: Style type to deploy ("professional" or "teaching")
266
+ activate: Whether to activate the style after deployment
211
267
 
212
268
  Returns:
213
269
  True if deployed successfully, False otherwise
@@ -219,26 +275,37 @@ class OutputStyleManager:
219
275
  return False
220
276
 
221
277
  try:
222
- # Ensure output-styles directory exists
278
+ style_config = self.styles[style]
279
+ target_path = style_config["target"]
280
+ style_name = style_config["name"]
281
+
282
+ # If content not provided, read from source
283
+ if content is None:
284
+ content = self.extract_output_style_content(style=style)
285
+
286
+ # Ensure styles directory exists
223
287
  self.output_style_dir.mkdir(parents=True, exist_ok=True)
224
288
 
225
289
  # Write the output style file
226
- self.output_style_path.write_text(content, encoding="utf-8")
227
- self.logger.info(f"Deployed output style to {self.output_style_path}")
290
+ target_path.write_text(content, encoding="utf-8")
291
+ self.logger.info(f"Deployed {style} style to {target_path}")
228
292
 
229
- # Activate the claude-mpm style
230
- self._activate_output_style()
293
+ # Activate the style if requested
294
+ if activate:
295
+ self._activate_output_style(style_name)
231
296
 
232
297
  return True
233
298
 
234
299
  except Exception as e:
235
- self.logger.error(f"Failed to deploy output style: {e}")
300
+ self.logger.error(f"Failed to deploy {style} style: {e}")
236
301
  return False
237
302
 
238
- def _activate_output_style(self) -> bool:
303
+ def _activate_output_style(self, style_name: str = "claude-mpm") -> bool:
239
304
  """
240
- Update Claude Code settings to activate the claude-mpm output style.
241
- Sets activeOutputStyle to "claude-mpm" once at startup.
305
+ Update Claude Code settings to activate a specific output style.
306
+
307
+ Args:
308
+ style_name: Name of the style to activate (e.g., "claude-mpm", "claude-mpm-teach")
242
309
 
243
310
  Returns:
244
311
  True if activated successfully, False otherwise
@@ -257,9 +324,9 @@ class OutputStyleManager:
257
324
  # Check current active style
258
325
  current_style = settings.get("activeOutputStyle")
259
326
 
260
- # Update active output style to claude-mpm if not already set
261
- if current_style != "claude-mpm":
262
- settings["activeOutputStyle"] = "claude-mpm"
327
+ # Update active output style if different
328
+ if current_style != style_name:
329
+ settings["activeOutputStyle"] = style_name
263
330
 
264
331
  # Ensure settings directory exists
265
332
  self.settings_file.parent.mkdir(parents=True, exist_ok=True)
@@ -270,10 +337,10 @@ class OutputStyleManager:
270
337
  )
271
338
 
272
339
  self.logger.info(
273
- f"✅ Activated claude-mpm output style (was: {current_style or 'none'})"
340
+ f"✅ Activated {style_name} output style (was: {current_style or 'none'})"
274
341
  )
275
342
  else:
276
- self.logger.debug("Claude MPM output style already active")
343
+ self.logger.debug(f"{style_name} output style already active")
277
344
 
278
345
  return True
279
346
 
@@ -319,7 +386,9 @@ class OutputStyleManager:
319
386
 
320
387
  return status
321
388
 
322
- def get_injectable_content(self, framework_loader=None) -> str:
389
+ def get_injectable_content(
390
+ self, framework_loader: Any = None, style: OutputStyleType = "professional"
391
+ ) -> str:
323
392
  """
324
393
  Get output style content for injection into instructions (for Claude < 1.0.83).
325
394
 
@@ -328,12 +397,13 @@ class OutputStyleManager:
328
397
 
329
398
  Args:
330
399
  framework_loader: Optional FrameworkLoader instance to reuse loaded content
400
+ style: Style type to extract ("professional" or "teaching")
331
401
 
332
402
  Returns:
333
403
  Simplified output style content for injection
334
404
  """
335
405
  # Extract the same content but without YAML frontmatter
336
- full_content = self.extract_output_style_content(framework_loader)
406
+ full_content = self.extract_output_style_content(framework_loader, style=style)
337
407
 
338
408
  # Remove YAML frontmatter
339
409
  lines = full_content.split("\n")
@@ -351,3 +421,63 @@ class OutputStyleManager:
351
421
 
352
422
  # If no frontmatter found, return as-is
353
423
  return full_content
424
+
425
+ def deploy_all_styles(self, activate_default: bool = True) -> Dict[str, bool]:
426
+ """
427
+ Deploy all available output styles to Claude Code.
428
+
429
+ Args:
430
+ activate_default: Whether to activate the professional style after deployment
431
+
432
+ Returns:
433
+ Dictionary mapping style names to deployment success status
434
+ """
435
+ results: Dict[str, bool] = {}
436
+
437
+ for style_type_key in self.styles:
438
+ # Deploy without activation
439
+ # Cast is safe because we know self.styles keys are OutputStyleType
440
+ style_type = cast("OutputStyleType", style_type_key)
441
+ success = self.deploy_output_style(style=style_type, activate=False)
442
+ results[style_type] = success
443
+
444
+ # Activate the default style if requested
445
+ if activate_default and results.get("professional", False):
446
+ self._activate_output_style("claude-mpm")
447
+
448
+ return results
449
+
450
+ def deploy_teaching_style(self, activate: bool = False) -> bool:
451
+ """
452
+ Deploy the teaching style specifically.
453
+
454
+ Args:
455
+ activate: Whether to activate the teaching style after deployment
456
+
457
+ Returns:
458
+ True if deployed successfully, False otherwise
459
+ """
460
+ return self.deploy_output_style(style="teaching", activate=activate)
461
+
462
+ def list_available_styles(self) -> Dict[str, Dict[str, str]]:
463
+ """
464
+ List all available output styles with their metadata.
465
+
466
+ Returns:
467
+ Dictionary mapping style types to their configuration
468
+ """
469
+ available_styles = {}
470
+
471
+ for style_type, config in self.styles.items():
472
+ source_exists = config["source"].exists()
473
+ target_exists = config["target"].exists()
474
+
475
+ available_styles[style_type] = {
476
+ "name": config["name"],
477
+ "source_path": str(config["source"]),
478
+ "target_path": str(config["target"]),
479
+ "source_exists": str(source_exists),
480
+ "deployed": str(target_exists),
481
+ }
482
+
483
+ return available_styles
@@ -0,0 +1,23 @@
1
+ """Protocol interfaces for dependency injection.
2
+
3
+ This module defines Protocol interfaces to break circular dependencies
4
+ using Python's typing.Protocol feature for structural subtyping.
5
+ """
6
+
7
+ from claude_mpm.core.protocols.runner_protocol import (
8
+ ClaudeRunnerProtocol,
9
+ SystemPromptProvider,
10
+ )
11
+ from claude_mpm.core.protocols.session_protocol import (
12
+ InteractiveSessionProtocol,
13
+ OneshotSessionProtocol,
14
+ SessionManagementProtocol,
15
+ )
16
+
17
+ __all__ = [
18
+ "ClaudeRunnerProtocol",
19
+ "InteractiveSessionProtocol",
20
+ "OneshotSessionProtocol",
21
+ "SessionManagementProtocol",
22
+ "SystemPromptProvider",
23
+ ]
@@ -0,0 +1,103 @@
1
+ """Protocol definitions for ClaudeRunner dependencies.
2
+
3
+ These protocols use Python's typing.Protocol for structural subtyping,
4
+ allowing dependency injection without circular imports.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Any, Optional, Protocol
9
+
10
+
11
+ class SystemPromptProvider(Protocol):
12
+ """Protocol for providing system prompts without circular dependency.
13
+
14
+ This protocol allows InteractiveSession to get system prompts without
15
+ directly importing ClaudeRunner, breaking the circular dependency.
16
+ """
17
+
18
+ def _create_system_prompt(self) -> str:
19
+ """Create the complete system prompt including instructions.
20
+
21
+ Returns:
22
+ Complete system prompt as string
23
+ """
24
+ ...
25
+
26
+
27
+ class ClaudeRunnerProtocol(Protocol):
28
+ """Protocol defining the interface InteractiveSession needs from ClaudeRunner.
29
+
30
+ This protocol breaks the circular dependency between InteractiveSession
31
+ and ClaudeRunner by defining only the methods that InteractiveSession
32
+ actually uses, without requiring the full ClaudeRunner import.
33
+
34
+ Design Decision: Uses Protocol instead of ABC to allow structural subtyping.
35
+ This means ClaudeRunner doesn't need to explicitly inherit from this protocol,
36
+ it just needs to implement these methods with matching signatures.
37
+ """
38
+
39
+ # Configuration attributes
40
+ enable_websocket: bool
41
+ enable_tickets: bool
42
+ log_level: str
43
+ claude_args: Optional[list]
44
+ launch_method: str
45
+ websocket_port: int
46
+ use_native_agents: bool
47
+ config: Any
48
+ session_log_file: Optional[Path]
49
+
50
+ # Service references
51
+ project_logger: Any
52
+ websocket_server: Any
53
+ command_handler_service: Any
54
+ subprocess_launcher_service: Any
55
+
56
+ def setup_agents(self) -> bool:
57
+ """Deploy native agents to .claude/agents/.
58
+
59
+ Returns:
60
+ True if successful, False otherwise
61
+ """
62
+ ...
63
+
64
+ def deploy_project_agents_to_claude(self) -> bool:
65
+ """Deploy project agents from .claude-mpm/agents/ to .claude/agents/.
66
+
67
+ Returns:
68
+ True if successful, False otherwise
69
+ """
70
+ ...
71
+
72
+ def _create_system_prompt(self) -> str:
73
+ """Create the complete system prompt including instructions.
74
+
75
+ Returns:
76
+ Complete system prompt as string
77
+ """
78
+ ...
79
+
80
+ def _get_version(self) -> str:
81
+ """Get version string.
82
+
83
+ Returns:
84
+ Version string
85
+ """
86
+ ...
87
+
88
+ def _log_session_event(self, event_data: dict) -> None:
89
+ """Log an event to the session log file.
90
+
91
+ Args:
92
+ event_data: Event data to log
93
+ """
94
+ ...
95
+
96
+ def _launch_subprocess_interactive(self, cmd: list, env: dict) -> None:
97
+ """Launch Claude as a subprocess with PTY for interactive mode.
98
+
99
+ Args:
100
+ cmd: Command to execute
101
+ env: Environment variables
102
+ """
103
+ ...