claude-mpm 4.0.19__py3-none-any.whl → 4.0.22__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/BUILD_NUMBER +1 -1
- claude_mpm/VERSION +1 -1
- claude_mpm/__main__.py +4 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +38 -2
- claude_mpm/agents/INSTRUCTIONS.md +74 -0
- claude_mpm/agents/OUTPUT_STYLE.md +84 -0
- claude_mpm/agents/WORKFLOW.md +308 -4
- claude_mpm/agents/agents_metadata.py +52 -0
- claude_mpm/agents/base_agent_loader.py +75 -19
- claude_mpm/agents/templates/__init__.py +4 -0
- claude_mpm/agents/templates/api_qa.json +206 -0
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +24 -16
- claude_mpm/agents/templates/ticketing.json +18 -5
- claude_mpm/agents/templates/vercel_ops_agent.json +281 -0
- claude_mpm/agents/templates/vercel_ops_instructions.md +582 -0
- claude_mpm/cli/__init__.py +23 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/commands/mcp_command_router.py +87 -1
- claude_mpm/cli/commands/mcp_install_commands.py +207 -26
- claude_mpm/cli/commands/memory.py +32 -5
- claude_mpm/cli/commands/run.py +33 -6
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/parsers/mcp_parser.py +23 -0
- claude_mpm/cli/parsers/run_parser.py +5 -0
- claude_mpm/cli/utils.py +17 -4
- claude_mpm/constants.py +1 -0
- claude_mpm/core/base_service.py +8 -2
- claude_mpm/core/config.py +122 -32
- claude_mpm/core/framework_loader.py +385 -34
- claude_mpm/core/interactive_session.py +77 -12
- claude_mpm/core/oneshot_session.py +7 -1
- claude_mpm/core/output_style_manager.py +468 -0
- claude_mpm/core/unified_paths.py +190 -21
- claude_mpm/hooks/claude_hooks/hook_handler.py +91 -16
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +3 -0
- claude_mpm/init.py +1 -0
- claude_mpm/scripts/socketio_daemon.py +67 -7
- claude_mpm/scripts/socketio_daemon_hardened.py +897 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +216 -10
- claude_mpm/services/agents/deployment/agent_template_builder.py +37 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +65 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +441 -0
- claude_mpm/services/agents/memory/__init__.py +0 -2
- claude_mpm/services/agents/memory/agent_memory_manager.py +577 -44
- claude_mpm/services/agents/memory/content_manager.py +144 -14
- claude_mpm/services/agents/memory/template_generator.py +7 -354
- claude_mpm/services/mcp_gateway/server/stdio_server.py +61 -169
- claude_mpm/services/memory_hook_service.py +62 -4
- claude_mpm/services/runner_configuration_service.py +5 -9
- claude_mpm/services/socketio/server/broadcaster.py +32 -1
- claude_mpm/services/socketio/server/core.py +4 -0
- claude_mpm/services/socketio/server/main.py +23 -4
- claude_mpm/services/subprocess_launcher_service.py +5 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/METADATA +1 -1
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/RECORD +60 -54
- claude_mpm/services/agents/memory/analyzer.py +0 -430
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"""Output style management for Claude MPM.
|
|
2
|
+
|
|
3
|
+
This module handles:
|
|
4
|
+
1. Claude version detection
|
|
5
|
+
2. Output style extraction from framework instructions
|
|
6
|
+
3. One-time deployment to Claude Desktop >= 1.0.83 at startup
|
|
7
|
+
4. Fallback injection for older versions
|
|
8
|
+
|
|
9
|
+
The output style is set once at startup and not monitored or enforced after that.
|
|
10
|
+
Users can change it if they want, and the system will respect their choice.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import logging
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import shutil
|
|
18
|
+
import subprocess
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Dict, Optional, Tuple
|
|
21
|
+
|
|
22
|
+
from ..utils.imports import safe_import
|
|
23
|
+
|
|
24
|
+
# Import with fallback support
|
|
25
|
+
get_logger = safe_import("claude_mpm.core.logger", "core.logger", ["get_logger"])
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OutputStyleManager:
|
|
29
|
+
"""Manages output style deployment and version-based handling."""
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
"""Initialize the output style manager."""
|
|
33
|
+
self.logger = get_logger("output_style_manager")
|
|
34
|
+
self.claude_version = self._detect_claude_version()
|
|
35
|
+
self.output_style_dir = Path.home() / ".claude" / "output-styles"
|
|
36
|
+
self.output_style_path = self.output_style_dir / "claude-mpm.md"
|
|
37
|
+
self.settings_file = Path.home() / ".claude" / "settings.json"
|
|
38
|
+
|
|
39
|
+
# Cache the output style content path
|
|
40
|
+
self.mpm_output_style_path = Path(__file__).parent.parent / "agents" / "OUTPUT_STYLE.md"
|
|
41
|
+
|
|
42
|
+
def _detect_claude_version(self) -> Optional[str]:
|
|
43
|
+
"""
|
|
44
|
+
Detect Claude Desktop version by running 'claude --version'.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Version string (e.g., "1.0.82") or None if Claude not found
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
# Run claude --version command
|
|
51
|
+
result = subprocess.run(
|
|
52
|
+
["claude", "--version"],
|
|
53
|
+
capture_output=True,
|
|
54
|
+
text=True,
|
|
55
|
+
timeout=5
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if result.returncode != 0:
|
|
59
|
+
self.logger.warning(f"Claude command failed: {result.stderr}")
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
# Parse version from output
|
|
63
|
+
# Expected format: "Claude 1.0.82" or similar
|
|
64
|
+
version_output = result.stdout.strip()
|
|
65
|
+
version_match = re.search(r"(\d+\.\d+\.\d+)", version_output)
|
|
66
|
+
|
|
67
|
+
if version_match:
|
|
68
|
+
version = version_match.group(1)
|
|
69
|
+
self.logger.info(f"Detected Claude version: {version}")
|
|
70
|
+
return version
|
|
71
|
+
else:
|
|
72
|
+
self.logger.warning(f"Could not parse version from: {version_output}")
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
except FileNotFoundError:
|
|
76
|
+
self.logger.info("Claude Desktop not found in PATH")
|
|
77
|
+
return None
|
|
78
|
+
except subprocess.TimeoutExpired:
|
|
79
|
+
self.logger.warning("Claude version check timed out")
|
|
80
|
+
return None
|
|
81
|
+
except Exception as e:
|
|
82
|
+
self.logger.warning(f"Error detecting Claude version: {e}")
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
def _compare_versions(self, version1: str, version2: str) -> int:
|
|
86
|
+
"""
|
|
87
|
+
Compare two version strings.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
version1: First version string
|
|
91
|
+
version2: Second version string
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
-1 if version1 < version2
|
|
95
|
+
0 if version1 == version2
|
|
96
|
+
1 if version1 > version2
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
v1_parts = [int(x) for x in version1.split('.')]
|
|
100
|
+
v2_parts = [int(x) for x in version2.split('.')]
|
|
101
|
+
|
|
102
|
+
# Pad shorter version with zeros
|
|
103
|
+
max_len = max(len(v1_parts), len(v2_parts))
|
|
104
|
+
v1_parts.extend([0] * (max_len - len(v1_parts)))
|
|
105
|
+
v2_parts.extend([0] * (max_len - len(v2_parts)))
|
|
106
|
+
|
|
107
|
+
for i in range(max_len):
|
|
108
|
+
if v1_parts[i] < v2_parts[i]:
|
|
109
|
+
return -1
|
|
110
|
+
elif v1_parts[i] > v2_parts[i]:
|
|
111
|
+
return 1
|
|
112
|
+
return 0
|
|
113
|
+
except Exception as e:
|
|
114
|
+
self.logger.warning(f"Error comparing versions: {e}")
|
|
115
|
+
return -1
|
|
116
|
+
|
|
117
|
+
def supports_output_styles(self) -> bool:
|
|
118
|
+
"""
|
|
119
|
+
Check if Claude Desktop supports output styles (>= 1.0.83).
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
True if Claude version >= 1.0.83, False otherwise
|
|
123
|
+
"""
|
|
124
|
+
if not self.claude_version:
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
return self._compare_versions(self.claude_version, "1.0.83") >= 0
|
|
128
|
+
|
|
129
|
+
def should_inject_content(self) -> bool:
|
|
130
|
+
"""
|
|
131
|
+
Check if output style content should be injected into instructions.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
True if Claude version < 1.0.83 or not detected, False otherwise
|
|
135
|
+
"""
|
|
136
|
+
return not self.supports_output_styles()
|
|
137
|
+
|
|
138
|
+
def extract_output_style_content(self, framework_loader=None) -> str:
|
|
139
|
+
"""
|
|
140
|
+
Extract output style content from framework instructions.
|
|
141
|
+
|
|
142
|
+
This extracts PM delegation behavior, tone, communication standards,
|
|
143
|
+
response formats, TodoWrite requirements, and workflow rules from:
|
|
144
|
+
- INSTRUCTIONS.md
|
|
145
|
+
- BASE_PM.md
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
framework_loader: Optional FrameworkLoader instance to reuse loaded content
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Formatted output style content in YAML frontmatter + markdown format
|
|
152
|
+
"""
|
|
153
|
+
# Build the content sections
|
|
154
|
+
sections = []
|
|
155
|
+
|
|
156
|
+
# Add YAML frontmatter
|
|
157
|
+
sections.append("---")
|
|
158
|
+
sections.append("name: Claude MPM")
|
|
159
|
+
sections.append("description: Multi-Agent Project Manager orchestration mode for delegation and coordination")
|
|
160
|
+
sections.append("---")
|
|
161
|
+
sections.append("")
|
|
162
|
+
|
|
163
|
+
# Header
|
|
164
|
+
sections.append("You are Claude Multi-Agent PM, a PROJECT MANAGER whose SOLE PURPOSE is to delegate work to specialized agents.")
|
|
165
|
+
sections.append("")
|
|
166
|
+
|
|
167
|
+
# Extract from INSTRUCTIONS.md
|
|
168
|
+
if framework_loader and framework_loader.framework_content.get("framework_instructions"):
|
|
169
|
+
instructions = framework_loader.framework_content["framework_instructions"]
|
|
170
|
+
sections.extend(self._extract_instructions_sections(instructions))
|
|
171
|
+
else:
|
|
172
|
+
# Load from file if no framework_loader provided
|
|
173
|
+
instructions_path = Path(__file__).parent.parent / "agents" / "INSTRUCTIONS.md"
|
|
174
|
+
if instructions_path.exists():
|
|
175
|
+
instructions = instructions_path.read_text()
|
|
176
|
+
sections.extend(self._extract_instructions_sections(instructions))
|
|
177
|
+
|
|
178
|
+
# Extract from BASE_PM.md
|
|
179
|
+
if framework_loader and framework_loader.framework_content.get("base_pm_instructions"):
|
|
180
|
+
base_pm = framework_loader.framework_content["base_pm_instructions"]
|
|
181
|
+
sections.extend(self._extract_base_pm_sections(base_pm))
|
|
182
|
+
else:
|
|
183
|
+
# Load from file if no framework_loader provided
|
|
184
|
+
base_pm_path = Path(__file__).parent.parent / "agents" / "BASE_PM.md"
|
|
185
|
+
if base_pm_path.exists():
|
|
186
|
+
base_pm = base_pm_path.read_text()
|
|
187
|
+
sections.extend(self._extract_base_pm_sections(base_pm))
|
|
188
|
+
|
|
189
|
+
return "\n".join(sections)
|
|
190
|
+
|
|
191
|
+
def _extract_instructions_sections(self, content: str) -> list:
|
|
192
|
+
"""Extract relevant sections from INSTRUCTIONS.md."""
|
|
193
|
+
sections = []
|
|
194
|
+
|
|
195
|
+
# Extract Primary Directive
|
|
196
|
+
if "## 🔴 PRIMARY DIRECTIVE" in content:
|
|
197
|
+
sections.append("## 🔴 PRIMARY DIRECTIVE - MANDATORY DELEGATION 🔴")
|
|
198
|
+
sections.append("")
|
|
199
|
+
sections.append("**YOU ARE STRICTLY FORBIDDEN FROM DOING ANY WORK DIRECTLY.**")
|
|
200
|
+
sections.append("")
|
|
201
|
+
sections.append("Direct implementation is ABSOLUTELY PROHIBITED unless the user EXPLICITLY overrides with phrases like:")
|
|
202
|
+
sections.append('- "do this yourself"')
|
|
203
|
+
sections.append('- "don\'t delegate"')
|
|
204
|
+
sections.append('- "implement directly"')
|
|
205
|
+
sections.append('- "you do it"')
|
|
206
|
+
sections.append('- "no delegation"')
|
|
207
|
+
sections.append("")
|
|
208
|
+
|
|
209
|
+
# Extract Core Identity and Rules
|
|
210
|
+
if "## Core Identity" in content:
|
|
211
|
+
sections.append("## Core Operating Rules")
|
|
212
|
+
sections.append("")
|
|
213
|
+
sections.append("**DEFAULT BEHAVIOR - ALWAYS DELEGATE**:")
|
|
214
|
+
sections.append("- 🔴 You MUST delegate 100% of ALL work to specialized agents by default")
|
|
215
|
+
sections.append("- 🔴 Direct action is STRICTLY FORBIDDEN without explicit user override")
|
|
216
|
+
sections.append("- 🔴 Even the simplest tasks MUST be delegated - NO EXCEPTIONS")
|
|
217
|
+
sections.append("- 🔴 When in doubt, ALWAYS DELEGATE - never act directly")
|
|
218
|
+
sections.append("")
|
|
219
|
+
sections.append("**Allowed Tools**:")
|
|
220
|
+
sections.append("- **Task** for delegation (YOUR PRIMARY FUNCTION)")
|
|
221
|
+
sections.append("- **TodoWrite** for tracking delegation progress ONLY")
|
|
222
|
+
sections.append("- **WebSearch/WebFetch** for gathering context BEFORE delegation")
|
|
223
|
+
sections.append("- **Direct answers** ONLY for questions about PM capabilities")
|
|
224
|
+
sections.append("")
|
|
225
|
+
|
|
226
|
+
# Extract Communication Standards
|
|
227
|
+
if "## Communication Standards" in content:
|
|
228
|
+
sections.append("## Communication Standards")
|
|
229
|
+
sections.append("")
|
|
230
|
+
sections.append("- **Tone**: Professional, neutral by default")
|
|
231
|
+
sections.append("- **Use**: \"Understood\", \"Confirmed\", \"Noted\"")
|
|
232
|
+
sections.append("- **No simplification** without explicit user request")
|
|
233
|
+
sections.append("- **No mocks** outside test environments")
|
|
234
|
+
sections.append("- **Complete implementations** only - no placeholders")
|
|
235
|
+
sections.append("- **FORBIDDEN**: Overeager enthusiasm (\"Excellent!\", \"Perfect!\", \"Amazing!\")")
|
|
236
|
+
sections.append("")
|
|
237
|
+
|
|
238
|
+
# Extract Error Handling
|
|
239
|
+
if "## Error Handling Protocol" in content:
|
|
240
|
+
sections.append("## Error Handling Protocol")
|
|
241
|
+
sections.append("")
|
|
242
|
+
sections.append("**3-Attempt Process**:")
|
|
243
|
+
sections.append("1. **First Failure**: Re-delegate with enhanced context")
|
|
244
|
+
sections.append("2. **Second Failure**: Mark \"ERROR - Attempt 2/3\", escalate if needed")
|
|
245
|
+
sections.append("3. **Third Failure**: TodoWrite escalation with user decision required")
|
|
246
|
+
sections.append("")
|
|
247
|
+
|
|
248
|
+
# Extract Standard Operating Procedure
|
|
249
|
+
if "## Standard Operating Procedure" in content:
|
|
250
|
+
sections.append("## Standard Operating Procedure")
|
|
251
|
+
sections.append("")
|
|
252
|
+
sections.append("1. **Analysis**: Parse request, assess context (NO TOOLS)")
|
|
253
|
+
sections.append("2. **Planning**: Agent selection, task breakdown, priority assignment")
|
|
254
|
+
sections.append("3. **Delegation**: Task Tool with enhanced format")
|
|
255
|
+
sections.append("4. **Monitoring**: Track progress via TodoWrite")
|
|
256
|
+
sections.append("5. **Integration**: Synthesize results, validate, report")
|
|
257
|
+
sections.append("")
|
|
258
|
+
|
|
259
|
+
return sections
|
|
260
|
+
|
|
261
|
+
def _extract_base_pm_sections(self, content: str) -> list:
|
|
262
|
+
"""Extract relevant sections from BASE_PM.md."""
|
|
263
|
+
sections = []
|
|
264
|
+
|
|
265
|
+
# Extract TodoWrite Requirements
|
|
266
|
+
if "## TodoWrite Framework Requirements" in content:
|
|
267
|
+
sections.append("## TodoWrite Requirements")
|
|
268
|
+
sections.append("")
|
|
269
|
+
sections.append("### Mandatory [Agent] Prefix Rules")
|
|
270
|
+
sections.append("")
|
|
271
|
+
sections.append("**ALWAYS use [Agent] prefix for delegated tasks**:")
|
|
272
|
+
sections.append("- ✅ `[Research] Analyze authentication patterns`")
|
|
273
|
+
sections.append("- ✅ `[Engineer] Implement user registration`")
|
|
274
|
+
sections.append("- ✅ `[QA] Test payment flow`")
|
|
275
|
+
sections.append("- ✅ `[Documentation] Update API docs`")
|
|
276
|
+
sections.append("")
|
|
277
|
+
sections.append("**NEVER use [PM] prefix for implementation tasks**")
|
|
278
|
+
sections.append("")
|
|
279
|
+
sections.append("### Task Status Management")
|
|
280
|
+
sections.append("")
|
|
281
|
+
sections.append("- `pending` - Task not yet started")
|
|
282
|
+
sections.append("- `in_progress` - Currently being worked on (ONE at a time)")
|
|
283
|
+
sections.append("- `completed` - Task finished successfully")
|
|
284
|
+
sections.append("")
|
|
285
|
+
|
|
286
|
+
# Extract PM Response Format
|
|
287
|
+
if "## PM Response Format" in content:
|
|
288
|
+
sections.append("## Response Format")
|
|
289
|
+
sections.append("")
|
|
290
|
+
sections.append("When completing delegations, provide structured summaries including:")
|
|
291
|
+
sections.append("- Request summary")
|
|
292
|
+
sections.append("- Agents used and task counts")
|
|
293
|
+
sections.append("- Tasks completed with [Agent] prefixes")
|
|
294
|
+
sections.append("- Files affected across all agents")
|
|
295
|
+
sections.append("- Blockers encountered and resolutions")
|
|
296
|
+
sections.append("- Next steps for user")
|
|
297
|
+
sections.append("- Key information to remember")
|
|
298
|
+
sections.append("")
|
|
299
|
+
|
|
300
|
+
return sections
|
|
301
|
+
|
|
302
|
+
def save_output_style(self, content: str) -> Path:
|
|
303
|
+
"""
|
|
304
|
+
Save output style content to OUTPUT_STYLE.md.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
content: The formatted output style content
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Path to the saved file
|
|
311
|
+
"""
|
|
312
|
+
try:
|
|
313
|
+
# Ensure the parent directory exists
|
|
314
|
+
self.mpm_output_style_path.parent.mkdir(parents=True, exist_ok=True)
|
|
315
|
+
|
|
316
|
+
# Write the content
|
|
317
|
+
self.mpm_output_style_path.write_text(content, encoding='utf-8')
|
|
318
|
+
self.logger.info(f"Saved output style to {self.mpm_output_style_path}")
|
|
319
|
+
|
|
320
|
+
return self.mpm_output_style_path
|
|
321
|
+
except Exception as e:
|
|
322
|
+
self.logger.error(f"Failed to save output style: {e}")
|
|
323
|
+
raise
|
|
324
|
+
|
|
325
|
+
def deploy_output_style(self, content: str) -> bool:
|
|
326
|
+
"""
|
|
327
|
+
Deploy output style to Claude Desktop if version >= 1.0.83.
|
|
328
|
+
Deploys the style file and activates it once.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
content: The output style content to deploy
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
True if deployed successfully, False otherwise
|
|
335
|
+
"""
|
|
336
|
+
if not self.supports_output_styles():
|
|
337
|
+
self.logger.info(f"Claude version {self.claude_version or 'unknown'} does not support output styles")
|
|
338
|
+
return False
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
# Ensure output-styles directory exists
|
|
342
|
+
self.output_style_dir.mkdir(parents=True, exist_ok=True)
|
|
343
|
+
|
|
344
|
+
# Write the output style file
|
|
345
|
+
self.output_style_path.write_text(content, encoding='utf-8')
|
|
346
|
+
self.logger.info(f"Deployed output style to {self.output_style_path}")
|
|
347
|
+
|
|
348
|
+
# Activate the claude-mpm style
|
|
349
|
+
self._activate_output_style()
|
|
350
|
+
|
|
351
|
+
return True
|
|
352
|
+
|
|
353
|
+
except Exception as e:
|
|
354
|
+
self.logger.error(f"Failed to deploy output style: {e}")
|
|
355
|
+
return False
|
|
356
|
+
|
|
357
|
+
def _activate_output_style(self) -> bool:
|
|
358
|
+
"""
|
|
359
|
+
Update Claude Desktop settings to activate the claude-mpm output style.
|
|
360
|
+
Sets activeOutputStyle to "claude-mpm" once at startup.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
True if activated successfully, False otherwise
|
|
364
|
+
"""
|
|
365
|
+
try:
|
|
366
|
+
# Load existing settings or create new
|
|
367
|
+
settings = {}
|
|
368
|
+
if self.settings_file.exists():
|
|
369
|
+
try:
|
|
370
|
+
settings = json.loads(self.settings_file.read_text())
|
|
371
|
+
except json.JSONDecodeError:
|
|
372
|
+
self.logger.warning("Could not parse existing settings.json, using defaults")
|
|
373
|
+
|
|
374
|
+
# Check current active style
|
|
375
|
+
current_style = settings.get("activeOutputStyle")
|
|
376
|
+
|
|
377
|
+
# Update active output style to claude-mpm if not already set
|
|
378
|
+
if current_style != "claude-mpm":
|
|
379
|
+
settings["activeOutputStyle"] = "claude-mpm"
|
|
380
|
+
|
|
381
|
+
# Ensure settings directory exists
|
|
382
|
+
self.settings_file.parent.mkdir(parents=True, exist_ok=True)
|
|
383
|
+
|
|
384
|
+
# Write updated settings
|
|
385
|
+
self.settings_file.write_text(
|
|
386
|
+
json.dumps(settings, indent=2),
|
|
387
|
+
encoding='utf-8'
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
self.logger.info(f"✅ Activated claude-mpm output style (was: {current_style or 'none'})")
|
|
391
|
+
else:
|
|
392
|
+
self.logger.debug("Claude MPM output style already active")
|
|
393
|
+
|
|
394
|
+
return True
|
|
395
|
+
|
|
396
|
+
except Exception as e:
|
|
397
|
+
self.logger.warning(f"Failed to update settings: {e}")
|
|
398
|
+
return False
|
|
399
|
+
|
|
400
|
+
def get_status_summary(self) -> Dict[str, str]:
|
|
401
|
+
"""
|
|
402
|
+
Get a summary of the output style status.
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
Dictionary with status information
|
|
406
|
+
"""
|
|
407
|
+
status = {
|
|
408
|
+
"claude_version": self.claude_version or "Not detected",
|
|
409
|
+
"supports_output_styles": "Yes" if self.supports_output_styles() else "No",
|
|
410
|
+
"deployment_mode": "Not initialized",
|
|
411
|
+
"active_style": "Unknown",
|
|
412
|
+
"file_status": "Not checked"
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if self.supports_output_styles():
|
|
416
|
+
status["deployment_mode"] = "Output style deployment"
|
|
417
|
+
|
|
418
|
+
# Check if file exists
|
|
419
|
+
if self.output_style_path.exists():
|
|
420
|
+
status["file_status"] = "Deployed"
|
|
421
|
+
else:
|
|
422
|
+
status["file_status"] = "Pending deployment"
|
|
423
|
+
|
|
424
|
+
# Check active style
|
|
425
|
+
if self.settings_file.exists():
|
|
426
|
+
try:
|
|
427
|
+
settings = json.loads(self.settings_file.read_text())
|
|
428
|
+
status["active_style"] = settings.get("activeOutputStyle", "none")
|
|
429
|
+
except Exception:
|
|
430
|
+
status["active_style"] = "Error reading settings"
|
|
431
|
+
else:
|
|
432
|
+
status["deployment_mode"] = "Framework injection"
|
|
433
|
+
status["file_status"] = "N/A (legacy mode)"
|
|
434
|
+
status["active_style"] = "N/A (legacy mode)"
|
|
435
|
+
|
|
436
|
+
return status
|
|
437
|
+
|
|
438
|
+
def get_injectable_content(self, framework_loader=None) -> str:
|
|
439
|
+
"""
|
|
440
|
+
Get output style content for injection into instructions (for Claude < 1.0.83).
|
|
441
|
+
|
|
442
|
+
This returns a simplified version without YAML frontmatter, suitable for
|
|
443
|
+
injection into the framework instructions.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
framework_loader: Optional FrameworkLoader instance to reuse loaded content
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
Simplified output style content for injection
|
|
450
|
+
"""
|
|
451
|
+
# Extract the same content but without YAML frontmatter
|
|
452
|
+
full_content = self.extract_output_style_content(framework_loader)
|
|
453
|
+
|
|
454
|
+
# Remove YAML frontmatter
|
|
455
|
+
lines = full_content.split('\n')
|
|
456
|
+
if lines[0] == '---':
|
|
457
|
+
# Find the closing ---
|
|
458
|
+
for i in range(1, len(lines)):
|
|
459
|
+
if lines[i] == '---':
|
|
460
|
+
# Skip frontmatter and empty lines after it
|
|
461
|
+
content_start = i + 1
|
|
462
|
+
while content_start < len(lines) and not lines[content_start].strip():
|
|
463
|
+
content_start += 1
|
|
464
|
+
return '\n'.join(lines[content_start:])
|
|
465
|
+
|
|
466
|
+
# If no frontmatter found, return as-is
|
|
467
|
+
return full_content
|
|
468
|
+
|