claude-mpm 3.7.1__py3-none-any.whl → 3.7.8__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 (52) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +18 -0
  3. claude_mpm/agents/frontmatter_validator.py +116 -17
  4. claude_mpm/agents/schema/agent_schema.json +1 -1
  5. claude_mpm/{dashboard → agents}/templates/.claude-mpm/memories/engineer_agent.md +1 -1
  6. claude_mpm/{dashboard/templates/.claude-mpm/memories/version_control_agent.md → agents/templates/.claude-mpm/memories/qa_agent.md} +2 -2
  7. claude_mpm/agents/templates/.claude-mpm/memories/research_agent.md +39 -0
  8. claude_mpm/agents/templates/code_analyzer.json +34 -12
  9. claude_mpm/agents/templates/data_engineer.json +5 -8
  10. claude_mpm/agents/templates/documentation.json +2 -2
  11. claude_mpm/agents/templates/engineer.json +6 -6
  12. claude_mpm/agents/templates/ops.json +3 -8
  13. claude_mpm/agents/templates/qa.json +2 -3
  14. claude_mpm/agents/templates/research.json +12 -9
  15. claude_mpm/agents/templates/security.json +4 -7
  16. claude_mpm/agents/templates/ticketing.json +161 -0
  17. claude_mpm/agents/templates/version_control.json +3 -3
  18. claude_mpm/agents/templates/web_qa.json +214 -0
  19. claude_mpm/agents/templates/web_ui.json +176 -0
  20. claude_mpm/cli/commands/agents.py +118 -1
  21. claude_mpm/cli/parser.py +11 -0
  22. claude_mpm/cli/ticket_cli.py +31 -0
  23. claude_mpm/core/framework_loader.py +102 -49
  24. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +46 -2
  25. claude_mpm/dashboard/templates/index.html +5 -5
  26. claude_mpm/services/agents/deployment/agent_deployment.py +9 -1
  27. claude_mpm/services/agents/deployment/async_agent_deployment.py +174 -13
  28. claude_mpm/services/agents/management/agent_capabilities_generator.py +21 -11
  29. claude_mpm/services/ticket_manager.py +207 -44
  30. claude_mpm/utils/agent_dependency_loader.py +66 -15
  31. claude_mpm/utils/robust_installer.py +587 -0
  32. {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/METADATA +17 -21
  33. {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/RECORD +37 -46
  34. claude_mpm/.claude-mpm/logs/hooks_20250728.log +0 -10
  35. claude_mpm/agents/agent-template.yaml +0 -83
  36. claude_mpm/agents/templates/test_integration.json +0 -113
  37. claude_mpm/cli/README.md +0 -108
  38. claude_mpm/cli_module/refactoring_guide.md +0 -253
  39. claude_mpm/config/async_logging_config.yaml +0 -145
  40. claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +0 -34
  41. claude_mpm/dashboard/.claude-mpm/memories/README.md +0 -36
  42. claude_mpm/dashboard/README.md +0 -121
  43. claude_mpm/dashboard/static/js/dashboard.js.backup +0 -1973
  44. claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +0 -36
  45. claude_mpm/hooks/README.md +0 -96
  46. claude_mpm/schemas/agent_schema.json +0 -435
  47. claude_mpm/services/framework_claude_md_generator/README.md +0 -92
  48. claude_mpm/services/version_control/VERSION +0 -1
  49. {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/WHEEL +0 -0
  50. {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/entry_points.txt +0 -0
  51. {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/licenses/LICENSE +0 -0
  52. {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/top_level.txt +0 -0
@@ -328,8 +328,20 @@ class FileToolTracker {
328
328
  */
329
329
  isFileOperation(event) {
330
330
  // File operations are tool events with tools that operate on files
331
- const fileTools = ['Read', 'Write', 'Edit', 'Grep', 'MultiEdit'];
332
- return event.tool_name && fileTools.includes(event.tool_name);
331
+ // Check case-insensitively since tool names can come in different cases
332
+ const fileTools = ['read', 'write', 'edit', 'grep', 'multiedit', 'glob', 'ls', 'bash', 'notebookedit'];
333
+ const toolName = event.tool_name ? event.tool_name.toLowerCase() : '';
334
+
335
+ // Also check if Bash commands involve file operations
336
+ if (toolName === 'bash' && event.tool_parameters) {
337
+ const command = event.tool_parameters.command || '';
338
+ // Check for common file operations in bash commands
339
+ if (command.match(/\b(cat|less|more|head|tail|touch|mv|cp|rm|mkdir|ls|find)\b/)) {
340
+ return true;
341
+ }
342
+ }
343
+
344
+ return toolName && fileTools.includes(toolName);
333
345
  }
334
346
 
335
347
  /**
@@ -341,11 +353,28 @@ class FileToolTracker {
341
353
  // Try various locations where file path might be stored
342
354
  if (event.tool_parameters?.file_path) return event.tool_parameters.file_path;
343
355
  if (event.tool_parameters?.path) return event.tool_parameters.path;
356
+ if (event.tool_parameters?.notebook_path) return event.tool_parameters.notebook_path;
344
357
  if (event.data?.tool_parameters?.file_path) return event.data.tool_parameters.file_path;
345
358
  if (event.data?.tool_parameters?.path) return event.data.tool_parameters.path;
359
+ if (event.data?.tool_parameters?.notebook_path) return event.data.tool_parameters.notebook_path;
346
360
  if (event.file_path) return event.file_path;
347
361
  if (event.path) return event.path;
348
362
 
363
+ // For Glob tool, use the pattern as a pseudo-path
364
+ if (event.tool_name?.toLowerCase() === 'glob' && event.tool_parameters?.pattern) {
365
+ return `[glob] ${event.tool_parameters.pattern}`;
366
+ }
367
+
368
+ // For Bash commands, try to extract file paths from the command
369
+ if (event.tool_name?.toLowerCase() === 'bash' && event.tool_parameters?.command) {
370
+ const command = event.tool_parameters.command;
371
+ // Try to extract file paths from common patterns
372
+ const fileMatch = command.match(/(?:cat|less|more|head|tail|touch|mv|cp|rm|mkdir|ls|find|echo.*>|sed|awk|grep)\s+([^\s;|&]+)/);
373
+ if (fileMatch && fileMatch[1]) {
374
+ return fileMatch[1];
375
+ }
376
+ }
377
+
349
378
  return null;
350
379
  }
351
380
 
@@ -383,7 +412,22 @@ class FileToolTracker {
383
412
  case 'write': return 'write';
384
413
  case 'edit': return 'edit';
385
414
  case 'multiedit': return 'edit';
415
+ case 'notebookedit': return 'edit';
386
416
  case 'grep': return 'search';
417
+ case 'glob': return 'search';
418
+ case 'ls': return 'list';
419
+ case 'bash':
420
+ // Check bash command for file operation type
421
+ const command = event.tool_parameters?.command || '';
422
+ if (command.match(/\b(cat|less|more|head|tail)\b/)) return 'read';
423
+ if (command.match(/\b(touch|echo.*>|tee)\b/)) return 'write';
424
+ if (command.match(/\b(sed|awk)\b/)) return 'edit';
425
+ if (command.match(/\b(grep|find)\b/)) return 'search';
426
+ if (command.match(/\b(ls|dir)\b/)) return 'list';
427
+ if (command.match(/\b(mv|cp)\b/)) return 'copy/move';
428
+ if (command.match(/\b(rm|rmdir)\b/)) return 'delete';
429
+ if (command.match(/\b(mkdir)\b/)) return 'create';
430
+ return 'bash';
387
431
  default: return toolName;
388
432
  }
389
433
  }
@@ -334,11 +334,11 @@
334
334
  <!-- New modular components -->
335
335
  <script src="/static/js/components/socket-manager.js"></script>
336
336
  <script src="/static/js/components/agent-inference.js"></script>
337
- <script src="/static/js/components/ui-state-manager.js"></script>
338
- <script src="/static/js/components/working-directory.js"></script>
339
- <script src="/static/js/components/file-tool-tracker.js"></script>
340
- <script src="/static/js/components/event-processor.js"></script>
341
- <script src="/static/js/components/export-manager.js"></script>
337
+ <script src="/static/js/components/ui-state-manager.js?v=1.0"></script>
338
+ <script src="/static/js/components/working-directory.js?v=1.0"></script>
339
+ <script src="/static/js/components/file-tool-tracker.js?v=1.1"></script>
340
+ <script src="/static/js/components/event-processor.js?v=1.0"></script>
341
+ <script src="/static/js/components/export-manager.js?v=1.0"></script>
342
342
 
343
343
  <!-- Main dashboard coordinator -->
344
344
  <script src="/static/js/dashboard.js"></script>
@@ -740,10 +740,18 @@ class AgentDeploymentService:
740
740
  # IMPORTANT: No spaces after commas - Claude Code requires exact format
741
741
  tools_str = ','.join(tools) if isinstance(tools, list) else tools
742
742
 
743
+ # Extract proper agent_id and name from template
744
+ agent_id = template_data.get('agent_id', agent_name)
745
+ display_name = template_data.get('metadata', {}).get('name', agent_id)
746
+
747
+ # Convert agent_id to Claude Code compatible name (replace underscores with hyphens)
748
+ # Claude Code requires name to match pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
749
+ claude_code_name = agent_id.replace('_', '-').lower()
750
+
743
751
  # Build frontmatter with only the fields Claude Code uses
744
752
  frontmatter_lines = [
745
753
  "---",
746
- f"name: {agent_name}",
754
+ f"name: {claude_code_name}",
747
755
  f"description: {description}",
748
756
  f"version: {version_string}",
749
757
  f"base_version: {self._format_version_display(base_version)}",
@@ -387,20 +387,181 @@ class AsyncAgentDeploymentService:
387
387
  return filtered
388
388
 
389
389
  def _build_agent_markdown_sync(self, agent_data: Dict[str, Any]) -> str:
390
- """Build agent markdown content (sync version for compatibility)."""
391
- # Simplified version - extend as needed
390
+ """Build agent markdown content matching the synchronous deployment format."""
391
+ from datetime import datetime
392
+
393
+ # Extract agent info from the loaded JSON data
392
394
  agent_name = agent_data.get('_agent_name', 'unknown')
393
- version = agent_data.get('version', '1.0.0')
394
- instructions = agent_data.get('instructions', '')
395
-
396
- return f"""---
397
- name: {agent_name}
398
- version: {version}
399
- author: claude-mpm
400
- ---
401
-
402
- {instructions}
403
- """
395
+
396
+ # Extract proper agent_id from template data (not filename)
397
+ agent_id = agent_data.get('agent_id', agent_name)
398
+
399
+ # Handle both 'agent_version' (new format) and 'version' (old format)
400
+ agent_version = self._parse_version(agent_data.get('agent_version') or agent_data.get('version', '1.0.0'))
401
+ base_version = (0, 1, 0) # Default base version for async deployment
402
+
403
+ # Format version string as semantic version
404
+ version_string = self._format_version_display(agent_version)
405
+
406
+ # Extract metadata using the same logic as synchronous deployment
407
+ # Check new format first (metadata.description), then old format
408
+ description = (
409
+ agent_data.get('metadata', {}).get('description') or
410
+ agent_data.get('configuration_fields', {}).get('description') or
411
+ agent_data.get('description') or
412
+ 'Agent for specialized tasks'
413
+ )
414
+
415
+ # Get tags from new format (metadata.tags) or old format
416
+ tags = (
417
+ agent_data.get('metadata', {}).get('tags') or
418
+ agent_data.get('configuration_fields', {}).get('tags') or
419
+ agent_data.get('tags') or
420
+ [agent_id, 'mpm-framework']
421
+ )
422
+
423
+ # Get tools from capabilities.tools in new format
424
+ tools = (
425
+ agent_data.get('capabilities', {}).get('tools') or
426
+ agent_data.get('configuration_fields', {}).get('tools') or
427
+ ["Read", "Write", "Edit", "Grep", "Glob", "LS"] # Default fallback
428
+ )
429
+
430
+ # Get model from capabilities.model in new format
431
+ model = (
432
+ agent_data.get('capabilities', {}).get('model') or
433
+ agent_data.get('configuration_fields', {}).get('model') or
434
+ "sonnet" # Default fallback
435
+ )
436
+
437
+ # Simplify model name for Claude Code
438
+ model_map = {
439
+ 'claude-4-sonnet-20250514': 'sonnet',
440
+ 'claude-sonnet-4-20250514': 'sonnet',
441
+ 'claude-opus-4-20250514': 'opus',
442
+ 'claude-3-opus-20240229': 'opus',
443
+ 'claude-3-haiku-20240307': 'haiku',
444
+ 'claude-3.5-sonnet': 'sonnet',
445
+ 'claude-3-sonnet': 'sonnet'
446
+ }
447
+ # Better fallback: extract the model type (opus/sonnet/haiku) from the string
448
+ if model not in model_map:
449
+ if 'opus' in model.lower():
450
+ model = 'opus'
451
+ elif 'sonnet' in model.lower():
452
+ model = 'sonnet'
453
+ elif 'haiku' in model.lower():
454
+ model = 'haiku'
455
+ else:
456
+ # Last resort: try to extract from hyphenated format
457
+ model = model_map.get(model, model.split('-')[-1] if '-' in model else model)
458
+ else:
459
+ model = model_map[model]
460
+
461
+ # Convert tools list to comma-separated string for Claude Code compatibility
462
+ # IMPORTANT: No spaces after commas - Claude Code requires exact format
463
+ tools_str = ','.join(tools) if isinstance(tools, list) else str(tools)
464
+
465
+ # Convert agent_id to Claude Code compatible name (replace underscores with hyphens)
466
+ # Claude Code requires name to match pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
467
+ claude_code_name = agent_id.replace('_', '-').lower()
468
+
469
+ # Build frontmatter with only the fields Claude Code uses
470
+ frontmatter_lines = [
471
+ "---",
472
+ f"name: {claude_code_name}",
473
+ f"description: {description}",
474
+ f"version: {version_string}",
475
+ f"base_version: {self._format_version_display(base_version)}",
476
+ f"author: claude-mpm", # Identify as system agent for deployment
477
+ f"tools: {tools_str}",
478
+ f"model: {model}"
479
+ ]
480
+
481
+ # Add optional fields if present
482
+ # Check for color in metadata section (new format) or root (old format)
483
+ color = (
484
+ agent_data.get('metadata', {}).get('color') or
485
+ agent_data.get('color')
486
+ )
487
+ if color:
488
+ frontmatter_lines.append(f"color: {color}")
489
+
490
+ frontmatter_lines.append("---")
491
+ frontmatter_lines.append("")
492
+ frontmatter_lines.append("")
493
+
494
+ frontmatter = '\n'.join(frontmatter_lines)
495
+
496
+ # Get the main content (instructions)
497
+ # Check multiple possible locations for instructions
498
+ content = (
499
+ agent_data.get('instructions') or
500
+ agent_data.get('narrative_fields', {}).get('instructions') or
501
+ agent_data.get('content') or
502
+ f"You are the {agent_id} agent. Perform tasks related to {agent_data.get('description', 'your specialization')}."
503
+ )
504
+
505
+ return frontmatter + content
506
+
507
+ def _parse_version(self, version_value: Any) -> tuple:
508
+ """
509
+ Parse version from various formats to semantic version tuple.
510
+
511
+ Handles:
512
+ - Integer values: 5 -> (0, 5, 0)
513
+ - String integers: "5" -> (0, 5, 0)
514
+ - Semantic versions: "2.1.0" -> (2, 1, 0)
515
+ - Invalid formats: returns (0, 0, 0)
516
+
517
+ Args:
518
+ version_value: Version in various formats
519
+
520
+ Returns:
521
+ Tuple of (major, minor, patch) for comparison
522
+ """
523
+ if isinstance(version_value, int):
524
+ # Legacy integer version - treat as minor version
525
+ return (0, version_value, 0)
526
+
527
+ if isinstance(version_value, str):
528
+ # Try to parse as simple integer
529
+ if version_value.isdigit():
530
+ return (0, int(version_value), 0)
531
+
532
+ # Try to parse semantic version (e.g., "2.1.0" or "v2.1.0")
533
+ import re
534
+ sem_ver_match = re.match(r'^v?(\d+)\.(\d+)\.(\d+)', version_value)
535
+ if sem_ver_match:
536
+ major = int(sem_ver_match.group(1))
537
+ minor = int(sem_ver_match.group(2))
538
+ patch = int(sem_ver_match.group(3))
539
+ return (major, minor, patch)
540
+
541
+ # Try to extract first number from string as minor version
542
+ num_match = re.search(r'(\d+)', version_value)
543
+ if num_match:
544
+ return (0, int(num_match.group(1)), 0)
545
+
546
+ # Default to 0.0.0 for invalid formats
547
+ return (0, 0, 0)
548
+
549
+ def _format_version_display(self, version_tuple: tuple) -> str:
550
+ """
551
+ Format version tuple for display.
552
+
553
+ Args:
554
+ version_tuple: Tuple of (major, minor, patch)
555
+
556
+ Returns:
557
+ Formatted version string
558
+ """
559
+ if isinstance(version_tuple, tuple) and len(version_tuple) == 3:
560
+ major, minor, patch = version_tuple
561
+ return f"{major}.{minor}.{patch}"
562
+ else:
563
+ # Fallback for legacy format
564
+ return str(version_tuple)
404
565
 
405
566
  async def cleanup(self):
406
567
  """Clean up resources."""
@@ -116,9 +116,12 @@ class AgentCapabilitiesGenerator:
116
116
  if len(capability_text) > 100:
117
117
  capability_text = capability_text[:97] + '...'
118
118
 
119
+ # Clean up the agent name for TodoWrite usage
120
+ clean_name = agent['name'].replace(' Agent', '').replace('-', ' ')
121
+
119
122
  capabilities.append({
120
- 'name': agent['name'],
121
- 'id': agent['id'],
123
+ 'name': clean_name, # Clean name for TodoWrite
124
+ 'id': agent['id'], # Agent ID for Task tool
122
125
  'capability_text': capability_text,
123
126
  'tools': ', '.join(agent.get('tools', [])[:5]) # First 5 tools
124
127
  })
@@ -132,26 +135,33 @@ class AgentCapabilitiesGenerator:
132
135
  Configured Jinja2 template
133
136
  """
134
137
  template_content = """
135
- ## Agent Names & Capabilities
136
- **Core Agents**: {{ core_agents }}
138
+ ## Available Agent Capabilities
139
+
140
+ You have the following specialized agents available for delegation:
137
141
 
138
142
  {% if agents_by_tier.project %}
139
143
  ### Project-Specific Agents
140
144
  {% for agent in agents_by_tier.project %}
141
- - **{{ agent.name }}** ({{ agent.id }}): {{ agent.description }}
145
+ - **{{ agent.name|replace(' Agent', '')|replace('-', ' ') }}** (`{{ agent.id }}`): {{ agent.description }}
142
146
  {% endfor %}
143
147
 
144
148
  {% endif %}
145
- **Agent Capabilities**:
149
+ ### Engineering Agents
146
150
  {% for cap in detailed_capabilities %}
147
- - **{{ cap.name }}**: {{ cap.capability_text }}
151
+ {% if cap.id in ['engineer', 'data_engineer', 'documentation', 'ops', 'security', 'ticketing', 'version_control', 'web_ui'] %}
152
+ - **{{ cap.name }}** (`{{ cap.id }}`): {{ cap.capability_text }}
153
+ {% endif %}
148
154
  {% endfor %}
149
155
 
150
- **Agent Name Formats** (both valid):
151
- - Capitalized: {{ detailed_capabilities | map(attribute='name') | join('", "') }}
152
- - Lowercase-hyphenated: {{ detailed_capabilities | map(attribute='id') | join('", "') }}
156
+ ### Research Agents
157
+ {% for cap in detailed_capabilities %}
158
+ {% if cap.id in ['code_analyzer', 'qa', 'research', 'web_qa'] %}
159
+ - **{{ cap.name }}** (`{{ cap.id }}`): {{ cap.capability_text }}
160
+ {% endif %}
161
+ {% endfor %}
153
162
 
154
- *Generated from {{ total_agents }} deployed agents*
163
+ **Total Available Agents**: {{ total_agents }}
164
+ Use the agent ID in parentheses when delegating tasks via the Task tool.
155
165
  """.strip()
156
166
 
157
167
  return Template(template_content)