gitflow-analytics 1.3.6__py3-none-any.whl → 3.3.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.
Files changed (48) hide show
  1. gitflow_analytics/_version.py +1 -1
  2. gitflow_analytics/classification/batch_classifier.py +156 -4
  3. gitflow_analytics/cli.py +897 -179
  4. gitflow_analytics/config/loader.py +40 -1
  5. gitflow_analytics/config/schema.py +4 -0
  6. gitflow_analytics/core/cache.py +20 -0
  7. gitflow_analytics/core/data_fetcher.py +1254 -228
  8. gitflow_analytics/core/git_auth.py +169 -0
  9. gitflow_analytics/core/git_timeout_wrapper.py +347 -0
  10. gitflow_analytics/core/metrics_storage.py +12 -3
  11. gitflow_analytics/core/progress.py +219 -18
  12. gitflow_analytics/core/subprocess_git.py +145 -0
  13. gitflow_analytics/extractors/ml_tickets.py +3 -2
  14. gitflow_analytics/extractors/tickets.py +93 -8
  15. gitflow_analytics/integrations/jira_integration.py +1 -1
  16. gitflow_analytics/integrations/orchestrator.py +47 -29
  17. gitflow_analytics/metrics/branch_health.py +3 -2
  18. gitflow_analytics/models/database.py +72 -1
  19. gitflow_analytics/pm_framework/adapters/jira_adapter.py +12 -5
  20. gitflow_analytics/pm_framework/orchestrator.py +8 -3
  21. gitflow_analytics/qualitative/classifiers/llm/openai_client.py +24 -4
  22. gitflow_analytics/qualitative/classifiers/llm_commit_classifier.py +3 -1
  23. gitflow_analytics/qualitative/core/llm_fallback.py +34 -2
  24. gitflow_analytics/reports/narrative_writer.py +118 -74
  25. gitflow_analytics/security/__init__.py +11 -0
  26. gitflow_analytics/security/config.py +189 -0
  27. gitflow_analytics/security/extractors/__init__.py +7 -0
  28. gitflow_analytics/security/extractors/dependency_checker.py +379 -0
  29. gitflow_analytics/security/extractors/secret_detector.py +197 -0
  30. gitflow_analytics/security/extractors/vulnerability_scanner.py +333 -0
  31. gitflow_analytics/security/llm_analyzer.py +347 -0
  32. gitflow_analytics/security/reports/__init__.py +5 -0
  33. gitflow_analytics/security/reports/security_report.py +358 -0
  34. gitflow_analytics/security/security_analyzer.py +414 -0
  35. gitflow_analytics/tui/app.py +3 -1
  36. gitflow_analytics/tui/progress_adapter.py +313 -0
  37. gitflow_analytics/tui/screens/analysis_progress_screen.py +407 -46
  38. gitflow_analytics/tui/screens/results_screen.py +219 -206
  39. gitflow_analytics/ui/__init__.py +21 -0
  40. gitflow_analytics/ui/progress_display.py +1477 -0
  41. gitflow_analytics/verify_activity.py +697 -0
  42. {gitflow_analytics-1.3.6.dist-info → gitflow_analytics-3.3.0.dist-info}/METADATA +2 -1
  43. {gitflow_analytics-1.3.6.dist-info → gitflow_analytics-3.3.0.dist-info}/RECORD +47 -31
  44. gitflow_analytics/cli_rich.py +0 -503
  45. {gitflow_analytics-1.3.6.dist-info → gitflow_analytics-3.3.0.dist-info}/WHEEL +0 -0
  46. {gitflow_analytics-1.3.6.dist-info → gitflow_analytics-3.3.0.dist-info}/entry_points.txt +0 -0
  47. {gitflow_analytics-1.3.6.dist-info → gitflow_analytics-3.3.0.dist-info}/licenses/LICENSE +0 -0
  48. {gitflow_analytics-1.3.6.dist-info → gitflow_analytics-3.3.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
1
  """Version information for gitflow-analytics."""
2
2
 
3
- __version__ = "1.3.6"
3
+ __version__ = "3.3.0"
4
4
  __version_info__ = tuple(int(x) for x in __version__.split("."))
@@ -45,6 +45,7 @@ class BatchCommitClassifier:
45
45
  batch_size: int = 50,
46
46
  confidence_threshold: float = 0.7,
47
47
  fallback_enabled: bool = True,
48
+ max_processing_time_minutes: int = 30, # Maximum time for classification
48
49
  ):
