crackerjack 0.37.8__py3-none-any.whl → 0.38.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/__init__.py +1 -1
- crackerjack/api.py +1 -2
- crackerjack/core/workflow_orchestrator.py +323 -171
- crackerjack/documentation/reference_generator.py +250 -162
- crackerjack/dynamic_config.py +25 -17
- crackerjack/hooks/lsp_hook.py +39 -24
- crackerjack/interactive.py +13 -22
- crackerjack/managers/publish_manager.py +1 -1
- crackerjack/mcp/tools/execution_tools_backup.py +34 -36
- crackerjack/plugins/hooks.py +1 -1
- crackerjack/services/config_merge.py +33 -30
- crackerjack/services/secure_subprocess.py +43 -25
- crackerjack/tools/validate_regex_patterns.py +1 -0
- {crackerjack-0.37.8.dist-info → crackerjack-0.38.0.dist-info}/METADATA +2 -2
- {crackerjack-0.37.8.dist-info → crackerjack-0.38.0.dist-info}/RECORD +18 -18
- {crackerjack-0.37.8.dist-info → crackerjack-0.38.0.dist-info}/WHEEL +0 -0
- {crackerjack-0.37.8.dist-info → crackerjack-0.38.0.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.37.8.dist-info → crackerjack-0.38.0.dist-info}/licenses/LICENSE +0 -0
crackerjack/__init__.py
CHANGED
crackerjack/api.py
CHANGED
|
@@ -8,8 +8,7 @@ from rich.console import Console
|
|
|
8
8
|
from .code_cleaner import CleaningResult, CodeCleaner, PackageCleaningResult
|
|
9
9
|
from .core.workflow_orchestrator import WorkflowOrchestrator
|
|
10
10
|
from .errors import CrackerjackError, ErrorCode
|
|
11
|
-
from .interactive import InteractiveCLI
|
|
12
|
-
from .interactive import WorkflowOptions as InteractiveWorkflowOptions
|
|
11
|
+
from .interactive import InteractiveCLI, InteractiveWorkflowOptions
|
|
13
12
|
from .models.config import WorkflowOptions
|
|
14
13
|
from .services.regex_patterns import SAFE_PATTERNS
|
|
15
14
|
|
|
@@ -67,10 +67,8 @@ class WorkflowPipeline:
|
|
|
67
67
|
|
|
68
68
|
# Initialize quality intelligence for advanced decision making
|
|
69
69
|
try:
|
|
70
|
-
quality_baseline = EnhancedQualityBaselineService(
|
|
71
|
-
self._quality_intelligence = QualityIntelligenceService(
|
|
72
|
-
quality_baseline, console
|
|
73
|
-
)
|
|
70
|
+
quality_baseline = EnhancedQualityBaselineService()
|
|
71
|
+
self._quality_intelligence = QualityIntelligenceService(quality_baseline)
|
|
74
72
|
except Exception:
|
|
75
73
|
# Fallback gracefully if quality intelligence is not available
|
|
76
74
|
self._quality_intelligence = None
|
|
@@ -171,22 +169,33 @@ class WorkflowPipeline:
|
|
|
171
169
|
|
|
172
170
|
def _initialize_zuban_lsp(self, options: OptionsProtocol) -> None:
|
|
173
171
|
"""Initialize Zuban LSP server if not disabled."""
|
|
174
|
-
|
|
172
|
+
if self._should_skip_zuban_lsp(options):
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
if self._is_zuban_lsp_already_running():
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
self._start_zuban_lsp_server(options)
|
|
179
|
+
|
|
180
|
+
def _should_skip_zuban_lsp(self, options: OptionsProtocol) -> bool:
|
|
181
|
+
"""Check if Zuban LSP server should be skipped."""
|
|
175
182
|
if getattr(options, "no_zuban_lsp", False):
|
|
176
183
|
self.logger.debug("Zuban LSP server disabled by --no-zuban-lsp flag")
|
|
177
|
-
return
|
|
184
|
+
return True
|
|
178
185
|
|
|
179
|
-
# Get configuration from options (will use config system if available)
|
|
180
186
|
config = getattr(options, "zuban_lsp", None)
|
|
181
187
|
if config and not config.enabled:
|
|
182
188
|
self.logger.debug("Zuban LSP server disabled in configuration")
|
|
183
|
-
return
|
|
189
|
+
return True
|
|
184
190
|
|
|
185
191
|
if config and not config.auto_start:
|
|
186
192
|
self.logger.debug("Zuban LSP server auto-start disabled in configuration")
|
|
187
|
-
return
|
|
193
|
+
return True
|
|
194
|
+
|
|
195
|
+
return False
|
|
188
196
|
|
|
189
|
-
|
|
197
|
+
def _is_zuban_lsp_already_running(self) -> bool:
|
|
198
|
+
"""Check if LSP server is already running to avoid duplicates."""
|
|
190
199
|
from crackerjack.services.server_manager import find_zuban_lsp_processes
|
|
191
200
|
|
|
192
201
|
existing_processes = find_zuban_lsp_processes()
|
|
@@ -194,20 +203,17 @@ class WorkflowPipeline:
|
|
|
194
203
|
self.logger.debug(
|
|
195
204
|
f"Zuban LSP server already running (PID: {existing_processes[0]['pid']})"
|
|
196
205
|
)
|
|
197
|
-
return
|
|
206
|
+
return True
|
|
207
|
+
return False
|
|
198
208
|
|
|
199
|
-
|
|
209
|
+
def _start_zuban_lsp_server(self, options: OptionsProtocol) -> None:
|
|
210
|
+
"""Start the Zuban LSP server in background."""
|
|
200
211
|
try:
|
|
201
212
|
import subprocess
|
|
202
213
|
import sys
|
|
203
214
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
zuban_lsp_port = config.port
|
|
207
|
-
zuban_lsp_mode = config.mode
|
|
208
|
-
else:
|
|
209
|
-
zuban_lsp_port = getattr(options, "zuban_lsp_port", 8677)
|
|
210
|
-
zuban_lsp_mode = getattr(options, "zuban_lsp_mode", "stdio")
|
|
215
|
+
config = getattr(options, "zuban_lsp", None)
|
|
216
|
+
zuban_lsp_port, zuban_lsp_mode = self._get_zuban_lsp_config(options, config)
|
|
211
217
|
|
|
212
218
|
cmd = [
|
|
213
219
|
sys.executable,
|
|
@@ -234,6 +240,17 @@ class WorkflowPipeline:
|
|
|
234
240
|
except Exception as e:
|
|
235
241
|
self.logger.warning(f"Failed to auto-start Zuban LSP server: {e}")
|
|
236
242
|
|
|
243
|
+
def _get_zuban_lsp_config(
|
|
244
|
+
self, options: OptionsProtocol, config: any
|
|
245
|
+
) -> tuple[int, str]:
|
|
246
|
+
"""Get Zuban LSP configuration values."""
|
|
247
|
+
if config:
|
|
248
|
+
return config.port, config.mode
|
|
249
|
+
return (
|
|
250
|
+
getattr(options, "zuban_lsp_port", 8677),
|
|
251
|
+
getattr(options, "zuban_lsp_mode", "stdio"),
|
|
252
|
+
)
|
|
253
|
+
|
|
237
254
|
def _log_zuban_lsp_status(self) -> None:
|
|
238
255
|
"""Display current Zuban LSP server status during workflow startup."""
|
|
239
256
|
from crackerjack.services.server_manager import find_zuban_lsp_processes
|
|
@@ -353,36 +370,9 @@ class WorkflowPipeline:
|
|
|
353
370
|
return
|
|
354
371
|
|
|
355
372
|
try:
|
|
356
|
-
|
|
357
|
-
{
|
|
358
|
-
"workflow_id": workflow_id,
|
|
359
|
-
"total_duration": duration,
|
|
360
|
-
"success": success,
|
|
361
|
-
"cache_metrics": self._cache.get_stats() if self._cache else {},
|
|
362
|
-
"memory_metrics": self._memory_optimizer.get_stats()
|
|
363
|
-
if hasattr(self._memory_optimizer, "get_stats")
|
|
364
|
-
else {},
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
# Generate benchmark comparison
|
|
373
|
+
self._gather_performance_metrics(workflow_id, duration, success)
|
|
368
374
|
benchmark_results = await self._performance_benchmarks.run_benchmark_suite()
|
|
369
|
-
|
|
370
|
-
# Display compact performance summary
|
|
371
|
-
if benchmark_results:
|
|
372
|
-
self.console.print("\n[cyan]📊 Performance Benchmark Summary[/cyan]")
|
|
373
|
-
self.console.print(f"Workflow Duration: [bold]{duration:.2f}s[/bold]")
|
|
374
|
-
|
|
375
|
-
# Show key performance improvements if available
|
|
376
|
-
for result in benchmark_results.results[:3]: # Top 3 results
|
|
377
|
-
if result.time_improvement_percentage > 0:
|
|
378
|
-
self.console.print(
|
|
379
|
-
f"[green]⚡[/green] {result.test_name}: {result.time_improvement_percentage:.1f}% faster"
|
|
380
|
-
)
|
|
381
|
-
|
|
382
|
-
if result.cache_hit_ratio > 0:
|
|
383
|
-
self.console.print(
|
|
384
|
-
f"[blue]🎯[/blue] Cache efficiency: {result.cache_hit_ratio:.0%}"
|
|
385
|
-
)
|
|
375
|
+
self._display_benchmark_results(benchmark_results, duration)
|
|
386
376
|
|
|
387
377
|
except Exception as e:
|
|
388
378
|
self.console.print(
|
|
@@ -392,6 +382,52 @@ class WorkflowPipeline:
|
|
|
392
382
|
if self.debugger.enabled:
|
|
393
383
|
self.debugger.print_debug_summary()
|
|
394
384
|
|
|
385
|
+
def _gather_performance_metrics(
|
|
386
|
+
self, workflow_id: str, duration: float, success: bool
|
|
387
|
+
) -> dict:
|
|
388
|
+
"""Gather performance metrics from workflow execution."""
|
|
389
|
+
return {
|
|
390
|
+
"workflow_id": workflow_id,
|
|
391
|
+
"total_duration": duration,
|
|
392
|
+
"success": success,
|
|
393
|
+
"cache_metrics": self._cache.get_stats() if self._cache else {},
|
|
394
|
+
"memory_metrics": self._memory_optimizer.get_stats()
|
|
395
|
+
if hasattr(self._memory_optimizer, "get_stats")
|
|
396
|
+
else {},
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
def _display_benchmark_results(
|
|
400
|
+
self, benchmark_results: t.Any, duration: float
|
|
401
|
+
) -> None:
|
|
402
|
+
"""Display compact performance summary."""
|
|
403
|
+
if not benchmark_results:
|
|
404
|
+
return
|
|
405
|
+
|
|
406
|
+
self.console.print("\n[cyan]📊 Performance Benchmark Summary[/cyan]")
|
|
407
|
+
self.console.print(f"Workflow Duration: [bold]{duration:.2f}s[/bold]")
|
|
408
|
+
|
|
409
|
+
self._show_performance_improvements(benchmark_results)
|
|
410
|
+
|
|
411
|
+
def _show_performance_improvements(self, benchmark_results: t.Any) -> None:
|
|
412
|
+
"""Show key performance improvements from benchmark results."""
|
|
413
|
+
for result in benchmark_results.results[:3]: # Top 3 results
|
|
414
|
+
self._display_time_improvement(result)
|
|
415
|
+
self._display_cache_efficiency(result)
|
|
416
|
+
|
|
417
|
+
def _display_time_improvement(self, result: t.Any) -> None:
|
|
418
|
+
"""Display time improvement percentage if available."""
|
|
419
|
+
if result.time_improvement_percentage > 0:
|
|
420
|
+
self.console.print(
|
|
421
|
+
f"[green]⚡[/green] {result.test_name}: {result.time_improvement_percentage:.1f}% faster"
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
def _display_cache_efficiency(self, result: t.Any) -> None:
|
|
425
|
+
"""Display cache hit ratio if available."""
|
|
426
|
+
if result.cache_hit_ratio > 0:
|
|
427
|
+
self.console.print(
|
|
428
|
+
f"[blue]🎯[/blue] Cache efficiency: {result.cache_hit_ratio:.0%}"
|
|
429
|
+
)
|
|
430
|
+
|
|
395
431
|
def _handle_user_interruption(self) -> bool:
|
|
396
432
|
self.console.print("Interrupted by user")
|
|
397
433
|
self.session.fail_task("workflow", "Interrupted by user")
|
|
@@ -418,11 +454,11 @@ class WorkflowPipeline:
|
|
|
418
454
|
success = success and config_success
|
|
419
455
|
|
|
420
456
|
quality_success = await self._execute_quality_phase(options, workflow_id)
|
|
421
|
-
|
|
422
|
-
success = False
|
|
457
|
+
success = success and quality_success
|
|
423
458
|
|
|
424
|
-
|
|
425
|
-
|
|
459
|
+
# If quality phase failed and we're in publishing mode, stop here
|
|
460
|
+
if not quality_success and self._is_publishing_workflow(options):
|
|
461
|
+
return False
|
|
426
462
|
|
|
427
463
|
# Execute publishing workflow if requested
|
|
428
464
|
publishing_success = await self._execute_publishing_workflow(
|
|
@@ -447,6 +483,30 @@ class WorkflowPipeline:
|
|
|
447
483
|
|
|
448
484
|
return success
|
|
449
485
|
|
|
486
|
+
def _handle_quality_phase_result(
|
|
487
|
+
self, success: bool, quality_success: bool, options: OptionsProtocol
|
|
488
|
+
) -> bool:
|
|
489
|
+
"""Handle the result of the quality phase execution."""
|
|
490
|
+
if not quality_success:
|
|
491
|
+
if self._is_publishing_workflow(options):
|
|
492
|
+
# For publishing workflows, quality failures should stop execution
|
|
493
|
+
return False
|
|
494
|
+
# For non-publishing workflows, we continue but mark as failed
|
|
495
|
+
return False
|
|
496
|
+
return success
|
|
497
|
+
|
|
498
|
+
def _handle_workflow_completion(
|
|
499
|
+
self, success: bool, publishing_success: bool, options: OptionsProtocol
|
|
500
|
+
) -> bool:
|
|
501
|
+
"""Handle workflow completion and determine final success status."""
|
|
502
|
+
# Only fail the overall workflow if publishing was explicitly requested and failed
|
|
503
|
+
if not publishing_success and (options.publish or options.all):
|
|
504
|
+
self.console.print(
|
|
505
|
+
"[red]❌ Publishing failed - overall workflow marked as failed[/red]"
|
|
506
|
+
)
|
|
507
|
+
return False
|
|
508
|
+
return success
|
|
509
|
+
|
|
450
510
|
def _is_publishing_workflow(self, options: OptionsProtocol) -> bool:
|
|
451
511
|
return bool(options.publish or options.all)
|
|
452
512
|
|
|
@@ -503,40 +563,52 @@ class WorkflowPipeline:
|
|
|
503
563
|
if not self._quality_intelligence:
|
|
504
564
|
return "Quality intelligence not available"
|
|
505
565
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
patterns = await self._quality_intelligence.identify_patterns()
|
|
509
|
-
|
|
510
|
-
# Make intelligent recommendations based on current state
|
|
511
|
-
recommendations = []
|
|
512
|
-
if anomalies:
|
|
513
|
-
high_severity_anomalies = [
|
|
514
|
-
a for a in anomalies if a.severity.name in ["CRITICAL", "HIGH"]
|
|
515
|
-
]
|
|
516
|
-
if high_severity_anomalies:
|
|
517
|
-
recommendations.append(
|
|
518
|
-
"comprehensive analysis recommended due to quality anomalies"
|
|
519
|
-
)
|
|
520
|
-
else:
|
|
521
|
-
recommendations.append("standard quality checks sufficient")
|
|
522
|
-
|
|
523
|
-
if patterns:
|
|
524
|
-
improving_patterns = [
|
|
525
|
-
p for p in patterns if p.trend_direction.name == "IMPROVING"
|
|
526
|
-
]
|
|
527
|
-
if improving_patterns:
|
|
528
|
-
recommendations.append("quality trending upward")
|
|
529
|
-
else:
|
|
530
|
-
recommendations.append("quality monitoring active")
|
|
531
|
-
|
|
532
|
-
if not recommendations:
|
|
533
|
-
recommendations.append("baseline quality analysis active")
|
|
566
|
+
anomalies = self._quality_intelligence.detect_anomalies()
|
|
567
|
+
patterns = self._quality_intelligence.identify_patterns()
|
|
534
568
|
|
|
569
|
+
recommendations = self._build_quality_recommendations(anomalies, patterns)
|
|
535
570
|
return "; ".join(recommendations)
|
|
536
571
|
|
|
537
572
|
except Exception as e:
|
|
538
573
|
return f"Quality intelligence analysis failed: {str(e)[:50]}..."
|
|
539
574
|
|
|
575
|
+
def _build_quality_recommendations(
|
|
576
|
+
self, anomalies: t.Any, patterns: t.Any
|
|
577
|
+
) -> list[str]:
|
|
578
|
+
"""Build quality recommendations based on anomalies and patterns."""
|
|
579
|
+
recommendations = []
|
|
580
|
+
|
|
581
|
+
if anomalies:
|
|
582
|
+
recommendations.extend(self._analyze_anomalies(anomalies))
|
|
583
|
+
|
|
584
|
+
if patterns:
|
|
585
|
+
recommendations.extend(self._analyze_patterns(patterns))
|
|
586
|
+
|
|
587
|
+
if not recommendations:
|
|
588
|
+
recommendations.append("baseline quality analysis active")
|
|
589
|
+
|
|
590
|
+
return recommendations
|
|
591
|
+
|
|
592
|
+
def _analyze_anomalies(self, anomalies: t.Any) -> list[str]:
|
|
593
|
+
"""Analyze anomalies and return recommendations."""
|
|
594
|
+
high_severity_anomalies = [
|
|
595
|
+
a for a in anomalies if a.severity.name in ("CRITICAL", "HIGH")
|
|
596
|
+
]
|
|
597
|
+
|
|
598
|
+
if high_severity_anomalies:
|
|
599
|
+
return ["comprehensive analysis recommended due to quality anomalies"]
|
|
600
|
+
return ["standard quality checks sufficient"]
|
|
601
|
+
|
|
602
|
+
def _analyze_patterns(self, patterns: t.Any) -> list[str]:
|
|
603
|
+
"""Analyze patterns and return recommendations."""
|
|
604
|
+
improving_patterns = [
|
|
605
|
+
p for p in patterns if p.trend_direction.name == "IMPROVING"
|
|
606
|
+
]
|
|
607
|
+
|
|
608
|
+
if improving_patterns:
|
|
609
|
+
return ["quality trending upward"]
|
|
610
|
+
return ["quality monitoring active"]
|
|
611
|
+
|
|
540
612
|
async def _execute_test_workflow(
|
|
541
613
|
self, options: OptionsProtocol, workflow_id: str
|
|
542
614
|
) -> bool:
|
|
@@ -864,27 +936,36 @@ class WorkflowPipeline:
|
|
|
864
936
|
def _execute_standard_hooks_workflow(self, options: OptionsProtocol) -> bool:
|
|
865
937
|
self._update_hooks_status_running()
|
|
866
938
|
|
|
867
|
-
|
|
868
|
-
if not fast_hooks_success:
|
|
939
|
+
if not self._execute_fast_hooks_workflow(options):
|
|
869
940
|
self._handle_hooks_completion(False)
|
|
870
941
|
return False
|
|
871
942
|
|
|
872
|
-
if
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
return False
|
|
876
|
-
|
|
877
|
-
if not self._run_post_cleaning_fast_hooks(options):
|
|
878
|
-
self._handle_hooks_completion(False)
|
|
879
|
-
return False
|
|
880
|
-
self._mark_code_cleaning_complete()
|
|
943
|
+
if not self._execute_cleaning_workflow_if_needed(options):
|
|
944
|
+
self._handle_hooks_completion(False)
|
|
945
|
+
return False
|
|
881
946
|
|
|
882
947
|
comprehensive_success = self._run_comprehensive_hooks_phase(options)
|
|
948
|
+
self._handle_hooks_completion(comprehensive_success)
|
|
883
949
|
|
|
884
|
-
|
|
885
|
-
self._handle_hooks_completion(hooks_success)
|
|
950
|
+
return comprehensive_success
|
|
886
951
|
|
|
887
|
-
|
|
952
|
+
def _execute_fast_hooks_workflow(self, options: OptionsProtocol) -> bool:
|
|
953
|
+
"""Execute fast hooks phase."""
|
|
954
|
+
return self._run_fast_hooks_phase(options)
|
|
955
|
+
|
|
956
|
+
def _execute_cleaning_workflow_if_needed(self, options: OptionsProtocol) -> bool:
|
|
957
|
+
"""Execute cleaning workflow if requested."""
|
|
958
|
+
if not getattr(options, "clean", False):
|
|
959
|
+
return True
|
|
960
|
+
|
|
961
|
+
if not self._run_code_cleaning_phase(options):
|
|
962
|
+
return False
|
|
963
|
+
|
|
964
|
+
if not self._run_post_cleaning_fast_hooks(options):
|
|
965
|
+
return False
|
|
966
|
+
|
|
967
|
+
self._mark_code_cleaning_complete()
|
|
968
|
+
return True
|
|
888
969
|
|
|
889
970
|
def _update_hooks_status_running(self) -> None:
|
|
890
971
|
if self._has_mcp_state_manager():
|
|
@@ -1094,15 +1175,16 @@ class WorkflowPipeline:
|
|
|
1094
1175
|
return test_success
|
|
1095
1176
|
|
|
1096
1177
|
def _should_verify_hook_fixes(self, fixes_applied: list[str]) -> bool:
|
|
1097
|
-
hook_fixes = [
|
|
1098
|
-
f
|
|
1099
|
-
for f in fixes_applied
|
|
1100
|
-
if "hook" not in f.lower()
|
|
1101
|
-
or "complexity" in f.lower()
|
|
1102
|
-
or "type" in f.lower()
|
|
1103
|
-
]
|
|
1178
|
+
hook_fixes = [fix for fix in fixes_applied if self._is_hook_related_fix(fix)]
|
|
1104
1179
|
return bool(hook_fixes)
|
|
1105
1180
|
|
|
1181
|
+
def _is_hook_related_fix(self, fix: str) -> bool:
|
|
1182
|
+
"""Check if a fix is related to hooks and should trigger hook verification."""
|
|
1183
|
+
fix_lower = fix.lower()
|
|
1184
|
+
return (
|
|
1185
|
+
"hook" not in fix_lower or "complexity" in fix_lower or "type" in fix_lower
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1106
1188
|
async def _verify_hook_fixes(self, options: OptionsProtocol) -> bool:
|
|
1107
1189
|
self.logger.info("Re-running comprehensive hooks to verify hook fixes")
|
|
1108
1190
|
hook_success = self.phases.run_comprehensive_hooks_only(options)
|
|
@@ -1277,35 +1359,30 @@ class WorkflowPipeline:
|
|
|
1277
1359
|
return issues
|
|
1278
1360
|
|
|
1279
1361
|
def _parse_comprehensive_hook_errors(self, error_msg: str) -> list[Issue]:
|
|
1280
|
-
issues: list[Issue] = []
|
|
1281
1362
|
error_lower = error_msg.lower()
|
|
1363
|
+
error_checkers = self._get_comprehensive_error_checkers()
|
|
1282
1364
|
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
if type_error_issue:
|
|
1289
|
-
issues.append(type_error_issue)
|
|
1290
|
-
|
|
1291
|
-
security_issue = self._check_security_error(error_lower)
|
|
1292
|
-
if security_issue:
|
|
1293
|
-
issues.append(security_issue)
|
|
1294
|
-
|
|
1295
|
-
performance_issue = self._check_performance_error(error_lower)
|
|
1296
|
-
if performance_issue:
|
|
1297
|
-
issues.append(performance_issue)
|
|
1298
|
-
|
|
1299
|
-
dead_code_issue = self._check_dead_code_error(error_lower)
|
|
1300
|
-
if dead_code_issue:
|
|
1301
|
-
issues.append(dead_code_issue)
|
|
1302
|
-
|
|
1303
|
-
regex_issue = self._check_regex_validation_error(error_lower)
|
|
1304
|
-
if regex_issue:
|
|
1305
|
-
issues.append(regex_issue)
|
|
1365
|
+
issues = []
|
|
1366
|
+
for check_func in error_checkers:
|
|
1367
|
+
issue = check_func(error_lower)
|
|
1368
|
+
if issue:
|
|
1369
|
+
issues.append(issue)
|
|
1306
1370
|
|
|
1307
1371
|
return issues
|
|
1308
1372
|
|
|
1373
|
+
def _get_comprehensive_error_checkers(
|
|
1374
|
+
self,
|
|
1375
|
+
) -> list[t.Callable[[str], Issue | None]]:
|
|
1376
|
+
"""Get list of error checking functions for comprehensive hooks."""
|
|
1377
|
+
return [
|
|
1378
|
+
self._check_complexity_error,
|
|
1379
|
+
self._check_type_error,
|
|
1380
|
+
self._check_security_error,
|
|
1381
|
+
self._check_performance_error,
|
|
1382
|
+
self._check_dead_code_error,
|
|
1383
|
+
self._check_regex_validation_error,
|
|
1384
|
+
]
|
|
1385
|
+
|
|
1309
1386
|
def _check_complexity_error(self, error_lower: str) -> Issue | None:
|
|
1310
1387
|
if "complexipy" in error_lower or "c901" in error_lower:
|
|
1311
1388
|
return Issue(
|
|
@@ -1404,24 +1481,66 @@ class WorkflowPipeline:
|
|
|
1404
1481
|
def _classify_issue(self, issue_str: str) -> tuple[IssueType, Priority]:
|
|
1405
1482
|
issue_lower = issue_str.lower()
|
|
1406
1483
|
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
if
|
|
1410
|
-
return
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
if self._is_dead_code_issue(issue_lower):
|
|
1417
|
-
return IssueType.DEAD_CODE, Priority.MEDIUM
|
|
1418
|
-
if self._is_performance_issue(issue_lower):
|
|
1419
|
-
return IssueType.PERFORMANCE, Priority.MEDIUM
|
|
1420
|
-
if self._is_import_error(issue_lower):
|
|
1421
|
-
return IssueType.IMPORT_ERROR, Priority.MEDIUM
|
|
1484
|
+
# Check high priority issues first
|
|
1485
|
+
high_priority_result = self._check_high_priority_issues(issue_lower)
|
|
1486
|
+
if high_priority_result:
|
|
1487
|
+
return high_priority_result
|
|
1488
|
+
|
|
1489
|
+
# Check medium priority issues
|
|
1490
|
+
medium_priority_result = self._check_medium_priority_issues(issue_lower)
|
|
1491
|
+
if medium_priority_result:
|
|
1492
|
+
return medium_priority_result
|
|
1422
1493
|
|
|
1494
|
+
# Default to formatting issue
|
|
1423
1495
|
return IssueType.FORMATTING, Priority.MEDIUM
|
|
1424
1496
|
|
|
1497
|
+
def _check_high_priority_issues(
|
|
1498
|
+
self, issue_lower: str
|
|
1499
|
+
) -> tuple[IssueType, Priority] | None:
|
|
1500
|
+
"""Check for high priority issue types.
|
|
1501
|
+
|
|
1502
|
+
Args:
|
|
1503
|
+
issue_lower: Lowercase issue string
|
|
1504
|
+
|
|
1505
|
+
Returns:
|
|
1506
|
+
Tuple of issue type and priority if found, None otherwise
|
|
1507
|
+
"""
|
|
1508
|
+
high_priority_checks = [
|
|
1509
|
+
(self._is_type_error, IssueType.TYPE_ERROR),
|
|
1510
|
+
(self._is_security_issue, IssueType.SECURITY),
|
|
1511
|
+
(self._is_complexity_issue, IssueType.COMPLEXITY),
|
|
1512
|
+
(self._is_regex_validation_issue, IssueType.REGEX_VALIDATION),
|
|
1513
|
+
]
|
|
1514
|
+
|
|
1515
|
+
for check_func, issue_type in high_priority_checks:
|
|
1516
|
+
if check_func(issue_lower):
|
|
1517
|
+
return issue_type, Priority.HIGH
|
|
1518
|
+
|
|
1519
|
+
return None
|
|
1520
|
+
|
|
1521
|
+
def _check_medium_priority_issues(
|
|
1522
|
+
self, issue_lower: str
|
|
1523
|
+
) -> tuple[IssueType, Priority] | None:
|
|
1524
|
+
"""Check for medium priority issue types.
|
|
1525
|
+
|
|
1526
|
+
Args:
|
|
1527
|
+
issue_lower: Lowercase issue string
|
|
1528
|
+
|
|
1529
|
+
Returns:
|
|
1530
|
+
Tuple of issue type and priority if found, None otherwise
|
|
1531
|
+
"""
|
|
1532
|
+
medium_priority_checks = [
|
|
1533
|
+
(self._is_dead_code_issue, IssueType.DEAD_CODE),
|
|
1534
|
+
(self._is_performance_issue, IssueType.PERFORMANCE),
|
|
1535
|
+
(self._is_import_error, IssueType.IMPORT_ERROR),
|
|
1536
|
+
]
|
|
1537
|
+
|
|
1538
|
+
for check_func, issue_type in medium_priority_checks:
|
|
1539
|
+
if check_func(issue_lower):
|
|
1540
|
+
return issue_type, Priority.MEDIUM
|
|
1541
|
+
|
|
1542
|
+
return None
|
|
1543
|
+
|
|
1425
1544
|
def _is_type_error(self, issue_lower: str) -> bool:
|
|
1426
1545
|
return any(
|
|
1427
1546
|
keyword in issue_lower for keyword in ("type", "annotation", "pyright")
|
|
@@ -1490,44 +1609,77 @@ class WorkflowPipeline:
|
|
|
1490
1609
|
async def _handle_security_gate_failure(
|
|
1491
1610
|
self, options: OptionsProtocol, allow_ai_fixing: bool = False
|
|
1492
1611
|
) -> bool:
|
|
1612
|
+
self._display_security_gate_failure_message()
|
|
1613
|
+
|
|
1614
|
+
if allow_ai_fixing:
|
|
1615
|
+
return await self._attempt_ai_assisted_security_fix(options)
|
|
1616
|
+
return self._handle_manual_security_fix()
|
|
1617
|
+
|
|
1618
|
+
def _display_security_gate_failure_message(self) -> None:
|
|
1619
|
+
"""Display initial security gate failure message."""
|
|
1493
1620
|
self.console.print(
|
|
1494
1621
|
"[red]🔒 SECURITY GATE: Critical security checks failed[/red]"
|
|
1495
1622
|
)
|
|
1496
1623
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
"[red]Security-critical hooks (bandit, pyright, gitleaks) must pass before publishing[/red]"
|
|
1500
|
-
)
|
|
1501
|
-
self.console.print(
|
|
1502
|
-
"[yellow]🤖 Attempting AI-assisted security issue resolution...[/yellow]"
|
|
1503
|
-
)
|
|
1624
|
+
async def _attempt_ai_assisted_security_fix(self, options: OptionsProtocol) -> bool:
|
|
1625
|
+
"""Attempt to fix security issues using AI assistance.
|
|
1504
1626
|
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1627
|
+
Args:
|
|
1628
|
+
options: Configuration options
|
|
1629
|
+
|
|
1630
|
+
Returns:
|
|
1631
|
+
True if security issues were resolved, False otherwise
|
|
1632
|
+
"""
|
|
1633
|
+
self._display_ai_fixing_messages()
|
|
1634
|
+
|
|
1635
|
+
ai_fix_success = await self._run_ai_agent_fixing_phase(options)
|
|
1636
|
+
if ai_fix_success:
|
|
1637
|
+
return self._verify_security_fix_success()
|
|
1638
|
+
|
|
1639
|
+
return False
|
|
1640
|
+
|
|
1641
|
+
def _display_ai_fixing_messages(self) -> None:
|
|
1642
|
+
"""Display messages about AI-assisted security fixing."""
|
|
1643
|
+
self.console.print(
|
|
1644
|
+
"[red]Security-critical hooks (bandit, pyright, gitleaks) must pass before publishing[/red]"
|
|
1645
|
+
)
|
|
1646
|
+
self.console.print(
|
|
1647
|
+
"[yellow]🤖 Attempting AI-assisted security issue resolution...[/yellow]"
|
|
1648
|
+
)
|
|
1649
|
+
|
|
1650
|
+
def _verify_security_fix_success(self) -> bool:
|
|
1651
|
+
"""Verify that AI fixes resolved the security issues.
|
|
1652
|
+
|
|
1653
|
+
Returns:
|
|
1654
|
+
True if security issues were resolved, False otherwise
|
|
1655
|
+
"""
|
|
1656
|
+
try:
|
|
1657
|
+
security_still_blocks = self._check_security_critical_failures()
|
|
1658
|
+
if not security_still_blocks:
|
|
1659
|
+
self.console.print(
|
|
1660
|
+
"[green]✅ AI agents resolved security issues - publishing allowed[/green]"
|
|
1661
|
+
)
|
|
1662
|
+
return True
|
|
1663
|
+
else:
|
|
1664
|
+
self.console.print(
|
|
1665
|
+
"[red]🔒 Security issues persist after AI fixing - publishing still BLOCKED[/red]"
|
|
1666
|
+
)
|
|
1667
|
+
return False
|
|
1668
|
+
except Exception as e:
|
|
1669
|
+
self.logger.warning(f"Security re-check failed: {e} - blocking publishing")
|
|
1529
1670
|
return False
|
|
1530
1671
|
|
|
1672
|
+
def _handle_manual_security_fix(self) -> bool:
|
|
1673
|
+
"""Handle security fix when AI assistance is not allowed.
|
|
1674
|
+
|
|
1675
|
+
Returns:
|
|
1676
|
+
Always False since manual intervention is required
|
|
1677
|
+
"""
|
|
1678
|
+
self.console.print(
|
|
1679
|
+
"[red]Security-critical hooks (bandit, pyright, gitleaks) must pass before publishing[/red]"
|
|
1680
|
+
)
|
|
1681
|
+
return False
|
|
1682
|
+
|
|
1531
1683
|
def _determine_ai_fixing_needed(
|
|
1532
1684
|
self,
|
|
1533
1685
|
testing_passed: bool,
|