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
@@ -3,28 +3,27 @@
3
3
  from pathlib import Path
4
4
  from typing import Optional
5
5
 
6
- from textual.widgets import Header, Footer, Button, Label, Static, Rule
7
- from textual.containers import Container, Vertical, Horizontal
8
- from textual.screen import Screen
9
6
  from textual.binding import Binding
7
+ from textual.containers import Container, Vertical
10
8
  from textual.message import Message
9
+ from textual.screen import Screen
10
+ from textual.widgets import Button, Footer, Header, Label, Rule, Static
11
11
 
12
- from ..widgets.export_modal import ExportModal
13
12
  from gitflow_analytics.config import Config
14
13
 
15
14
 
16
15
  class MainScreen(Screen):
17
16
  """
18
17
  Main dashboard screen showing project information and navigation options.
19
-
18
+
20
19
  WHY: Serves as the primary entry point for the TUI, providing users with
21
20
  an overview of their configuration and clear navigation to all major features.
22
-
21
+
23
22
  DESIGN DECISION: Uses a dashboard layout rather than a menu-driven approach
24
23
  to provide immediate visibility into the current configuration status and
25
24
  quick access to common operations.
26
25
  """
27
-
26
+
28
27
  BINDINGS = [
29
28
  Binding("ctrl+q", "quit", "Quit"),
30
29
  Binding("ctrl+n", "new_analysis", "New Analysis"),
@@ -33,73 +32,84 @@ class MainScreen(Screen):
33
32
  Binding("c", "cache_status", "Cache Status"),
34
33
  Binding("i", "manage_identities", "Identities"),
35
34
  ]
36
-
35
+
37
36
  class NewAnalysisRequested(Message):
38
37
  """Message sent when new analysis is requested."""
38
+
39
39
  def __init__(self) -> None:
40
40
  super().__init__()
41
-
41
+
42
42
  class ConfigurationRequested(Message):
43
43
  """Message sent when configuration is requested."""
44
+
44
45
  def __init__(self) -> None:
45
46
  super().__init__()
46
-
47
+
47
48
  class CacheStatusRequested(Message):
48
49
  """Message sent when cache status is requested."""
50
+
49
51
  def __init__(self) -> None:
50
52
  super().__init__()
51
-
53
+
52
54
  class IdentityManagementRequested(Message):
53
55
  """Message sent when identity management is requested."""
56
+
54
57
  def __init__(self) -> None:
55
58
  super().__init__()
56
-
59
+
57
60
  class HelpRequested(Message):
58
61
  """Message sent when help is requested."""
62
+
59
63
  def __init__(self) -> None:
60
64
  super().__init__()
61
-
65
+
62
66
  def __init__(
63
67
  self,
64
68
  config: Optional[Config] = None,
65
69
  config_path: Optional[Path] = None,
66
70
  *,
67
71
  name: Optional[str] = None,
68
- id: Optional[str] = None
72
+ id: Optional[str] = None,
69
73
  ) -> None:
70
74
  super().__init__(name=name, id=id)
71
75
  self.config = config
72
76
  self.config_path = config_path
73
-
77
+
74
78
  def compose(self):
75
79
  """Compose the main screen."""
76
80
  yield Header()
77
-
81
+
78
82
  with Container(id="main-container"):
79
83
  yield Label("GitFlow Analytics", classes="screen-title")
80
84
  yield Static("Developer Productivity Analysis Tool", id="subtitle")
81
-
85
+
82
86
  # Configuration status section
83
87
  with Container(classes="status-panel"):
84
88
  yield Label("Configuration Status", classes="section-title")
85
-
89
+
86
90
  if self.config:
87
- config_status = f"✅ Loaded: {self.config_path}" if self.config_path else "✅ Configuration loaded"
91
+ config_status = (
92
+ f"✅ Loaded: {self.config_path}"
93
+ if self.config_path
94
+ else "✅ Configuration loaded"
95
+ )
88
96
  yield Static(config_status, id="config-status")
89
-
97
+
90
98
  # Show key configuration details
91
99
  details = self._get_config_summary()
92
100
  yield Static(details, id="config-details")
93
101
  else:
94
102
  yield Static("❌ No configuration loaded", id="config-status")
95
- yield Static("Load or create a configuration to begin analysis", id="config-help")
96
-
103
+ yield Static(
104
+ "Load or create a configuration to begin analysis", id="config-help"
105
+ )
106
+
97
107
  yield Rule()
98
-
108
+
99
109
  # Main actions section
