claude-mpm 4.0.28__py3-none-any.whl → 4.0.30__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 (89) hide show
  1. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +48 -3
  2. claude_mpm/agents/BASE_PM.md +20 -15
  3. claude_mpm/agents/INSTRUCTIONS.md +12 -2
  4. claude_mpm/agents/templates/agent-manager.json +24 -0
  5. claude_mpm/agents/templates/agent-manager.md +304 -0
  6. claude_mpm/agents/templates/documentation.json +16 -3
  7. claude_mpm/agents/templates/engineer.json +19 -5
  8. claude_mpm/agents/templates/ops.json +19 -5
  9. claude_mpm/agents/templates/qa.json +16 -3
  10. claude_mpm/agents/templates/refactoring_engineer.json +25 -7
  11. claude_mpm/agents/templates/research.json +19 -5
  12. claude_mpm/cli/__init__.py +4 -0
  13. claude_mpm/cli/commands/__init__.py +4 -0
  14. claude_mpm/cli/commands/agent_manager.py +521 -0
  15. claude_mpm/cli/commands/agents.py +2 -1
  16. claude_mpm/cli/commands/cleanup.py +1 -1
  17. claude_mpm/cli/commands/doctor.py +209 -0
  18. claude_mpm/cli/commands/mcp.py +3 -3
  19. claude_mpm/cli/commands/mcp_install_commands.py +12 -30
  20. claude_mpm/cli/commands/mcp_server_commands.py +9 -9
  21. claude_mpm/cli/commands/memory.py +1 -1
  22. claude_mpm/cli/commands/run.py +31 -2
  23. claude_mpm/cli/commands/run_config_checker.py +1 -1
  24. claude_mpm/cli/parsers/agent_manager_parser.py +247 -0
  25. claude_mpm/cli/parsers/base_parser.py +12 -1
  26. claude_mpm/cli/parsers/mcp_parser.py +1 -1
  27. claude_mpm/cli/parsers/run_parser.py +1 -1
  28. claude_mpm/cli/shared/__init__.py +1 -1
  29. claude_mpm/cli/startup_logging.py +463 -0
  30. claude_mpm/constants.py +2 -0
  31. claude_mpm/core/claude_runner.py +81 -2
  32. claude_mpm/core/constants.py +2 -2
  33. claude_mpm/core/framework_loader.py +45 -11
  34. claude_mpm/core/interactive_session.py +82 -3
  35. claude_mpm/core/output_style_manager.py +6 -6
  36. claude_mpm/core/socketio_pool.py +2 -2
  37. claude_mpm/core/unified_paths.py +128 -0
  38. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  39. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  40. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  41. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  42. claude_mpm/dashboard/static/css/dashboard.css +170 -0
  43. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  44. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  45. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  46. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +21 -3
  47. claude_mpm/dashboard/static/js/components/module-viewer.js +129 -1
  48. claude_mpm/dashboard/static/js/dashboard.js +116 -0
  49. claude_mpm/dashboard/static/js/socket-client.js +0 -1
  50. claude_mpm/hooks/claude_hooks/connection_pool.py +1 -1
  51. claude_mpm/hooks/claude_hooks/hook_handler.py +1 -1
  52. claude_mpm/scripts/mcp_server.py +2 -2
  53. claude_mpm/services/agents/agent_builder.py +455 -0
  54. claude_mpm/services/agents/deployment/agent_template_builder.py +10 -3
  55. claude_mpm/services/agents/deployment/agent_validator.py +1 -0
  56. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +69 -1
  57. claude_mpm/services/diagnostics/__init__.py +18 -0
  58. claude_mpm/services/diagnostics/checks/__init__.py +30 -0
  59. claude_mpm/services/diagnostics/checks/agent_check.py +319 -0
  60. claude_mpm/services/diagnostics/checks/base_check.py +64 -0
  61. claude_mpm/services/diagnostics/checks/claude_desktop_check.py +283 -0
  62. claude_mpm/services/diagnostics/checks/common_issues_check.py +354 -0
  63. claude_mpm/services/diagnostics/checks/configuration_check.py +300 -0
  64. claude_mpm/services/diagnostics/checks/filesystem_check.py +233 -0
  65. claude_mpm/services/diagnostics/checks/installation_check.py +255 -0
  66. claude_mpm/services/diagnostics/checks/mcp_check.py +315 -0
  67. claude_mpm/services/diagnostics/checks/monitor_check.py +282 -0
  68. claude_mpm/services/diagnostics/checks/startup_log_check.py +322 -0
  69. claude_mpm/services/diagnostics/diagnostic_runner.py +247 -0
  70. claude_mpm/services/diagnostics/doctor_reporter.py +283 -0
  71. claude_mpm/services/diagnostics/models.py +120 -0
  72. claude_mpm/services/mcp_gateway/core/interfaces.py +1 -1
  73. claude_mpm/services/mcp_gateway/main.py +1 -1
  74. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +3 -3
  75. claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
  76. claude_mpm/services/mcp_gateway/server/stdio_server.py +3 -3
  77. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +2 -2
  78. claude_mpm/services/memory/__init__.py +2 -0
  79. claude_mpm/services/socketio/handlers/connection.py +27 -33
  80. claude_mpm/services/socketio/handlers/registry.py +39 -7
  81. claude_mpm/services/socketio/server/core.py +72 -22
  82. claude_mpm/validation/frontmatter_validator.py +1 -1
  83. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/METADATA +4 -1
  84. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/RECORD +89 -67
  85. /claude_mpm/cli/shared/{command_base.py → base_command.py} +0 -0
  86. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/WHEEL +0 -0
  87. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/entry_points.txt +0 -0
  88. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/licenses/LICENSE +0 -0
  89. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/top_level.txt +0 -0
