claude-mpm 3.5.1__py3-none-any.whl → 3.5.4__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 (30) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +29 -2
  3. claude_mpm/agents/agent_loader.py +109 -15
  4. claude_mpm/agents/base_agent.json +1 -1
  5. claude_mpm/agents/frontmatter_validator.py +448 -0
  6. claude_mpm/agents/templates/data_engineer.json +4 -3
  7. claude_mpm/agents/templates/documentation.json +4 -3
  8. claude_mpm/agents/templates/engineer.json +4 -3
  9. claude_mpm/agents/templates/ops.json +4 -3
  10. claude_mpm/agents/templates/pm.json +5 -4
  11. claude_mpm/agents/templates/qa.json +4 -3
  12. claude_mpm/agents/templates/research.json +8 -7
  13. claude_mpm/agents/templates/security.json +4 -3
  14. claude_mpm/agents/templates/test_integration.json +4 -3
  15. claude_mpm/agents/templates/version_control.json +4 -3
  16. claude_mpm/cli/__main__.py +24 -0
  17. claude_mpm/cli/commands/agents.py +354 -6
  18. claude_mpm/cli/parser.py +36 -0
  19. claude_mpm/constants.py +2 -0
  20. claude_mpm/core/agent_registry.py +4 -1
  21. claude_mpm/core/claude_runner.py +224 -8
  22. claude_mpm/services/agents/deployment/agent_deployment.py +39 -9
  23. claude_mpm/services/agents/registry/agent_registry.py +22 -1
  24. claude_mpm/validation/agent_validator.py +56 -1
  25. {claude_mpm-3.5.1.dist-info → claude_mpm-3.5.4.dist-info}/METADATA +18 -3
  26. {claude_mpm-3.5.1.dist-info → claude_mpm-3.5.4.dist-info}/RECORD +30 -28
  27. {claude_mpm-3.5.1.dist-info → claude_mpm-3.5.4.dist-info}/WHEEL +0 -0
  28. {claude_mpm-3.5.1.dist-info → claude_mpm-3.5.4.dist-info}/entry_points.txt +0 -0
  29. {claude_mpm-3.5.1.dist-info → claude_mpm-3.5.4.dist-info}/licenses/LICENSE +0 -0
  30. {claude_mpm-3.5.1.dist-info → claude_mpm-3.5.4.dist-info}/top_level.txt +0 -0
@@ -6,10 +6,15 @@ and cleaning agent deployments.
6
6
  """
7
7
 
8
8
  from pathlib import Path
9
+ import json
10
+ import yaml
11
+ from typing import Dict, Any, Optional
9
12
 
10
13
  from ...core.logger import get_logger
11
14
  from ...constants import AgentCommands
12
15
  from ..utils import get_agent_versions_display
16
+ from ...core.agent_registry import AgentRegistryAdapter
17
+ from ...agents.frontmatter_validator import FrontmatterValidator
13
18
 
14
19
 
15
20
  def manage_agents(args):
@@ -55,6 +60,12 @@ def manage_agents(args):
55
60
  elif args.agents_command == AgentCommands.CLEAN.value:
56
61
  _clean_agents(args, deployment_service)
57
62
 
63
+ elif args.agents_command == AgentCommands.VIEW.value:
64
+ _view_agent(args)
65
+
66
+ elif args.agents_command == AgentCommands.FIX.value:
67
+ _fix_agents(args)
68
+
58
69
  except ImportError:
59
70
  logger.error("Agent deployment service not available")
60
71
  print("Error: Agent deployment service not available")
@@ -71,10 +82,13 @@ def _list_agents(args, deployment_service):
71
82
  currently deployed. This helps them understand the agent ecosystem.
72
83
 
73
84
  Args:
74
- args: Command arguments with 'system' and 'deployed' flags
85
+ args: Command arguments with 'system', 'deployed', and 'by_tier' flags
75
86
  deployment_service: Agent deployment service instance
76
87
  """
77
- if args.system:
88
+ if hasattr(args, 'by_tier') and args.by_tier:
89
+ # List agents grouped by tier
90
+ _list_agents_by_tier()
91
+ elif args.system:
78
92
  # List available agent templates
79
93
  print("Available Agent Templates:")
80
94
  print("-" * 80)
@@ -114,21 +128,22 @@ def _list_agents(args, deployment_service):
114
128
 
115
129
  else:
116
130
  # Default: show usage
117
- print("Use --system to list system agents or --deployed to list deployed agents")
131
+ print("Use --system to list system agents, --deployed to list deployed agents, or --by-tier to group by precedence")
118
132
 
