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.
- crackerjack/CLAUDE.md +1005 -0
- crackerjack/RULES.md +380 -0
- crackerjack/__init__.py +42 -13
- crackerjack/__main__.py +227 -299
- crackerjack/agents/__init__.py +41 -0
- crackerjack/agents/architect_agent.py +281 -0
- crackerjack/agents/base.py +170 -0
- crackerjack/agents/coordinator.py +512 -0
- crackerjack/agents/documentation_agent.py +498 -0
- crackerjack/agents/dry_agent.py +388 -0
- crackerjack/agents/formatting_agent.py +245 -0
- crackerjack/agents/import_optimization_agent.py +281 -0
- crackerjack/agents/performance_agent.py +669 -0
- crackerjack/agents/proactive_agent.py +104 -0
- crackerjack/agents/refactoring_agent.py +788 -0
- crackerjack/agents/security_agent.py +529 -0
- crackerjack/agents/test_creation_agent.py +657 -0
- crackerjack/agents/test_specialist_agent.py +486 -0
- crackerjack/agents/tracker.py +212 -0
- crackerjack/api.py +560 -0
- crackerjack/cli/__init__.py +24 -0
- crackerjack/cli/facade.py +104 -0
- crackerjack/cli/handlers.py +267 -0
- crackerjack/cli/interactive.py +471 -0
- crackerjack/cli/options.py +409 -0
- crackerjack/cli/utils.py +18 -0
- crackerjack/code_cleaner.py +618 -928
- crackerjack/config/__init__.py +19 -0
- crackerjack/config/hooks.py +218 -0
- crackerjack/core/__init__.py +0 -0
- crackerjack/core/async_workflow_orchestrator.py +406 -0
- crackerjack/core/autofix_coordinator.py +200 -0
- crackerjack/core/container.py +104 -0
- crackerjack/core/enhanced_container.py +542 -0
- crackerjack/core/performance.py +243 -0
- crackerjack/core/phase_coordinator.py +585 -0
- crackerjack/core/proactive_workflow.py +316 -0
- crackerjack/core/session_coordinator.py +289 -0
- crackerjack/core/workflow_orchestrator.py +826 -0
- crackerjack/dynamic_config.py +94 -103
- crackerjack/errors.py +263 -41
- crackerjack/executors/__init__.py +11 -0
- crackerjack/executors/async_hook_executor.py +431 -0
- crackerjack/executors/cached_hook_executor.py +242 -0
- crackerjack/executors/hook_executor.py +345 -0
- crackerjack/executors/individual_hook_executor.py +669 -0
- crackerjack/intelligence/__init__.py +44 -0
- crackerjack/intelligence/adaptive_learning.py +751 -0
- crackerjack/intelligence/agent_orchestrator.py +551 -0
- crackerjack/intelligence/agent_registry.py +414 -0
- crackerjack/intelligence/agent_selector.py +502 -0
- crackerjack/intelligence/integration.py +290 -0
- crackerjack/interactive.py +576 -315
- crackerjack/managers/__init__.py +11 -0
- crackerjack/managers/async_hook_manager.py +135 -0
- crackerjack/managers/hook_manager.py +137 -0
- crackerjack/managers/publish_manager.py +433 -0
- crackerjack/managers/test_command_builder.py +151 -0
- crackerjack/managers/test_executor.py +443 -0
- crackerjack/managers/test_manager.py +258 -0
- crackerjack/managers/test_manager_backup.py +1124 -0
- crackerjack/managers/test_progress.py +114 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +336 -0
- crackerjack/mcp/client_runner.py +104 -0
- crackerjack/mcp/context.py +621 -0
- crackerjack/mcp/dashboard.py +636 -0
- crackerjack/mcp/enhanced_progress_monitor.py +479 -0
- crackerjack/mcp/file_monitor.py +336 -0
- crackerjack/mcp/progress_components.py +569 -0
- crackerjack/mcp/progress_monitor.py +949 -0
- crackerjack/mcp/rate_limiter.py +332 -0
- crackerjack/mcp/server.py +22 -0
- crackerjack/mcp/server_core.py +244 -0
- crackerjack/mcp/service_watchdog.py +501 -0
- crackerjack/mcp/state.py +395 -0
- crackerjack/mcp/task_manager.py +257 -0
- crackerjack/mcp/tools/__init__.py +17 -0
- crackerjack/mcp/tools/core_tools.py +249 -0
- crackerjack/mcp/tools/error_analyzer.py +308 -0
- crackerjack/mcp/tools/execution_tools.py +372 -0
- crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
- crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
- crackerjack/mcp/tools/intelligence_tools.py +314 -0
- crackerjack/mcp/tools/monitoring_tools.py +502 -0
- crackerjack/mcp/tools/proactive_tools.py +384 -0
- crackerjack/mcp/tools/progress_tools.py +217 -0
- crackerjack/mcp/tools/utility_tools.py +341 -0
- crackerjack/mcp/tools/workflow_executor.py +565 -0
- crackerjack/mcp/websocket/__init__.py +14 -0
- crackerjack/mcp/websocket/app.py +39 -0
- crackerjack/mcp/websocket/endpoints.py +559 -0
- crackerjack/mcp/websocket/jobs.py +253 -0
- crackerjack/mcp/websocket/server.py +116 -0
- crackerjack/mcp/websocket/websocket_handler.py +78 -0
- crackerjack/mcp/websocket_server.py +10 -0
- crackerjack/models/__init__.py +31 -0
- crackerjack/models/config.py +93 -0
- crackerjack/models/config_adapter.py +230 -0
- crackerjack/models/protocols.py +118 -0
- crackerjack/models/task.py +154 -0
- crackerjack/monitoring/ai_agent_watchdog.py +450 -0
- crackerjack/monitoring/regression_prevention.py +638 -0
- crackerjack/orchestration/__init__.py +0 -0
- crackerjack/orchestration/advanced_orchestrator.py +970 -0
- crackerjack/orchestration/coverage_improvement.py +223 -0
- crackerjack/orchestration/execution_strategies.py +341 -0
- crackerjack/orchestration/test_progress_streamer.py +636 -0
- crackerjack/plugins/__init__.py +15 -0
- crackerjack/plugins/base.py +200 -0
- crackerjack/plugins/hooks.py +246 -0
- crackerjack/plugins/loader.py +335 -0
- crackerjack/plugins/managers.py +259 -0
- crackerjack/py313.py +8 -3
- crackerjack/services/__init__.py +22 -0
- crackerjack/services/cache.py +314 -0
- crackerjack/services/config.py +358 -0
- crackerjack/services/config_integrity.py +99 -0
- crackerjack/services/contextual_ai_assistant.py +516 -0
- crackerjack/services/coverage_ratchet.py +356 -0
- crackerjack/services/debug.py +736 -0
- crackerjack/services/dependency_monitor.py +617 -0
- crackerjack/services/enhanced_filesystem.py +439 -0
- crackerjack/services/file_hasher.py +151 -0
- crackerjack/services/filesystem.py +421 -0
- crackerjack/services/git.py +176 -0
- crackerjack/services/health_metrics.py +611 -0
- crackerjack/services/initialization.py +873 -0
- crackerjack/services/log_manager.py +286 -0
- crackerjack/services/logging.py +174 -0
- crackerjack/services/metrics.py +578 -0
- crackerjack/services/pattern_cache.py +362 -0
- crackerjack/services/pattern_detector.py +515 -0
- crackerjack/services/performance_benchmarks.py +653 -0
- crackerjack/services/security.py +163 -0
- crackerjack/services/server_manager.py +234 -0
- crackerjack/services/smart_scheduling.py +144 -0
- crackerjack/services/tool_version_service.py +61 -0
- crackerjack/services/unified_config.py +437 -0
- crackerjack/services/version_checker.py +248 -0
- crackerjack/slash_commands/__init__.py +14 -0
- crackerjack/slash_commands/init.md +122 -0
- crackerjack/slash_commands/run.md +163 -0
- crackerjack/slash_commands/status.md +127 -0
- crackerjack-0.31.7.dist-info/METADATA +742 -0
- crackerjack-0.31.7.dist-info/RECORD +149 -0
- crackerjack-0.31.7.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -34
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/crackerjack.py +0 -3805
- crackerjack/pyproject.toml +0 -286
- crackerjack-0.30.3.dist-info/METADATA +0 -1290
- crackerjack-0.30.3.dist-info/RECORD +0 -16
- {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/WHEEL +0 -0
- {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)
|