crackerjack 0.30.3__py3-none-any.whl → 0.31.7__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 crackerjack might be problematic. Click here for more details.

Files changed (156) hide show
  1. crackerjack/CLAUDE.md +1005 -0
  2. crackerjack/RULES.md +380 -0
  3. crackerjack/__init__.py +42 -13
  4. crackerjack/__main__.py +227 -299
  5. crackerjack/agents/__init__.py +41 -0
  6. crackerjack/agents/architect_agent.py +281 -0
  7. crackerjack/agents/base.py +170 -0
  8. crackerjack/agents/coordinator.py +512 -0
  9. crackerjack/agents/documentation_agent.py +498 -0
  10. crackerjack/agents/dry_agent.py +388 -0
  11. crackerjack/agents/formatting_agent.py +245 -0
  12. crackerjack/agents/import_optimization_agent.py +281 -0
  13. crackerjack/agents/performance_agent.py +669 -0
  14. crackerjack/agents/proactive_agent.py +104 -0
  15. crackerjack/agents/refactoring_agent.py +788 -0
  16. crackerjack/agents/security_agent.py +529 -0
  17. crackerjack/agents/test_creation_agent.py +657 -0
  18. crackerjack/agents/test_specialist_agent.py +486 -0
  19. crackerjack/agents/tracker.py +212 -0
  20. crackerjack/api.py +560 -0
  21. crackerjack/cli/__init__.py +24 -0
  22. crackerjack/cli/facade.py +104 -0
  23. crackerjack/cli/handlers.py +267 -0
  24. crackerjack/cli/interactive.py +471 -0
  25. crackerjack/cli/options.py +409 -0
  26. crackerjack/cli/utils.py +18 -0
  27. crackerjack/code_cleaner.py +618 -928
  28. crackerjack/config/__init__.py +19 -0
  29. crackerjack/config/hooks.py +218 -0
  30. crackerjack/core/__init__.py +0 -0
  31. crackerjack/core/async_workflow_orchestrator.py +406 -0
  32. crackerjack/core/autofix_coordinator.py +200 -0
  33. crackerjack/core/container.py +104 -0
  34. crackerjack/core/enhanced_container.py +542 -0
  35. crackerjack/core/performance.py +243 -0
  36. crackerjack/core/phase_coordinator.py +585 -0
  37. crackerjack/core/proactive_workflow.py +316 -0
  38. crackerjack/core/session_coordinator.py +289 -0
  39. crackerjack/core/workflow_orchestrator.py +826 -0
  40. crackerjack/dynamic_config.py +94 -103
  41. crackerjack/errors.py +263 -41
  42. crackerjack/executors/__init__.py +11 -0
  43. crackerjack/executors/async_hook_executor.py +431 -0
  44. crackerjack/executors/cached_hook_executor.py +242 -0
  45. crackerjack/executors/hook_executor.py +345 -0
  46. crackerjack/executors/individual_hook_executor.py +669 -0
  47. crackerjack/intelligence/__init__.py +44 -0
  48. crackerjack/intelligence/adaptive_learning.py +751 -0
  49. crackerjack/intelligence/agent_orchestrator.py +551 -0
  50. crackerjack/intelligence/agent_registry.py +414 -0
  51. crackerjack/intelligence/agent_selector.py +502 -0
  52. crackerjack/intelligence/integration.py +290 -0
  53. crackerjack/interactive.py +576 -315
  54. crackerjack/managers/__init__.py +11 -0
  55. crackerjack/managers/async_hook_manager.py +135 -0
  56. crackerjack/managers/hook_manager.py +137 -0
  57. crackerjack/managers/publish_manager.py +433 -0
  58. crackerjack/managers/test_command_builder.py +151 -0
  59. crackerjack/managers/test_executor.py +443 -0
  60. crackerjack/managers/test_manager.py +258 -0
  61. crackerjack/managers/test_manager_backup.py +1124 -0
  62. crackerjack/managers/test_progress.py +114 -0
  63. crackerjack/mcp/__init__.py +0 -0
  64. crackerjack/mcp/cache.py +336 -0
  65. crackerjack/mcp/client_runner.py +104 -0
  66. crackerjack/mcp/context.py +621 -0
  67. crackerjack/mcp/dashboard.py +636 -0
  68. crackerjack/mcp/enhanced_progress_monitor.py +479 -0
  69. crackerjack/mcp/file_monitor.py +336 -0
  70. crackerjack/mcp/progress_components.py +569 -0
  71. crackerjack/mcp/progress_monitor.py +949 -0
  72. crackerjack/mcp/rate_limiter.py +332 -0
  73. crackerjack/mcp/server.py +22 -0
  74. crackerjack/mcp/server_core.py +244 -0
  75. crackerjack/mcp/service_watchdog.py +501 -0
  76. crackerjack/mcp/state.py +395 -0
  77. crackerjack/mcp/task_manager.py +257 -0
  78. crackerjack/mcp/tools/__init__.py +17 -0
  79. crackerjack/mcp/tools/core_tools.py +249 -0
  80. crackerjack/mcp/tools/error_analyzer.py +308 -0
  81. crackerjack/mcp/tools/execution_tools.py +372 -0
  82. crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
  83. crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
  84. crackerjack/mcp/tools/intelligence_tools.py +314 -0
  85. crackerjack/mcp/tools/monitoring_tools.py +502 -0
  86. crackerjack/mcp/tools/proactive_tools.py +384 -0
  87. crackerjack/mcp/tools/progress_tools.py +217 -0
  88. crackerjack/mcp/tools/utility_tools.py +341 -0
  89. crackerjack/mcp/tools/workflow_executor.py +565 -0
  90. crackerjack/mcp/websocket/__init__.py +14 -0
  91. crackerjack/mcp/websocket/app.py +39 -0
  92. crackerjack/mcp/websocket/endpoints.py +559 -0
  93. crackerjack/mcp/websocket/jobs.py +253 -0
  94. crackerjack/mcp/websocket/server.py +116 -0
  95. crackerjack/mcp/websocket/websocket_handler.py +78 -0
  96. crackerjack/mcp/websocket_server.py +10 -0
  97. crackerjack/models/__init__.py +31 -0
  98. crackerjack/models/config.py +93 -0
  99. crackerjack/models/config_adapter.py +230 -0
  100. crackerjack/models/protocols.py +118 -0
  101. crackerjack/models/task.py +154 -0
  102. crackerjack/monitoring/ai_agent_watchdog.py +450 -0
  103. crackerjack/monitoring/regression_prevention.py +638 -0
  104. crackerjack/orchestration/__init__.py +0 -0
  105. crackerjack/orchestration/advanced_orchestrator.py +970 -0
  106. crackerjack/orchestration/coverage_improvement.py +223 -0
  107. crackerjack/orchestration/execution_strategies.py +341 -0
  108. crackerjack/orchestration/test_progress_streamer.py +636 -0
  109. crackerjack/plugins/__init__.py +15 -0
  110. crackerjack/plugins/base.py +200 -0
  111. crackerjack/plugins/hooks.py +246 -0
  112. crackerjack/plugins/loader.py +335 -0
  113. crackerjack/plugins/managers.py +259 -0
  114. crackerjack/py313.py +8 -3
  115. crackerjack/services/__init__.py +22 -0
  116. crackerjack/services/cache.py +314 -0
  117. crackerjack/services/config.py +358 -0
  118. crackerjack/services/config_integrity.py +99 -0
  119. crackerjack/services/contextual_ai_assistant.py +516 -0
  120. crackerjack/services/coverage_ratchet.py +356 -0
  121. crackerjack/services/debug.py +736 -0
  122. crackerjack/services/dependency_monitor.py +617 -0
  123. crackerjack/services/enhanced_filesystem.py +439 -0
  124. crackerjack/services/file_hasher.py +151 -0
  125. crackerjack/services/filesystem.py +421 -0
  126. crackerjack/services/git.py +176 -0
  127. crackerjack/services/health_metrics.py +611 -0
  128. crackerjack/services/initialization.py +873 -0
  129. crackerjack/services/log_manager.py +286 -0
  130. crackerjack/services/logging.py +174 -0
  131. crackerjack/services/metrics.py +578 -0
  132. crackerjack/services/pattern_cache.py +362 -0
  133. crackerjack/services/pattern_detector.py +515 -0
  134. crackerjack/services/performance_benchmarks.py +653 -0
  135. crackerjack/services/security.py +163 -0
  136. crackerjack/services/server_manager.py +234 -0
  137. crackerjack/services/smart_scheduling.py +144 -0
  138. crackerjack/services/tool_version_service.py +61 -0
  139. crackerjack/services/unified_config.py +437 -0
  140. crackerjack/services/version_checker.py +248 -0
  141. crackerjack/slash_commands/__init__.py +14 -0
  142. crackerjack/slash_commands/init.md +122 -0
  143. crackerjack/slash_commands/run.md +163 -0
  144. crackerjack/slash_commands/status.md +127 -0
  145. crackerjack-0.31.7.dist-info/METADATA +742 -0
  146. crackerjack-0.31.7.dist-info/RECORD +149 -0
  147. crackerjack-0.31.7.dist-info/entry_points.txt +2 -0
  148. crackerjack/.gitignore +0 -34
  149. crackerjack/.libcst.codemod.yaml +0 -18
  150. crackerjack/.pdm.toml +0 -1
  151. crackerjack/crackerjack.py +0 -3805
  152. crackerjack/pyproject.toml +0 -286
  153. crackerjack-0.30.3.dist-info/METADATA +0 -1290
  154. crackerjack-0.30.3.dist-info/RECORD +0 -16
  155. {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/WHEEL +0 -0
  156. {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,223 @@
1
+ """Coverage improvement orchestration.
2
+
3
+ This module provides proactive test coverage improvement by analyzing coverage gaps
4
+ and triggering the TestCreationAgent to automatically generate missing tests.
5
+ Integrated into the AI agent workflow after successful test execution.
6
+ """
7
+
8
+ import logging
9
+ import typing as t
10
+ from pathlib import Path
11
+
12
+ from crackerjack.agents.base import Issue, IssueType, Priority
13
+ from crackerjack.agents.test_creation_agent import TestCreationAgent
14
+ from crackerjack.services.coverage_ratchet import CoverageRatchetService
15
+
16
+
17
+ class CoverageImprovementOrchestrator:
18
+ """Orchestrates automatic test coverage improvement."""
19
+
20
+ def __init__(self, project_path: Path, console: t.Any = None) -> None:
21
+ self.project_path = project_path
22
+ self.logger = logging.getLogger(__name__)
23
+ self.console = console
24
+ self.coverage_service = CoverageRatchetService(project_path, console)
25
+ self.min_coverage_improvement = 2.0 # Minimum 2% improvement target per run
26
+
27
+ async def should_improve_coverage(
28
+ self, current_coverage: float | None = None
29
+ ) -> bool:
30
+ """Determine if coverage improvement should be attempted.
31
+
32
+ Args:
33
+ current_coverage: Current coverage percentage, will be detected if None
34
+
35
+ Returns:
36
+ True if coverage improvement should be attempted
37
+ """
38
+ try:
39
+ if current_coverage is None:
40
+ coverage_status = self.coverage_service.get_status_report()
41
+ current_coverage = coverage_status.get("current_coverage", 0.0)
42
+
43
+ # Always try to improve if coverage is below 100%
44
+ if current_coverage is not None and current_coverage < 100.0:
45
+ self.logger.info(
46
+ f"Coverage at {current_coverage:.1f}% - improvement recommended"
47
+ )
48
+ return True
49
+
50
+ self.logger.info("Coverage at 100% - no improvement needed")
51
+ return False
52
+
53
+ except Exception as e:
54
+ self.logger.warning(f"Could not determine coverage status: {e}")
55
+ # Default to trying improvement if we can't determine coverage
56
+ return True
57
+
58
+ async def create_coverage_improvement_issue(
59
+ self, coverage_gap: float | None = None
60
+ ) -> Issue:
61
+ """Create an issue for coverage improvement.
62
+
63
+ Args:
64
+ coverage_gap: Percentage gap to 100% coverage
65
+
66
+ Returns:
67
+ Issue configured for coverage improvement
68
+ """
69
+ if coverage_gap is None:
70
+ try:
71
+ coverage_status = self.coverage_service.get_status_report()
72
+ current_coverage = coverage_status.get("current_coverage", 0.0)
73
+ coverage_gap = 100.0 - current_coverage
74
+ except Exception:
75
+ coverage_gap = 90.0 # Default gap if we can't determine
76
+
77
+ message = (
78
+ f"Proactive coverage improvement requested. "
79
+ f"Gap to 100%: {coverage_gap:.1f}%. "
80
+ f"Target improvement: {min(self.min_coverage_improvement, coverage_gap) if coverage_gap is not None else self.min_coverage_improvement:.1f}%"
81
+ )
82
+
83
+ return Issue(
84
+ id="coverage_improvement_proactive",
85
+ type=IssueType.COVERAGE_IMPROVEMENT,
86
+ severity=Priority.MEDIUM,
87
+ message=message,
88
+ file_path=None, # Project-wide improvement
89
+ stage="coverage_improvement",
90
+ )
91
+
92
+ async def execute_coverage_improvement(
93
+ self, agent_context: t.Any
94
+ ) -> dict[str, t.Any]:
95
+ """Execute proactive coverage improvement.
96
+
97
+ Args:
98
+ agent_context: AgentContext for the TestCreationAgent
99
+
100
+ Returns:
101
+ Dictionary with improvement results
102
+ """
103
+ try:
104
+ self.logger.info("Starting proactive coverage improvement")
105
+
106
+ # Check if improvement is needed
107
+ if not await self.should_improve_coverage():
108
+ return self._create_skipped_result("Coverage improvement not needed")
109
+
110
+ # Create coverage improvement issue and agent
111
+ issue = await self.create_coverage_improvement_issue()
112
+ test_agent = TestCreationAgent(agent_context)
113
+
114
+ # Validate agent confidence
115
+ confidence = await test_agent.can_handle(issue)
116
+ if confidence < 0.5:
117
+ return self._create_low_confidence_result(confidence)
118
+
119
+ # Execute the coverage improvement
120
+ fix_result = await test_agent.analyze_and_fix(issue)
121
+ result = self._create_completion_result(fix_result)
122
+
123
+ self._log_and_display_results(fix_result)
124
+ return result
125
+
126
+ except Exception as e:
127
+ return self._create_error_result(e)
128
+
129
+ def _create_skipped_result(self, reason: str) -> dict[str, t.Any]:
130
+ """Create result dict for skipped coverage improvement."""
131
+ return {
132
+ "status": "skipped",
133
+ "reason": reason,
134
+ "coverage_at_100": True,
135
+ }
136
+
137
+ def _create_low_confidence_result(self, confidence: float) -> dict[str, t.Any]:
138
+ """Create result dict for low confidence scenario."""
139
+ self.logger.warning(f"TestCreationAgent confidence too low: {confidence}")
140
+ return {
141
+ "status": "skipped",
142
+ "reason": "Agent confidence too low",
143
+ "confidence": confidence,
144
+ }
145
+
146
+ def _create_completion_result(self, fix_result: t.Any) -> dict[str, t.Any]:
147
+ """Create result dict from fix results."""
148
+ return {
149
+ "status": "completed" if fix_result.success else "failed",
150
+ "confidence": fix_result.confidence,
151
+ "fixes_applied": fix_result.fixes_applied,
152
+ "files_modified": fix_result.files_modified,
153
+ "recommendations": fix_result.recommendations,
154
+ }
155
+
156
+ def _log_and_display_results(self, fix_result: t.Any) -> None:
157
+ """Log and display the results of coverage improvement."""
158
+ if fix_result.success:
159
+ self.logger.info(
160
+ f"Coverage improvement successful: {len(fix_result.fixes_applied)} fixes applied"
161
+ )
162
+ if self.console:
163
+ self.console.print(
164
+ f"[green]📈[/green] Coverage improved: {len(fix_result.fixes_applied)} "
165
+ f"tests created in {len(fix_result.files_modified)} files"
166
+ )
167
+ else:
168
+ self.logger.warning("Coverage improvement failed")
169
+ if self.console:
170
+ self.console.print(
171
+ "[yellow]⚠️[/yellow] Coverage improvement attempt completed with issues"
172
+ )
173
+
174
+ def _create_error_result(self, error: Exception) -> dict[str, t.Any]:
175
+ """Create result dict for error scenarios."""
176
+ self.logger.error(f"Coverage improvement failed with error: {error}")
177
+ return {
178
+ "status": "error",
179
+ "error": str(error),
180
+ "fixes_applied": [],
181
+ "files_modified": [],
182
+ }
183
+
184
+ async def get_coverage_improvement_recommendations(self) -> list[str]:
185
+ """Get recommendations for coverage improvement strategies.
186
+
187
+ Returns:
188
+ List of strategic recommendations for improving coverage
189
+ """
190
+ recommendations = [
191
+ "Focus on core business logic functions first",
192
+ "Add tests for error handling and edge cases",
193
+ "Consider property-based testing for complex algorithms",
194
+ "Test integration points and configuration handling",
195
+ "Add parametrized tests for different input scenarios",
196
+ ]
197
+
198
+ from contextlib import suppress
199
+
200
+ with suppress(Exception):
201
+ # Add coverage-specific recommendations based on current state
202
+ coverage_status = self.coverage_service.get_status_report()
203
+ current_coverage = coverage_status.get("current_coverage", 0.0)
204
+
205
+ if current_coverage < 25.0:
206
+ recommendations.insert(
207
+ 0, "Start with basic import and instantiation tests"
208
+ )
209
+ elif current_coverage < 50.0:
210
+ recommendations.insert(0, "Focus on testing public method interfaces")
211
+ elif current_coverage < 75.0:
212
+ recommendations.insert(0, "Add tests for internal helper methods")
213
+ else:
214
+ recommendations.insert(0, "Target remaining edge cases and error paths")
215
+
216
+ return recommendations
217
+
218
+
219
+ async def create_coverage_improvement_orchestrator(
220
+ project_path: Path, console: t.Any = None
221
+ ) -> CoverageImprovementOrchestrator:
222
+ """Create a coverage improvement orchestrator instance."""
223
+ return CoverageImprovementOrchestrator(project_path, console)
@@ -0,0 +1,341 @@
1
+ import typing as t
2
+ from dataclasses import dataclass
3
+ from enum import Enum
4
+ from pathlib import Path
5
+
6
+ from rich.console import Console
7
+
8
+ from crackerjack.config.hooks import HookStrategy
9
+ from crackerjack.models.protocols import OptionsProtocol
10
+
11
+
12
+ class ExecutionStrategy(str, Enum):
13
+ BATCH = "batch"
14
+ INDIVIDUAL = "individual"
15
+ ADAPTIVE = "adaptive"
16
+ SELECTIVE = "selective"
17
+
18
+
19
+ class ProgressLevel(str, Enum):
20
+ BASIC = "basic"
21
+ DETAILED = "detailed"
22
+ GRANULAR = "granular"
23
+ STREAMING = "streaming"
24
+
25
+
26
+ class StreamingMode(str, Enum):
27
+ WEBSOCKET = "websocket"
28
+ FILE = "file"
29
+ HYBRID = "hybrid"
30
+
31
+
32
+ class AICoordinationMode(str, Enum):
33
+ SINGLE_AGENT = "single - agent"
34
+ MULTI_AGENT = "multi - agent"
35
+ COORDINATOR = "coordinator"
36
+
37
+
38
+ class AIIntelligence(str, Enum):
39
+ BASIC = "basic"
40
+ ADAPTIVE = "adaptive"
41
+ LEARNING = "learning"
42
+
43
+
44
+ @dataclass
45
+ class OrchestrationConfig:
46
+ execution_strategy: ExecutionStrategy = ExecutionStrategy.BATCH
47
+ progress_level: ProgressLevel = ProgressLevel.DETAILED
48
+ streaming_mode: StreamingMode = StreamingMode.WEBSOCKET
49
+ ai_coordination_mode: AICoordinationMode = AICoordinationMode.SINGLE_AGENT
50
+ ai_intelligence: AIIntelligence = AIIntelligence.BASIC
51
+
52
+ correlation_tracking: bool = True
53
+ failure_analysis: bool = True
54
+ intelligent_retry: bool = True
55
+
56
+ max_parallel_hooks: int = 3
57
+ max_parallel_tests: int = 4
58
+ timeout_multiplier: float = 1.0
59
+
60
+ debug_level: str = "standard"
61
+ log_individual_outputs: bool = False
62
+ preserve_temp_files: bool = False
63
+
64
+ def to_dict(self) -> dict[str, t.Any]:
65
+ return {
66
+ "execution_strategy": self.execution_strategy.value,
67
+ "progress_level": self.progress_level.value,
68
+ "streaming_mode": self.streaming_mode.value,
69
+ "ai_coordination_mode": self.ai_coordination_mode.value,
70
+ "ai_intelligence": self.ai_intelligence.value,
71
+ "correlation_tracking": self.correlation_tracking,
72
+ "failure_analysis": self.failure_analysis,
73
+ "intelligent_retry": self.intelligent_retry,
74
+ "max_parallel_hooks": self.max_parallel_hooks,
75
+ "max_parallel_tests": self.max_parallel_tests,
76
+ "timeout_multiplier": self.timeout_multiplier,
77
+ "debug_level": self.debug_level,
78
+ "log_individual_outputs": self.log_individual_outputs,
79
+ "preserve_temp_files": self.preserve_temp_files,
80
+ }
81
+
82
+
83
+ class ExecutionContext:
84
+ def __init__(
85
+ self,
86
+ pkg_path: Path,
87
+ options: OptionsProtocol,
88
+ previous_failures: list[str] | None = None,
89
+ changed_files: list[Path] | None = None,
90
+ iteration_count: int = 1,
91
+ ) -> None:
92
+ self.pkg_path = pkg_path
93
+ self.options = options
94
+ self.previous_failures = previous_failures or []
95
+ self.changed_files = changed_files or []
96
+ self.iteration_count = iteration_count
97
+
98
+ self.total_python_files = len(list(pkg_path.rglob(" * .py")))
99
+ self.total_test_files = len(list(pkg_path.glob("tests / test_ * .py")))
100
+ self.has_complex_setup = self._detect_complex_setup()
101
+ self.estimated_hook_duration = self._estimate_hook_duration()
102
+
103
+ def _detect_complex_setup(self) -> bool:
104
+ complex_indicators = [
105
+ (self.pkg_path / "pyproject.toml").exists(),
106
+ (self.pkg_path / "setup.py").exists(),
107
+ (self.pkg_path / "requirements.txt").exists(),
108
+ len(list(self.pkg_path.rglob(" * .py"))) > 50,
109
+ len(list(self.pkg_path.glob("tests / test_ * .py"))) > 20,
110
+ ]
111
+ return sum(complex_indicators) >= 3
112
+
113
+ def _estimate_hook_duration(self) -> float:
114
+ base_time = 30.0
115
+ file_factor = self.total_python_files * 0.5
116
+ test_factor = self.total_test_files * 2.0
117
+
118
+ return base_time + file_factor + test_factor
119
+
120
+
121
+ class StrategySelector:
122
+ def __init__(self, console: Console) -> None:
123
+ self.console = console
124
+
125
+ def select_strategy(
126
+ self,
127
+ config: OrchestrationConfig,
128
+ context: ExecutionContext,
129
+ ) -> ExecutionStrategy:
130
+ if config.execution_strategy != ExecutionStrategy.ADAPTIVE:
131
+ return config.execution_strategy
132
+
133
+ return self._select_adaptive_strategy(context)
134
+
135
+ def _select_adaptive_strategy(self, context: ExecutionContext) -> ExecutionStrategy:
136
+ if context.iteration_count == 1:
137
+ return ExecutionStrategy.BATCH
138
+
139
+ if len(context.previous_failures) > 5:
140
+ self.console.print(
141
+ "[yellow]🧠 Switching to individual execution due to multiple failures[/yellow]",
142
+ )
143
+ return ExecutionStrategy.INDIVIDUAL
144
+
145
+ if context.changed_files and len(context.changed_files) < 5:
146
+ self.console.print(
147
+ "[cyan]🎯 Using selective execution for targeted file changes[/cyan]",
148
+ )
149
+ return ExecutionStrategy.SELECTIVE
150
+
151
+ if context.has_complex_setup and context.iteration_count > 2:
152
+ return ExecutionStrategy.INDIVIDUAL
153
+
154
+ return ExecutionStrategy.BATCH
155
+
156
+ def select_hook_subset(
157
+ self,
158
+ strategy: HookStrategy,
159
+ execution_strategy: ExecutionStrategy,
160
+ context: ExecutionContext,
161
+ ) -> HookStrategy:
162
+ if execution_strategy == ExecutionStrategy.SELECTIVE:
163
+ return self._create_selective_strategy(strategy, context)
164
+
165
+ return strategy
166
+
167
+ def _create_selective_strategy(
168
+ self,
169
+ strategy: HookStrategy,
170
+ context: ExecutionContext,
171
+ ) -> HookStrategy:
172
+ priority_hooks = set(context.previous_failures)
173
+
174
+ if context.changed_files:
175
+ for file_path in context.changed_files:
176
+ if file_path.suffix == ".py":
177
+ priority_hooks.update(["pyright", "ruff - check", "ruff - format"])
178
+ if "test" in str(file_path):
179
+ priority_hooks.update(["pytest", "coverage"])
180
+ if str(file_path).endswith(("setup.py", "pyproject.toml")):
181
+ priority_hooks.update(["bandit", "creosote", "detect - secrets"])
182
+
183
+ selected_hooks = [
184
+ hook for hook in strategy.hooks if hook.name in priority_hooks
185
+ ]
186
+
187
+ if not selected_hooks:
188
+ selected_hooks = strategy.hooks[:3]
189
+
190
+ self.console.print(
191
+ f"[cyan]🎯 Selected {len(selected_hooks)} hooks for targeted execution: "
192
+ f"{', '.join(h.name for h in selected_hooks)}[/cyan]",
193
+ )
194
+
195
+ return HookStrategy(
196
+ name=f"{strategy.name}_selective",
197
+ hooks=selected_hooks,
198
+ timeout=strategy.timeout,
199
+ retry_policy=strategy.retry_policy,
200
+ parallel=strategy.parallel,
201
+ max_workers=min(strategy.max_workers, len(selected_hooks)),
202
+ )
203
+
204
+
205
+ class OrchestrationPlanner:
206
+ def __init__(self, console: Console) -> None:
207
+ self.console = console
208
+ self.strategy_selector = StrategySelector(console)
209
+
210
+ def create_execution_plan(
211
+ self,
212
+ config: OrchestrationConfig,
213
+ context: ExecutionContext,
214
+ hook_strategies: list[HookStrategy],
215
+ ) -> "ExecutionPlan":
216
+ execution_strategy = self.strategy_selector.select_strategy(config, context)
217
+
218
+ hook_plans = []
219
+ for strategy in hook_strategies:
220
+ selected_strategy = self.strategy_selector.select_hook_subset(
221
+ strategy,
222
+ execution_strategy,
223
+ context,
224
+ )
225
+ hook_plans.append(
226
+ {
227
+ "strategy": selected_strategy,
228
+ "execution_mode": execution_strategy,
229
+ "estimated_duration": self._estimate_strategy_duration(
230
+ selected_strategy,
231
+ ),
232
+ },
233
+ )
234
+
235
+ test_plan = self._create_test_plan(config, context, execution_strategy)
236
+
237
+ ai_plan = self._create_ai_plan(config, context)
238
+
239
+ return ExecutionPlan(
240
+ config=config,
241
+ execution_strategy=execution_strategy,
242
+ hook_plans=hook_plans,
243
+ test_plan=test_plan,
244
+ ai_plan=ai_plan,
245
+ estimated_total_duration=sum(
246
+ plan["estimated_duration"] for plan in hook_plans
247
+ )
248
+ + test_plan["estimated_duration"],
249
+ )
250
+
251
+ def _estimate_strategy_duration(self, strategy: HookStrategy) -> float:
252
+ base_time_per_hook = 10.0
253
+ return len(strategy.hooks) * base_time_per_hook
254
+
255
+ def _create_test_plan(
256
+ self,
257
+ config: OrchestrationConfig,
258
+ context: ExecutionContext,
259
+ execution_strategy: ExecutionStrategy,
260
+ ) -> dict[str, t.Any]:
261
+ if execution_strategy == ExecutionStrategy.INDIVIDUAL:
262
+ test_mode = "individual_with_progress"
263
+ elif execution_strategy == ExecutionStrategy.SELECTIVE:
264
+ test_mode = "selective"
265
+ else:
266
+ test_mode = "full_suite"
267
+
268
+ return {
269
+ "mode": test_mode,
270
+ "parallel_workers": min(
271
+ config.max_parallel_tests,
272
+ context.total_test_files,
273
+ ),
274
+ "estimated_duration": context.total_test_files * 2.0,
275
+ "progress_tracking": config.progress_level
276
+ in (ProgressLevel.DETAILED, ProgressLevel.GRANULAR),
277
+ }
278
+
279
+ def _create_ai_plan(
280
+ self,
281
+ config: OrchestrationConfig,
282
+ context: ExecutionContext,
283
+ ) -> dict[str, t.Any]:
284
+ return {
285
+ "mode": config.ai_coordination_mode,
286
+ "intelligence_level": config.ai_intelligence,
287
+ "batch_processing": True,
288
+ "correlation_tracking": config.correlation_tracking,
289
+ "failure_analysis": config.failure_analysis,
290
+ "adaptive_retry": config.intelligent_retry,
291
+ }
292
+
293
+
294
+ @dataclass
295
+ class ExecutionPlan:
296
+ config: OrchestrationConfig
297
+ execution_strategy: ExecutionStrategy
298
+ hook_plans: list[dict[str, t.Any]]
299
+ test_plan: dict[str, t.Any]
300
+ ai_plan: dict[str, t.Any]
301
+ estimated_total_duration: float
302
+
303
+ def print_plan_summary(self, console: Console) -> None:
304
+ console.print("\n" + "=" * 80)
305
+ console.print(
306
+ "[bold bright_blue]🎯 ORCHESTRATED EXECUTION PLAN[/bold bright_blue]",
307
+ )
308
+ console.print("=" * 80)
309
+
310
+ console.print(f"[bold]Strategy: [/bold] {self.execution_strategy.value}")
311
+ console.print(
312
+ f"[bold]AI Mode: [/bold] {self.config.ai_coordination_mode.value}",
313
+ )
314
+ console.print(
315
+ f"[bold]Progress Level: [/bold] {self.config.progress_level.value}",
316
+ )
317
+ console.print(
318
+ f"[bold]Estimated Duration: [/bold] {self.estimated_total_duration: .1f}s",
319
+ )
320
+
321
+ console.print("\n[bold cyan]Hook Execution: [/bold cyan]")
322
+ for i, plan in enumerate(self.hook_plans, 1):
323
+ strategy = plan["strategy"]
324
+ console.print(
325
+ f" {i}. {strategy.name} - {len(strategy.hooks)} hooks "
326
+ f"({plan['estimated_duration']: .1f}s)",
327
+ )
328
+
329
+ console.print("\n[bold green]Test Execution: [/bold green]")
330
+ console.print(f" Mode: {self.test_plan['mode']}")
331
+ console.print(f" Workers: {self.test_plan['parallel_workers']}")
332
+ console.print(f" Duration: {self.test_plan['estimated_duration']: .1f}s")
333
+
334
+ console.print("\n[bold magenta]AI Coordination: [/bold magenta]")
335
+ console.print(f" Mode: {self.ai_plan['mode'].value}")
336
+ console.print(f" Intelligence: {self.ai_plan['intelligence_level'].value}")
337
+ console.print(
338
+ f" Batch Processing: {'✅' if self.ai_plan['batch_processing'] else '❌'}",
339
+ )
340
+
341
+ console.print("=" * 80)