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,471 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from enum import Enum, auto
|
|
3
|
+
|
|
4
|
+
from rich.box import ROUNDED
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.layout import Layout
|
|
7
|
+
from rich.live import Live
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.prompt import Confirm, Prompt
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
from rich.tree import Tree
|
|
13
|
+
|
|
14
|
+
from crackerjack.core.workflow_orchestrator import WorkflowOrchestrator
|
|
15
|
+
from crackerjack.errors import CrackerjackError, ErrorCode, handle_error
|
|
16
|
+
from crackerjack.models.protocols import OptionsProtocol
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TaskStatus(Enum):
|
|
20
|
+
PENDING = auto()
|
|
21
|
+
RUNNING = auto()
|
|
22
|
+
SUCCESS = auto()
|
|
23
|
+
FAILED = auto()
|
|
24
|
+
SKIPPED = auto()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class InteractiveTask:
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
name: str,
|
|
31
|
+
description: str,
|
|
32
|
+
phase_method: str,
|
|
33
|
+
dependencies: list["InteractiveTask"] | None = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
self.name = name
|
|
36
|
+
self.description = description
|
|
37
|
+
self.phase_method = phase_method
|
|
38
|
+
self.dependencies = dependencies or []
|
|
39
|
+
self.status = TaskStatus.PENDING
|
|
40
|
+
self.start_time: float | None = None
|
|
41
|
+
self.end_time: float | None = None
|
|
42
|
+
self.error: CrackerjackError | None = None
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def duration(self) -> float | None:
|
|
46
|
+
if self.start_time is None:
|
|
47
|
+
return None
|
|
48
|
+
end = self.end_time or time.time()
|
|
49
|
+
return end - self.start_time
|
|
50
|
+
|
|
51
|
+
def start(self) -> None:
|
|
52
|
+
self.status = TaskStatus.RUNNING
|
|
53
|
+
self.start_time = time.time()
|
|
54
|
+
|
|
55
|
+
def complete(self, success: bool = True) -> None:
|
|
56
|
+
self.end_time = time.time()
|
|
57
|
+
self.status = TaskStatus.SUCCESS if success else TaskStatus.FAILED
|
|
58
|
+
|
|
59
|
+
def skip(self) -> None:
|
|
60
|
+
self.status = TaskStatus.SKIPPED
|
|
61
|
+
|
|
62
|
+
def fail(self, error: CrackerjackError) -> None:
|
|
63
|
+
self.end_time = time.time()
|
|
64
|
+
self.status = TaskStatus.FAILED
|
|
65
|
+
self.error = error
|
|
66
|
+
|
|
67
|
+
def can_run(self) -> bool:
|
|
68
|
+
return all(
|
|
69
|
+
dep.status in (TaskStatus.SUCCESS, TaskStatus.SKIPPED)
|
|
70
|
+
for dep in self.dependencies
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def __str__(self) -> str:
|
|
74
|
+
return f"{self.name} ({self.status.name})"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class InteractiveWorkflowManager:
|
|
78
|
+
def __init__(self, console: Console, orchestrator: WorkflowOrchestrator) -> None:
|
|
79
|
+
self.console = console
|
|
80
|
+
self.orchestrator = orchestrator
|
|
81
|
+
self.tasks: dict[str, InteractiveTask] = {}
|
|
82
|
+
self.current_task: InteractiveTask | None = None
|
|
83
|
+
self.layout = Layout()
|
|
84
|
+
self._setup_layout()
|
|
85
|
+
|
|
86
|
+
def _setup_layout(self) -> None:
|
|
87
|
+
self.layout.split_column(
|
|
88
|
+
Layout(name="header", size=3),
|
|
89
|
+
Layout(name="main"),
|
|
90
|
+
Layout(name="footer", size=3),
|
|
91
|
+
)
|
|
92
|
+
self.layout["main"].split_row(
|
|
93
|
+
Layout(name="tasks", minimum_size=40),
|
|
94
|
+
Layout(name="details", ratio=2),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def setup_workflow(self, options: OptionsProtocol) -> None:
|
|
98
|
+
self.tasks.clear()
|
|
99
|
+
self._setup_cleaning_task(options)
|
|
100
|
+
self._setup_hooks_task(options)
|
|
101
|
+
self._setup_testing_task(options)
|
|
102
|
+
self._setup_publishing_task(options)
|
|
103
|
+
self._setup_commit_task(options)
|
|
104
|
+
|
|
105
|
+
def _setup_cleaning_task(self, options: OptionsProtocol) -> None:
|
|
106
|
+
if options.clean:
|
|
107
|
+
self.add_task(
|
|
108
|
+
"cleaning",
|
|
109
|
+
"Clean code (remove docstrings, comments)",
|
|
110
|
+
"run_cleaning_phase",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def _setup_hooks_task(self, options: OptionsProtocol) -> None:
|
|
114
|
+
if not options.skip_hooks:
|
|
115
|
+
deps = ["cleaning"] if options.clean else []
|
|
116
|
+
self.add_task(
|
|
117
|
+
"hooks",
|
|
118
|
+
"Run pre - commit hooks (fast + comprehensive)",
|
|
119
|
+
"run_hooks_phase",
|
|
120
|
+
dependencies=deps,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def _setup_testing_task(self, options: OptionsProtocol) -> None:
|
|
124
|
+
if options.test:
|
|
125
|
+
deps = (
|
|
126
|
+
["hooks"]
|
|
127
|
+
if not options.skip_hooks
|
|
128
|
+
else (["cleaning"] if options.clean else [])
|
|
129
|
+
)
|
|
130
|
+
self.add_task(
|
|
131
|
+
"testing",
|
|
132
|
+
"Run tests with coverage",
|
|
133
|
+
"run_testing_phase",
|
|
134
|
+
dependencies=deps,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def _setup_publishing_task(self, options: OptionsProtocol) -> None:
|
|
138
|
+
if options.publish or options.all or options.bump:
|
|
139
|
+
all_deps = self._get_publishing_dependencies()
|
|
140
|
+
self.add_task(
|
|
141
|
+
"publishing",
|
|
142
|
+
"Version bump and publish to PyPI",
|
|
143
|
+
"run_publishing_phase",
|
|
144
|
+
dependencies=all_deps,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def _setup_commit_task(self, options: OptionsProtocol) -> None:
|
|
148
|
+
if options.commit:
|
|
149
|
+
all_deps = list(self.tasks.keys())
|
|
150
|
+
self.add_task(
|
|
151
|
+
"commit",
|
|
152
|
+
"Commit changes and push to Git",
|
|
153
|
+
"run_commit_phase",
|
|
154
|
+
dependencies=all_deps[:-1] if all_deps else [],
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def _get_publishing_dependencies(self) -> list[str]:
|
|
158
|
+
if "testing" in self.tasks:
|
|
159
|
+
return ["testing"]
|
|
160
|
+
elif "hooks" in self.tasks:
|
|
161
|
+
return ["hooks"]
|
|
162
|
+
elif "cleaning" in self.tasks:
|
|
163
|
+
return ["cleaning"]
|
|
164
|
+
return []
|
|
165
|
+
|
|
166
|
+
def add_task(
|
|
167
|
+
self,
|
|
168
|
+
name: str,
|
|
169
|
+
description: str,
|
|
170
|
+
phase_method: str,
|
|
171
|
+
dependencies: list[str] | None = None,
|
|
172
|
+
) -> InteractiveTask:
|
|
173
|
+
dep_tasks: list[InteractiveTask] = []
|
|
174
|
+
if dependencies:
|
|
175
|
+
for dep_name in dependencies:
|
|
176
|
+
if dep_name not in self.tasks:
|
|
177
|
+
msg = f"Dependency task '{dep_name}' not found"
|
|
178
|
+
raise ValueError(msg)
|
|
179
|
+
dep_tasks.append(self.tasks[dep_name])
|
|
180
|
+
|
|
181
|
+
task = InteractiveTask(name, description, phase_method, dep_tasks)
|
|
182
|
+
self.tasks[name] = task
|
|
183
|
+
return task
|
|
184
|
+
|
|
185
|
+
def get_next_task(self) -> InteractiveTask | None:
|
|
186
|
+
for task in self.tasks.values():
|
|
187
|
+
if task.status == TaskStatus.PENDING and task.can_run():
|
|
188
|
+
return task
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
def execute_task(self, task: InteractiveTask, options: OptionsProtocol) -> bool:
|
|
192
|
+
self.current_task = task
|
|
193
|
+
task.start()
|
|
194
|
+
try:
|
|
195
|
+
phase_method = getattr(self.orchestrator, task.phase_method)
|
|
196
|
+
success = phase_method(options)
|
|
197
|
+
task.complete(success)
|
|
198
|
+
return success
|
|
199
|
+
except Exception as e:
|
|
200
|
+
error = CrackerjackError(
|
|
201
|
+
message=str(e),
|
|
202
|
+
error_code=ErrorCode.COMMAND_EXECUTION_ERROR,
|
|
203
|
+
)
|
|
204
|
+
task.fail(error)
|
|
205
|
+
return False
|
|
206
|
+
finally:
|
|
207
|
+
self.current_task = None
|
|
208
|
+
|
|
209
|
+
def create_task_tree(self) -> Tree:
|
|
210
|
+
tree = Tree("🔧 Workflow Tasks")
|
|
211
|
+
for task in self.tasks.values():
|
|
212
|
+
status_emoji = {
|
|
213
|
+
TaskStatus.PENDING: "⏳",
|
|
214
|
+
TaskStatus.RUNNING: "🔄",
|
|
215
|
+
TaskStatus.SUCCESS: "✅",
|
|
216
|
+
TaskStatus.FAILED: "❌",
|
|
217
|
+
TaskStatus.SKIPPED: "⏭️",
|
|
218
|
+
}
|
|
219
|
+
emoji = status_emoji.get(task.status, "❓")
|
|
220
|
+
label = f"{emoji} {task.name}"
|
|
221
|
+
if task.duration is not None:
|
|
222
|
+
label += f" ({task.duration: .1f}s)"
|
|
223
|
+
task_node = tree.add(label)
|
|
224
|
+
task_node.add(f"📝 {task.description}")
|
|
225
|
+
if task.error:
|
|
226
|
+
task_node.add(f"❌ {task.error.message}")
|
|
227
|
+
|
|
228
|
+
return tree
|
|
229
|
+
|
|
230
|
+
def create_details_panel(self) -> Panel:
|
|
231
|
+
if self.current_task is None:
|
|
232
|
+
content = Text("No task currently running", style="dim")
|
|
233
|
+
else:
|
|
234
|
+
task = self.current_task
|
|
235
|
+
content = Text()
|
|
236
|
+
content.append(f"🔄 Running: {task.name}\n", style="bold cyan")
|
|
237
|
+
content.append(f"📝 {task.description}\n")
|
|
238
|
+
if task.duration is not None:
|
|
239
|
+
content.append(f"⏱️ Duration: {task.duration: .1f}s\n")
|
|
240
|
+
if task.dependencies:
|
|
241
|
+
content.append("\n📋 Dependencies: \n", style="bold")
|
|
242
|
+
for dep in task.dependencies:
|
|
243
|
+
status_emoji = "✅" if dep.status == TaskStatus.SUCCESS else "❌"
|
|
244
|
+
content.append(f" {status_emoji} {dep.name}\n")
|
|
245
|
+
|
|
246
|
+
return Panel(content, title="Current Task", border_style="cyan")
|
|
247
|
+
|
|
248
|
+
def create_header(self, pkg_version: str) -> Panel:
|
|
249
|
+
header_text = Text()
|
|
250
|
+
header_text.append("🚀 Crackerjack Interactive Mode ", style="bold cyan")
|
|
251
|
+
header_text.append(f"v{pkg_version}", style="dim")
|
|
252
|
+
|
|
253
|
+
return Panel(header_text, style="cyan")
|
|
254
|
+
|
|
255
|
+
def create_footer(self) -> Panel:
|
|
256
|
+
footer_text = Text()
|
|
257
|
+
footer_text.append("Press ", style="dim")
|
|
258
|
+
footer_text.append("Ctrl + C", style="bold red")
|
|
259
|
+
footer_text.append(" to cancel • ", style="dim")
|
|
260
|
+
footer_text.append("Enter", style="bold green")
|
|
261
|
+
footer_text.append(" to continue", style="dim")
|
|
262
|
+
|
|
263
|
+
return Panel(footer_text, style="green")
|
|
264
|
+
|
|
265
|
+
def update_layout(self, pkg_version: str) -> None:
|
|
266
|
+
self.layout["header"].update(self.create_header(pkg_version))
|
|
267
|
+
self.layout["tasks"].update(
|
|
268
|
+
Panel(self.create_task_tree(), title="Tasks", border_style="blue"),
|
|
269
|
+
)
|
|
270
|
+
self.layout["details"].update(self.create_details_panel())
|
|
271
|
+
self.layout["footer"].update(self.create_footer())
|
|
272
|
+
|
|
273
|
+
def run_workflow(self, options: OptionsProtocol, pkg_version: str) -> bool:
|
|
274
|
+
if not self._initialize_workflow(options, pkg_version):
|
|
275
|
+
return False
|
|
276
|
+
|
|
277
|
+
with Live(self.layout) as live:
|
|
278
|
+
if not self._execute_workflow_tasks(live, options, pkg_version):
|
|
279
|
+
return False
|
|
280
|
+
|
|
281
|
+
return self._finalize_workflow()
|
|
282
|
+
|
|
283
|
+
def _initialize_workflow(self, options: OptionsProtocol, pkg_version: str) -> bool:
|
|
284
|
+
self.setup_workflow(options)
|
|
285
|
+
if not self.tasks:
|
|
286
|
+
self.console.print(
|
|
287
|
+
"[yellow]⚠️ No tasks to execute based on options[/yellow]",
|
|
288
|
+
)
|
|
289
|
+
return True
|
|
290
|
+
|
|
291
|
+
self.update_layout(pkg_version)
|
|
292
|
+
self.console.print(self.layout)
|
|
293
|
+
|
|
294
|
+
if not Confirm.ask("\n🚀 Start workflow?", default=True):
|
|
295
|
+
self.console.print("[yellow]⏹️ Workflow cancelled[/yellow]")
|
|
296
|
+
return False
|
|
297
|
+
|
|
298
|
+
return True
|
|
299
|
+
|
|
300
|
+
def _execute_workflow_tasks(
|
|
301
|
+
self,
|
|
302
|
+
live: Live,
|
|
303
|
+
options: OptionsProtocol,
|
|
304
|
+
pkg_version: str,
|
|
305
|
+
) -> bool:
|
|
306
|
+
while True:
|
|
307
|
+
next_task = self.get_next_task()
|
|
308
|
+
if next_task is None:
|
|
309
|
+
break
|
|
310
|
+
|
|
311
|
+
self.update_layout(pkg_version)
|
|
312
|
+
live.update(self.layout)
|
|
313
|
+
|
|
314
|
+
success = self.execute_task(next_task, options)
|
|
315
|
+
self.update_layout(pkg_version)
|
|
316
|
+
live.update(self.layout)
|
|
317
|
+
|
|
318
|
+
if not success and not self._handle_task_failure(live, next_task):
|
|
319
|
+
return False
|
|
320
|
+
|
|
321
|
+
return True
|
|
322
|
+
|
|
323
|
+
def _handle_task_failure(self, live: Live, failed_task: InteractiveTask) -> bool:
|
|
324
|
+
live.stop()
|
|
325
|
+
retry = Confirm.ask(
|
|
326
|
+
f"\n❌ Task '{failed_task.name}' failed. Continue anyway?",
|
|
327
|
+
default=False,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
if not retry:
|
|
331
|
+
self.console.print("[red]⏹️ Workflow stopped due to task failure[/red]")
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
failed_task.skip()
|
|
335
|
+
live.start()
|
|
336
|
+
return True
|
|
337
|
+
|
|
338
|
+
def _finalize_workflow(self) -> bool:
|
|
339
|
+
self.show_final_results()
|
|
340
|
+
|
|
341
|
+
success_count = sum(
|
|
342
|
+
1 for task in self.tasks.values() if task.status == TaskStatus.SUCCESS
|
|
343
|
+
)
|
|
344
|
+
total_tasks = len(self.tasks)
|
|
345
|
+
|
|
346
|
+
return success_count == total_tasks or all(
|
|
347
|
+
task.status in (TaskStatus.SUCCESS, TaskStatus.SKIPPED)
|
|
348
|
+
for task in self.tasks.values()
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
def show_final_results(self) -> None:
|
|
352
|
+
success_count = sum(
|
|
353
|
+
1 for task in self.tasks.values() if task.status == TaskStatus.SUCCESS
|
|
354
|
+
)
|
|
355
|
+
failed_count = sum(
|
|
356
|
+
1 for task in self.tasks.values() if task.status == TaskStatus.FAILED
|
|
357
|
+
)
|
|
358
|
+
skipped_count = sum(
|
|
359
|
+
1 for task in self.tasks.values() if task.status == TaskStatus.SKIPPED
|
|
360
|
+
)
|
|
361
|
+
table = Table(title="📊 Workflow Results", box=ROUNDED)
|
|
362
|
+
table.add_column("Task", style="bold")
|
|
363
|
+
table.add_column("Status", justify="center")
|
|
364
|
+
table.add_column("Duration", justify="right")
|
|
365
|
+
table.add_column("Details")
|
|
366
|
+
for task in self.tasks.values():
|
|
367
|
+
status_styles = {
|
|
368
|
+
TaskStatus.SUCCESS: "green",
|
|
369
|
+
TaskStatus.FAILED: "red",
|
|
370
|
+
TaskStatus.SKIPPED: "yellow",
|
|
371
|
+
TaskStatus.PENDING: "dim",
|
|
372
|
+
TaskStatus.RUNNING: "cyan",
|
|
373
|
+
}
|
|
374
|
+
status_text = task.status.name
|
|
375
|
+
style = status_styles.get(task.status, "white")
|
|
376
|
+
duration_text = f"{task.duration: .1f}s" if task.duration else " - "
|
|
377
|
+
details = task.error.message if task.error else task.description
|
|
378
|
+
table.add_row(
|
|
379
|
+
task.name,
|
|
380
|
+
f"[{style}]{status_text}[/{style}]",
|
|
381
|
+
duration_text,
|
|
382
|
+
details,
|
|
383
|
+
)
|
|
384
|
+
self.console.print("\n")
|
|
385
|
+
self.console.print(table)
|
|
386
|
+
if failed_count == 0:
|
|
387
|
+
self.console.print(
|
|
388
|
+
f"\n[bold green]🎉 Workflow completed ! {success_count} / {len(self.tasks)} tasks successful[/bold green]",
|
|
389
|
+
)
|
|
390
|
+
else:
|
|
391
|
+
self.console.print(
|
|
392
|
+
f"\n[bold yellow]⚠️ Workflow completed with issues: {failed_count} failed, {skipped_count} skipped[/bold yellow]",
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
class InteractiveCLI:
|
|
397
|
+
def __init__(self, pkg_version: str, console: Console | None = None) -> None:
|
|
398
|
+
self.pkg_version = pkg_version
|
|
399
|
+
self.console = console or Console(force_terminal=True)
|
|
400
|
+
self.orchestrator = WorkflowOrchestrator(console=self.console)
|
|
401
|
+
self.workflow_manager = InteractiveWorkflowManager(
|
|
402
|
+
self.console,
|
|
403
|
+
self.orchestrator,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
def launch(self, options: OptionsProtocol) -> None:
|
|
407
|
+
try:
|
|
408
|
+
self._show_welcome()
|
|
409
|
+
updated_options = self._get_user_preferences(options)
|
|
410
|
+
success = self.workflow_manager.run_workflow(
|
|
411
|
+
updated_options,
|
|
412
|
+
self.pkg_version,
|
|
413
|
+
)
|
|
414
|
+
if not success:
|
|
415
|
+
raise SystemExit(1)
|
|
416
|
+
except KeyboardInterrupt:
|
|
417
|
+
self.console.print("\n[yellow]⏹️ Interactive session cancelled[/yellow]")
|
|
418
|
+
raise SystemExit(130)
|
|
419
|
+
except Exception as e:
|
|
420
|
+
error = CrackerjackError(
|
|
421
|
+
message=str(e),
|
|
422
|
+
error_code=ErrorCode.UNEXPECTED_ERROR,
|
|
423
|
+
)
|
|
424
|
+
handle_error(error, self.console)
|
|
425
|
+
raise SystemExit(1)
|
|
426
|
+
|
|
427
|
+
def _show_welcome(self) -> None:
|
|
428
|
+
welcome_panel = Panel(
|
|
429
|
+
f"[bold cyan]Welcome to Crackerjack Interactive Mode ! [/bold cyan]\n\n"
|
|
430
|
+
f"Version: {self.pkg_version}\n"
|
|
431
|
+
f"This interactive interface will guide you through the crackerjack workflow\n"
|
|
432
|
+
f"with real - time feedback and customizable options.",
|
|
433
|
+
title="🚀 Crackerjack Interactive",
|
|
434
|
+
border_style="cyan",
|
|
435
|
+
)
|
|
436
|
+
self.console.print(welcome_panel)
|
|
437
|
+
self.console.print()
|
|
438
|
+
|
|
439
|
+
def _get_user_preferences(self, options: OptionsProtocol) -> OptionsProtocol:
|
|
440
|
+
self.console.print("[bold]🔧 Workflow Configuration[/bold]")
|
|
441
|
+
self.console.print("Configure your crackerjack workflow: \n")
|
|
442
|
+
updated_options = type(options)(**vars(options))
|
|
443
|
+
updated_options.clean = Confirm.ask(
|
|
444
|
+
"🧹 Clean code (remove docstrings, comments)?",
|
|
445
|
+
default=options.clean,
|
|
446
|
+
)
|
|
447
|
+
updated_options.test = Confirm.ask("🧪 Run tests?", default=options.test)
|
|
448
|
+
updated_options.commit = Confirm.ask(
|
|
449
|
+
"📝 Commit changes to git?",
|
|
450
|
+
default=options.commit,
|
|
451
|
+
)
|
|
452
|
+
if not any([options.publish, options.all, options.bump]):
|
|
453
|
+
if Confirm.ask("📦 Bump version and publish?", default=False):
|
|
454
|
+
version_type = Prompt.ask(
|
|
455
|
+
"Version bump type",
|
|
456
|
+
choices=["patch", "minor", "major", "interactive"],
|
|
457
|
+
default="patch",
|
|
458
|
+
)
|
|
459
|
+
updated_options.publish = version_type
|
|
460
|
+
if Confirm.ask("\n⚙️ Configure advanced options?", default=False):
|
|
461
|
+
updated_options.verbose = Confirm.ask(
|
|
462
|
+
"Enable verbose output?",
|
|
463
|
+
default=options.verbose,
|
|
464
|
+
)
|
|
465
|
+
self.console.print()
|
|
466
|
+
return updated_options
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def launch_interactive_cli(pkg_version: str, options: OptionsProtocol) -> None:
|
|
470
|
+
cli = InteractiveCLI(pkg_version)
|
|
471
|
+
cli.launch(options)
|