crackerjack 0.32.0__py3-none-any.whl → 0.33.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/core/enhanced_container.py +67 -0
- crackerjack/core/phase_coordinator.py +183 -44
- crackerjack/core/workflow_orchestrator.py +459 -138
- crackerjack/managers/publish_manager.py +22 -5
- crackerjack/managers/test_command_builder.py +4 -2
- crackerjack/managers/test_manager.py +15 -4
- crackerjack/mcp/server_core.py +162 -34
- crackerjack/mcp/tools/core_tools.py +1 -1
- crackerjack/mcp/tools/execution_tools.py +8 -3
- crackerjack/mixins/__init__.py +5 -0
- crackerjack/mixins/error_handling.py +214 -0
- crackerjack/models/config.py +9 -0
- crackerjack/models/protocols.py +69 -0
- crackerjack/models/task.py +3 -0
- crackerjack/security/__init__.py +1 -1
- crackerjack/security/audit.py +92 -78
- crackerjack/services/config.py +3 -2
- crackerjack/services/config_merge.py +11 -5
- crackerjack/services/coverage_ratchet.py +22 -0
- crackerjack/services/git.py +37 -24
- crackerjack/services/initialization.py +25 -9
- crackerjack/services/memory_optimizer.py +477 -0
- crackerjack/services/parallel_executor.py +474 -0
- crackerjack/services/performance_benchmarks.py +292 -577
- crackerjack/services/performance_cache.py +443 -0
- crackerjack/services/performance_monitor.py +633 -0
- crackerjack/services/security.py +63 -0
- crackerjack/services/security_logger.py +9 -1
- crackerjack/services/terminal_utils.py +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.0.dist-info}/METADATA +2 -2
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.0.dist-info}/RECORD +34 -27
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.0.dist-info}/WHEEL +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.0.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import time
|
|
2
3
|
import typing as t
|
|
3
4
|
from pathlib import Path
|
|
@@ -13,6 +14,12 @@ from crackerjack.services.logging import (
|
|
|
13
14
|
get_logger,
|
|
14
15
|
setup_structured_logging,
|
|
15
16
|
)
|
|
17
|
+
from crackerjack.services.memory_optimizer import get_memory_optimizer, memory_optimized
|
|
18
|
+
from crackerjack.services.performance_cache import get_performance_cache
|
|
19
|
+
from crackerjack.services.performance_monitor import (
|
|
20
|
+
get_performance_monitor,
|
|
21
|
+
phase_monitor,
|
|
22
|
+
)
|
|
16
23
|
|
|
17
24
|
from .phase_coordinator import PhaseCoordinator
|
|
18
25
|
from .session_coordinator import SessionCoordinator
|
|
@@ -45,6 +52,11 @@ class WorkflowPipeline:
|
|
|
45
52
|
self.logger = get_logger("crackerjack.pipeline")
|
|
46
53
|
self._debugger = None
|
|
47
54
|
|
|
55
|
+
# Performance optimization services
|
|
56
|
+
self._performance_monitor = get_performance_monitor()
|
|
57
|
+
self._memory_optimizer = get_memory_optimizer()
|
|
58
|
+
self._cache = get_performance_cache()
|
|
59
|
+
|
|
48
60
|
@property
|
|
49
61
|
def debugger(self):
|
|
50
62
|
if self._debugger is None:
|
|
@@ -56,7 +68,16 @@ class WorkflowPipeline:
|
|
|
56
68
|
|
|
57
69
|
return os.environ.get("AI_AGENT_DEBUG", "0") == "1"
|
|
58
70
|
|
|
71
|
+
@memory_optimized
|
|
59
72
|
async def run_complete_workflow(self, options: OptionsProtocol) -> bool:
|
|
73
|
+
workflow_id = f"workflow_{int(time.time())}"
|
|
74
|
+
|
|
75
|
+
# Start performance monitoring
|
|
76
|
+
self._performance_monitor.start_workflow(workflow_id)
|
|
77
|
+
|
|
78
|
+
# Start cache service if not already running
|
|
79
|
+
await self._cache.start()
|
|
80
|
+
|
|
60
81
|
with LoggingContext(
|
|
61
82
|
"workflow_execution",
|
|
62
83
|
testing=getattr(options, "test", False),
|
|
@@ -66,17 +87,34 @@ class WorkflowPipeline:
|
|
|
66
87
|
self._initialize_workflow_session(options)
|
|
67
88
|
|
|
68
89
|
try:
|
|
69
|
-
success = await self._execute_workflow_with_timing(
|
|
90
|
+
success = await self._execute_workflow_with_timing(
|
|
91
|
+
options, start_time, workflow_id
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Finalize performance monitoring
|
|
95
|
+
workflow_perf = self._performance_monitor.end_workflow(
|
|
96
|
+
workflow_id, success
|
|
97
|
+
)
|
|
98
|
+
self.logger.info(
|
|
99
|
+
f"Workflow performance: {workflow_perf.performance_score:.1f} score, "
|
|
100
|
+
f"{workflow_perf.total_duration_seconds:.2f}s duration"
|
|
101
|
+
)
|
|
102
|
+
|
|
70
103
|
return success
|
|
71
104
|
|
|
72
105
|
except KeyboardInterrupt:
|
|
106
|
+
self._performance_monitor.end_workflow(workflow_id, False)
|
|
73
107
|
return self._handle_user_interruption()
|
|
74
108
|
|
|
75
109
|
except Exception as e:
|
|
110
|
+
self._performance_monitor.end_workflow(workflow_id, False)
|
|
76
111
|
return self._handle_workflow_exception(e)
|
|
77
112
|
|
|
78
113
|
finally:
|
|
79
114
|
self.session.cleanup_resources()
|
|
115
|
+
# Optimize memory after workflow completion
|
|
116
|
+
self._memory_optimizer.optimize_memory()
|
|
117
|
+
await self._cache.stop()
|
|
80
118
|
|
|
81
119
|
def _initialize_workflow_session(self, options: OptionsProtocol) -> None:
|
|
82
120
|
self.session.initialize_session_tracking(options)
|
|
@@ -113,9 +151,9 @@ class WorkflowPipeline:
|
|
|
113
151
|
)
|
|
114
152
|
|
|
115
153
|
async def _execute_workflow_with_timing(
|
|
116
|
-
self, options: OptionsProtocol, start_time: float
|
|
154
|
+
self, options: OptionsProtocol, start_time: float, workflow_id: str
|
|
117
155
|
) -> bool:
|
|
118
|
-
success = await self._execute_workflow_phases(options)
|
|
156
|
+
success = await self._execute_workflow_phases(options, workflow_id)
|
|
119
157
|
self.session.finalize_session(start_time, success)
|
|
120
158
|
|
|
121
159
|
duration = time.time() - start_time
|
|
@@ -161,62 +199,144 @@ class WorkflowPipeline:
|
|
|
161
199
|
)
|
|
162
200
|
return False
|
|
163
201
|
|
|
164
|
-
async def _execute_workflow_phases(
|
|
202
|
+
async def _execute_workflow_phases(
|
|
203
|
+
self, options: OptionsProtocol, workflow_id: str
|
|
204
|
+
) -> bool:
|
|
205
|
+
"""Execute all workflow phases with proper security gates and performance monitoring."""
|
|
165
206
|
success = True
|
|
166
|
-
self.phases.run_configuration_phase(options)
|
|
167
207
|
|
|
168
|
-
#
|
|
169
|
-
|
|
170
|
-
|
|
208
|
+
# Configuration phase with monitoring
|
|
209
|
+
with phase_monitor(workflow_id, "configuration"):
|
|
210
|
+
config_success = self.phases.run_configuration_phase(options)
|
|
211
|
+
success = success and config_success
|
|
212
|
+
|
|
213
|
+
# Execute quality phase (includes testing and comprehensive checks)
|
|
214
|
+
quality_success = await self._execute_quality_phase(options, workflow_id)
|
|
171
215
|
if not quality_success:
|
|
172
216
|
success = False
|
|
173
|
-
#
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
# Only exit early if no publishing/commit operations are requested
|
|
177
|
-
return False
|
|
217
|
+
# For publishing workflows, enforce security gates
|
|
218
|
+
if self._is_publishing_workflow(options):
|
|
219
|
+
return False # Exit early - publishing requires ALL quality checks
|
|
178
220
|
|
|
179
|
-
if
|
|
221
|
+
# Execute publishing workflow if requested
|
|
222
|
+
if not await self._execute_publishing_workflow(options, workflow_id):
|
|
180
223
|
success = False
|
|
181
|
-
self.session.fail_task("workflow", "Publishing failed")
|
|
182
224
|
return False
|
|
183
|
-
|
|
225
|
+
|
|
226
|
+
# Execute commit workflow if requested
|
|
227
|
+
if not await self._execute_commit_workflow(options, workflow_id):
|
|
184
228
|
success = False
|
|
185
229
|
|
|
186
230
|
return success
|
|
187
231
|
|
|
188
|
-
|
|
232
|
+
def _is_publishing_workflow(self, options: OptionsProtocol) -> bool:
|
|
233
|
+
"""Check if this is a publishing workflow that requires strict security gates."""
|
|
234
|
+
return bool(options.publish or options.all or options.commit)
|
|
235
|
+
|
|
236
|
+
async def _execute_publishing_workflow(
|
|
237
|
+
self, options: OptionsProtocol, workflow_id: str
|
|
238
|
+
) -> bool:
|
|
239
|
+
"""Execute publishing workflow with proper error handling and monitoring."""
|
|
240
|
+
if not options.publish and not options.all:
|
|
241
|
+
return True
|
|
242
|
+
|
|
243
|
+
with phase_monitor(workflow_id, "publishing"):
|
|
244
|
+
if not self.phases.run_publishing_phase(options):
|
|
245
|
+
self.session.fail_task("workflow", "Publishing failed")
|
|
246
|
+
return False
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
async def _execute_commit_workflow(
|
|
250
|
+
self, options: OptionsProtocol, workflow_id: str
|
|
251
|
+
) -> bool:
|
|
252
|
+
"""Execute commit workflow with proper error handling and monitoring."""
|
|
253
|
+
if not options.commit:
|
|
254
|
+
return True
|
|
255
|
+
|
|
256
|
+
with phase_monitor(workflow_id, "commit"):
|
|
257
|
+
if not self.phases.run_commit_phase(options):
|
|
258
|
+
return False
|
|
259
|
+
return True
|
|
260
|
+
|
|
261
|
+
async def _execute_quality_phase(
|
|
262
|
+
self, options: OptionsProtocol, workflow_id: str
|
|
263
|
+
) -> bool:
|
|
189
264
|
if hasattr(options, "fast") and options.fast:
|
|
190
|
-
return self.
|
|
265
|
+
return await self._run_fast_hooks_phase_monitored(options, workflow_id)
|
|
191
266
|
if hasattr(options, "comp") and options.comp:
|
|
192
|
-
return self.
|
|
267
|
+
return await self._run_comprehensive_hooks_phase_monitored(
|
|
268
|
+
options, workflow_id
|
|
269
|
+
)
|
|
193
270
|
if getattr(options, "test", False):
|
|
194
|
-
return await self._execute_test_workflow(options)
|
|
195
|
-
return self.
|
|
271
|
+
return await self._execute_test_workflow(options, workflow_id)
|
|
272
|
+
return await self._execute_standard_hooks_workflow_monitored(
|
|
273
|
+
options, workflow_id
|
|
274
|
+
)
|
|
196
275
|
|
|
197
|
-
async def _execute_test_workflow(
|
|
276
|
+
async def _execute_test_workflow(
|
|
277
|
+
self, options: OptionsProtocol, workflow_id: str
|
|
278
|
+
) -> bool:
|
|
198
279
|
iteration = self._start_iteration_tracking(options)
|
|
199
280
|
|
|
200
|
-
|
|
281
|
+
# Execute initial phases (fast hooks + optional cleaning)
|
|
282
|
+
if not await self._execute_initial_phases(options, workflow_id, iteration):
|
|
201
283
|
return False
|
|
202
284
|
|
|
203
|
-
# Run
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
285
|
+
# Run main quality phases
|
|
286
|
+
(
|
|
287
|
+
testing_passed,
|
|
288
|
+
comprehensive_passed,
|
|
289
|
+
) = await self._run_main_quality_phases_async(options, workflow_id)
|
|
290
|
+
|
|
291
|
+
# Handle workflow completion based on agent mode
|
|
292
|
+
return await self._handle_workflow_completion(
|
|
293
|
+
options, iteration, testing_passed, comprehensive_passed, workflow_id
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
async def _execute_initial_phases(
|
|
297
|
+
self, options: OptionsProtocol, workflow_id: str, iteration: int
|
|
298
|
+
) -> bool:
|
|
299
|
+
"""Execute fast hooks and optional code cleaning phases."""
|
|
300
|
+
# Fast hooks with performance monitoring
|
|
301
|
+
with phase_monitor(workflow_id, "fast_hooks") as monitor:
|
|
302
|
+
if not await self._run_initial_fast_hooks_async(
|
|
303
|
+
options, iteration, monitor
|
|
304
|
+
):
|
|
209
305
|
return False
|
|
210
|
-
self._mark_code_cleaning_complete()
|
|
211
306
|
|
|
212
|
-
|
|
307
|
+
# Run code cleaning if enabled
|
|
308
|
+
return self._execute_optional_cleaning_phase(options)
|
|
309
|
+
|
|
310
|
+
def _execute_optional_cleaning_phase(self, options: OptionsProtocol) -> bool:
|
|
311
|
+
"""Execute code cleaning phase if enabled."""
|
|
312
|
+
if not getattr(options, "clean", False):
|
|
313
|
+
return True
|
|
314
|
+
|
|
315
|
+
if not self._run_code_cleaning_phase(options):
|
|
316
|
+
return False
|
|
317
|
+
|
|
318
|
+
# Run fast hooks again after cleaning for sanity check
|
|
319
|
+
if not self._run_post_cleaning_fast_hooks(options):
|
|
320
|
+
return False
|
|
321
|
+
|
|
322
|
+
self._mark_code_cleaning_complete()
|
|
323
|
+
return True
|
|
213
324
|
|
|
325
|
+
async def _handle_workflow_completion(
|
|
326
|
+
self,
|
|
327
|
+
options: OptionsProtocol,
|
|
328
|
+
iteration: int,
|
|
329
|
+
testing_passed: bool,
|
|
330
|
+
comprehensive_passed: bool,
|
|
331
|
+
workflow_id: str = "unknown",
|
|
332
|
+
) -> bool:
|
|
333
|
+
"""Handle workflow completion based on agent mode."""
|
|
214
334
|
if options.ai_agent:
|
|
215
335
|
return await self._handle_ai_agent_workflow(
|
|
216
|
-
options, iteration, testing_passed, comprehensive_passed
|
|
336
|
+
options, iteration, testing_passed, comprehensive_passed, workflow_id
|
|
217
337
|
)
|
|
218
338
|
|
|
219
|
-
return self._handle_standard_workflow(
|
|
339
|
+
return await self._handle_standard_workflow(
|
|
220
340
|
options, iteration, testing_passed, comprehensive_passed
|
|
221
341
|
)
|
|
222
342
|
|
|
@@ -234,9 +354,38 @@ class WorkflowPipeline:
|
|
|
234
354
|
return False
|
|
235
355
|
return True
|
|
236
356
|
|
|
237
|
-
def
|
|
238
|
-
|
|
239
|
-
|
|
357
|
+
async def _run_main_quality_phases_async(
|
|
358
|
+
self, options: OptionsProtocol, workflow_id: str
|
|
359
|
+
) -> tuple[bool, bool]:
|
|
360
|
+
# Run testing and comprehensive phases in parallel where possible
|
|
361
|
+
testing_task = asyncio.create_task(
|
|
362
|
+
self._run_testing_phase_async(options, workflow_id)
|
|
363
|
+
)
|
|
364
|
+
comprehensive_task = asyncio.create_task(
|
|
365
|
+
self._run_comprehensive_hooks_phase_monitored(options, workflow_id)
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
results = await asyncio.gather(
|
|
369
|
+
testing_task, comprehensive_task, return_exceptions=True
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# Handle exceptions and ensure boolean types
|
|
373
|
+
testing_result, comprehensive_result = results
|
|
374
|
+
|
|
375
|
+
if isinstance(testing_result, Exception):
|
|
376
|
+
self.logger.error(f"Testing phase failed with exception: {testing_result}")
|
|
377
|
+
testing_passed = False
|
|
378
|
+
else:
|
|
379
|
+
testing_passed = bool(testing_result)
|
|
380
|
+
|
|
381
|
+
if isinstance(comprehensive_result, Exception):
|
|
382
|
+
self.logger.error(
|
|
383
|
+
f"Comprehensive hooks failed with exception: {comprehensive_result}"
|
|
384
|
+
)
|
|
385
|
+
comprehensive_passed = False
|
|
386
|
+
else:
|
|
387
|
+
comprehensive_passed = bool(comprehensive_result)
|
|
388
|
+
|
|
240
389
|
return testing_passed, comprehensive_passed
|
|
241
390
|
|
|
242
391
|
async def _handle_ai_agent_workflow(
|
|
@@ -245,53 +394,90 @@ class WorkflowPipeline:
|
|
|
245
394
|
iteration: int,
|
|
246
395
|
testing_passed: bool,
|
|
247
396
|
comprehensive_passed: bool,
|
|
397
|
+
workflow_id: str = "unknown",
|
|
248
398
|
) -> bool:
|
|
249
|
-
#
|
|
399
|
+
# Handle security gates first
|
|
400
|
+
if not await self._process_security_gates(options):
|
|
401
|
+
return False
|
|
402
|
+
|
|
403
|
+
# Determine if AI fixing is needed
|
|
404
|
+
needs_ai_fixing = self._determine_ai_fixing_needed(
|
|
405
|
+
testing_passed, comprehensive_passed, bool(options.publish or options.all)
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
if needs_ai_fixing:
|
|
409
|
+
return await self._execute_ai_fixing_workflow(options, iteration)
|
|
410
|
+
|
|
411
|
+
# Handle success case without AI fixing
|
|
412
|
+
return self._finalize_ai_workflow_success(
|
|
413
|
+
options, iteration, testing_passed, comprehensive_passed
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
async def _process_security_gates(self, options: OptionsProtocol) -> bool:
|
|
417
|
+
"""Process security gates for publishing operations."""
|
|
250
418
|
publishing_requested, security_blocks = (
|
|
251
419
|
self._check_security_gates_for_publishing(options)
|
|
252
420
|
)
|
|
253
421
|
|
|
254
|
-
if publishing_requested and security_blocks:
|
|
255
|
-
|
|
256
|
-
security_fix_result = await self._handle_security_gate_failure(
|
|
257
|
-
options, allow_ai_fixing=True
|
|
258
|
-
)
|
|
259
|
-
if not security_fix_result:
|
|
260
|
-
return False
|
|
261
|
-
# If AI fixing resolved security issues, continue with normal flow
|
|
422
|
+
if not (publishing_requested and security_blocks):
|
|
423
|
+
return True
|
|
262
424
|
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
425
|
+
# Try AI fixing for security issues, then re-check
|
|
426
|
+
security_fix_result = await self._handle_security_gate_failure(
|
|
427
|
+
options, allow_ai_fixing=True
|
|
266
428
|
)
|
|
429
|
+
return security_fix_result
|
|
267
430
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
431
|
+
async def _execute_ai_fixing_workflow(
|
|
432
|
+
self, options: OptionsProtocol, iteration: int
|
|
433
|
+
) -> bool:
|
|
434
|
+
"""Execute AI fixing workflow and handle debugging."""
|
|
435
|
+
success = await self._run_ai_agent_fixing_phase(options)
|
|
436
|
+
if self._should_debug():
|
|
437
|
+
self.debugger.log_iteration_end(iteration, success)
|
|
438
|
+
return success
|
|
439
|
+
|
|
440
|
+
def _finalize_ai_workflow_success(
|
|
441
|
+
self,
|
|
442
|
+
options: OptionsProtocol,
|
|
443
|
+
iteration: int,
|
|
444
|
+
testing_passed: bool,
|
|
445
|
+
comprehensive_passed: bool,
|
|
446
|
+
) -> bool:
|
|
447
|
+
"""Finalize AI workflow when no fixing is needed."""
|
|
448
|
+
publishing_requested = bool(options.publish or options.all)
|
|
273
449
|
|
|
274
|
-
# Determine final success based on publishing requirements
|
|
275
450
|
final_success = self._determine_workflow_success(
|
|
276
|
-
testing_passed,
|
|
277
|
-
comprehensive_passed,
|
|
278
|
-
publishing_requested,
|
|
279
|
-
workflow_type="ai",
|
|
451
|
+
testing_passed, comprehensive_passed, publishing_requested
|
|
280
452
|
)
|
|
281
453
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
and final_success
|
|
286
|
-
and not (testing_passed and comprehensive_passed)
|
|
287
|
-
):
|
|
288
|
-
self._show_security_audit_warning()
|
|
454
|
+
self._show_partial_success_warning_if_needed(
|
|
455
|
+
publishing_requested, final_success, testing_passed, comprehensive_passed
|
|
456
|
+
)
|
|
289
457
|
|
|
290
458
|
if self._should_debug():
|
|
291
459
|
self.debugger.log_iteration_end(iteration, final_success)
|
|
460
|
+
|
|
292
461
|
return final_success
|
|
293
462
|
|
|
294
|
-
def
|
|
463
|
+
def _show_partial_success_warning_if_needed(
|
|
464
|
+
self,
|
|
465
|
+
publishing_requested: bool,
|
|
466
|
+
final_success: bool,
|
|
467
|
+
testing_passed: bool,
|
|
468
|
+
comprehensive_passed: bool,
|
|
469
|
+
) -> None:
|
|
470
|
+
"""Show security audit warning for partial success in publishing workflows."""
|
|
471
|
+
should_show_warning = (
|
|
472
|
+
publishing_requested
|
|
473
|
+
and final_success
|
|
474
|
+
and not (testing_passed and comprehensive_passed)
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
if should_show_warning:
|
|
478
|
+
self._show_security_audit_warning()
|
|
479
|
+
|
|
480
|
+
async def _handle_standard_workflow(
|
|
295
481
|
self,
|
|
296
482
|
options: OptionsProtocol,
|
|
297
483
|
iteration: int,
|
|
@@ -305,14 +491,13 @@ class WorkflowPipeline:
|
|
|
305
491
|
|
|
306
492
|
if publishing_requested and security_blocks:
|
|
307
493
|
# Standard workflow cannot bypass security gates
|
|
308
|
-
return self._handle_security_gate_failure(options
|
|
494
|
+
return await self._handle_security_gate_failure(options)
|
|
309
495
|
|
|
310
496
|
# Determine success based on publishing requirements
|
|
311
497
|
success = self._determine_workflow_success(
|
|
312
498
|
testing_passed,
|
|
313
499
|
comprehensive_passed,
|
|
314
500
|
publishing_requested,
|
|
315
|
-
workflow_type="standard",
|
|
316
501
|
)
|
|
317
502
|
|
|
318
503
|
# Show security audit warning for partial success in publishing workflows
|
|
@@ -324,7 +509,7 @@ class WorkflowPipeline:
|
|
|
324
509
|
self._show_security_audit_warning()
|
|
325
510
|
elif publishing_requested and not success:
|
|
326
511
|
self.console.print(
|
|
327
|
-
"[red]❌
|
|
512
|
+
"[red]❌ Quality checks failed - cannot proceed to publishing[/red]"
|
|
328
513
|
)
|
|
329
514
|
|
|
330
515
|
# Show verbose failure details if requested
|
|
@@ -494,36 +679,67 @@ class WorkflowPipeline:
|
|
|
494
679
|
self._mcp_state_manager.update_stage_status("comprehensive", "completed")
|
|
495
680
|
|
|
496
681
|
async def _run_ai_agent_fixing_phase(self, options: OptionsProtocol) -> bool:
|
|
497
|
-
self.
|
|
498
|
-
self.logger.info("Starting AI agent fixing phase")
|
|
499
|
-
self._log_debug_phase_start()
|
|
682
|
+
self._initialize_ai_fixing_phase(options)
|
|
500
683
|
|
|
501
684
|
try:
|
|
502
|
-
#
|
|
503
|
-
|
|
504
|
-
if getattr(options, "clean", False) and not self._has_code_cleaning_run():
|
|
505
|
-
self.console.print(
|
|
506
|
-
"\n[bold yellow]🤖 AI agents recommend running code cleaning first for better results...[/bold yellow]"
|
|
507
|
-
)
|
|
508
|
-
if self._run_code_cleaning_phase(options):
|
|
509
|
-
# Run fast hooks sanity check after cleaning
|
|
510
|
-
self._run_post_cleaning_fast_hooks(options)
|
|
511
|
-
self._mark_code_cleaning_complete()
|
|
685
|
+
# Prepare environment for AI agents
|
|
686
|
+
self._prepare_ai_fixing_environment(options)
|
|
512
687
|
|
|
513
|
-
|
|
514
|
-
issues = await self.
|
|
688
|
+
# Setup coordinator and collect issues
|
|
689
|
+
agent_coordinator, issues = await self._setup_ai_fixing_workflow()
|
|
515
690
|
|
|
516
691
|
if not issues:
|
|
517
692
|
return self._handle_no_issues_found()
|
|
518
693
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
return await self._process_fix_results(options, fix_result)
|
|
694
|
+
# Execute AI fixing
|
|
695
|
+
return await self._execute_ai_fixes(options, agent_coordinator, issues)
|
|
523
696
|
|
|
524
697
|
except Exception as e:
|
|
525
698
|
return self._handle_fixing_phase_error(e)
|
|
526
699
|
|
|
700
|
+
def _initialize_ai_fixing_phase(self, options: OptionsProtocol) -> None:
|
|
701
|
+
"""Initialize the AI fixing phase with status updates and logging."""
|
|
702
|
+
self._update_mcp_status("ai_fixing", "running")
|
|
703
|
+
self.logger.info("Starting AI agent fixing phase")
|
|
704
|
+
self._log_debug_phase_start()
|
|
705
|
+
|
|
706
|
+
def _prepare_ai_fixing_environment(self, options: OptionsProtocol) -> None:
|
|
707
|
+
"""Prepare the environment for AI agents by running optional code cleaning."""
|
|
708
|
+
should_run_cleaning = (
|
|
709
|
+
getattr(options, "clean", False) and not self._has_code_cleaning_run()
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
if not should_run_cleaning:
|
|
713
|
+
return
|
|
714
|
+
|
|
715
|
+
self.console.print(
|
|
716
|
+
"\n[bold yellow]🤖 AI agents recommend running code cleaning first for better results...[/bold yellow]"
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
if self._run_code_cleaning_phase(options):
|
|
720
|
+
# Run fast hooks sanity check after cleaning
|
|
721
|
+
self._run_post_cleaning_fast_hooks(options)
|
|
722
|
+
self._mark_code_cleaning_complete()
|
|
723
|
+
|
|
724
|
+
async def _setup_ai_fixing_workflow(
|
|
725
|
+
self,
|
|
726
|
+
) -> tuple[AgentCoordinator, list[t.Any]]:
|
|
727
|
+
"""Setup agent coordinator and collect issues to fix."""
|
|
728
|
+
agent_coordinator = self._setup_agent_coordinator()
|
|
729
|
+
issues = await self._collect_issues_from_failures()
|
|
730
|
+
return agent_coordinator, issues
|
|
731
|
+
|
|
732
|
+
async def _execute_ai_fixes(
|
|
733
|
+
self,
|
|
734
|
+
options: OptionsProtocol,
|
|
735
|
+
agent_coordinator: AgentCoordinator,
|
|
736
|
+
issues: list[t.Any],
|
|
737
|
+
) -> bool:
|
|
738
|
+
"""Execute AI fixes and process results."""
|
|
739
|
+
self.logger.info(f"AI agents will attempt to fix {len(issues)} issues")
|
|
740
|
+
fix_result = await agent_coordinator.handle_issues(issues)
|
|
741
|
+
return await self._process_fix_results(options, fix_result)
|
|
742
|
+
|
|
527
743
|
def _log_debug_phase_start(self) -> None:
|
|
528
744
|
if self._should_debug():
|
|
529
745
|
self.debugger.log_workflow_phase(
|
|
@@ -1142,26 +1358,23 @@ class WorkflowPipeline:
|
|
|
1142
1358
|
) -> bool:
|
|
1143
1359
|
"""Determine if AI fixing is needed based on test results and publishing requirements."""
|
|
1144
1360
|
if publishing_requested:
|
|
1145
|
-
# For publish/commit workflows,
|
|
1146
|
-
return not testing_passed and not comprehensive_passed
|
|
1147
|
-
else:
|
|
1148
|
-
# For regular workflows, trigger AI fixing if either fails
|
|
1361
|
+
# For publish/commit workflows, trigger AI fixing if either fails (since both must pass)
|
|
1149
1362
|
return not testing_passed or not comprehensive_passed
|
|
1363
|
+
# For regular workflows, trigger AI fixing if either fails
|
|
1364
|
+
return not testing_passed or not comprehensive_passed
|
|
1150
1365
|
|
|
1151
1366
|
def _determine_workflow_success(
|
|
1152
1367
|
self,
|
|
1153
1368
|
testing_passed: bool,
|
|
1154
1369
|
comprehensive_passed: bool,
|
|
1155
1370
|
publishing_requested: bool,
|
|
1156
|
-
workflow_type: str,
|
|
1157
1371
|
) -> bool:
|
|
1158
1372
|
"""Determine workflow success based on test results and workflow type."""
|
|
1159
1373
|
if publishing_requested:
|
|
1160
|
-
# For publishing workflows,
|
|
1161
|
-
return testing_passed or comprehensive_passed
|
|
1162
|
-
else:
|
|
1163
|
-
# For regular workflows, both must pass
|
|
1374
|
+
# For publishing workflows, ALL quality checks (tests AND comprehensive hooks) must pass
|
|
1164
1375
|
return testing_passed and comprehensive_passed
|
|
1376
|
+
# For regular workflows, both must pass as well
|
|
1377
|
+
return testing_passed and comprehensive_passed
|
|
1165
1378
|
|
|
1166
1379
|
def _show_verbose_failure_details(
|
|
1167
1380
|
self, testing_passed: bool, comprehensive_passed: bool
|
|
@@ -1212,60 +1425,68 @@ class WorkflowPipeline:
|
|
|
1212
1425
|
|
|
1213
1426
|
def _get_recent_fast_hook_results(self) -> list[t.Any]:
|
|
1214
1427
|
"""Get recent fast hook results from session tracker."""
|
|
1215
|
-
results = []
|
|
1216
|
-
|
|
1217
1428
|
# Try to get results from session tracker
|
|
1218
|
-
|
|
1219
|
-
for task_id, task_data in self.session.session_tracker.tasks.items():
|
|
1220
|
-
if task_id == "fast_hooks" and hasattr(task_data, "hook_results"):
|
|
1221
|
-
results.extend(task_data.hook_results)
|
|
1429
|
+
results = self._extract_hook_results_from_session("fast_hooks")
|
|
1222
1430
|
|
|
1223
1431
|
# If no results from session, create mock failed results for critical hooks
|
|
1224
|
-
# This ensures we fail securely when we can't determine actual status
|
|
1225
1432
|
if not results:
|
|
1226
|
-
|
|
1227
|
-
for hook_name in critical_fast_hooks:
|
|
1228
|
-
# Create a mock result that appears to have failed
|
|
1229
|
-
# This will trigger security blocking if we can't determine actual status
|
|
1230
|
-
mock_result = type(
|
|
1231
|
-
"MockResult",
|
|
1232
|
-
(),
|
|
1233
|
-
{
|
|
1234
|
-
"name": hook_name,
|
|
1235
|
-
"status": "unknown", # Unknown status = fail securely
|
|
1236
|
-
"output": "Unable to determine hook status",
|
|
1237
|
-
},
|
|
1238
|
-
)()
|
|
1239
|
-
results.append(mock_result)
|
|
1433
|
+
results = self._create_mock_hook_results(["gitleaks"])
|
|
1240
1434
|
|
|
1241
1435
|
return results
|
|
1242
1436
|
|
|
1243
|
-
def
|
|
1244
|
-
"""
|
|
1437
|
+
def _extract_hook_results_from_session(self, hook_type: str) -> list[t.Any]:
|
|
1438
|
+
"""Extract hook results from session tracker for given hook type."""
|
|
1245
1439
|
results = []
|
|
1246
1440
|
|
|
1247
|
-
|
|
1248
|
-
if
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1441
|
+
session_tracker = self._get_session_tracker()
|
|
1442
|
+
if not session_tracker:
|
|
1443
|
+
return results
|
|
1444
|
+
|
|
1445
|
+
for task_id, task_data in session_tracker.tasks.items():
|
|
1446
|
+
if task_id == hook_type and hasattr(task_data, "hook_results"):
|
|
1447
|
+
if task_data.hook_results:
|
|
1253
1448
|
results.extend(task_data.hook_results)
|
|
1254
1449
|
|
|
1450
|
+
return results
|
|
1451
|
+
|
|
1452
|
+
def _get_session_tracker(self) -> t.Any | None:
|
|
1453
|
+
"""Get session tracker if available."""
|
|
1454
|
+
return (
|
|
1455
|
+
getattr(self.session, "session_tracker", None)
|
|
1456
|
+
if hasattr(self.session, "session_tracker")
|
|
1457
|
+
else None
|
|
1458
|
+
)
|
|
1459
|
+
|
|
1460
|
+
def _create_mock_hook_results(self, critical_hooks: list[str]) -> list[t.Any]:
|
|
1461
|
+
"""Create mock failed results for critical hooks to fail securely."""
|
|
1462
|
+
results = []
|
|
1463
|
+
|
|
1464
|
+
for hook_name in critical_hooks:
|
|
1465
|
+
mock_result = self._create_mock_hook_result(hook_name)
|
|
1466
|
+
results.append(mock_result)
|
|
1467
|
+
|
|
1468
|
+
return results
|
|
1469
|
+
|
|
1470
|
+
def _create_mock_hook_result(self, hook_name: str) -> t.Any:
|
|
1471
|
+
"""Create a mock result that appears to have failed for security purposes."""
|
|
1472
|
+
return type(
|
|
1473
|
+
"MockResult",
|
|
1474
|
+
(),
|
|
1475
|
+
{
|
|
1476
|
+
"name": hook_name,
|
|
1477
|
+
"status": "unknown", # Unknown status = fail securely
|
|
1478
|
+
"output": "Unable to determine hook status",
|
|
1479
|
+
},
|
|
1480
|
+
)()
|
|
1481
|
+
|
|
1482
|
+
def _get_recent_comprehensive_hook_results(self) -> list[t.Any]:
|
|
1483
|
+
"""Get recent comprehensive hook results from session tracker."""
|
|
1484
|
+
# Try to get results from session tracker
|
|
1485
|
+
results = self._extract_hook_results_from_session("comprehensive_hooks")
|
|
1486
|
+
|
|
1255
1487
|
# If no results from session, create mock failed results for critical hooks
|
|
1256
1488
|
if not results:
|
|
1257
|
-
|
|
1258
|
-
for hook_name in critical_comprehensive_hooks:
|
|
1259
|
-
mock_result = type(
|
|
1260
|
-
"MockResult",
|
|
1261
|
-
(),
|
|
1262
|
-
{
|
|
1263
|
-
"name": hook_name,
|
|
1264
|
-
"status": "unknown", # Unknown status = fail securely
|
|
1265
|
-
"output": "Unable to determine hook status",
|
|
1266
|
-
},
|
|
1267
|
-
)()
|
|
1268
|
-
results.append(mock_result)
|
|
1489
|
+
results = self._create_mock_hook_results(["bandit", "pyright"])
|
|
1269
1490
|
|
|
1270
1491
|
return results
|
|
1271
1492
|
|
|
@@ -1325,6 +1546,106 @@ class WorkflowPipeline:
|
|
|
1325
1546
|
"[yellow]⚠️ Some non-critical quality checks failed - consider reviewing before production deployment[/yellow]"
|
|
1326
1547
|
)
|
|
1327
1548
|
|
|
1549
|
+
# Performance-optimized async methods
|
|
1550
|
+
async def _run_initial_fast_hooks_async(
|
|
1551
|
+
self, options: OptionsProtocol, iteration: int, monitor: t.Any
|
|
1552
|
+
) -> bool:
|
|
1553
|
+
"""Run initial fast hooks asynchronously with monitoring."""
|
|
1554
|
+
monitor.record_sequential_op() # Fast hooks run sequentially for safety
|
|
1555
|
+
fast_hooks_passed = self._run_fast_hooks_phase(options)
|
|
1556
|
+
if not fast_hooks_passed:
|
|
1557
|
+
if options.ai_agent and self._should_debug():
|
|
1558
|
+
self.debugger.log_iteration_end(iteration, False)
|
|
1559
|
+
return False
|
|
1560
|
+
return True
|
|
1561
|
+
|
|
1562
|
+
async def _run_fast_hooks_phase_monitored(
|
|
1563
|
+
self, options: OptionsProtocol, workflow_id: str
|
|
1564
|
+
) -> bool:
|
|
1565
|
+
"""Run fast hooks phase with performance monitoring."""
|
|
1566
|
+
with phase_monitor(workflow_id, "fast_hooks") as monitor:
|
|
1567
|
+
monitor.record_sequential_op()
|
|
1568
|
+
return self._run_fast_hooks_phase(options)
|
|
1569
|
+
|
|
1570
|
+
async def _run_comprehensive_hooks_phase_monitored(
|
|
1571
|
+
self, options: OptionsProtocol, workflow_id: str
|
|
1572
|
+
) -> bool:
|
|
1573
|
+
"""Run comprehensive hooks phase with performance monitoring."""
|
|
1574
|
+
with phase_monitor(workflow_id, "comprehensive_hooks") as monitor:
|
|
1575
|
+
monitor.record_sequential_op()
|
|
1576
|
+
return self._run_comprehensive_hooks_phase(options)
|
|
1577
|
+
|
|
1578
|
+
async def _run_testing_phase_async(
|
|
1579
|
+
self, options: OptionsProtocol, workflow_id: str
|
|
1580
|
+
) -> bool:
|
|
1581
|
+
"""Run testing phase asynchronously with monitoring."""
|
|
1582
|
+
with phase_monitor(workflow_id, "testing") as monitor:
|
|
1583
|
+
monitor.record_sequential_op()
|
|
1584
|
+
return self._run_testing_phase(options)
|
|
1585
|
+
|
|
1586
|
+
async def _execute_standard_hooks_workflow_monitored(
|
|
1587
|
+
self, options: OptionsProtocol, workflow_id: str
|
|
1588
|
+
) -> bool:
|
|
1589
|
+
"""Execute standard hooks workflow with performance monitoring."""
|
|
1590
|
+
with phase_monitor(workflow_id, "hooks") as monitor:
|
|
1591
|
+
self._update_hooks_status_running()
|
|
1592
|
+
|
|
1593
|
+
# Execute fast hooks phase
|
|
1594
|
+
fast_hooks_success = self._execute_monitored_fast_hooks_phase(
|
|
1595
|
+
options, monitor
|
|
1596
|
+
)
|
|
1597
|
+
if not fast_hooks_success:
|
|
1598
|
+
self._handle_hooks_completion(False)
|
|
1599
|
+
return False
|
|
1600
|
+
|
|
1601
|
+
# Execute optional cleaning phase
|
|
1602
|
+
if not self._execute_monitored_cleaning_phase(options):
|
|
1603
|
+
self._handle_hooks_completion(False)
|
|
1604
|
+
return False
|
|
1605
|
+
|
|
1606
|
+
# Execute comprehensive hooks phase
|
|
1607
|
+
comprehensive_success = self._execute_monitored_comprehensive_phase(
|
|
1608
|
+
options, monitor
|
|
1609
|
+
)
|
|
1610
|
+
|
|
1611
|
+
# Complete workflow
|
|
1612
|
+
hooks_success = fast_hooks_success and comprehensive_success
|
|
1613
|
+
self._handle_hooks_completion(hooks_success)
|
|
1614
|
+
return hooks_success
|
|
1615
|
+
|
|
1616
|
+
def _execute_monitored_fast_hooks_phase(
|
|
1617
|
+
self, options: OptionsProtocol, monitor: t.Any
|
|
1618
|
+
) -> bool:
|
|
1619
|
+
"""Execute fast hooks phase with monitoring."""
|
|
1620
|
+
fast_hooks_success = self._run_fast_hooks_phase(options)
|
|
1621
|
+
if fast_hooks_success:
|
|
1622
|
+
monitor.record_sequential_op()
|
|
1623
|
+
return fast_hooks_success
|
|
1624
|
+
|
|
1625
|
+
def _execute_monitored_cleaning_phase(self, options: OptionsProtocol) -> bool:
|
|
1626
|
+
"""Execute optional code cleaning phase."""
|
|
1627
|
+
if not getattr(options, "clean", False):
|
|
1628
|
+
return True
|
|
1629
|
+
|
|
1630
|
+
if not self._run_code_cleaning_phase(options):
|
|
1631
|
+
return False
|
|
1632
|
+
|
|
1633
|
+
# Run fast hooks again after cleaning for sanity check
|
|
1634
|
+
if not self._run_post_cleaning_fast_hooks(options):
|
|
1635
|
+
return False
|
|
1636
|
+
|
|
1637
|
+
self._mark_code_cleaning_complete()
|
|
1638
|
+
return True
|
|
1639
|
+
|
|
1640
|
+
def _execute_monitored_comprehensive_phase(
|
|
1641
|
+
self, options: OptionsProtocol, monitor: t.Any
|
|
1642
|
+
) -> bool:
|
|
1643
|
+
"""Execute comprehensive hooks phase with monitoring."""
|
|
1644
|
+
comprehensive_success = self._run_comprehensive_hooks_phase(options)
|
|
1645
|
+
if comprehensive_success:
|
|
1646
|
+
monitor.record_sequential_op()
|
|
1647
|
+
return comprehensive_success
|
|
1648
|
+
|
|
1328
1649
|
|
|
1329
1650
|
class WorkflowOrchestrator:
|
|
1330
1651
|
def __init__(
|