119
133
 
120
134
  def _deploy_agents(args, deployment_service, force=False):
121
135
  """
122
- Deploy system agents.
136
+ Deploy both system and project agents.
123
137
 
124
138
  WHY: Agents need to be deployed to the working directory for Claude Code to use them.
125
- This function handles both regular and forced deployment.
139
+ This function handles both regular and forced deployment, including project-specific agents.
126
140
 
127
141
  Args:
128
142
  args: Command arguments with optional 'target' path
129
143
  deployment_service: Agent deployment service instance
130
144
  force: Whether to force rebuild all agents
131
145
  """
146
+ # Deploy system agents first
132
147
  if force:
133
148
  print("Force deploying all system agents...")
134
149
  else:
@@ -136,6 +151,34 @@ def _deploy_agents(args, deployment_service, force=False):
136
151
 
137
152
  results = deployment_service.deploy_agents(args.target, force_rebuild=force)
138
153
 
154
+ # Also deploy project agents if they exist
155
+ from pathlib import Path
156
+ project_agents_dir = Path.cwd() / '.claude-mpm' / 'agents'
157
+ if project_agents_dir.exists():
158
+ json_files = list(project_agents_dir.glob('*.json'))
159
+ if json_files:
160
+ print(f"\nDeploying {len(json_files)} project agents...")
161
+ from claude_mpm.services.agents.deployment.agent_deployment import AgentDeploymentService
162
+ project_service = AgentDeploymentService(
163
+ templates_dir=project_agents_dir,
164
+ base_agent_path=project_agents_dir / 'base_agent.json' if (project_agents_dir / 'base_agent.json').exists() else None
165
+ )
166
+ project_results = project_service.deploy_agents(
167
+ target_dir=args.target if args.target else Path.cwd() / '.claude' / 'agents',
168
+ force_rebuild=force,
169
+ deployment_mode='project'
170
+ )
171
+
172
+ # Merge project results into main results
173
+ if project_results.get('deployed'):
174
+ results['deployed'].extend(project_results['deployed'])
175
+ print(f"✓ Deployed {len(project_results['deployed'])} project agents")
176
+ if project_results.get('updated'):
177
+ results['updated'].extend(project_results['updated'])
178
+ print(f"✓ Updated {len(project_results['updated'])} project agents")
179
+ if project_results.get('errors'):
180
+ results['errors'].extend(project_results['errors'])
181
+
139
182
  if results["deployed"]:
140
183
  print(f"\n✓ Successfully deployed {len(results['deployed'])} agents to {results['target_dir']}")
141
184
  for agent in results["deployed"]:
@@ -188,4 +231,309 @@ def _clean_agents(args, deployment_service):
188
231
  if results["errors"]:
189
232
  print("\n❌ Errors during cleanup:")
190
233
  for error in results["errors"]:
