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.
Files changed (61) hide show
  1. claude_mpm/BUILD_NUMBER +1 -1
  2. claude_mpm/VERSION +1 -1
  3. claude_mpm/__main__.py +4 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +38 -2
  5. claude_mpm/agents/INSTRUCTIONS.md +74 -0
  6. claude_mpm/agents/OUTPUT_STYLE.md +84 -0
  7. claude_mpm/agents/WORKFLOW.md +308 -4
  8. claude_mpm/agents/agents_metadata.py +52 -0
  9. claude_mpm/agents/base_agent_loader.py +75 -19
  10. claude_mpm/agents/templates/__init__.py +4 -0
  11. claude_mpm/agents/templates/api_qa.json +206 -0
  12. claude_mpm/agents/templates/qa.json +1 -1
  13. claude_mpm/agents/templates/research.json +24 -16
  14. claude_mpm/agents/templates/ticketing.json +18 -5
  15. claude_mpm/agents/templates/vercel_ops_agent.json +281 -0
  16. claude_mpm/agents/templates/vercel_ops_instructions.md +582 -0
  17. claude_mpm/cli/__init__.py +23 -1
  18. claude_mpm/cli/__main__.py +4 -0
  19. claude_mpm/cli/commands/mcp_command_router.py +87 -1
  20. claude_mpm/cli/commands/mcp_install_commands.py +207 -26
  21. claude_mpm/cli/commands/memory.py +32 -5
  22. claude_mpm/cli/commands/run.py +33 -6
  23. claude_mpm/cli/parsers/base_parser.py +5 -0
  24. claude_mpm/cli/parsers/mcp_parser.py +23 -0
  25. claude_mpm/cli/parsers/run_parser.py +5 -0
  26. claude_mpm/cli/utils.py +17 -4
  27. claude_mpm/constants.py +1 -0
  28. claude_mpm/core/base_service.py +8 -2
  29. claude_mpm/core/config.py +122 -32
  30. claude_mpm/core/framework_loader.py +385 -34
  31. claude_mpm/core/interactive_session.py +77 -12
  32. claude_mpm/core/oneshot_session.py +7 -1
  33. claude_mpm/core/output_style_manager.py +468 -0
  34. claude_mpm/core/unified_paths.py +190 -21
  35. claude_mpm/hooks/claude_hooks/hook_handler.py +91 -16
  36. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +3 -0
  37. claude_mpm/init.py +1 -0
  38. claude_mpm/scripts/socketio_daemon.py +67 -7
  39. claude_mpm/scripts/socketio_daemon_hardened.py +897 -0
  40. claude_mpm/services/agents/deployment/agent_deployment.py +216 -10
  41. claude_mpm/services/agents/deployment/agent_template_builder.py +37 -1
  42. claude_mpm/services/agents/deployment/async_agent_deployment.py +65 -1
  43. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +441 -0
  44. claude_mpm/services/agents/memory/__init__.py +0 -2
  45. claude_mpm/services/agents/memory/agent_memory_manager.py +577 -44
  46. claude_mpm/services/agents/memory/content_manager.py +144 -14
  47. claude_mpm/services/agents/memory/template_generator.py +7 -354
  48. claude_mpm/services/mcp_gateway/server/stdio_server.py +61 -169
  49. claude_mpm/services/memory_hook_service.py +62 -4
  50. claude_mpm/services/runner_configuration_service.py +5 -9
  51. claude_mpm/services/socketio/server/broadcaster.py +32 -1
  52. claude_mpm/services/socketio/server/core.py +4 -0
  53. claude_mpm/services/socketio/server/main.py +23 -4
  54. claude_mpm/services/subprocess_launcher_service.py +5 -0
  55. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/METADATA +1 -1
  56. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/RECORD +60 -54
  57. claude_mpm/services/agents/memory/analyzer.py +0 -430
  58. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/WHEEL +0 -0
  59. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/entry_points.txt +0 -0
  60. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/licenses/LICENSE +0 -0
  61. {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
+