49
50
  """Initialize the batch classifier.
50
51
 
@@ -60,6 +61,8 @@ class BatchCommitClassifier:
60
61
  self.batch_size = min(batch_size, 50) # Limit for token constraints
61
62
  self.confidence_threshold = confidence_threshold
62
63
  self.fallback_enabled = fallback_enabled
64
+ self.max_processing_time_minutes = max_processing_time_minutes
65
+ self.classification_start_time = None
63
66
 
64
67
  # Initialize LLM classifier
65
68
  # Handle different config types
@@ -84,9 +87,25 @@ class BatchCommitClassifier:
84
87
  llm_config_obj = LLMConfig()
85
88
 
86
89
  self.llm_classifier = LLMCommitClassifier(config=llm_config_obj, cache_dir=cache_dir)
87
- logger.info(
88
- f"LLM Classifier initialized with API key: {'Yes' if llm_config_obj.api_key else 'No'}"
89
- )
90
+
91
+ # Warn if no API key is configured
92
+ if not llm_config_obj.api_key:
93
+ logger.warning(
94
+ "No API key configured for LLM classification. "
95
+ "Will fall back to rule-based classification."
96
+ )
97
+ # Set a flag to skip LLM calls entirely
98
+ self.llm_enabled = False
99
+ else:
100
+ self.llm_enabled = True
101
+ logger.info(
102
+ f"LLM Classifier initialized with API key: Yes (model: {llm_config_obj.model})"
103
+ )
104
+
105
+ # Circuit breaker for LLM API failures
106
+ self.api_failure_count = 0
107
+ self.max_consecutive_failures = 5
108
+ self.circuit_breaker_open = False
90
109
 
91
110
  # Rule-based fallback patterns for when LLM fails
92
111
  self.fallback_patterns = {
@@ -165,6 +184,7 @@ class BatchCommitClassifier:
165
184
  Dictionary containing classification results and statistics
166
185
  """
167
186
  logger.info(f"Starting batch classification from {start_date.date()} to {end_date.date()}")
187
+ self.classification_start_time = datetime.utcnow()
168
188
 
169
189
  # Get daily batches to process
170
190
  batches_to_process = self._get_batches_to_process(
@@ -206,6 +226,18 @@ class BatchCommitClassifier:
206
226
  f"Processing repository {repo_num}/{len(repo_batches)}: {repo_name} ({len(repo_batch_list)} batches, {repo_commit_count} commits)"
207
227
  )
208
228
 
229
+ # Check if we've exceeded max processing time
230
+ if self.classification_start_time:
231
+ elapsed_minutes = (
232
+ datetime.utcnow() - self.classification_start_time
233
+ ).total_seconds() / 60
234
+ if elapsed_minutes > self.max_processing_time_minutes:
235
+ logger.error(
236
+ f"Classification exceeded maximum time limit of {self.max_processing_time_minutes} minutes. "
237
+ f"Stopping classification to prevent hanging."
238
+ )
239
+ break
240
+
209
241
  # Process this repository's batches by week for optimal context
210
242
  weekly_batches = self._group_batches_by_week(repo_batch_list)
211
243
 
@@ -403,6 +435,30 @@ class BatchCommitClassifier:
403
435
  leave=False,
404
436
  ) as batch_ctx:
405
437
  for i in range(0, len(week_commits), self.batch_size):
438
+ # Check for timeout before processing each batch
439
+ if self.classification_start_time:
440
+ elapsed_minutes = (
441
+ datetime.utcnow() - self.classification_start_time
442
+ ).total_seconds() / 60
443
+ if elapsed_minutes > self.max_processing_time_minutes:
444
+ logger.error(
445
+ f"Classification timeout after {elapsed_minutes:.1f} minutes. "
446
+ f"Processed {len(classified_commits)}/{len(week_commits)} commits."
447
+ )
448
+ # Use fallback for remaining commits
449
+ remaining_commits = week_commits[i:]
450
+ for commit in remaining_commits:
451
+ classified_commits.append(
452
+ {
453
+ "commit_hash": commit["commit_hash"],
454
+ "category": "maintenance",
455
+ "confidence": 0.2,
456
+ "method": "timeout_fallback",
457
+ "error": "Classification timeout",
458
+ }
459
+ )
460
+ break
461
+
406
462
  batch_num = i // self.batch_size + 1
407
463
  batch_commits = week_commits[i : i + self.batch_size]