191
- print(f" - {error}")
234
+ print(f" - {error}")
235
+
236
+
237
+ def _list_agents_by_tier():
238
+ """
239
+ List agents grouped by precedence tier.
240
+
241
+ WHY: Users need to understand which agents are active across different tiers
242
+ and which version takes precedence when multiple versions exist.
243
+ """
244
+ try:
245
+ adapter = AgentRegistryAdapter()
246
+ if not adapter.registry:
247
+ print("❌ Could not initialize agent registry")
248
+ return
249
+
250
+ # Get all agents and group by tier
251
+ all_agents = adapter.registry.list_agents()
252
+
253
+ # Group agents by tier and name
254
+ tiers = {'project': {}, 'user': {}, 'system': {}}
255
+ agent_names = set()
256
+
257
+ for agent_id, metadata in all_agents.items():
258
+ tier = metadata.get('tier', 'system')
259
+ if tier in tiers:
260
+ tiers[tier][agent_id] = metadata
261
+ agent_names.add(agent_id)
262
+
263
+ # Display header
264
+ print("\n" + "=" * 80)
265
+ print(" " * 25 + "AGENT HIERARCHY BY TIER")
266
+ print("=" * 80)
267
+ print("\nPrecedence: PROJECT > USER > SYSTEM")
268
+ print("(Agents in higher tiers override those in lower tiers)\n")
269
+
270
+ # Display each tier
271
+ tier_order = [('PROJECT', 'project'), ('USER', 'user'), ('SYSTEM', 'system')]
272
+
273
+ for tier_display, tier_key in tier_order:
274
+ agents = tiers[tier_key]
275
+ print(f"\n{'─' * 35} {tier_display} TIER {'─' * 35}")
276
+
277
+ if not agents:
278
+ print(f" No agents at {tier_key} level")
279
+ else:
280
+ # Check paths to determine actual locations
281
+ if tier_key == 'project':
282
+ print(f" Location: .claude-mpm/agents/ (in current project)")
283
+ elif tier_key == 'user':
284
+ print(f" Location: ~/.claude-mpm/agents/")
285
+ else:
286
+ print(f" Location: Built-in framework agents")
287
+
288
+ print(f"\n Found {len(agents)} agent(s):\n")
289
+
290
+ for agent_id, metadata in sorted(agents.items()):
291
+ # Check if this agent is overridden by higher tiers
292
+ is_active = True
293
+ overridden_by = []
294
+
295
+ for check_tier_display, check_tier_key in tier_order:
296
+ if check_tier_key == tier_key:
297
+ break
298
+ if agent_id in tiers[check_tier_key]:
299
+ is_active = False
300
+ overridden_by.append(check_tier_display)
301
+
302
+ # Display agent info
303
+ status = "✓ ACTIVE" if is_active else f"⊗ OVERRIDDEN by {', '.join(overridden_by)}"
304
+ print(f" 📄 {agent_id:<20} [{status}]")
305
+
306
+ # Show metadata
307
+ if 'description' in metadata:
308
+ print(f" Description: {metadata['description']}")
309
+ if 'path' in metadata:
310
+ path = Path(metadata['path'])
311
+ print(f" File: {path.name}")
312
+ print()
313
+
314
+ # Summary
315
+ print("\n" + "=" * 80)
316
+ print("SUMMARY:")
317
+ print(f" Total unique agents: {len(agent_names)}")
318
+ print(f" Project agents: {len(tiers['project'])}")
319
+ print(f" User agents: {len(tiers['user'])}")
320
+ print(f" System agents: {len(tiers['system'])}")
321
+ print("=" * 80 + "\n")
322
+
323
+ except Exception as e:
324
+ print(f"❌ Error listing agents by tier: {e}")
325
+
326
+
327
+ def _view_agent(args):
328
+ """
329
+ View detailed information about a specific agent.
330
+
331
+ WHY: Users need to inspect agent configurations, frontmatter, and instructions
332
+ to understand what an agent does and how it's configured.
333
+
334
+ Args:
335
+ args: Command arguments with 'agent_name' attribute
336
+ """
337
+ if not hasattr(args, 'agent_name') or not args.agent_name:
338
+ print("❌ Please specify an agent name to view")
339
+ print("Usage: claude-mpm agents view <agent_name>")
340
+ return
341
+
342
+ try:
343
+ adapter = AgentRegistryAdapter()
344
+ if not adapter.registry:
345
+ print("❌ Could not initialize agent registry")
346
+ return
347
+
348
+ # Get the agent
349
+ agent = adapter.registry.get_agent(args.agent_name)
350
+ if not agent:
351
+ print(f"❌ Agent '{args.agent_name}' not found")
352
+ print("\nAvailable agents:")
353
+ all_agents = adapter.registry.list_agents()
354
+ for agent_id in sorted(all_agents.keys()):
355
+ print(f" - {agent_id}")
356
+ return
357
+
358
+ # Read the agent file
359
+ agent_path = Path(agent.path)
360
+ if not agent_path.exists():
361
+ print(f"❌ Agent file not found: {agent_path}")
362
+ return
363
+
364
+ with open(agent_path, 'r') as f:
365
+ content = f.read()
366
+
367
+ # Display agent information
368
+ print("\n" + "=" * 80)
369
+ print(f" AGENT: {agent.name}")
370
+ print("=" * 80)
371
+
372
+ # Basic info
373
+ print(f"\n📋 BASIC INFORMATION:")
374
+ print(f" Name: {agent.name}")
375
+ print(f" Type: {agent.type}")
376
+ print(f" Tier: {agent.tier.upper()}")
377
+ print(f" Path: {agent_path}")
378
+ if agent.description:
379
+ print(f" Description: {agent.description}")
380
+ if agent.specializations:
381
+ print(f" Specializations: {', '.join(agent.specializations)}")
382
+
383
+ # Extract and display frontmatter
384
+ if content.startswith("---"):
385
+ try:
386
+ end_marker = content.find("\n---\n", 4)
387
+ if end_marker == -1:
388
+ end_marker = content.find("\n---\r\n", 4)
389
+
390
+ if end_marker != -1:
391
+ frontmatter_str = content[4:end_marker]
392
+ frontmatter = yaml.safe_load(frontmatter_str)
393
+
394
+ print(f"\n📝 FRONTMATTER:")
395
+ for key, value in frontmatter.items():
396
+ if isinstance(value, list):
397
+ print(f" {key}: [{', '.join(str(v) for v in value)}]")
398
+ elif isinstance(value, dict):
399
+ print(f" {key}:")
400
+ for k, v in value.items():
401
+ print(f" {k}: {v}")
402
+ else:
403
+ print(f" {key}: {value}")
404
+
405
+ # Extract instructions preview
406
+ instructions_start = end_marker + 5
407
+ instructions = content[instructions_start:].strip()
408
+
409
+ if instructions:
410
+ print(f"\n📖 INSTRUCTIONS PREVIEW (first 500 chars):")
411
+ print(" " + "-" * 76)
412
+ preview = instructions[:500]
413
+ if len(instructions) > 500:
414
+ preview += "...\n\n [Truncated - {:.1f}KB total]".format(len(instructions) / 1024)
415
+
416
+ for line in preview.split('\n'):
417
+ print(f" {line}")
418
+ print(" " + "-" * 76)
419
+ except Exception as e:
420
+ print(f"\n⚠️ Could not parse frontmatter: {e}")
421
+ else:
422
+ print(f"\n⚠️ No frontmatter found in agent file")
423
+
424
+ # File stats
425
+ import os
426
+ stat = os.stat(agent_path)
427
+ from datetime import datetime
428
+ modified = datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
429
+ print(f"\n📊 FILE STATS:")
430
+ print(f" Size: {stat.st_size:,} bytes")
431
+ print(f" Last modified: {modified}")
432
+
433
+ print("\n" + "=" * 80 + "\n")
434
+
435
+ except Exception as e:
436
+ print(f"❌ Error viewing agent: {e}")
437
+
438
+
439
+ def _fix_agents(args):
440
+ """
441
+ Fix agent frontmatter issues using FrontmatterValidator.
442
+
443
+ WHY: Agent files may have formatting issues in their frontmatter that prevent
444
+ proper loading. This command automatically fixes common issues.
445
+
446
+ Args:
447
+ args: Command arguments with 'agent_name', 'dry_run', and 'all' flags
448
+ """
449
+ validator = FrontmatterValidator()
450
+
451
+ try:
452
+ adapter = AgentRegistryAdapter()
453
+ if not adapter.registry:
454
+ print("❌ Could not initialize agent registry")
455
+ return
456
+
457
+ # Determine which agents to fix
458
+ agents_to_fix = []
459
+
460
+ if hasattr(args, 'all') and args.all:
461
+ # Fix all agents
462
+ all_agents = adapter.registry.list_agents()
463
+ for agent_id, metadata in all_agents.items():
464
+ agents_to_fix.append((agent_id, metadata['path']))
465
+ print(f"\n🔧 Checking {len(agents_to_fix)} agent(s) for frontmatter issues...\n")
466
+ elif hasattr(args, 'agent_name') and args.agent_name:
467
+ # Fix specific agent
468
+ agent = adapter.registry.get_agent(args.agent_name)
469
+ if not agent:
470
+ print(f"❌ Agent '{args.agent_name}' not found")
471
+ return
472
+ agents_to_fix.append((agent.name, agent.path))
473
+ print(f"\n🔧 Checking agent '{agent.name}' for frontmatter issues...\n")
474
+ else:
475
+ print("❌ Please specify an agent name or use --all to fix all agents")
476
+ print("Usage: claude-mpm agents fix [agent_name] [--dry-run] [--all]")
477
+ return
478
+
479
+ dry_run = hasattr(args, 'dry_run') and args.dry_run
480
+ if dry_run:
481
+ print("🔍 DRY RUN MODE - No changes will be made\n")
482
+
483
+ # Process each agent
484
+ total_issues = 0
485
+ total_fixed = 0
486
+
487
+ for agent_name, agent_path in agents_to_fix:
488
+ path = Path(agent_path)
489
+ if not path.exists():
490
+ print(f"⚠️ Skipping {agent_name}: File not found at {path}")
491
+ continue
492
+
493
+ print(f"📄 {agent_name}:")
494
+
495
+ # Validate and potentially fix
496
+ result = validator.correct_file(path, dry_run=dry_run)
497
+
498
+ if result.is_valid and not result.corrections:
499
+ print(" ✓ No issues found")
500
+ else:
501
+ if result.errors:
502
+ print(" ❌ Errors:")
503
+ for error in result.errors:
504
+ print(f" - {error}")
505
+ total_issues += len(result.errors)
506
+
507
+ if result.warnings:
508
+ print(" ⚠️ Warnings:")
509
+ for warning in result.warnings:
510
+ print(f" - {warning}")
511
+ total_issues += len(result.warnings)
512
+
513
+ if result.corrections:
514
+ if dry_run:
515
+ print(" 🔧 Would fix:")
516
+ else:
517
+ print(" ✓ Fixed:")
518
+ total_fixed += len(result.corrections)
519
+ for correction in result.corrections:
520
+ print(f" - {correction}")
521
+
522
+ print()
523
+
524
+ # Summary
525
+ print("=" * 80)
526
+ print("SUMMARY:")
527
+ print(f" Agents checked: {len(agents_to_fix)}")
528
+ print(f" Total issues found: {total_issues}")
529
+ if dry_run:
530
+ print(f" Issues that would be fixed: {sum(1 for _, path in agents_to_fix if validator.validate_file(Path(path)).corrections)}")
531
+ print("\n💡 Run without --dry-run to apply fixes")
532
+ else:
533
+ print(f" Issues fixed: {total_fixed}")
534
+ if total_fixed > 0:
535
+ print("\n✓ Frontmatter issues have been fixed!")
536
+ print("=" * 80 + "\n")
537
+
538
+ except Exception as e:
539
+ print(f"❌ Error fixing agents: {e}")
claude_mpm/cli/parser.py CHANGED
@@ -306,6 +306,42 @@ def create_parser(prog_name: str = "claude-mpm", version: str = "0.0.0") -> argp
306
306
  action="store_true",