100
110
  with Container(classes="actions-panel"):
101
111
  yield Label("Available Actions", classes="section-title")
102
-
112
+
103
113
  with Vertical(id="action-buttons"):
104
114
  if self.config:
105
115
  yield Button("🚀 Run Analysis", variant="primary", id="run-analysis")
@@ -109,84 +119,89 @@ class MainScreen(Screen):
109
119
  else:
110
120
  yield Button("📁 Load Configuration", variant="primary", id="load-config")
111
121
  yield Button("➕ Create New Configuration", id="new-config")
112
-
122
+
113
123
  yield Rule()
114
124
  yield Button("❓ Help & Documentation", id="help")
115
-
125
+
116
126
  # Quick stats section (if config loaded)
117
127
  if self.config:
118
128
  with Container(classes="stats-panel"):
119
129
  yield Label("Quick Information", classes="section-title")
120
130
  stats = self._get_quick_stats()
121
131
  yield Static(stats, id="quick-stats")
122
-
132
+
123
133
  yield Footer()
124
-
134
+
125
135
  def _get_config_summary(self) -> str:
126
136
  """
127
137
  Generate configuration summary for display.
128
-
138
+
129
139
  WHY: Provides users with immediate visibility into their current
130
140
  configuration without needing to navigate to a separate screen.
131
141
  """
132
142
  if not self.config:
133
143
  return ""
134
-
144
+
135
145
  lines = []
136
-
146
+
137
147
  # Repository count
138
148
  repo_count = len(self.config.repositories) if self.config.repositories else 0
139
149
  if self.config.github.organization and not self.config.repositories:
140
150
  lines.append(f"• Organization: {self.config.github.organization} (auto-discovery)")
141
151
  else:
142
152
  lines.append(f"• Repositories: {repo_count} configured")
143
-
153
+
144
154
  # GitHub integration
145
155
  if self.config.github.token:
146
156
  lines.append("✅ GitHub API configured")
147
157
  else:
148
158
  lines.append("⚠️ GitHub API not configured")
149
-
159
+
150
160
  # Qualitative analysis
151
- if hasattr(self.config, 'qualitative') and self.config.qualitative and self.config.qualitative.enabled:
161
+ if (
162
+ hasattr(self.config, "qualitative")
163
+ and self.config.qualitative
164
+ and self.config.qualitative.enabled
165
+ ):
152
166
  lines.append("✅ Qualitative analysis enabled")
153
167
  else:
154
168
  lines.append("⚠️ Qualitative analysis disabled")
155
-
169
+
156
170
  # JIRA integration
157
- if hasattr(self.config, 'jira') and self.config.jira and self.config.jira.base_url:
171
+ if hasattr(self.config, "jira") and self.config.jira and self.config.jira.base_url:
158
172
  lines.append("✅ JIRA integration configured")
159
173
  else:
160
174
  lines.append("⚠️ JIRA integration not configured")
161
-
175
+
162
176
  # Cache configuration
163
177
  lines.append(f"• Cache TTL: {self.config.cache.ttl_hours}h")
164
-
178
+
165
179
  return "\n".join(lines)
166
-
180
+
167
181
  def _get_quick_stats(self) -> str:
168
182
  """
169
183
  Generate quick statistics if cache data is available.
170
-
184
+
171
185
  WHY: Shows users what data is already available from previous runs,
172
186
  helping them understand if they need to run a new analysis.
173
187
  """
174
188
  try:
175
189
  from ....core.cache import GitAnalysisCache
176
-
190
+
177
191
  cache = GitAnalysisCache(self.config.cache.directory)
178
192
  stats = cache.get_cache_stats()
179
-
193
+
180
194
  lines = []
181
195
  lines.append(f"• Cached commits: {stats['cached_commits']:,}")
182
196
  lines.append(f"• Cached PRs: {stats['cached_prs']:,}")
183
197
  lines.append(f"• Cached issues: {stats['cached_issues']:,}")
184
-
185
- if stats['stale_commits'] > 0:
198
+
199
+ if stats["stale_commits"] > 0:
186
200
  lines.append(f"• Stale entries: {stats['stale_commits']:,}")
187
-
201
+
188
202
  # Cache size
189
203
  import os
204
+
190
205
  cache_size = 0
191
206
  try:
192
207
  for root, _dirs, files in os.walk(self.config.cache.directory):
@@ -194,14 +209,14 @@ class MainScreen(Screen):
194
209
  cache_size += os.path.getsize(os.path.join(root, f))
