crackerjack 0.30.3__py3-none-any.whl → 0.31.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of crackerjack might be problematic. Click here for more details.

Files changed (155) hide show
  1. crackerjack/CLAUDE.md +1005 -0
  2. crackerjack/RULES.md +380 -0
  3. crackerjack/__init__.py +42 -13
  4. crackerjack/__main__.py +225 -299
  5. crackerjack/agents/__init__.py +41 -0
  6. crackerjack/agents/architect_agent.py +281 -0
  7. crackerjack/agents/base.py +169 -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 +652 -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 +401 -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 +561 -0
  37. crackerjack/core/proactive_workflow.py +316 -0
  38. crackerjack/core/session_coordinator.py +289 -0
  39. crackerjack/core/workflow_orchestrator.py +640 -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 +411 -0
  58. crackerjack/managers/test_command_builder.py +151 -0
  59. crackerjack/managers/test_executor.py +435 -0
  60. crackerjack/managers/test_manager.py +258 -0
  61. crackerjack/managers/test_manager_backup.py +1124 -0
  62. crackerjack/managers/test_progress.py +144 -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 +615 -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 +370 -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 +141 -0
  88. crackerjack/mcp/tools/utility_tools.py +341 -0
  89. crackerjack/mcp/tools/workflow_executor.py +360 -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/execution_strategies.py +341 -0
  107. crackerjack/orchestration/test_progress_streamer.py +636 -0
  108. crackerjack/plugins/__init__.py +15 -0
  109. crackerjack/plugins/base.py +200 -0
  110. crackerjack/plugins/hooks.py +246 -0
  111. crackerjack/plugins/loader.py +335 -0
  112. crackerjack/plugins/managers.py +259 -0
  113. crackerjack/py313.py +8 -3
  114. crackerjack/services/__init__.py +22 -0
  115. crackerjack/services/cache.py +314 -0
  116. crackerjack/services/config.py +347 -0
  117. crackerjack/services/config_integrity.py +99 -0
  118. crackerjack/services/contextual_ai_assistant.py +516 -0
  119. crackerjack/services/coverage_ratchet.py +347 -0
  120. crackerjack/services/debug.py +736 -0
  121. crackerjack/services/dependency_monitor.py +617 -0
  122. crackerjack/services/enhanced_filesystem.py +439 -0
  123. crackerjack/services/file_hasher.py +151 -0
  124. crackerjack/services/filesystem.py +395 -0
  125. crackerjack/services/git.py +165 -0
  126. crackerjack/services/health_metrics.py +611 -0
  127. crackerjack/services/initialization.py +847 -0
  128. crackerjack/services/log_manager.py +286 -0
  129. crackerjack/services/logging.py +174 -0
  130. crackerjack/services/metrics.py +578 -0
  131. crackerjack/services/pattern_cache.py +362 -0
  132. crackerjack/services/pattern_detector.py +515 -0
  133. crackerjack/services/performance_benchmarks.py +653 -0
  134. crackerjack/services/security.py +163 -0
  135. crackerjack/services/server_manager.py +234 -0
  136. crackerjack/services/smart_scheduling.py +144 -0
  137. crackerjack/services/tool_version_service.py +61 -0
  138. crackerjack/services/unified_config.py +437 -0
  139. crackerjack/services/version_checker.py +248 -0
  140. crackerjack/slash_commands/__init__.py +14 -0
  141. crackerjack/slash_commands/init.md +122 -0
  142. crackerjack/slash_commands/run.md +163 -0
  143. crackerjack/slash_commands/status.md +127 -0
  144. crackerjack-0.31.4.dist-info/METADATA +742 -0
  145. crackerjack-0.31.4.dist-info/RECORD +148 -0
  146. crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
  147. crackerjack/.gitignore +0 -34
  148. crackerjack/.libcst.codemod.yaml +0 -18
  149. crackerjack/.pdm.toml +0 -1
  150. crackerjack/crackerjack.py +0 -3805
  151. crackerjack/pyproject.toml +0 -286
  152. crackerjack-0.30.3.dist-info/METADATA +0 -1290
  153. crackerjack-0.30.3.dist-info/RECORD +0 -16
  154. {crackerjack-0.30.3.dist-info → crackerjack-0.31.4.dist-info}/WHEEL +0 -0
  155. {crackerjack-0.30.3.dist-info → crackerjack-0.31.4.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)