307
307
  help="List deployed agents"
308
308
  )
309
+ list_agents_parser.add_argument(
310
+ "--by-tier",
311
+ action="store_true",
312
+ help="List agents grouped by precedence tier (PROJECT > USER > SYSTEM)"
313
+ )
314
+
315
+ # View agent details
316
+ view_agent_parser = agents_subparsers.add_parser(
317
+ AgentCommands.VIEW.value,
318
+ help="View detailed information about a specific agent"
319
+ )
320
+ view_agent_parser.add_argument(
321
+ "agent_name",
322
+ help="Name of the agent to view"
323
+ )
324
+
325
+ # Fix agent frontmatter
326
+ fix_agents_parser = agents_subparsers.add_parser(
327
+ AgentCommands.FIX.value,
328
+ help="Fix agent frontmatter issues"
329
+ )
330
+ fix_agents_parser.add_argument(
331
+ "agent_name",
332
+ nargs="?",
333
+ help="Name of specific agent to fix (fix all if not specified with --all)"
334
+ )
335
+ fix_agents_parser.add_argument(
336
+ "--dry-run",
337
+ action="store_true",
338
+ help="Preview changes without applying them"
339
+ )
340
+ fix_agents_parser.add_argument(
341
+ "--all",
342
+ action="store_true",
343
+ help="Fix all agents"
344
+ )
309
345
 