195
210
  cache_size_mb = cache_size / 1024 / 1024
196
211
  lines.append(f"• Cache size: {cache_size_mb:.1f} MB")
197
- except:
212
+ except Exception:
198
213
  pass
199
-
214
+
200
215
  return "\n".join(lines)
201
-
216
+
202
217
  except Exception:
203
218
  return "Cache statistics unavailable"
204
-
219
+
205
220
  def on_button_pressed(self, event: Button.Pressed) -> None:
206
221
  """Handle button press events."""
207
222
  button_actions = {
@@ -213,92 +228,94 @@ class MainScreen(Screen):
213
228
  "manage-identities": self._manage_identities,
214
229
  "help": self._show_help,
215
230
  }
216
-
231
+
217
232
  action = button_actions.get(event.button.id)
218
233
  if action:
219
234
  action()
220
-
235
+
221
236
  def _run_analysis(self) -> None:
222
237
  """Request new analysis."""
223
238
  if not self.config:
224
239
  self.notify("Please load or create a configuration first", severity="error")
225
240
  return
226
241
  self.post_message(self.NewAnalysisRequested())
227
-
242
+
228
243
  def _load_config(self) -> None:
229
244
  """Request configuration loading."""
230
245
  self.post_message(self.ConfigurationRequested())
231
-
246
+
232
247
  def _new_config(self) -> None:
233
248
  """Request new configuration creation."""
234
249
  self.post_message(self.ConfigurationRequested())
235
-
250
+
236
251
  def _edit_config(self) -> None:
237
252
  """Request configuration editing."""
238
253
  self.post_message(self.ConfigurationRequested())
239
-
254
+
240
255
  def _cache_status(self) -> None:
241
256
  """Request cache status display."""
242
257
  self.post_message(self.CacheStatusRequested())
243
-
258
+
244
259
  def _manage_identities(self) -> None:
245
260
  """Request identity management."""
246
261
  self.post_message(self.IdentityManagementRequested())
247
-
262
+
248
263
  def _show_help(self) -> None:
249
264
  """Request help display."""
250
265
  self.post_message(self.HelpRequested())
251
-
266
+
252
267
  # Action handlers for key bindings
253
268
  def action_quit(self) -> None:
254
269
  """Quit the application."""
255
270
  self.app.exit()
256
-
271
+
257
272
  def action_new_analysis(self) -> None:
258
273
  """Start new analysis via keyboard shortcut."""
259
274
  self._run_analysis()
260
-
275
+
261
276
  def action_open_config(self) -> None:
262
277
  """Open configuration via keyboard shortcut."""
263
278
  self._load_config()
264
-
279
+
265
280
  def action_help(self) -> None:
266
281
  """Show help via keyboard shortcut."""
267
282
  self._show_help()
268
-
283
+
269
284
  def action_cache_status(self) -> None:
270
285
  """Show cache status via keyboard shortcut."""
271
286
  self._cache_status()
272
-
287
+
273
288
  def action_manage_identities(self) -> None:
274
289
  """Manage identities via keyboard shortcut."""
275
290
  self._manage_identities()
276
-
291
+
277
292
  def update_config(self, config: Config, config_path: Optional[Path] = None) -> None:
278
293
  """
279
294
  Update the configuration and refresh the display.
280
-
295
+
281
296
  WHY: Allows the main screen to be updated when configuration changes
282
297
  without requiring a full screen rebuild, maintaining user context.
283
298
  """
284
299
  self.config = config
285
300
  self.config_path = config_path
286
-
301
+
287
302
  # Update configuration status
288
303
  if self.config:
289
- config_status = f"✅ Loaded: {self.config_path}" if self.config_path else "✅ Configuration loaded"
304
+ config_status = (
305
+ f"✅ Loaded: {self.config_path}" if self.config_path else "✅ Configuration loaded"
306
+ )
290
307
  self.query_one("#config-status", Static).update(config_status)
291
-
308
+
292
309
  # Update configuration details
293
310
  details = self._get_config_summary()
294
311
  self.query_one("#config-details", Static).update(details)
295
-
312
+
296
313
  # Update quick stats if available
297
314
  try:
298
315
  stats = self._get_quick_stats()
299
316
  self.query_one("#quick-stats", Static).update(stats)
300
- except:
317
+ except Exception:
301
318
  pass
302
-
319
+
303
320
  # Refresh to rebuild buttons with new state
304
- self.refresh(recompose=True)
321
+ self.refresh(recompose=True)