@@ -36,7 +36,7 @@ try:
36
36
  except ImportError:
37
37
  # Fallback values if constants module not available
38
38
  class NetworkConfig:
39
- SOCKETIO_PORT_RANGE = (8080, 8099)
39
+ SOCKETIO_PORT_RANGE = (8765, 8785)
40
40
  RECONNECTION_DELAY = 0.5
41
41
  SOCKET_WAIT_TIMEOUT = 1.0
42
42
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
- """MCP Server launcher script for Claude Desktop.
2
+ """MCP Server launcher script for Claude Code.
3
3
 
4
- This script launches the MCP gateway server for Claude Desktop integration.
4
+ This script launches the MCP gateway server for Claude Code integration.
5
5
  It handles proper Python path setup and error reporting to stderr.
6
6
  """
7
7
 
@@ -0,0 +1,455 @@
1
+ """Agent Builder Service for programmatic agent creation and management.
2
+
3
+ This service provides comprehensive agent lifecycle management including:
4
+ - Template-based agent generation
5
+ - Agent variant creation with inheritance
6
+ - Configuration validation and sanitization
7
+ - PM instruction customization
8
+ - Integration with deployment services
9
+ """
10
+
11
+ import json
12
+ import logging
13
+ import re
14
+ from datetime import datetime
15
+ from pathlib import Path
16
+ from typing import Any, Dict, List, Optional, Tuple
17
+
18
+ from claude_mpm.core.exceptions import AgentDeploymentError
19
+ from claude_mpm.core.logging_config import get_logger
20
+
21
+
22
+ class AgentBuilderService:
23
+ """Service for building and managing agent configurations."""
24
+
25
+ # Valid agent models
26
+ VALID_MODELS = ["sonnet", "opus", "haiku"]
27
+
28
+ # Valid tool choices
29
+ VALID_TOOL_CHOICES = ["auto", "required", "any", "none"]
30
+
31
+ # Agent categories
32
+ AGENT_CATEGORIES = [
33
+ "engineering", "qa", "documentation", "ops",
34
+ "research", "security", "system", "utility"
35
+ ]
36
+
37
+ def __init__(self, templates_dir: Optional[Path] = None):
38
+ """Initialize the Agent Builder Service.
39
+
40
+ Args:
41
+ templates_dir: Path to agent templates directory
42
+ """
43
+ self.logger = get_logger(__name__)
44
+ self.templates_dir = templates_dir or Path(__file__).parent.parent.parent / "agents" / "templates"
45
+ self._template_cache = {}
46
+
47
+ def create_agent(
48
+ self,
49
+ agent_id: str,
50
+ name: str,
51
+ description: str,
52
+ model: str = "sonnet",
53
+ tool_choice: str = "auto",
54
+ instructions: Optional[str] = None,
55
+ metadata: Optional[Dict[str, Any]] = None,
56
+ base_template: Optional[str] = None
57
+ ) -> Dict[str, Any]:
58
+ """Create a new agent configuration.
59
+
60
+ Args:
61
+ agent_id: Unique identifier for the agent
62
+ name: Display name for the agent
63
+ description: Agent purpose and capabilities
64
+ model: LLM model to use (sonnet/opus/haiku)
65
+ tool_choice: Tool selection strategy
66
+ instructions: Markdown instructions content
67
+ metadata: Additional agent metadata
68
+ base_template: Optional base template to extend
69
+
70
+ Returns:
71
+ Complete agent configuration dictionary
72
+
73
+ Raises:
74
+ AgentDeploymentError: If validation fails
75
+ """
76
+ # Validate inputs
77
+ self._validate_agent_id(agent_id)
78
+ self._validate_model(model)
79
+ self._validate_tool_choice(tool_choice)
80
+
81
+ # Start with base template if provided
82
+ if base_template:
83
+ config = self._load_template(base_template)
84
+ config["id"] = agent_id # Override ID
85
+ else:
86
+ config = {}
87
+
88
+ # Build agent configuration
89
+ config.update({
90
+ "id": agent_id,
91
+ "name": name,
92
+ "prompt": f"{agent_id}.md",
93
+ "model": model,
94
+ "tool_choice": tool_choice
95
+ })
96
+
97
+ # Build metadata
98
+ agent_metadata = {
99
+ "description": description,
100
+ "version": "1.0.0",
101
+ "created": datetime.now().isoformat(),
102
+ "author": "Agent Manager",
103
+ "category": "custom"
104
+ }
105
+
106
+ if metadata:
107
+ agent_metadata.update(metadata)
108
+
109
+ config["metadata"] = agent_metadata
110
+
111
+ # Generate instructions if not provided
112
+ if instructions is None:
113
+ instructions = self._generate_default_instructions(agent_id, name, description)
114
+
115
+ return config, instructions
116
+
117
+ def create_variant(
118
+ self,
119
+ base_agent_id: str,
120
+ variant_id: str,
121
+ variant_name: str,
122
+ modifications: Dict[str, Any],
123
+ instructions_append: Optional[str] = None
124
+ ) -> Tuple[Dict[str, Any], str]:
125
+ """Create an agent variant based on an existing agent.
126
+
127
+ Args:
128
+ base_agent_id: ID of the base agent to extend
129
+ variant_id: Unique ID for the variant
130
+ variant_name: Display name for the variant
131
+ modifications: Configuration changes for the variant
132
+ instructions_append: Additional instructions to append
133
+
134
+ Returns:
135
+ Tuple of (variant configuration, variant instructions)
136
+
137
+ Raises:
138
+ AgentDeploymentError: If base agent not found or validation fails
139
+ """
140
+ # Load base agent
141
+ base_config = self._load_template(base_agent_id)
142
+ base_instructions = self._load_instructions(base_agent_id)
143
+
144
+ # Validate variant ID
145
+ self._validate_agent_id(variant_id)
146
+
147
+ # Create variant configuration
148
+ variant_config = base_config.copy()
149
+ variant_config["id"] = variant_id
150
+ variant_config["name"] = variant_name
151
+ variant_config["prompt"] = f"{variant_id}.md"
152
+
153
+ # Apply modifications
154
+ for key, value in modifications.items():
155
+ if key in ["model", "tool_choice"]:
156
+ if key == "model":
157
+ self._validate_model(value)
158
+ elif key == "tool_choice":
159
+ self._validate_tool_choice(value)
160
+ variant_config[key] = value
161
+
162
+ # Update metadata
163
+ if "metadata" not in variant_config:
164
+ variant_config["metadata"] = {}
165
+
166
+ variant_config["metadata"].update({
167
+ "base_agent": base_agent_id,
168
+ "variant": True,
169
+ "variant_created": datetime.now().isoformat()
170
+ })
171
+
172
+ # Build variant instructions
173
+ variant_instructions = f"# {variant_name} (Variant of {base_config.get('name', base_agent_id)})\n\n"
174
+ variant_instructions += base_instructions
175
+
176
+ if instructions_append:
177
+ variant_instructions += f"\n\n## Variant-Specific Instructions\n\n{instructions_append}"
178
+
179
+ return variant_config, variant_instructions
180
+
181
+ def validate_configuration(self, config: Dict[str, Any]) -> List[str]:
182
+ """Validate an agent configuration.
183
+
184
+ Args:
185
+ config: Agent configuration to validate
186
+
187
+ Returns:
188
+ List of validation errors (empty if valid)
189
+ """
190
+ errors = []
191
+
192
+ # Required fields
193
+ required_fields = ["id", "name", "prompt", "model"]
194
+ for field in required_fields:
195
+ if field not in config:
196
+ errors.append(f"Missing required field: {field}")
197
+
198
+ # Validate ID
199
+ if "id" in config:
200
+ try:
201
+ self._validate_agent_id(config["id"])
202
+ except AgentDeploymentError as e:
203
+ errors.append(str(e))
204
+
205
+ # Validate model
206
+ if "model" in config:
207
+ try:
208
+ self._validate_model(config["model"])
209
+ except AgentDeploymentError as e:
210
+ errors.append(str(e))
211
+
212
+ # Validate tool_choice
213
+ if "tool_choice" in config:
214
+ try:
215
+ self._validate_tool_choice(config["tool_choice"])
216
+ except AgentDeploymentError as e:
217
+ errors.append(str(e))
218
+
219
+ # Validate metadata
220
+ if "metadata" in config:
221
+ if not isinstance(config["metadata"], dict):
222
+ errors.append("Metadata must be a dictionary")
223
+
224
+ return errors
225
+
226
+ def generate_pm_instructions(
227
+ self,
228
+ delegation_patterns: Optional[List[str]] = None,
229
+ workflow_overrides: Optional[Dict[str, str]] = None,
230
+ custom_rules: Optional[List[str]] = None
231
+ ) -> str:
232
+ """Generate customized PM instructions.
233
+
234
+ Args:
235
+ delegation_patterns: Custom delegation patterns
236
+ workflow_overrides: Workflow sequence modifications
237
+ custom_rules: Additional PM rules
238
+
239
+ Returns:
240
+ Customized PM instructions markdown
241
+ """
242
+ instructions = "# Custom PM Instructions\n\n"
243
+
244
+ if delegation_patterns:
245
+ instructions += "## Custom Delegation Patterns\n\n"
246
+ for pattern in delegation_patterns:
247
+ instructions += f"- {pattern}\n"
248
+ instructions += "\n"
249
+
250
+ if workflow_overrides:
251
+ instructions += "## Workflow Overrides\n\n"
252
+ for workflow, override in workflow_overrides.items():
253
+ instructions += f"### {workflow}\n{override}\n\n"
254
+
255
+ if custom_rules:
256
+ instructions += "## Additional Rules\n\n"
257
+ for rule in custom_rules:
258
+ instructions += f"- {rule}\n"
259
+
260
+ return instructions
261
+
262
+ def list_available_templates(self) -> List[Dict[str, Any]]:
263
+ """List all available agent templates.
264
+
265
+ Returns:
266
+ List of template metadata dictionaries
267
+ """
268
+ templates = []
269
+
270
+ if not self.templates_dir.exists():
271
+ return templates
272
+
273
+ for template_file in self.templates_dir.glob("*.json"):
274
+ try:
275
+ with open(template_file, 'r') as f:
276
+ config = json.load(f)
277
+
278
+ # Use filename stem as ID if not specified in config
279
+ template_id = config.get("id") or template_file.stem
280
+
281
+ templates.append({
282
+ "id": template_id,
283
+ "name": config.get("name", template_id),
284
+ "description": config.get("metadata", {}).get("description"),
285
+ "category": config.get("metadata", {}).get("category"),
286
+ "version": config.get("metadata", {}).get("version"),
287
+ "file": str(template_file)
288
+ })
289
+ except Exception as e:
290
+ self.logger.warning(f"Failed to load template {template_file}: {e}")
291
+
292
+ return templates
293
+
294
+ def _validate_agent_id(self, agent_id: str) -> None:
295
+ """Validate agent ID format.
296
+
297
+ Args:
298
+ agent_id: Agent ID to validate
299
+
300
+ Raises:
301
+ AgentDeploymentError: If ID is invalid
302
+ """
303
+ if not agent_id:
304
+ raise AgentDeploymentError("Agent ID cannot be empty")
305
+
306
+ if len(agent_id) > 50:
307
+ raise AgentDeploymentError("Agent ID must be 50 characters or less")
308
+
309
+ if not re.match(r'^[a-z0-9-]+$', agent_id):
310
+ raise AgentDeploymentError(
311
+ "Agent ID must contain only lowercase letters, numbers, and hyphens"
312
+ )
313
+
314
+ def _validate_model(self, model: str) -> None:
315
+ """Validate model selection.
316
+
317
+ Args:
318
+ model: Model to validate
319
+
320
+ Raises:
321
+ AgentDeploymentError: If model is invalid
322
+ """
323
+ if model not in self.VALID_MODELS:
324
+ raise AgentDeploymentError(
325
+ f"Invalid model '{model}'. Must be one of: {', '.join(self.VALID_MODELS)}"
326
+ )
327
+
328
+ def _validate_tool_choice(self, tool_choice: str) -> None:
329
+ """Validate tool choice setting.
330
+
331
+ Args:
332
+ tool_choice: Tool choice to validate
333
+
334
+ Raises:
335
+ AgentDeploymentError: If tool choice is invalid
336
+ """
337
+ if tool_choice not in self.VALID_TOOL_CHOICES:
338
+ raise AgentDeploymentError(
339
+ f"Invalid tool_choice '{tool_choice}'. Must be one of: {', '.join(self.VALID_TOOL_CHOICES)}"
340
+ )
341
+
342
+ def _load_template(self, template_id: str) -> Dict[str, Any]:
343
+ """Load an agent template.
344
+
345
+ Args:
346
+ template_id: Template ID to load
347
+
348
+ Returns:
349
+ Template configuration dictionary
350
+
351
+ Raises:
352
+ AgentDeploymentError: If template not found
353
+ """
354
+ if template_id in self._template_cache:
355
+ return self._template_cache[template_id].copy()
356
+
357
+ template_file = self.templates_dir / f"{template_id}.json"
358
+
359
+ if not template_file.exists():
360
+ raise AgentDeploymentError(f"Template '{template_id}' not found")
361
+
362
+ try:
363
+ with open(template_file, 'r') as f:
364
+ config = json.load(f)
365
+ self._template_cache[template_id] = config
366
+ return config.copy()
367
+ except Exception as e:
368
+ raise AgentDeploymentError(f"Failed to load template '{template_id}': {e}")
369
+
370
+ def _load_instructions(self, agent_id: str) -> str:
371
+ """Load agent instructions.
372
+
373
+ Args:
374
+ agent_id: Agent ID to load instructions for
375
+
376
+ Returns:
377
+ Instructions markdown content
378
+
379
+ Raises:
380
+ AgentDeploymentError: If instructions not found
381
+ """
382
+ # Try multiple possible locations
383
+ possible_files = [
384
+ self.templates_dir / f"{agent_id}.md",
385
+ self.templates_dir / f"{agent_id}_instructions.md",
386
+ self.templates_dir / f"{agent_id}-instructions.md"
387
+ ]
388
+
389
+ for instructions_file in possible_files:
390
+ if instructions_file.exists():
391
+ try:
392
+ with open(instructions_file, 'r') as f:
393
+ return f.read()
394
+ except Exception as e:
395
+ self.logger.warning(f"Failed to read instructions from {instructions_file}: {e}")
396
+
397
+ # If no instructions found, return empty
398
+ return ""
399
+
400
+ def _generate_default_instructions(self, agent_id: str, name: str, description: str) -> str:
401
+ """Generate default agent instructions.
402
+
403
+ Args:
404
+ agent_id: Agent identifier
405
+ name: Agent display name
406
+ description: Agent description
407
+
408
+ Returns:
409
+ Default instructions markdown
410
+ """
411
+ return f"""# {name}
412
+
413
+ ## Core Identity
414
+
415
+ You are {name}, a specialized agent in the Claude MPM framework.
416
+
417
+ ## Purpose
418
+
419
+ {description}
420
+
421
+ ## Responsibilities
422
+
423
+ - Primary focus on your specialized domain
424
+ - Collaborate with other agents as needed
425
+ - Follow Claude MPM framework conventions
426
+ - Maintain high quality standards
427
+
428
+ ## Operating Principles
429
+
430
+ 1. **Expertise**: Apply deep knowledge in your domain
431
+ 2. **Efficiency**: Complete tasks effectively and quickly
432
+ 3. **Communication**: Provide clear, actionable responses
433
+ 4. **Collaboration**: Work well with other agents
434
+ 5. **Quality**: Maintain high standards in all outputs
435
+
436
+ ## Output Format
437
+
438
+ Provide structured responses with:
439
+ - Clear summaries of actions taken
440
+ - Detailed results when appropriate
441
+ - Any issues or blockers encountered
442
+ - Recommendations for next steps
443
+
444
+ ## Integration
445
+
446
+ - Follow framework patterns and conventions
447
+ - Use appropriate tools for the task
448
+ - Coordinate with PM for complex workflows
449
+ - Report completion status clearly
450
+
451
+ ---
452
+
453
+ *Agent ID: {agent_id}*
454
+ *Generated by Agent Manager*
455
+ """
@@ -60,17 +60,23 @@ class AgentTemplateBuilder:
60
60
  raise
61
61
 
62
62
  # Extract tools from template with fallback
63
+ # Handle both dict and list formats for capabilities (backward compatibility)
64
+ capabilities = template_data.get("capabilities", {})
65
+ capabilities_tools = capabilities.get("tools") if isinstance(capabilities, dict) else None
66
+
63
67
  tools = (
64
68
  template_data.get("tools")
65
- or template_data.get("capabilities", {}).get("tools")
69
+ or capabilities_tools
66
70
  or template_data.get("configuration_fields", {}).get("tools")
67
71
  or ["Read", "Write", "Edit", "Grep", "Glob", "LS"] # Default fallback
68
72
  )
69
73
 
70
74
  # Extract model from template with fallback
75
+ capabilities_model = capabilities.get("model") if isinstance(capabilities, dict) else None
76
+
71
77
  model = (
72
78
  template_data.get("model")
73
- or template_data.get("capabilities", {}).get("model")
79
+ or capabilities_model
74
80
  or template_data.get("configuration_fields", {}).get("model")
75
81
  or "sonnet" # Default fallback
76
82
  )
@@ -132,7 +138,8 @@ class AgentTemplateBuilder:
132
138
  # Extract custom metadata fields
133
139
  agent_version = template_data.get("agent_version", "1.0.0")
134
140
  agent_type = template_data.get("agent_type", "general")
135
- model_type = template_data.get("capabilities", {}).get("model", "sonnet")
141
+ # Use the capabilities_model we already extracted earlier
142
+ model_type = capabilities_model or "sonnet"
136
143
 
137
144
  # Map our model types to Claude Code format
138
145
  if model_type in ["opus", "sonnet", "haiku"]:
@@ -324,6 +324,7 @@ class AgentValidator:
324
324
  agent_info = {
325
325
  "file": agent_file.name,
326
326
  "name": agent_file.stem,
327
+ "path": str(agent_file),
327
328
  "description": "No description",
328
329
  "version": "unknown",
329
330
  }
@@ -363,6 +363,10 @@ class MultiSourceAgentDeploymentService:
363
363
  source_match = re.search(r'^source:\s*(.+)$', deployed_content, re.MULTILINE)
364
364
  if source_match:
365
365
  deployed_source = source_match.group(1).strip()
366
+
367
+ # If source is still unknown, try to infer it from deployment context
368
+ if deployed_source == "unknown":
369
+ deployed_source = self._infer_agent_source_from_context(agent_name, deployed_agents_dir)
366
370
  except Exception as e:
367
371
  self.logger.warning(f"Error reading deployed agent '{agent_name}': {e}")
368
372
  comparison_results["needs_update"].append(agent_name)
@@ -438,4 +442,68 @@ class MultiSourceAgentDeploymentService:
438
442
  f"{downgrade['template_version']}"
439
443
  )
440
444
 
441
- return comparison_results
445
+ return comparison_results
446
+
447
+ def _infer_agent_source_from_context(self, agent_name: str, deployed_agents_dir: Path) -> str:
448
+ """Infer the source of a deployed agent when source metadata is missing.
449
+
450
+ This method attempts to determine the agent source based on:
451
+ 1. Deployment context (development vs pipx)
452
+ 2. Agent naming patterns
453
+ 3. Known system agents
454
+
455
+ Args:
456
+ agent_name: Name of the agent
457
+ deployed_agents_dir: Directory where agent is deployed
458
+
459
+ Returns:
460
+ Inferred source string (system/project/user)
461
+ """
462
+ # List of known system agents that ship with claude-mpm
463
+ system_agents = {
464
+ "pm", "engineer", "qa", "research", "documentation", "ops",
465
+ "security", "web-ui", "api-qa", "version-control"
466
+ }
467
+
468
+ # If this is a known system agent, it's from system
469
+ if agent_name in system_agents:
470
+ return "system"
471
+
472
+ # Check deployment context
473
+ from ....core.unified_paths import get_path_manager
474
+ path_manager = get_path_manager()
475
+
476
+ # If deployed_agents_dir is under user home/.claude/agents, check context
477
+ user_claude_dir = Path.home() / ".claude" / "agents"
478
+ if deployed_agents_dir == user_claude_dir:
479
+ # Check if we're in development mode
480
+ try:
481
+ from ....core.unified_paths import DeploymentContext, PathContext
482
+ deployment_context = PathContext.detect_deployment_context()
483
+
484
+ if deployment_context in (DeploymentContext.DEVELOPMENT, DeploymentContext.EDITABLE_INSTALL):
485
+ # In development mode, unknown agents are likely system agents being tested
486
+ return "system"
487
+ elif deployment_context == DeploymentContext.PIPX_INSTALL:
488
+ # In pipx mode, unknown agents could be system agents
489
+ # Check if agent follows system naming patterns
490
+ if agent_name.count('-') <= 2 and len(agent_name) <= 20:
491
+ return "system"
492
+ except Exception:
493
+ pass
494
+
495
+ # Check if deployed to project-specific directory
496
+ try:
497
+ project_root = path_manager.project_root
498
+ if str(deployed_agents_dir).startswith(str(project_root)):
499
+ return "project"
500
+ except Exception:
501
+ pass
502
+
503
+ # Default inference based on naming patterns
504
+ # System agents typically have simple names
505
+ if '-' not in agent_name or agent_name.count('-') <= 1:
506
+ return "system"
507
+
508
+ # Complex names are more likely to be user/project agents
509
+ return "user"
@@ -0,0 +1,18 @@
1
+ """
2
+ Diagnostic service for claude-mpm health checks.
3
+
4
+ WHY: Provide a comprehensive diagnostic tool to help users identify and fix
5
+ common issues with their claude-mpm installation and configuration.
6
+
7
+ DESIGN DECISIONS:
8
+ - Modular check system for easy extension
9
+ - Interface-based design for consistency
10
+ - Clear status levels (ok, warning, error)
11
+ - Actionable fix suggestions
12
+ """
13
+
14
+ from .diagnostic_runner import DiagnosticRunner
15
+ from .doctor_reporter import DoctorReporter
16
+ from .models import DiagnosticResult, DiagnosticStatus
17
+
18
+ __all__ = ["DiagnosticRunner", "DoctorReporter", "DiagnosticResult", "DiagnosticStatus"]
@@ -0,0 +1,30 @@
1
+ """
2
+ Diagnostic checks for claude-mpm doctor command.
3
+
4
+ WHY: Modular checks allow for easy extension and testing of individual
5
+ diagnostic components.
6
+ """
7
+
8
+ from .agent_check import AgentCheck
9
+ from .base_check import BaseDiagnosticCheck
10
+ from .claude_desktop_check import ClaudeDesktopCheck
11
+ from .common_issues_check import CommonIssuesCheck
12
+ from .configuration_check import ConfigurationCheck
13
+ from .filesystem_check import FilesystemCheck
14
+ from .installation_check import InstallationCheck
15
+ from .mcp_check import MCPCheck
16
+ from .monitor_check import MonitorCheck
17
+ from .startup_log_check import StartupLogCheck
18
+
19
+ __all__ = [
20
+ "BaseDiagnosticCheck",
21
+ "InstallationCheck",
22
+ "ConfigurationCheck",
23
+ "ClaudeDesktopCheck",
24
+ "AgentCheck",
25
+ "MCPCheck",
26
+ "MonitorCheck",
27
+ "FilesystemCheck",
28
+ "CommonIssuesCheck",
29
+ "StartupLogCheck",
30
+ ]