310
346
  # Deploy agents
311
347
  deploy_agents_parser = agents_subparsers.add_parser(
claude_mpm/constants.py CHANGED
@@ -51,6 +51,8 @@ class CLICommands(str, Enum):
51
51
  class AgentCommands(str, Enum):
52
52
  """Agent subcommand constants."""
53
53
  LIST = "list"
54
+ VIEW = "view"
55
+ FIX = "fix"
54
56
  DEPLOY = "deploy"
55
57
  FORCE_DEPLOY = "force-deploy"
56
58
  CLEAN = "clean"
@@ -55,6 +55,9 @@ class SimpleAgentRegistry:
55
55
  # Check multiple possible locations, including project-level
56
56
  agent_locations = [
57
57
  # Project-level agents (highest priority)
58
+ # Project-level deployed agents (highest priority - what Claude Code uses)
59
+ Path.cwd() / ".claude" / "agents",
60
+ # Project-level source agents (fallback)
58
61
  Path.cwd() / ".claude-mpm" / "agents",
59
62
  # Framework/system agents
60
63
  self.framework_path / "src" / "claude_mpm" / "agents" / "templates",
@@ -98,7 +101,7 @@ class SimpleAgentRegistry:
98
101
  def _determine_tier(self, agent_path: Path) -> str:
99
102
  """Determine agent tier based on path."""
100
103
  path_str = str(agent_path)
101
- if 'project' in path_str or '.claude-mpm' in path_str:
104
+ if 'project' in path_str or '.claude-mpm' in path_str or '.claude/agents' in path_str:
102
105
  return 'project'
103
106
  elif 'user' in path_str or str(Path.home()) in path_str:
104
107
  return 'user'