408
464
  progress.set_description(
@@ -558,6 +614,13 @@ class BatchCommitClassifier:
558
614
  batch_id = str(uuid.uuid4())
559
615
  logger.info(f"Starting LLM classification for batch {batch_id} with {len(commits)} commits")
560
616
 
617
+ # Add timeout warning for large batches
618
+ if len(commits) > 20:
619
+ logger.warning(
620
+ f"Large batch size ({len(commits)} commits) may take longer to process. "
621
+ f"Consider reducing batch_size if timeouts occur."
622
+ )
623
+
561
624
  # Prepare batch for LLM classification
562
625
  enhanced_commits = []
563
626
  for commit in commits:
@@ -573,12 +636,68 @@ class BatchCommitClassifier:
573
636
  enhanced_commit["ticket_context"] = relevant_tickets
574
637
  enhanced_commits.append(enhanced_commit)
575
638
 
639
+ # Check if LLM is enabled before attempting classification
640
+ if not self.llm_enabled:
641
+ logger.debug(f"LLM disabled, using fallback for batch {batch_id[:8]}")
642
+ # Skip directly to fallback
643
+ fallback_results = []
644
+ for commit in commits:
645
+ category = self._fallback_classify_commit(commit)
646
+ fallback_results.append(
647
+ {
648
+ "commit_hash": commit["commit_hash"],
649
+ "category": category,
650
+ "confidence": 0.3, # Low confidence for fallback
651
+ "method": "fallback_only",
652
+ "error": "LLM not configured",
653
+ "batch_id": batch_id,
654
+ }
655
+ )
656
+ return fallback_results
657
+
658
+ # Check circuit breaker status
659
+ if self.circuit_breaker_open:
660
+ logger.info(
661
+ f"Circuit breaker OPEN - Skipping LLM API call for batch {batch_id[:8]} "
662
+ f"after {self.api_failure_count} consecutive failures. Using fallback classification."
663
+ )
664
+ # Use fallback for all commits
665
+ fallback_results = []
666
+ for commit in commits:
667
+ category = self._fallback_classify_commit(commit)
668
+ fallback_results.append(
669
+ {
670
+ "commit_hash": commit["commit_hash"],
671
+ "category": category,
672
+ "confidence": 0.3, # Low confidence for fallback
673
+ "method": "circuit_breaker_fallback",
674
+ "error": "Circuit breaker open - API repeatedly failing",
675
+ "batch_id": batch_id,
676
+ }
677
+ )
678
+ return fallback_results
679
+
576
680
  try:
577
681
  # Use LLM classifier with enhanced context
682
+ logger.debug(f"Calling LLM classifier for batch {batch_id[:8]}...")
683
+ start_time = datetime.utcnow()
684
+
578
685
  llm_results = self.llm_classifier.classify_commits_batch(
579
686
  enhanced_commits, batch_id=batch_id, include_confidence=True
580
687
  )
581
688
 
689
+ elapsed = (datetime.utcnow() - start_time).total_seconds()
690
+ logger.info(f"LLM classification for batch {batch_id[:8]} took {elapsed:.2f}s")
691
+
692
+ # Reset circuit breaker on successful LLM call
693
+ if self.api_failure_count > 0:
694
+ logger.info(
695
+ f"LLM API call succeeded - Resetting circuit breaker "
696
+ f"(was at {self.api_failure_count} failures)"
697
+ )
698
+ self.api_failure_count = 0
699
+ self.circuit_breaker_open = False
700
+
582
701
  # Process LLM results and add fallbacks
583
702
  processed_results = []
584
703
  for _i, (commit, llm_result) in enumerate(zip(commits, llm_results)):
@@ -616,7 +735,40 @@ class BatchCommitClassifier:
616
735
  return processed_results
617
736
 
618
737
  except Exception as e:
619
- logger.error(f"LLM classification failed for batch {batch_id}: {e}")
738
+ # Track consecutive failures for circuit breaker
739
+ self.api_failure_count += 1
740
+ logger.error(
741
+ f"LLM classification failed for batch {batch_id}: {e} "
742
+ f"(Failure {self.api_failure_count}/{self.max_consecutive_failures})"
743
+ )
744
+
745
+ # Open circuit breaker after max consecutive failures
746
+ if (
747
+ self.api_failure_count >= self.max_consecutive_failures
748
+ and not self.circuit_breaker_open
749
+ ):
750
+ self.circuit_breaker_open = True
751
+ logger.error(
752
+ f"CIRCUIT BREAKER OPENED after {self.api_failure_count} consecutive API failures. "
753
+ f"All subsequent batches will use fallback classification until API recovers. "
754
+ f"This prevents the system from hanging on repeated timeouts."
755
+ )
756
+
757
+ # Provide more context about the failure
758
+ if "timeout" in str(e).lower():
759
+ logger.error(
760
+ f"Classification timed out. Consider: \n"
761
+ f" 1. Reducing batch_size (current: {self.batch_size})\n"
762
+ f" 2. Increasing timeout_seconds in LLM config\n"
763
+ f" 3. Checking API service status"
764
+ )
765
+ elif "connection" in str(e).lower():
766
+ logger.error(
767
+ "Connection error. Check:\n"
768
+ " 1. Internet connectivity\n"
769
+ " 2. API endpoint availability\n"
770
+ " 3. Firewall/proxy settings"
771
+ )
620
772
 
621
773
  # Fall back to rule-based classification for entire batch
622
774
  if self.fallback_enabled: