gitflow-analytics 1.0.3__py3-none-any.whl → 1.3.6__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 (116) hide show
  1. gitflow_analytics/_version.py +1 -1
  2. gitflow_analytics/classification/__init__.py +31 -0
  3. gitflow_analytics/classification/batch_classifier.py +752 -0
  4. gitflow_analytics/classification/classifier.py +464 -0
  5. gitflow_analytics/classification/feature_extractor.py +725 -0
  6. gitflow_analytics/classification/linguist_analyzer.py +574 -0
  7. gitflow_analytics/classification/model.py +455 -0
  8. gitflow_analytics/cli.py +4108 -350
  9. gitflow_analytics/cli_rich.py +198 -48
  10. gitflow_analytics/config/__init__.py +43 -0
  11. gitflow_analytics/config/errors.py +261 -0
  12. gitflow_analytics/config/loader.py +904 -0
  13. gitflow_analytics/config/profiles.py +264 -0
  14. gitflow_analytics/config/repository.py +124 -0
  15. gitflow_analytics/config/schema.py +441 -0
  16. gitflow_analytics/config/validator.py +154 -0
  17. gitflow_analytics/config.py +44 -508
  18. gitflow_analytics/core/analyzer.py +1209 -98
  19. gitflow_analytics/core/cache.py +1337 -29
  20. gitflow_analytics/core/data_fetcher.py +1193 -0
  21. gitflow_analytics/core/identity.py +363 -14
  22. gitflow_analytics/core/metrics_storage.py +526 -0
  23. gitflow_analytics/core/progress.py +372 -0
  24. gitflow_analytics/core/schema_version.py +269 -0
  25. gitflow_analytics/extractors/ml_tickets.py +1100 -0
  26. gitflow_analytics/extractors/story_points.py +8 -1
  27. gitflow_analytics/extractors/tickets.py +749 -11
  28. gitflow_analytics/identity_llm/__init__.py +6 -0
  29. gitflow_analytics/identity_llm/analysis_pass.py +231 -0
  30. gitflow_analytics/identity_llm/analyzer.py +464 -0
  31. gitflow_analytics/identity_llm/models.py +76 -0
  32. gitflow_analytics/integrations/github_integration.py +175 -11
  33. gitflow_analytics/integrations/jira_integration.py +461 -24
  34. gitflow_analytics/integrations/orchestrator.py +124 -1
  35. gitflow_analytics/metrics/activity_scoring.py +322 -0
  36. gitflow_analytics/metrics/branch_health.py +470 -0
  37. gitflow_analytics/metrics/dora.py +379 -20
  38. gitflow_analytics/models/database.py +843 -53
  39. gitflow_analytics/pm_framework/__init__.py +115 -0
  40. gitflow_analytics/pm_framework/adapters/__init__.py +50 -0
  41. gitflow_analytics/pm_framework/adapters/jira_adapter.py +1845 -0
  42. gitflow_analytics/pm_framework/base.py +406 -0
  43. gitflow_analytics/pm_framework/models.py +211 -0
  44. gitflow_analytics/pm_framework/orchestrator.py +652 -0
  45. gitflow_analytics/pm_framework/registry.py +333 -0
  46. gitflow_analytics/qualitative/__init__.py +9 -10
  47. gitflow_analytics/qualitative/chatgpt_analyzer.py +259 -0
  48. gitflow_analytics/qualitative/classifiers/__init__.py +3 -3
  49. gitflow_analytics/qualitative/classifiers/change_type.py +518 -244
  50. gitflow_analytics/qualitative/classifiers/domain_classifier.py +272 -165
  51. gitflow_analytics/qualitative/classifiers/intent_analyzer.py +321 -222
  52. gitflow_analytics/qualitative/classifiers/llm/__init__.py +35 -0
  53. gitflow_analytics/qualitative/classifiers/llm/base.py +193 -0
  54. gitflow_analytics/qualitative/classifiers/llm/batch_processor.py +383 -0
  55. gitflow_analytics/qualitative/classifiers/llm/cache.py +479 -0
  56. gitflow_analytics/qualitative/classifiers/llm/cost_tracker.py +435 -0
  57. gitflow_analytics/qualitative/classifiers/llm/openai_client.py +403 -0
  58. gitflow_analytics/qualitative/classifiers/llm/prompts.py +373 -0
  59. gitflow_analytics/qualitative/classifiers/llm/response_parser.py +287 -0
  60. gitflow_analytics/qualitative/classifiers/llm_commit_classifier.py +607 -0
  61. gitflow_analytics/qualitative/classifiers/risk_analyzer.py +215 -189
  62. gitflow_analytics/qualitative/core/__init__.py +4 -4
  63. gitflow_analytics/qualitative/core/llm_fallback.py +239 -235
  64. gitflow_analytics/qualitative/core/nlp_engine.py +157 -148
  65. gitflow_analytics/qualitative/core/pattern_cache.py +214 -192
  66. gitflow_analytics/qualitative/core/processor.py +381 -248
  67. gitflow_analytics/qualitative/enhanced_analyzer.py +2236 -0
  68. gitflow_analytics/qualitative/example_enhanced_usage.py +420 -0
  69. gitflow_analytics/qualitative/models/__init__.py +7 -7
  70. gitflow_analytics/qualitative/models/schemas.py +155 -121
  71. gitflow_analytics/qualitative/utils/__init__.py +4 -4
  72. gitflow_analytics/qualitative/utils/batch_processor.py +136 -123
  73. gitflow_analytics/qualitative/utils/cost_tracker.py +142 -140
  74. gitflow_analytics/qualitative/utils/metrics.py +172 -158
  75. gitflow_analytics/qualitative/utils/text_processing.py +146 -104
  76. gitflow_analytics/reports/__init__.py +100 -0
  77. gitflow_analytics/reports/analytics_writer.py +539 -14
  78. gitflow_analytics/reports/base.py +648 -0
  79. gitflow_analytics/reports/branch_health_writer.py +322 -0
  80. gitflow_analytics/reports/classification_writer.py +924 -0
  81. gitflow_analytics/reports/cli_integration.py +427 -0
  82. gitflow_analytics/reports/csv_writer.py +1676 -212
  83. gitflow_analytics/reports/data_models.py +504 -0
  84. gitflow_analytics/reports/database_report_generator.py +427 -0
  85. gitflow_analytics/reports/example_usage.py +344 -0
  86. gitflow_analytics/reports/factory.py +499 -0
  87. gitflow_analytics/reports/formatters.py +698 -0
  88. gitflow_analytics/reports/html_generator.py +1116 -0
  89. gitflow_analytics/reports/interfaces.py +489 -0
  90. gitflow_analytics/reports/json_exporter.py +2770 -0
  91. gitflow_analytics/reports/narrative_writer.py +2287 -158
  92. gitflow_analytics/reports/story_point_correlation.py +1144 -0
  93. gitflow_analytics/reports/weekly_trends_writer.py +389 -0
  94. gitflow_analytics/training/__init__.py +5 -0
  95. gitflow_analytics/training/model_loader.py +377 -0
  96. gitflow_analytics/training/pipeline.py +550 -0
  97. gitflow_analytics/tui/__init__.py +1 -1
  98. gitflow_analytics/tui/app.py +129 -126
  99. gitflow_analytics/tui/screens/__init__.py +3 -3
  100. gitflow_analytics/tui/screens/analysis_progress_screen.py +188 -179
  101. gitflow_analytics/tui/screens/configuration_screen.py +154 -178
  102. gitflow_analytics/tui/screens/loading_screen.py +100 -110
  103. gitflow_analytics/tui/screens/main_screen.py +89 -72
  104. gitflow_analytics/tui/screens/results_screen.py +305 -281
  105. gitflow_analytics/tui/widgets/__init__.py +2 -2
  106. gitflow_analytics/tui/widgets/data_table.py +67 -69
  107. gitflow_analytics/tui/widgets/export_modal.py +76 -76
  108. gitflow_analytics/tui/widgets/progress_widget.py +41 -46
  109. gitflow_analytics-1.3.6.dist-info/METADATA +1015 -0
  110. gitflow_analytics-1.3.6.dist-info/RECORD +122 -0
  111. gitflow_analytics-1.0.3.dist-info/METADATA +0 -490
  112. gitflow_analytics-1.0.3.dist-info/RECORD +0 -62
  113. {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.6.dist-info}/WHEEL +0 -0
  114. {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.6.dist-info}/entry_points.txt +0 -0
  115. {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.6.dist-info}/licenses/LICENSE +0 -0
  116. {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,264 @@
1
+ """Configuration profiles and presets for GitFlow Analytics."""
2
+
3
+ from typing import Any, Optional
4
+
5
+
6
+ class ConfigurationProfile:
7
+ """Base class for configuration profiles."""
8
+
9
+ name: str = ""
10
+ description: str = ""
11
+
12
+ @classmethod
13
+ def get_settings(cls) -> dict[str, Any]:
14
+ """Get the profile settings.
15
+
16
+ Returns:
17
+ Dictionary of configuration settings for this profile
18
+ """
19
+ raise NotImplementedError
20
+
21
+
22
+ class PerformanceProfile(ConfigurationProfile):
23
+ """Performance-optimized configuration profile.
24
+
25
+ This profile prioritizes speed over accuracy and completeness.
26
+ Suitable for large codebases or quick analysis runs.
27
+ """
28
+
29
+ name = "performance"
30
+ description = "Optimized for speed with large repositories"
31
+
32
+ @classmethod
33
+ def get_settings(cls) -> dict[str, Any]:
34
+ """Get performance-optimized settings."""
35
+ return {
36
+ "analysis": {
37
+ "branch_analysis": {
38
+ "strategy": "main_only",
39
+ "max_branches_per_repo": 10,
40
+ "branch_commit_limit": 500,
41
+ },
42
+ "ml_categorization": {
43
+ "enabled": False, # Disable ML for speed
44
+ "batch_size": 500,
45
+ },
46
+ "commit_classification": {
47
+ "enabled": False, # Disable classification for speed
48
+ },
49
+ "llm_classification": {
50
+ "enabled": False, # Disable LLM for speed
51
+ },
52
+ },
53
+ "cache": {
54
+ "ttl_hours": 336, # 2 weeks cache
55
+ "max_size_mb": 1000,
56
+ },
57
+ "output": {
58
+ "formats": ["csv"], # CSV only for speed
59
+ },
60
+ }
61
+
62
+
63
+ class QualityProfile(ConfigurationProfile):
64
+ """Quality-focused configuration profile.
65
+
66
+ This profile enables all analysis features for maximum insight.
67
+ Suitable for detailed analysis and reporting.
68
+ """
69
+
70
+ name = "quality"
71
+ description = "Maximum analysis depth and accuracy"
72
+
73
+ @classmethod
74
+ def get_settings(cls) -> dict[str, Any]:
75
+ """Get quality-focused settings."""
76
+ return {
77
+ "analysis": {
78
+ "branch_analysis": {
79
+ "strategy": "smart",
80
+ "max_branches_per_repo": 100,
81
+ "active_days_threshold": 180,
82
+ "branch_commit_limit": 2000,
83
+ },
84
+ "ml_categorization": {
85
+ "enabled": True,
86
+ "min_confidence": 0.7,
87
+ "semantic_weight": 0.8,
88
+ "batch_size": 50,
89
+ },
90
+ "commit_classification": {
91
+ "enabled": True,
92
+ "confidence_threshold": 0.6,
93
+ "auto_retrain": True,
94
+ },
95
+ "auto_identity_analysis": True,
96
+ },
97
+ "cache": {
98
+ "ttl_hours": 72, # Shorter cache for freshness
99
+ "max_size_mb": 500,
100
+ },
101
+ "output": {
102
+ "formats": ["csv", "markdown", "json"],
103
+ },
104
+ }
105
+
106
+
107
+ class BalancedProfile(ConfigurationProfile):
108
+ """Balanced configuration profile.
109
+
110
+ This profile provides a good balance between performance and quality.
111
+ Suitable for most use cases.
112
+ """
113
+
114
+ name = "balanced"
115
+ description = "Balanced performance and quality (default)"
116
+
117
+ @classmethod
118
+ def get_settings(cls) -> dict[str, Any]:
119
+ """Get balanced settings."""
120
+ return {
121
+ "analysis": {
122
+ "branch_analysis": {
123
+ "strategy": "smart",
124
+ "max_branches_per_repo": 50,
125
+ "active_days_threshold": 90,
126
+ "branch_commit_limit": 1000,
127
+ },
128
+ "ml_categorization": {
129
+ "enabled": True,
130
+ "min_confidence": 0.6,
131
+ "semantic_weight": 0.7,
132
+ "batch_size": 100,
133
+ },
134
+ "commit_classification": {
135
+ "enabled": True,
136
+ "confidence_threshold": 0.5,
137
+ },
138
+ },
139
+ "cache": {
140
+ "ttl_hours": 168, # 1 week
141
+ "max_size_mb": 500,
142
+ },
143
+ "output": {
144
+ "formats": ["csv", "markdown"],
145
+ },
146
+ }
147
+
148
+
149
+ class MinimalProfile(ConfigurationProfile):
150
+ """Minimal configuration profile.
151
+
152
+ This profile runs only essential analysis features.
153
+ Suitable for basic metrics and quick overview.
154
+ """
155
+
156
+ name = "minimal"
157
+ description = "Essential features only"
158
+
159
+ @classmethod
160
+ def get_settings(cls) -> dict[str, Any]:
161
+ """Get minimal settings."""
162
+ return {
163
+ "analysis": {
164
+ "branch_analysis": {
165
+ "strategy": "main_only",
166
+ },
167
+ "ml_categorization": {
168
+ "enabled": False,
169
+ },
170
+ "commit_classification": {
171
+ "enabled": False,
172
+ },
173
+ "llm_classification": {
174
+ "enabled": False,
175
+ },
176
+ "auto_identity_analysis": False,
177
+ },
178
+ "output": {
179
+ "formats": ["csv"],
180
+ },
181
+ "cache": {
182
+ "ttl_hours": 720, # 30 days
183
+ },
184
+ }
185
+
186
+
187
+ class ProfileManager:
188
+ """Manages configuration profiles."""
189
+
190
+ # Registry of available profiles
191
+ _profiles: dict[str, type[ConfigurationProfile]] = {
192
+ "performance": PerformanceProfile,
193
+ "quality": QualityProfile,
194
+ "balanced": BalancedProfile,
195
+ "minimal": MinimalProfile,
196
+ }
197
+
198
+ @classmethod
199
+ def get_profile(cls, name: str) -> Optional[type[ConfigurationProfile]]:
200
+ """Get a configuration profile by name.
201
+
202
+ Args:
203
+ name: Profile name
204
+
205
+ Returns:
206
+ Profile class or None if not found
207
+ """
208
+ return cls._profiles.get(name.lower())
209
+
210
+ @classmethod
211
+ def list_profiles(cls) -> dict[str, str]:
212
+ """List available profiles.
213
+
214
+ Returns:
215
+ Dictionary of profile names to descriptions
216
+ """
217
+ return {name: profile.description for name, profile in cls._profiles.items()}
218
+
219
+ @classmethod
220
+ def apply_profile(cls, config_data: dict[str, Any], profile_name: str) -> dict[str, Any]:
221
+ """Apply a profile to configuration data.
222
+
223
+ Args:
224
+ config_data: Base configuration data
225
+ profile_name: Name of profile to apply
226
+
227
+ Returns:
228
+ Updated configuration data with profile settings
229
+
230
+ Raises:
231
+ ValueError: If profile not found
232
+ """
233
+ profile_class = cls.get_profile(profile_name)
234
+ if not profile_class:
235
+ available = ", ".join(cls._profiles.keys())
236
+ raise ValueError(
237
+ f"Unknown configuration profile: {profile_name}. "
238
+ f"Available profiles: {available}"
239
+ )
240
+
241
+ profile_settings = profile_class.get_settings()
242
+ # Profile settings are the base, config_data overrides them
243
+ return cls._deep_merge(profile_settings, config_data)
244
+
245
+ @staticmethod
246
+ def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
247
+ """Deep merge two dictionaries.
248
+
249
+ Args:
250
+ base: Base dictionary
251
+ override: Dictionary with values to override
252
+
253
+ Returns:
254
+ Merged dictionary
255
+ """
256
+ result = base.copy()
257
+
258
+ for key, value in override.items():
259
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
260
+ result[key] = ProfileManager._deep_merge(result[key], value)
261
+ else:
262
+ result[key] = value
263
+
264
+ return result
@@ -0,0 +1,124 @@
1
+ """Repository discovery and management for GitFlow Analytics."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ from github import Github
7
+
8
+ from .schema import GitHubConfig, RepositoryConfig
9
+
10
+
11
+ class RepositoryManager:
12
+ """Manages repository discovery and configuration."""
13
+
14
+ def __init__(self, github_config: GitHubConfig):
15
+ """Initialize repository manager.
16
+
17
+ Args:
18
+ github_config: GitHub configuration
19
+ """
20
+ self.github_config = github_config
21
+
22
+ def discover_organization_repositories(
23
+ self, clone_base_path: Optional[Path] = None
24
+ ) -> list[RepositoryConfig]:
25
+ """Discover repositories from GitHub organization.
26
+
27
+ Args:
28
+ clone_base_path: Base directory where repos should be cloned/found.
29
+
30
+ Returns:
31
+ List of discovered repository configurations.
32
+ """
33
+ if not self.github_config.organization or not self.github_config.token:
34
+ return []
35
+
36
+ github_client = Github(self.github_config.token, base_url=self.github_config.base_url)
37
+
38
+ try:
39
+ org = github_client.get_organization(self.github_config.organization)
40
+ discovered_repos = []
41
+
42
+ if clone_base_path is None:
43
+ raise ValueError("No base path available for repository cloning")
44
+
45
+ for repo in org.get_repos():
46
+ # Skip archived repositories
47
+ if repo.archived:
48
+ continue
49
+
50
+ # Create repository configuration
51
+ repo_path = clone_base_path / repo.name
52
+ repo_config = RepositoryConfig(
53
+ name=repo.name,
54
+ path=repo_path,
55
+ github_repo=repo.full_name,
56
+ project_key=repo.name.upper().replace("-", "_"),
57
+ branch=repo.default_branch,
58
+ )
59
+ discovered_repos.append(repo_config)
60
+
61
+ return discovered_repos
62
+
63
+ except Exception as e:
64
+ raise ValueError(
65
+ f"Failed to discover repositories from organization "
66
+ f"{self.github_config.organization}: {e}"
67
+ ) from e
68
+
69
+ def process_repository_config(
70
+ self, repo_data: dict, index: int, config_path: Path
71
+ ) -> RepositoryConfig:
72
+ """Process a single repository configuration entry.
73
+
74
+ Args:
75
+ repo_data: Repository configuration data
76
+ index: Repository index in the list (for error messages)
77
+ config_path: Path to configuration file (for error messages)
78
+
79
+ Returns:
80
+ RepositoryConfig instance
81
+
82
+ Raises:
83
+ ValueError: If repository configuration is invalid
84
+ """
85
+ from .errors import InvalidValueError, MissingFieldError
86
+
87
+ # Validate required repository fields
88
+ if not isinstance(repo_data, dict):
89
+ raise InvalidValueError(
90
+ f"repositories[{index}]",
91
+ type(repo_data).__name__,
92
+ "must be a YAML object with name and path",
93
+ config_path,
94
+ valid_values=["object with 'name' and 'path' fields"],
95
+ )
96
+
97
+ if "name" not in repo_data or repo_data["name"] is None:
98
+ raise MissingFieldError(
99
+ "name", f"repositories[{index}]", config_path, example='name: "your-repo-name"'
100
+ )
101
+
102
+ if "path" not in repo_data or repo_data["path"] is None:
103
+ raise MissingFieldError(
104
+ "path",
105
+ f"repositories[{index}] ('{repo_data.get('name', 'unknown')}')",
106
+ config_path,
107
+ example='path: "/path/to/repo"',
108
+ )
109
+
110
+ # Handle github_repo with owner/organization fallback
111
+ github_repo = repo_data.get("github_repo")
112
+ if github_repo and "/" not in github_repo:
113
+ if self.github_config.organization:
114
+ github_repo = f"{self.github_config.organization}/{github_repo}"
115
+ elif self.github_config.owner:
116
+ github_repo = f"{self.github_config.owner}/{github_repo}"
117
+
118
+ return RepositoryConfig(
119
+ name=repo_data["name"],
120
+ path=repo_data["path"],
121
+ github_repo=github_repo,
122
+ project_key=repo_data.get("project_key"),
123
+ branch=repo_data.get("branch"),
124
+ )