claude-mpm 4.12.1__py3-none-any.whl → 4.13.0__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (38) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/PM_INSTRUCTIONS.md +110 -459
  3. claude_mpm/agents/templates/README.md +465 -0
  4. claude_mpm/agents/templates/circuit_breakers.md +638 -0
  5. claude_mpm/agents/templates/git_file_tracking.md +584 -0
  6. claude_mpm/agents/templates/pm_examples.md +474 -0
  7. claude_mpm/agents/templates/pm_red_flags.md +240 -0
  8. claude_mpm/agents/templates/response_format.md +583 -0
  9. claude_mpm/agents/templates/validation_templates.md +312 -0
  10. claude_mpm/cli/__init__.py +10 -0
  11. claude_mpm/cli/commands/agents.py +31 -0
  12. claude_mpm/cli/commands/agents_detect.py +380 -0
  13. claude_mpm/cli/commands/agents_recommend.py +309 -0
  14. claude_mpm/cli/commands/auto_configure.py +564 -0
  15. claude_mpm/cli/parsers/agents_parser.py +9 -0
  16. claude_mpm/cli/parsers/auto_configure_parser.py +253 -0
  17. claude_mpm/cli/parsers/base_parser.py +7 -0
  18. claude_mpm/core/log_manager.py +2 -0
  19. claude_mpm/services/agents/__init__.py +18 -5
  20. claude_mpm/services/agents/auto_config_manager.py +797 -0
  21. claude_mpm/services/agents/observers.py +547 -0
  22. claude_mpm/services/agents/recommender.py +568 -0
  23. claude_mpm/services/core/__init__.py +33 -1
  24. claude_mpm/services/core/interfaces/__init__.py +16 -1
  25. claude_mpm/services/core/interfaces/agent.py +184 -0
  26. claude_mpm/services/core/interfaces/project.py +121 -0
  27. claude_mpm/services/core/models/__init__.py +46 -0
  28. claude_mpm/services/core/models/agent_config.py +397 -0
  29. claude_mpm/services/core/models/toolchain.py +306 -0
  30. claude_mpm/services/project/__init__.py +23 -0
  31. claude_mpm/services/project/detection_strategies.py +719 -0
  32. claude_mpm/services/project/toolchain_analyzer.py +581 -0
  33. {claude_mpm-4.12.1.dist-info → claude_mpm-4.13.0.dist-info}/METADATA +1 -1
  34. {claude_mpm-4.12.1.dist-info → claude_mpm-4.13.0.dist-info}/RECORD +38 -18
  35. {claude_mpm-4.12.1.dist-info → claude_mpm-4.13.0.dist-info}/WHEEL +0 -0
  36. {claude_mpm-4.12.1.dist-info → claude_mpm-4.13.0.dist-info}/entry_points.txt +0 -0
  37. {claude_mpm-4.12.1.dist-info → claude_mpm-4.13.0.dist-info}/licenses/LICENSE +0 -0
  38. {claude_mpm-4.12.1.dist-info → claude_mpm-4.13.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,797 @@
1
+ """
2
+ Auto-Configuration Manager Service for Claude MPM Framework
3
+ ===========================================================
4
+
5
+ WHY: Orchestrates the complete auto-configuration workflow from toolchain
6
+ analysis through agent deployment. Provides a single entry point for automated
7
+ project configuration with safety checks and rollback capabilities.
8
+
9
+ DESIGN DECISION: Implements the Facade pattern to simplify complex interactions
10
+ between ToolchainAnalyzer, AgentRecommender, and AgentDeployment services.
11
+ Uses Observer pattern for progress tracking and supports dry-run mode for
12
+ safe previewing.
13
+
14
+ Part of TSK-0054: Auto-Configuration Feature - Phase 4
15
+ """
16
+
17
+ import time
18
+ import traceback
19
+ from datetime import datetime, timezone
20
+ from pathlib import Path
21
+ from typing import Any, Dict, List, Optional
22
+
23
+ import yaml
24
+
25
+ from ...core.base_service import BaseService
26
+ from ..core.interfaces.agent import IAgentRegistry, IAutoConfigManager
27
+ from ..core.models.agent_config import (
28
+ AgentRecommendation,
29
+ ConfigurationPreview,
30
+ ConfigurationResult,
31
+ ConfigurationStatus,
32
+ ValidationIssue,
33
+ ValidationResult,
34
+ ValidationSeverity,
35
+ )
36
+ from ..core.models.toolchain import ToolchainAnalysis
37
+ from .observers import IDeploymentObserver, NullObserver
38
+ from .recommender import AgentRecommenderService
39
+
40
+
41
+ class AutoConfigManagerService(BaseService, IAutoConfigManager):
42
+ """
43
+ Service for automated agent configuration and deployment.
44
+
45
+ This service orchestrates:
46
+ 1. Toolchain analysis to understand project technology stack
47
+ 2. Agent recommendations based on detected toolchain
48
+ 3. Configuration validation with comprehensive checks
49
+ 4. User confirmation (optional) before deployment
50
+ 5. Agent deployment with progress tracking
51
+ 6. Rollback on failure to maintain consistency
52
+ 7. Configuration persistence for future reference
53
+
54
+ Safety Features:
55
+ - Minimum confidence threshold (default 0.8)
56
+ - Dry-run mode for preview without changes
57
+ - Validation gates to block invalid configurations
58
+ - Rollback capability for failed deployments
59
+ - User confirmation for destructive operations
60
+
61
+ Performance:
62
+ - Complete workflow: <30 seconds for typical projects
63
+ - Validation: <1 second
64
+ - Preview generation: <5 seconds
65
+ - Uses caching where applicable
66
+ """
67
+
68
+ def __init__(
69
+ self,
70
+ toolchain_analyzer: Optional[Any] = None,
71
+ agent_recommender: Optional[AgentRecommenderService] = None,
72
+ agent_registry: Optional[IAgentRegistry] = None,
73
+ agent_deployment: Optional[Any] = None,
74
+ config: Optional[Dict[str, Any]] = None,
75
+ container: Optional[Any] = None,
76
+ ):
77
+ """
78
+ Initialize the Auto-Configuration Manager Service.
79
+
80
+ Args:
81
+ toolchain_analyzer: Service for analyzing project toolchains
82
+ agent_recommender: Service for recommending agents
83
+ agent_registry: Service for agent discovery and metadata
84
+ agent_deployment: Service for deploying agents
85
+ config: Optional configuration dictionary
86
+ container: Optional service container for dependency injection
87
+ """
88
+ super().__init__(
89
+ name="AutoConfigManagerService",
90
+ config=config,
91
+ enable_enhanced_features=False,
92
+ container=container,
93
+ )
94
+
95
+ # Store service dependencies
96
+ self._toolchain_analyzer = toolchain_analyzer
97
+ self._agent_recommender = agent_recommender
98
+ self._agent_registry = agent_registry
99
+ self._agent_deployment = agent_deployment
100
+
101
+ # Configuration settings
102
+ self._min_confidence_default = 0.8
103
+ self._max_rollback_attempts = 3
104
+ self._deployment_timeout_seconds = 300 # 5 minutes
105
+ self._config_file_name = "auto-config.yaml"
106
+
107
+ self.logger.info("AutoConfigManagerService initialized")
108
+
109
+ async def _initialize(self) -> None:
110
+ """Initialize the service (required by BaseService)."""
111
+ # Lazy initialization of dependencies if needed
112
+ if self._toolchain_analyzer is None:
113
+ try:
114
+ from ..project.toolchain_analyzer import ToolchainAnalyzerService
115
+
116
+ self._toolchain_analyzer = ToolchainAnalyzerService()
117
+ self.logger.info("Initialized ToolchainAnalyzerService")
118
+ except Exception as e:
119
+ self.logger.warning(
120
+ f"Failed to initialize ToolchainAnalyzerService: {e}"
121
+ )
122
+
123
+ if self._agent_recommender is None:
124
+ try:
125
+ self._agent_recommender = AgentRecommenderService()
126
+ self.logger.info("Initialized AgentRecommenderService")
127
+ except Exception as e:
128
+ self.logger.warning(
129
+ f"Failed to initialize AgentRecommenderService: {e}"
130
+ )
131
+
132
+ async def _cleanup(self) -> None:
133
+ """Cleanup service resources (required by BaseService)."""
134
+ # Clear any cached data
135
+
136
+ async def auto_configure(
137
+ self,
138
+ project_path: Path,
139
+ confirmation_required: bool = True,
140
+ dry_run: bool = False,
141
+ min_confidence: float = 0.8,
142
+ observer: Optional[IDeploymentObserver] = None,
143
+ ) -> ConfigurationResult:
144
+ """
145
+ Perform automated agent configuration.
146
+
147
+ Complete end-to-end configuration workflow:
148
+ 1. Analyze project toolchain
149
+ 2. Generate agent recommendations
150
+ 3. Validate proposed configuration
151
+ 4. Request user confirmation (if required)
152
+ 5. Deploy approved agents
153
+ 6. Verify deployment success
154
+
155
+ Args:
156
+ project_path: Path to the project root directory
157
+ confirmation_required: Whether to require user approval before deployment
158
+ dry_run: If True, preview only without deploying
159
+ min_confidence: Minimum confidence score for recommendations (0.0-1.0)
160
+ observer: Optional observer for progress tracking
161
+
162
+ Returns:
163
+ ConfigurationResult: Complete configuration results including
164
+ deployed agents, validation results, and any errors
165
+
166
+ Raises:
167
+ FileNotFoundError: If project_path does not exist
168
+ PermissionError: If unable to write to project directory
169
+ ValueError: If min_confidence is invalid
170
+ """
171
+ # Validate inputs
172
+ if not project_path.exists():
173
+ raise FileNotFoundError(f"Project path does not exist: {project_path}")
174
+ if not project_path.is_dir():
175
+ raise ValueError(f"Project path is not a directory: {project_path}")
176
+ if not (0.0 <= min_confidence <= 1.0):
177
+ raise ValueError(
178
+ f"min_confidence must be between 0.0 and 1.0, got {min_confidence}"
179
+ )
180
+
181
+ # Use NullObserver if none provided
182
+ if observer is None:
183
+ observer = NullObserver()
184
+
185
+ start_time = time.time()
186
+ self.logger.info(
187
+ f"Starting auto-configuration for project: {project_path} "
188
+ f"(dry_run={dry_run}, min_confidence={min_confidence})"
189
+ )
190
+
191
+ try:
192
+ # Step 1: Analyze toolchain
193
+ analysis_start = time.time()
194
+ observer.on_analysis_started(str(project_path))
195
+
196
+ toolchain = await self._analyze_toolchain(project_path)
197
+ analysis_duration = (time.time() - analysis_start) * 1000
198
+
199
+ observer.on_analysis_completed(toolchain, analysis_duration)
200
+ self.logger.info(
201
+ f"Toolchain analysis complete: {toolchain.primary_language} "
202
+ f"with {len(toolchain.frameworks)} frameworks"
203
+ )
204
+
205
+ # Step 2: Generate recommendations
206
+ rec_start = time.time()
207
+ observer.on_recommendation_started()
208
+
209
+ recommendations = await self._generate_recommendations(
210
+ toolchain, min_confidence
211
+ )
212
+ rec_duration = (time.time() - rec_start) * 1000
213
+
214
+ observer.on_recommendation_completed(recommendations, rec_duration)
215
+ self.logger.info(f"Generated {len(recommendations)} agent recommendations")
216
+
217
+ if not recommendations:
218
+ return ConfigurationResult(
219
+ status=ConfigurationStatus.SUCCESS,
220
+ message="No agents recommended for this project configuration",
221
+ recommendations=recommendations,
222
+ metadata={
223
+ "duration_ms": (time.time() - start_time) * 1000,
224
+ "dry_run": dry_run,
225
+ },
226
+ )
227
+
228
+ # Step 3: Validate configuration
229
+ observer.on_validation_started()
230
+
231
+ validation_result = self.validate_configuration(recommendations)
232
+
233
+ observer.on_validation_completed(
234
+ validation_result.is_valid,
235
+ validation_result.error_count,
236
+ validation_result.warning_count,
237
+ )
238
+
239
+ if not validation_result.is_valid:
240
+ self.logger.error(
241
+ f"Validation failed with {validation_result.error_count} errors"
242
+ )
243
+ return ConfigurationResult(
244
+ status=ConfigurationStatus.VALIDATION_ERROR,
245
+ validation_errors=[
246
+ issue.message for issue in validation_result.errors
247
+ ],
248
+ validation_warnings=[
249
+ issue.message for issue in validation_result.warnings
250
+ ],
251
+ recommendations=recommendations,
252
+ message="Configuration validation failed",
253
+ metadata={
254
+ "duration_ms": (time.time() - start_time) * 1000,
255
+ "dry_run": dry_run,
256
+ },
257
+ )
258
+
259
+ # Step 4: Handle dry-run
260
+ if dry_run:
261
+ self.logger.info("Dry-run mode: skipping deployment")
262
+ return ConfigurationResult(
263
+ status=ConfigurationStatus.SUCCESS,
264
+ validation_warnings=[
265
+ issue.message for issue in validation_result.warnings
266
+ ],
267
+ recommendations=recommendations,
268
+ message=f"Dry-run complete: would deploy {len(recommendations)} agents",
269
+ metadata={
270
+ "duration_ms": (time.time() - start_time) * 1000,
271
+ "dry_run": True,
272
+ },
273
+ )
274
+
275
+ # Step 5: User confirmation
276
+ if confirmation_required:
277
+ confirmed = await self._request_confirmation(
278
+ recommendations, validation_result
279
+ )
280
+ if not confirmed:
281
+ self.logger.info("User cancelled auto-configuration")
282
+ return ConfigurationResult(
283
+ status=ConfigurationStatus.USER_CANCELLED,
284
+ recommendations=recommendations,
285
+ message="Auto-configuration cancelled by user",
286
+ metadata={
287
+ "duration_ms": (time.time() - start_time) * 1000,
288
+ "dry_run": dry_run,
289
+ },
290
+ )
291
+
292
+ # Step 6: Deploy agents
293
+ deploy_start = time.time()
294
+ observer.on_deployment_started(len(recommendations))
295
+
296
+ deployed_agents, failed_agents = await self._deploy_agents(
297
+ project_path, recommendations, observer
298
+ )
299
+ deploy_duration = (time.time() - deploy_start) * 1000
300
+
301
+ observer.on_deployment_completed(
302
+ len(deployed_agents), len(failed_agents), deploy_duration
303
+ )
304
+
305
+ # Step 7: Handle deployment failures
306
+ if failed_agents:
307
+ self.logger.warning(
308
+ f"Deployment completed with {len(failed_agents)} failures"
309
+ )
310
+
311
+ # Attempt rollback
312
+ if deployed_agents:
313
+ observer.on_rollback_started(deployed_agents)
314
+ rollback_success = await self._rollback_deployment(
315
+ project_path, deployed_agents
316
+ )
317
+ observer.on_rollback_completed(rollback_success)
318
+
319
+ return ConfigurationResult(
320
+ status=(
321
+ ConfigurationStatus.PARTIAL_SUCCESS
322
+ if deployed_agents
323
+ else ConfigurationStatus.FAILURE
324
+ ),
325
+ deployed_agents=deployed_agents,
326
+ failed_agents=failed_agents,
327
+ validation_warnings=[
328
+ issue.message for issue in validation_result.warnings
329
+ ],
330
+ recommendations=recommendations,
331
+ message=f"Deployment completed with issues: {len(deployed_agents)} succeeded, {len(failed_agents)} failed",
332
+ metadata={
333
+ "duration_ms": (time.time() - start_time) * 1000,
334
+ "dry_run": dry_run,
335
+ "rollback_attempted": len(deployed_agents) > 0,
336
+ },
337
+ )
338
+
339
+ # Step 8: Save configuration
340
+ await self._save_configuration(project_path, toolchain, recommendations)
341
+
342
+ # Success!
343
+ total_duration = (time.time() - start_time) * 1000
344
+ self.logger.info(
345
+ f"Auto-configuration completed successfully in {total_duration:.0f}ms: "
346
+ f"deployed {len(deployed_agents)} agents"
347
+ )
348
+
349
+ return ConfigurationResult(
350
+ status=ConfigurationStatus.SUCCESS,
351
+ deployed_agents=deployed_agents,
352
+ validation_warnings=[
353
+ issue.message for issue in validation_result.warnings
354
+ ],
355
+ recommendations=recommendations,
356
+ message=f"Successfully configured {len(deployed_agents)} agents",
357
+ metadata={
358
+ "duration_ms": total_duration,
359
+ "dry_run": dry_run,
360
+ },
361
+ )
362
+
363
+ except Exception as e:
364
+ self.logger.error(f"Auto-configuration failed: {e}", exc_info=True)
365
+ observer.on_error("auto-configuration", str(e), e)
366
+
367
+ return ConfigurationResult(
368
+ status=ConfigurationStatus.FAILURE,
369
+ message=f"Auto-configuration failed: {e}",
370
+ metadata={
371
+ "duration_ms": (time.time() - start_time) * 1000,
372
+ "dry_run": dry_run,
373
+ "error": str(e),
374
+ "traceback": traceback.format_exc(),
375
+ },
376
+ )
377
+
378
+ def validate_configuration(
379
+ self, recommendations: List[AgentRecommendation]
380
+ ) -> ValidationResult:
381
+ """
382
+ Validate proposed configuration before deployment.
383
+
384
+ Performs comprehensive validation:
385
+ - Checks for agent existence
386
+ - Verifies no conflicts (multiple agents for same role)
387
+ - Validates minimum confidence threshold
388
+ - Checks deployment prerequisites
389
+ - Warns about unmatched toolchains
390
+
391
+ Args:
392
+ recommendations: List of agent recommendations to validate
393
+
394
+ Returns:
395
+ ValidationResult: Validation result with any warnings or errors
396
+
397
+ Raises:
398
+ ValueError: If recommendations list is empty or invalid
399
+ """
400
+ if not recommendations:
401
+ raise ValueError("Cannot validate empty recommendations list")
402
+
403
+ issues: List[ValidationIssue] = []
404
+ validated_agents: List[str] = []
405
+
406
+ # Track agent roles to detect conflicts
407
+ role_agents: Dict[str, List[str]] = {}
408
+
409
+ for recommendation in recommendations:
410
+ agent_id = recommendation.agent_id
411
+ validated_agents.append(agent_id)
412
+
413
+ # Check 1: Agent existence
414
+ if self._agent_registry:
415
+ try:
416
+ # Try to get agent metadata to verify it exists
417
+ agent = self._agent_registry.get_agent(agent_id)
418
+ if agent is None:
419
+ issues.append(
420
+ ValidationIssue(
421
+ severity=ValidationSeverity.ERROR,
422
+ message=f"Agent '{agent_id}' does not exist",
423
+ agent_id=agent_id,
424
+ suggested_fix="Remove this recommendation or ensure agent is installed",
425
+ )
426
+ )
427
+ continue
428
+ except Exception as e:
429
+ issues.append(
430
+ ValidationIssue(
431
+ severity=ValidationSeverity.WARNING,
432
+ message=f"Could not verify agent '{agent_id}': {e}",
433
+ agent_id=agent_id,
434
+ )
435
+ )
436
+
437
+ # Check 2: Confidence threshold
438
+ if recommendation.confidence_score < 0.5:
439
+ issues.append(
440
+ ValidationIssue(
441
+ severity=ValidationSeverity.WARNING,
442
+ message=f"Low confidence score ({recommendation.confidence_score:.2f}) for agent '{agent_id}'",
443
+ agent_id=agent_id,
444
+ suggested_fix="Consider reviewing agent capabilities or adjusting threshold",
445
+ )
446
+ )
447
+ elif recommendation.confidence_score < 0.7:
448
+ issues.append(
449
+ ValidationIssue(
450
+ severity=ValidationSeverity.INFO,
451
+ message=f"Moderate confidence score ({recommendation.confidence_score:.2f}) for agent '{agent_id}'",
452
+ agent_id=agent_id,
453
+ )
454
+ )
455
+
456
+ # Check 3: Track roles for conflict detection
457
+ if recommendation.capabilities:
458
+ for spec in recommendation.capabilities.specializations:
459
+ role = spec.value
460
+ if role not in role_agents:
461
+ role_agents[role] = []
462
+ role_agents[role].append(agent_id)
463
+
464
+ # Check 4: Recommendation concerns
465
+ if recommendation.has_concerns:
466
+ for concern in recommendation.concerns:
467
+ issues.append(
468
+ ValidationIssue(
469
+ severity=ValidationSeverity.INFO,
470
+ message=f"Concern for '{agent_id}': {concern}",
471
+ agent_id=agent_id,
472
+ )
473
+ )
474
+
475
+ # Check 5: Role conflicts (multiple agents for same role)
476
+ for role, agents in role_agents.items():
477
+ if len(agents) > 1:
478
+ issues.append(
479
+ ValidationIssue(
480
+ severity=ValidationSeverity.WARNING,
481
+ message=f"Multiple agents ({', '.join(agents)}) recommended for role '{role}'",
482
+ suggested_fix="Consider selecting the highest confidence agent for this role",
483
+ )
484
+ )
485
+
486
+ # Determine if validation passes (no errors)
487
+ is_valid = not any(
488
+ issue.severity == ValidationSeverity.ERROR for issue in issues
489
+ )
490
+
491
+ self.logger.info(
492
+ f"Validation complete: {len(validated_agents)} agents, "
493
+ f"{len([i for i in issues if i.severity == ValidationSeverity.ERROR])} errors, "
494
+ f"{len([i for i in issues if i.severity == ValidationSeverity.WARNING])} warnings"
495
+ )
496
+
497
+ return ValidationResult(
498
+ is_valid=is_valid,
499
+ issues=issues,
500
+ validated_agents=validated_agents,
501
+ metadata={"validation_timestamp": datetime.now(timezone.utc).isoformat()},
502
+ )
503
+
504
+ def preview_configuration(
505
+ self, project_path: Path, min_confidence: float = 0.8
506
+ ) -> ConfigurationPreview:
507
+ """
508
+ Preview what would be configured without applying changes.
509
+
510
+ Performs analysis and recommendation without making any changes:
511
+ - Analyzes project toolchain
512
+ - Generates recommendations
513
+ - Validates configuration
514
+ - Returns preview of what would be deployed
515
+
516
+ Args:
517
+ project_path: Path to the project root directory
518
+ min_confidence: Minimum confidence score for recommendations
519
+
520
+ Returns:
521
+ ConfigurationPreview: Preview of configuration that would be applied
522
+
523
+ Raises:
524
+ FileNotFoundError: If project_path does not exist
525
+ """
526
+ if not project_path.exists():
527
+ raise FileNotFoundError(f"Project path does not exist: {project_path}")
528
+
529
+ self.logger.info(f"Generating configuration preview for: {project_path}")
530
+
531
+ try:
532
+ # Run analysis and recommendations synchronously for preview
533
+ import asyncio
534
+
535
+ loop = asyncio.get_event_loop()
536
+
537
+ # Analyze toolchain
538
+ if asyncio.iscoroutinefunction(self._analyze_toolchain):
539
+ toolchain = loop.run_until_complete(
540
+ self._analyze_toolchain(project_path)
541
+ )
542
+ else:
543
+ toolchain = self._analyze_toolchain(project_path)
544
+
545
+ # Generate recommendations
546
+ if asyncio.iscoroutinefunction(self._generate_recommendations):
547
+ recommendations = loop.run_until_complete(
548
+ self._generate_recommendations(toolchain, min_confidence)
549
+ )
550
+ else:
551
+ recommendations = self._generate_recommendations(
552
+ toolchain, min_confidence
553
+ )
554
+
555
+ # Validate configuration
556
+ validation_result = self.validate_configuration(recommendations)
557
+
558
+ # Estimate deployment time (5 seconds per agent)
559
+ estimated_time = len(recommendations) * 5.0
560
+
561
+ # Determine what would be deployed
562
+ would_deploy = [
563
+ rec.agent_id
564
+ for rec in recommendations
565
+ if rec.confidence_score >= min_confidence
566
+ ]
567
+ would_skip = [
568
+ rec.agent_id
569
+ for rec in recommendations
570
+ if rec.confidence_score < min_confidence
571
+ ]
572
+
573
+ preview = ConfigurationPreview(
574
+ recommendations=recommendations,
575
+ validation_result=validation_result,
576
+ estimated_deployment_time=estimated_time,
577
+ would_deploy=would_deploy,
578
+ would_skip=would_skip,
579
+ requires_confirmation=True,
580
+ metadata={
581
+ "preview_timestamp": datetime.now(timezone.utc).isoformat(),
582
+ "toolchain_summary": {
583
+ "primary_language": toolchain.primary_language,
584
+ "frameworks": [fw.name for fw in toolchain.frameworks],
585
+ "deployment_target": (
586
+ toolchain.deployment_target.platform
587
+ if toolchain.deployment_target
588
+ else None
589
+ ),
590
+ },
591
+ },
592
+ )
593
+
594
+ self.logger.info(
595
+ f"Preview generated: {preview.deployment_count} agents would be deployed"
596
+ )
597
+ return preview
598
+
599
+ except Exception as e:
600
+ self.logger.error(f"Failed to generate preview: {e}", exc_info=True)
601
+ raise
602
+
603
+ # Private helper methods
604
+
605
+ async def _analyze_toolchain(self, project_path: Path) -> ToolchainAnalysis:
606
+ """Analyze project toolchain."""
607
+ if self._toolchain_analyzer is None:
608
+ raise RuntimeError("ToolchainAnalyzer not initialized")
609
+
610
+ return self._toolchain_analyzer.analyze_toolchain(project_path)
611
+
612
+ async def _generate_recommendations(
613
+ self, toolchain: ToolchainAnalysis, min_confidence: float
614
+ ) -> List[AgentRecommendation]:
615
+ """Generate agent recommendations."""
616
+ if self._agent_recommender is None:
617
+ raise RuntimeError("AgentRecommender not initialized")
618
+
619
+ constraints = {"min_confidence": min_confidence}
620
+ return self._agent_recommender.recommend_agents(toolchain, constraints)
621
+
622
+ async def _request_confirmation(
623
+ self, recommendations: List[AgentRecommendation], validation: ValidationResult
624
+ ) -> bool:
625
+ """
626
+ Request user confirmation before deployment.
627
+
628
+ TODO: Implement interactive confirmation dialog.
629
+ For now, returns True (auto-approve) to enable testing.
630
+
631
+ Args:
632
+ recommendations: List of recommended agents
633
+ validation: Validation results
634
+
635
+ Returns:
636
+ bool: True if user confirms, False otherwise
637
+ """
638
+ # TODO: Implement interactive confirmation
639
+ # For now, auto-approve if validation passed
640
+ return validation.is_valid
641
+
642
+ async def _deploy_agents(
643
+ self,
644
+ project_path: Path,
645
+ recommendations: List[AgentRecommendation],
646
+ observer: IDeploymentObserver,
647
+ ) -> tuple[List[str], List[str]]:
648
+ """
649
+ Deploy recommended agents.
650
+
651
+ Args:
652
+ project_path: Project root directory
653
+ recommendations: List of recommendations to deploy
654
+ observer: Observer for progress tracking
655
+
656
+ Returns:
657
+ Tuple of (deployed_agent_ids, failed_agent_ids)
658
+ """
659
+ deployed = []
660
+ failed = []
661
+
662
+ # Sort recommendations by deployment priority
663
+ sorted_recs = sorted(recommendations, key=lambda r: r.deployment_priority)
664
+
665
+ for index, recommendation in enumerate(sorted_recs, 1):
666
+ agent_id = recommendation.agent_id
667
+ agent_name = recommendation.agent_name
668
+
669
+ try:
670
+ observer.on_agent_deployment_started(
671
+ agent_id, agent_name, index, len(sorted_recs)
672
+ )
673
+
674
+ # TODO: Integrate with actual AgentDeploymentService
675
+ # For now, simulate deployment
676
+ await self._deploy_single_agent(agent_id, project_path)
677
+
678
+ observer.on_agent_deployment_completed(
679
+ agent_id, agent_name, success=True
680
+ )
681
+ deployed.append(agent_id)
682
+ self.logger.info(f"Successfully deployed agent: {agent_id}")
683
+
684
+ except Exception as e:
685
+ self.logger.error(
686
+ f"Failed to deploy agent '{agent_id}': {e}", exc_info=True
687
+ )
688
+ observer.on_agent_deployment_completed(
689
+ agent_id, agent_name, success=False, error=str(e)
690
+ )
691
+ failed.append(agent_id)
692
+
693
+ return deployed, failed
694
+
695
+ async def _deploy_single_agent(self, agent_id: str, project_path: Path) -> None:
696
+ """
697
+ Deploy a single agent.
698
+
699
+ TODO: Integrate with AgentDeploymentService.
700
+
701
+ Args:
702
+ agent_id: Agent identifier
703
+ project_path: Project root directory
704
+
705
+ Raises:
706
+ Exception: If deployment fails
707
+ """
708
+ # TODO: Implement actual deployment logic
709
+ # For now, simulate deployment with a small delay
710
+ import asyncio
711
+
712
+ await asyncio.sleep(0.1)
713
+
714
+ # Placeholder: will integrate with AgentDeploymentService
715
+ self.logger.debug(f"Deployed agent {agent_id} to {project_path}")
716
+
717
+ async def _rollback_deployment(
718
+ self, project_path: Path, deployed_agents: List[str]
719
+ ) -> bool:
720
+ """
721
+ Rollback deployed agents after failure.
722
+
723
+ Args:
724
+ project_path: Project root directory
725
+ deployed_agents: List of agent IDs to rollback
726
+
727
+ Returns:
728
+ bool: True if rollback succeeded, False otherwise
729
+ """
730
+ self.logger.warning(f"Rolling back {len(deployed_agents)} deployed agents")
731
+
732
+ try:
733
+ # TODO: Implement actual rollback logic
734
+ # For now, log the rollback attempt
735
+ for agent_id in deployed_agents:
736
+ self.logger.info(f"Rolling back agent: {agent_id}")
737
+
738
+ return True
739
+
740
+ except Exception as e:
741
+ self.logger.error(f"Rollback failed: {e}", exc_info=True)
742
+ return False
743
+
744
+ async def _save_configuration(
745
+ self,
746
+ project_path: Path,
747
+ toolchain: ToolchainAnalysis,
748
+ recommendations: List[AgentRecommendation],
749
+ ) -> None:
750
+ """
751
+ Save auto-configuration metadata to project.
752
+
753
+ Args:
754
+ project_path: Project root directory
755
+ toolchain: Toolchain analysis results
756
+ recommendations: Agent recommendations that were deployed
757
+ """
758
+ config_dir = project_path / ".claude-mpm"
759
+ config_dir.mkdir(exist_ok=True)
760
+
761
+ config_file = config_dir / self._config_file_name
762
+
763
+ config_data = {
764
+ "auto_config": {
765
+ "enabled": True,
766
+ "last_run": datetime.now(timezone.utc).isoformat(),
767
+ "toolchain_snapshot": {
768
+ "primary_language": toolchain.primary_language,
769
+ "frameworks": [fw.name for fw in toolchain.frameworks],
770
+ "deployment_targets": (
771
+ [toolchain.deployment_target.platform]
772
+ if toolchain.deployment_target
773
+ else []
774
+ ),
775
+ },
776
+ "deployed_agents": [
777
+ {
778
+ "agent_id": rec.agent_id,
779
+ "agent_name": rec.agent_name,
780
+ "confidence": rec.confidence_score,
781
+ "deployed_at": datetime.now(timezone.utc).isoformat(),
782
+ }
783
+ for rec in recommendations
784
+ ],
785
+ "user_overrides": {"disabled_agents": [], "custom_agents": []},
786
+ }
787
+ }
788
+
789
+ try:
790
+ with config_file.open("w", encoding="utf-8") as f:
791
+ yaml.dump(config_data, f, default_flow_style=False, sort_keys=False)
792
+
793
+ self.logger.info(f"Saved auto-configuration to: {config_file}")
794
+
795
+ except Exception as e:
796
+ self.logger.error(f"Failed to save configuration: {e}", exc_info=True)
797
+ # Don't raise - configuration save is non-critical