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.
- gitflow_analytics/_version.py +1 -1
- gitflow_analytics/classification/batch_classifier.py +156 -4
- gitflow_analytics/cli.py +897 -179
- gitflow_analytics/config/loader.py +40 -1
- gitflow_analytics/config/schema.py +4 -0
- gitflow_analytics/core/cache.py +20 -0
- gitflow_analytics/core/data_fetcher.py +1254 -228
- gitflow_analytics/core/git_auth.py +169 -0
- gitflow_analytics/core/git_timeout_wrapper.py +347 -0
- gitflow_analytics/core/metrics_storage.py +12 -3
- gitflow_analytics/core/progress.py +219 -18
- gitflow_analytics/core/subprocess_git.py +145 -0
- gitflow_analytics/extractors/ml_tickets.py +3 -2
- gitflow_analytics/extractors/tickets.py +93 -8
- gitflow_analytics/integrations/jira_integration.py +1 -1
- gitflow_analytics/integrations/orchestrator.py +47 -29
- gitflow_analytics/metrics/branch_health.py +3 -2
- gitflow_analytics/models/database.py +72 -1
- gitflow_analytics/pm_framework/adapters/jira_adapter.py +12 -5
- gitflow_analytics/pm_framework/orchestrator.py +8 -3
- gitflow_analytics/qualitative/classifiers/llm/openai_client.py +24 -4
- gitflow_analytics/qualitative/classifiers/llm_commit_classifier.py +3 -1
- gitflow_analytics/qualitative/core/llm_fallback.py +34 -2
- gitflow_analytics/reports/narrative_writer.py +118 -74
- gitflow_analytics/security/__init__.py +11 -0
- gitflow_analytics/security/config.py +189 -0
- gitflow_analytics/security/extractors/__init__.py +7 -0
- gitflow_analytics/security/extractors/dependency_checker.py +379 -0
- gitflow_analytics/security/extractors/secret_detector.py +197 -0
- gitflow_analytics/security/extractors/vulnerability_scanner.py +333 -0
- gitflow_analytics/security/llm_analyzer.py +347 -0
- gitflow_analytics/security/reports/__init__.py +5 -0
- gitflow_analytics/security/reports/security_report.py +358 -0
- gitflow_analytics/security/security_analyzer.py +414 -0
- gitflow_analytics/tui/app.py +3 -1
- gitflow_analytics/tui/progress_adapter.py +313 -0
- gitflow_analytics/tui/screens/analysis_progress_screen.py +407 -46
- gitflow_analytics/tui/screens/results_screen.py +219 -206
- gitflow_analytics/ui/__init__.py +21 -0
- gitflow_analytics/ui/progress_display.py +1477 -0
- gitflow_analytics/verify_activity.py +697 -0
- {gitflow_analytics-1.3.6.dist-info → gitflow_analytics-3.3.0.dist-info}/METADATA +2 -1
- {gitflow_analytics-1.3.6.dist-info → gitflow_analytics-3.3.0.dist-info}/RECORD +47 -31
- gitflow_analytics/cli_rich.py +0 -503
- {gitflow_analytics-1.3.6.dist-info → gitflow_analytics-3.3.0.dist-info}/WHEEL +0 -0
- {gitflow_analytics-1.3.6.dist-info → gitflow_analytics-3.3.0.dist-info}/entry_points.txt +0 -0
- {gitflow_analytics-1.3.6.dist-info → gitflow_analytics-3.3.0.dist-info}/licenses/LICENSE +0 -0
- {gitflow_analytics-1.3.6.dist-info → gitflow_analytics-3.3.0.dist-info}/top_level.txt +0 -0
gitflow_analytics/_version.py
CHANGED
|
@@ -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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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:
|