claude-mpm 4.1.2__py3-none-any.whl → 4.1.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 (87) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +16 -19
  3. claude_mpm/agents/MEMORY.md +21 -49
  4. claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +156 -0
  5. claude_mpm/agents/templates/api_qa.json +36 -116
  6. claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +42 -9
  7. claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +29 -6
  8. claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +34 -6
  9. claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +41 -9
  10. claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +30 -8
  11. claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +2 -2
  12. claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +29 -6
  13. claude_mpm/agents/templates/backup/research_memory_efficient.json +2 -2
  14. claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +41 -9
  15. claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +23 -7
  16. claude_mpm/agents/templates/code_analyzer.json +18 -36
  17. claude_mpm/agents/templates/data_engineer.json +43 -14
  18. claude_mpm/agents/templates/documentation.json +55 -74
  19. claude_mpm/agents/templates/engineer.json +57 -40
  20. claude_mpm/agents/templates/imagemagick.json +7 -2
  21. claude_mpm/agents/templates/memory_manager.json +1 -1
  22. claude_mpm/agents/templates/ops.json +36 -4
  23. claude_mpm/agents/templates/project_organizer.json +23 -71
  24. claude_mpm/agents/templates/qa.json +34 -2
  25. claude_mpm/agents/templates/refactoring_engineer.json +9 -5
  26. claude_mpm/agents/templates/research.json +36 -4
  27. claude_mpm/agents/templates/security.json +29 -2
  28. claude_mpm/agents/templates/ticketing.json +3 -3
  29. claude_mpm/agents/templates/vercel_ops_agent.json +2 -2
  30. claude_mpm/agents/templates/version_control.json +28 -2
  31. claude_mpm/agents/templates/web_qa.json +38 -151
  32. claude_mpm/agents/templates/web_ui.json +2 -2
  33. claude_mpm/cli/commands/agent_manager.py +221 -1
  34. claude_mpm/cli/commands/agents.py +556 -1009
  35. claude_mpm/cli/commands/memory.py +248 -927
  36. claude_mpm/cli/commands/run.py +139 -484
  37. claude_mpm/cli/parsers/agent_manager_parser.py +34 -0
  38. claude_mpm/cli/startup_logging.py +76 -0
  39. claude_mpm/core/agent_registry.py +6 -10
  40. claude_mpm/core/framework_loader.py +205 -595
  41. claude_mpm/core/log_manager.py +49 -1
  42. claude_mpm/core/logging_config.py +2 -4
  43. claude_mpm/hooks/claude_hooks/event_handlers.py +7 -117
  44. claude_mpm/hooks/claude_hooks/hook_handler.py +91 -755
  45. claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
  46. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
  47. claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
  48. claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
  49. claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
  50. claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
  51. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
  52. claude_mpm/services/agents/deployment/agent_deployment.py +42 -454
  53. claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
  54. claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
  55. claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
  56. claude_mpm/services/agents/memory/agent_memory_manager.py +42 -508
  57. claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
  58. claude_mpm/services/agents/memory/memory_file_service.py +103 -0
  59. claude_mpm/services/agents/memory/memory_format_service.py +201 -0
  60. claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
  61. claude_mpm/services/agents/registry/__init__.py +1 -1
  62. claude_mpm/services/cli/__init__.py +18 -0
  63. claude_mpm/services/cli/agent_cleanup_service.py +407 -0
  64. claude_mpm/services/cli/agent_dependency_service.py +395 -0
  65. claude_mpm/services/cli/agent_listing_service.py +463 -0
  66. claude_mpm/services/cli/agent_output_formatter.py +605 -0
  67. claude_mpm/services/cli/agent_validation_service.py +589 -0
  68. claude_mpm/services/cli/dashboard_launcher.py +424 -0
  69. claude_mpm/services/cli/memory_crud_service.py +617 -0
  70. claude_mpm/services/cli/memory_output_formatter.py +604 -0
  71. claude_mpm/services/cli/session_manager.py +513 -0
  72. claude_mpm/services/cli/socketio_manager.py +498 -0
  73. claude_mpm/services/cli/startup_checker.py +370 -0
  74. claude_mpm/services/core/cache_manager.py +311 -0
  75. claude_mpm/services/core/memory_manager.py +637 -0
  76. claude_mpm/services/core/path_resolver.py +498 -0
  77. claude_mpm/services/core/service_container.py +520 -0
  78. claude_mpm/services/core/service_interfaces.py +436 -0
  79. claude_mpm/services/diagnostics/checks/agent_check.py +65 -19
  80. claude_mpm/services/memory/router.py +116 -10
  81. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/METADATA +1 -1
  82. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/RECORD +86 -55
  83. claude_mpm/cli/commands/run_config_checker.py +0 -159
  84. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/WHEEL +0 -0
  85. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/entry_points.txt +0 -0
  86. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/licenses/LICENSE +0 -0
  87. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/top_level.txt +0 -0
@@ -27,7 +27,6 @@ ROLLBACK PROCEDURES:
27
27
  - Version tracking allows targeted rollbacks
28
28
  """
29
29
 
30
- import os
31
30
  import time
32
31
  from pathlib import Path
33
32
  from typing import Any, Dict, List, Optional, Tuple
@@ -48,7 +47,10 @@ from .agent_metrics_collector import AgentMetricsCollector
48
47
  from .agent_template_builder import AgentTemplateBuilder
49
48
  from .agent_validator import AgentValidator
50
49
  from .agent_version_manager import AgentVersionManager
50
+ from .base_agent_locator import BaseAgentLocator
51
+ from .deployment_results_manager import DeploymentResultsManager
51
52
  from .multi_source_deployment_service import MultiSourceAgentDeploymentService
53
+ from .single_agent_deployer import SingleAgentDeployer
52
54
 
53
55
 
54
56
  class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
@@ -121,20 +123,23 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
121
123
  # Initialize validator service
122
124
  self.validator = AgentValidator()
123
125
 
126
+ # Initialize base agent locator service
127
+ self.base_agent_locator = BaseAgentLocator(self.logger)
128
+
129
+ # Initialize deployment results manager
130
+ self.results_manager = DeploymentResultsManager(self.logger)
131
+
132
+ # Initialize single agent deployer
133
+ self.single_agent_deployer = SingleAgentDeployer(
134
+ self.template_builder,
135
+ self.version_manager,
136
+ self.results_manager,
137
+ self.logger,
138
+ )
139
+
124
140
  # Initialize filesystem manager service
125
141
  self.filesystem_manager = AgentFileSystemManager()
126
142
 
127
- # Initialize deployment metrics tracking
128
- self._deployment_metrics = {
129
- "total_deployments": 0,
130
- "successful_deployments": 0,
131
- "failed_deployments": 0,
132
- "migrations_performed": 0,
133
- "version_migration_count": 0,
134
- "agent_type_counts": {},
135
- "deployment_errors": {},
136
- }
137
-
138
143
  # Determine the actual working directory using configuration
139
144
  # Priority: param > config > environment > current directory
140
145
  self.working_directory = self.get_config_value(
@@ -169,7 +174,9 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
169
174
  self.base_agent_path = configured_base_agent
170
175
  else:
171
176
  # Priority-based search for base_agent.json
172
- self.base_agent_path = self._find_base_agent_file()
177
+ self.base_agent_path = self.base_agent_locator.find_base_agent_file(
178
+ paths.agents_dir
179
+ )
173
180
 
174
181
  # Initialize configuration manager (after base_agent_path is set)
175
182
  self.configuration_manager = AgentConfigurationManager(self.base_agent_path)
@@ -180,87 +187,6 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
180
187
  self.logger.info(f"Templates directory: {self.templates_dir}")
181
188
  self.logger.info(f"Base agent path: {self.base_agent_path}")
182
189
 
183
- def _find_base_agent_file(self) -> Path:
184
- """Find base agent file with priority-based search.
185
-
186
- Priority order:
187
- 1. Environment variable override (CLAUDE_MPM_BASE_AGENT_PATH)
188
- 2. Current working directory (for local development)
189
- 3. Known development locations
190
- 4. User override location (~/.claude/agents/)
191
- 5. Framework agents directory (from paths)
192
- """
193
- # Priority 0: Check environment variable override
194
- env_path = os.environ.get("CLAUDE_MPM_BASE_AGENT_PATH")
195
- if env_path:
196
- env_base_agent = Path(env_path)
197
- if env_base_agent.exists():
198
- self.logger.info(
199
- f"Using environment variable base_agent: {env_base_agent}"
200
- )
201
- return env_base_agent
202
- self.logger.warning(
203
- f"CLAUDE_MPM_BASE_AGENT_PATH set but file doesn't exist: {env_base_agent}"
204
- )
205
-
206
- # Priority 1: Check current working directory for local development
207
- cwd = Path.cwd()
208
- cwd_base_agent = cwd / "src" / "claude_mpm" / "agents" / "base_agent.json"
209
- if cwd_base_agent.exists():
210
- self.logger.info(
211
- f"Using local development base_agent from cwd: {cwd_base_agent}"
212
- )
213
- return cwd_base_agent
214
-
215
- # Priority 2: Check known development locations
216
- known_dev_paths = [
217
- Path(
218
- "/Users/masa/Projects/claude-mpm/src/claude_mpm/agents/base_agent.json"
219
- ),
220
- Path.home()
221
- / "Projects"
222
- / "claude-mpm"
223
- / "src"
224
- / "claude_mpm"
225
- / "agents"
226
- / "base_agent.json",
227
- Path.home()
228
- / "projects"
229
- / "claude-mpm"
230
- / "src"
231
- / "claude_mpm"
232
- / "agents"
233
- / "base_agent.json",
234
- ]
235
-
236
- for dev_path in known_dev_paths:
237
- if dev_path.exists():
238
- self.logger.info(f"Using development base_agent: {dev_path}")
239
- return dev_path
240
-
241
- # Priority 3: Check user override location
242
- user_base_agent = Path.home() / ".claude" / "agents" / "base_agent.json"
243
- if user_base_agent.exists():
244
- self.logger.info(f"Using user override base_agent: {user_base_agent}")
245
- return user_base_agent
246
-
247
- # Priority 4: Use framework agents directory (fallback)
248
- framework_base_agent = paths.agents_dir / "base_agent.json"
249
- if framework_base_agent.exists():
250
- self.logger.info(f"Using framework base_agent: {framework_base_agent}")
251
- return framework_base_agent
252
-
253
- # If still not found, log all searched locations and raise error
254
- self.logger.error("Base agent file not found in any location:")
255
- self.logger.error(f" 1. CWD: {cwd_base_agent}")
256
- self.logger.error(f" 2. Dev paths: {known_dev_paths}")
257
- self.logger.error(f" 3. User: {user_base_agent}")
258
- self.logger.error(f" 4. Framework: {framework_base_agent}")
259
-
260
- # Final fallback to framework path even if it doesn't exist
261
- # (will fail later with better error message)
262
- return framework_base_agent
263
-
264
190
  def deploy_agents(
265
191
  self,
266
192
  target_dir: Optional[Path] = None,
@@ -362,7 +288,9 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
362
288
  agents_dir = self._determine_agents_directory(target_dir)
363
289
 
364
290
  # Initialize results dictionary
365
- results = self._initialize_deployment_results(agents_dir, deployment_start_time)
291
+ results = self.results_manager.initialize_deployment_results(
292
+ agents_dir, deployment_start_time
293
+ )
366
294
 
367
295
  try:
368
296
  # Create agents directory if needed
@@ -372,7 +300,9 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
372
300
  self._repair_existing_agents(agents_dir, results)
373
301
 
374
302
  # Log deployment source tier
375
- source_tier = self._determine_source_tier()
303
+ source_tier = self.base_agent_locator.determine_source_tier(
304
+ self.templates_dir
305
+ )
376
306
  self.logger.info(
377
307
  f"Building and deploying {source_tier} agents to: {agents_dir}"
378
308
  )
@@ -439,7 +369,7 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
439
369
  else "single"
440
370
  )
441
371
 
442
- self._deploy_single_agent(
372
+ self.single_agent_deployer.deploy_single_agent(
443
373
  template_file=template_file_path,
444
374
  agents_dir=agents_dir,
445
375
  base_agent_data=base_agent_data,
@@ -478,31 +408,24 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
478
408
  results["errors"].append(error_msg)
479
409
 
480
410
  # METRICS: Track deployment failure
481
- self._deployment_metrics["failed_deployments"] += 1
482
- error_type = type(e).__name__
483
- self._deployment_metrics["deployment_errors"][error_type] = (
484
- self._deployment_metrics["deployment_errors"].get(error_type, 0) + 1
485
- )
411
+ self.results_manager.update_deployment_metrics(False, type(e).__name__)
486
412
 
487
413
  # METRICS: Calculate final deployment metrics
488
- deployment_end_time = time.time()
489
- deployment_duration = (deployment_end_time - deployment_start_time) * 1000 # ms
490
-
491
- results["metrics"]["end_time"] = deployment_end_time
492
- results["metrics"]["duration_ms"] = deployment_duration
414
+ self.results_manager.finalize_results(results, deployment_start_time)
493
415
 
494
416
  # METRICS: Update rolling averages and statistics
417
+ deployment_duration = results["metrics"].get("duration_ms", 0)
495
418
  self.metrics_collector.update_deployment_metrics(deployment_duration, results)
496
419
 
497
420
  return results
498
421
 
499
422
  def get_deployment_metrics(self) -> Dict[str, Any]:
500
423
  """Get current deployment metrics."""
501
- return self.metrics_collector.get_deployment_metrics()
424
+ return self.results_manager.get_deployment_metrics()
502
425
 
503
426
  def reset_metrics(self) -> None:
504
427
  """Reset deployment metrics."""
505
- return self.metrics_collector.reset_metrics()
428
+ return self.results_manager.reset_metrics()
506
429
 
507
430
  def set_claude_environment(
508
431
  self, config_dir: Optional[Path] = None
@@ -543,74 +466,15 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
543
466
  - Properly loads base_agent_data before building agent content
544
467
  """
545
468
  try:
546
- # Find the template file
547
- template_file = self.templates_dir / f"{agent_name}.json"
548
- if not template_file.exists():
549
- self.logger.error(f"Agent template not found: {agent_name}")
550
- return False
551
-
552
- # Ensure target directory exists
553
- # target_dir should already be the agents directory
554
- target_dir.mkdir(parents=True, exist_ok=True)
555
-
556
- # Build and deploy the agent
557
- target_file = target_dir / f"{agent_name}.md"
558
-
559
- # Check if update is needed
560
- if not force_rebuild and target_file.exists():
561
- # Load base agent data for version checking
562
- base_agent_data = {}
563
- base_agent_version = (0, 0, 0)
564
- if self.base_agent_path.exists():
565
- try:
566
- import json
567
-
568
- base_agent_data = json.loads(self.base_agent_path.read_text())
569
- base_agent_version = self.version_manager.parse_version(
570
- base_agent_data.get("base_version")
571
- or base_agent_data.get("version", 0)
572
- )
573
- except Exception as e:
574
- self.logger.warning(
575
- f"Could not load base agent for version check: {e}"
576
- )
577
-
578
- needs_update, reason = self.version_manager.check_agent_needs_update(
579
- target_file, template_file, base_agent_version
580
- )
581
- if not needs_update:
582
- self.logger.info(f"Agent {agent_name} is up to date")
583
- return True
584
- self.logger.info(f"Updating agent {agent_name}: {reason}")
585
-
586
- # Load base agent data for building
587
- base_agent_data = {}
588
- if self.base_agent_path.exists():
589
- try:
590
- import json
591
-
592
- base_agent_data = json.loads(self.base_agent_path.read_text())
593
- except Exception as e:
594
- self.logger.warning(f"Could not load base agent: {e}")
595
-
596
- # Build the agent markdown
597
- # For single agent deployment, determine source from template location
598
- source_info = self._determine_agent_source(template_file)
599
- agent_content = self.template_builder.build_agent_markdown(
600
- agent_name, template_file, base_agent_data, source_info
601
- )
602
- if not agent_content:
603
- self.logger.error(f"Failed to build agent content for {agent_name}")
604
- return False
605
-
606
- # Write to target file
607
- target_file.write_text(agent_content)
608
- self.logger.info(
609
- f"Successfully deployed agent: {agent_name} to {target_file}"
469
+ return self.single_agent_deployer.deploy_agent(
470
+ agent_name,
471
+ self.templates_dir,
472
+ target_dir,
473
+ self.base_agent_path,
474
+ force_rebuild,
475
+ self.working_directory,
610
476
  )
611
477
 
612
- return True
613
-
614
478
  except AgentDeploymentError:
615
479
  # Re-raise our custom exceptions
616
480
  raise
@@ -764,11 +628,9 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
764
628
  self.logger.info(f"Async deployment completed in {duration_ms:.1f}ms")
765
629
 
766
630
  # Update internal metrics
767
- self._deployment_metrics["total_deployments"] += 1
768
- if not results.get("errors"):
769
- self._deployment_metrics["successful_deployments"] += 1
770
- else:
771
- self._deployment_metrics["failed_deployments"] += 1
631
+ self.results_manager.update_deployment_metrics(
632
+ not results.get("errors")
633
+ )
772
634
 
773
635
  return results
774
636
 
@@ -793,43 +655,6 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
793
655
  resolver = AgentsDirectoryResolver(self.working_directory)
794
656
  return resolver.determine_agents_directory(target_dir)
795
657
 
796
- def _initialize_deployment_results(
797
- self, agents_dir: Path, deployment_start_time: float
798
- ) -> Dict[str, Any]:
799
- """
800
- Initialize the deployment results dictionary.
801
-
802
- WHY: Consistent result structure ensures all deployment
803
- operations return the same format for easier processing.
804
-
805
- Args:
806
- agents_dir: Target agents directory
807
- deployment_start_time: Start time for metrics
808
-
809
- Returns:
810
- Initialized results dictionary
811
- """
812
- return {
813
- "target_dir": str(agents_dir),
814
- "deployed": [],
815
- "errors": [],
816
- "skipped": [],
817
- "updated": [],
818
- "migrated": [], # Track agents migrated from old format
819
- "converted": [], # Track YAML to MD conversions
820
- "repaired": [], # Track agents with repaired frontmatter
821
- "total": 0,
822
- # METRICS: Add detailed timing and performance data to results
823
- "metrics": {
824
- "start_time": deployment_start_time,
825
- "end_time": None,
826
- "duration_ms": None,
827
- "agent_timings": {}, # Track individual agent deployment times
828
- "validation_times": {}, # Track template validation times
829
- "resource_usage": {}, # Could track memory/CPU if needed
830
- },
831
- }
832
-
833
658
  def _repair_existing_agents(
834
659
  self, agents_dir: Path, results: Dict[str, Any]
835
660
  ) -> None:
@@ -852,12 +677,6 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
852
677
  for agent_name in repair_results["repaired"]:
853
678
  self.logger.debug(f" - Repaired: {agent_name}")
854
679
 
855
- def _determine_source_tier(self) -> str:
856
- """Determine the source tier for logging."""
857
- from .deployment_type_detector import DeploymentTypeDetector
858
-
859
- return DeploymentTypeDetector.determine_source_tier(self.templates_dir)
860
-
861
680
  def _load_base_agent(self) -> tuple:
862
681
  """Load base agent content and version."""
863
682
  return self.configuration_manager.load_base_agent()
@@ -866,205 +685,6 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
866
685
  """Get and filter template files based on exclusion rules."""
867
686
  return self.discovery_service.get_filtered_templates(excluded_agents, config)
868
687
 
869
- def _deploy_single_agent(
870
- self,
871
- template_file: Path,
872
- agents_dir: Path,
873
- base_agent_data: dict,
874
- base_agent_version: tuple,
875
- force_rebuild: bool,
876
- deployment_mode: str,
877
- results: Dict[str, Any],
878
- source_info: str = "unknown",
879
- ) -> None:
880
- """
881
- Deploy a single agent template.
882
-
883
- WHY: Extracting single agent deployment logic reduces complexity
884
- and makes the main deployment loop more readable.
885
-
886
- Args:
887
- template_file: Agent template file
888
- agents_dir: Target agents directory
889
- base_agent_data: Base agent data
890
- base_agent_version: Base agent version
891
- force_rebuild: Whether to force rebuild
892
- deployment_mode: Deployment mode (update/project)
893
- results: Results dictionary to update
894
- source_info: Source of the agent (system/project/user)
895
- """
896
- try:
897
- # METRICS: Track individual agent deployment time
898
- agent_start_time = time.time()
899
-
900
- agent_name = template_file.stem
901
- target_file = agents_dir / f"{agent_name}.md"
902
-
903
- # Check if agent needs update
904
- needs_update, is_migration, reason = self._check_update_status(
905
- target_file,
906
- template_file,
907
- base_agent_version,
908
- force_rebuild,
909
- deployment_mode,
910
- )
911
-
912
- # Skip if exists and doesn't need update (only in update mode)
913
- if (
914
- target_file.exists()
915
- and not needs_update
916
- and deployment_mode != "project"
917
- ):
918
- results["skipped"].append(agent_name)
919
- self.logger.debug(f"Skipped up-to-date agent: {agent_name}")
920
- return
921
-
922
- # Build the agent file as markdown with YAML frontmatter
923
- agent_content = self.template_builder.build_agent_markdown(
924
- agent_name, template_file, base_agent_data, source_info
925
- )
926
-
927
- # Write the agent file
928
- is_update = target_file.exists()
929
- target_file.write_text(agent_content)
930
-
931
- # Record metrics and update results
932
- self._record_agent_deployment(
933
- agent_name,
934
- template_file,
935
- target_file,
936
- is_update,
937
- is_migration,
938
- reason,
939
- agent_start_time,
940
- results,
941
- )
942
-
943
- except AgentDeploymentError as e:
944
- # Re-raise our custom exceptions
945
- self.logger.error(str(e))
946
- results["errors"].append(str(e))
947
- except Exception as e:
948
- # Wrap generic exceptions with context
949
- error_msg = f"Failed to build {template_file.name}: {e}"
950
- self.logger.error(error_msg)
951
- results["errors"].append(error_msg)
952
-
953
- def _check_update_status(
954
- self,
955
- target_file: Path,
956
- template_file: Path,
957
- base_agent_version: tuple,
958
- force_rebuild: bool,
959
- deployment_mode: str,
960
- ) -> tuple:
961
- """
962
- Check if agent needs update and determine status.
963
-
964
- WHY: Centralized update checking logic ensures consistent
965
- version comparison and migration detection.
966
-
967
- Args:
968
- target_file: Target agent file
969
- template_file: Template file
970
- base_agent_version: Base agent version
971
- force_rebuild: Whether to force rebuild
972
- deployment_mode: Deployment mode
973
-
974
- Returns:
975
- Tuple of (needs_update, is_migration, reason)
976
- """
977
- needs_update = force_rebuild
978
- is_migration = False
979
- reason = ""
980
-
981
- # In project deployment mode, always deploy regardless of version
982
- if deployment_mode == "project":
983
- if target_file.exists():
984
- needs_update = True
985
- self.logger.debug(
986
- f"Project deployment mode: will deploy {template_file.stem}"
987
- )
988
- else:
989
- needs_update = True
990
- elif not needs_update and target_file.exists():
991
- # In update mode, check version compatibility
992
- needs_update, reason = self.version_manager.check_agent_needs_update(
993
- target_file, template_file, base_agent_version
994
- )
995
- if needs_update:
996
- # Check if this is a migration from old format
997
- if "migration needed" in reason:
998
- is_migration = True
999
- self.logger.info(f"Migrating agent {template_file.stem}: {reason}")
1000
- else:
1001
- self.logger.info(
1002
- f"Agent {template_file.stem} needs update: {reason}"
1003
- )
1004
-
1005
- return needs_update, is_migration, reason
1006
-
1007
- def _record_agent_deployment(
1008
- self,
1009
- agent_name: str,
1010
- template_file: Path,
1011
- target_file: Path,
1012
- is_update: bool,
1013
- is_migration: bool,
1014
- reason: str,
1015
- agent_start_time: float,
1016
- results: Dict[str, Any],
1017
- ) -> None:
1018
- """
1019
- Record deployment metrics and update results.
1020
-
1021
- WHY: Centralized metrics recording ensures consistent tracking
1022
- of deployment performance and statistics.
1023
-
1024
- Args:
1025
- agent_name: Name of the agent
1026
- template_file: Template file
1027
- target_file: Target file
1028
- is_update: Whether this is an update
1029
- is_migration: Whether this is a migration
1030
- reason: Update/migration reason
1031
- agent_start_time: Start time for this agent
1032
- results: Results dictionary to update
1033
- """
1034
- # METRICS: Record deployment time for this agent
1035
- agent_deployment_time = (time.time() - agent_start_time) * 1000 # Convert to ms
1036
- results["metrics"]["agent_timings"][agent_name] = agent_deployment_time
1037
-
1038
- # METRICS: Update agent type deployment counts
1039
- self._deployment_metrics["agent_type_counts"][agent_name] = (
1040
- self._deployment_metrics["agent_type_counts"].get(agent_name, 0) + 1
1041
- )
1042
-
1043
- deployment_info = {
1044
- "name": agent_name,
1045
- "template": str(template_file),
1046
- "target": str(target_file),
1047
- "deployment_time_ms": agent_deployment_time,
1048
- }
1049
-
1050
- if is_migration:
1051
- deployment_info["reason"] = reason
1052
- results["migrated"].append(deployment_info)
1053
- self.logger.info(
1054
- f"Successfully migrated agent: {agent_name} to semantic versioning"
1055
- )
1056
-
1057
- # METRICS: Track migration statistics
1058
- self._deployment_metrics["migrations_performed"] += 1
1059
- self._deployment_metrics["version_migration_count"] += 1
1060
-
1061
- elif is_update:
1062
- results["updated"].append(deployment_info)
1063
- self.logger.debug(f"Updated agent: {agent_name}")
1064
- else:
1065
- results["deployed"].append(deployment_info)
1066
- self.logger.debug(f"Built and deployed agent: {agent_name}")
1067
-
1068
688
  def _validate_and_repair_existing_agents(self, agents_dir: Path) -> Dict[str, Any]:
1069
689
  """Validate and repair broken frontmatter in existing agent files."""
1070
690
  from .agent_frontmatter_validator import AgentFrontmatterValidator
@@ -1072,38 +692,6 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
1072
692
  validator = AgentFrontmatterValidator(self.logger)
1073
693
  return validator.validate_and_repair_existing_agents(agents_dir)
1074
694
 
1075
- def _determine_agent_source(self, template_path: Path) -> str:
1076
- """Determine the source of an agent from its template path.
1077
-
1078
- WHY: When deploying single agents, we need to track their source
1079
- for proper version management and debugging.
1080
-
1081
- Args:
1082
- template_path: Path to the agent template
1083
-
1084
- Returns:
1085
- Source string (system/project/user/unknown)
1086
- """
1087
- template_str = str(template_path.resolve())
1088
-
1089
- # Check if it's a system template
1090
- if (
1091
- "/claude_mpm/agents/templates/" in template_str
1092
- or "/src/claude_mpm/agents/templates/" in template_str
1093
- ):
1094
- return "system"
1095
-
1096
- # Check if it's a project agent
1097
- if "/.claude-mpm/agents/" in template_str:
1098
- # Check if it's in the current working directory
1099
- if str(self.working_directory) in template_str:
1100
- return "project"
1101
- # Check if it's in user home
1102
- if str(Path.home()) in template_str:
1103
- return "user"
1104
-
1105
- return "unknown"
1106
-
1107
695
  def _should_use_multi_source_deployment(self, deployment_mode: str) -> bool:
1108
696
  """Determine if multi-source deployment should be used.
1109
697