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
@@ -1,38 +1,38 @@
1
1
  """Main TUI application for GitFlow Analytics."""
2
2
 
3
- import asyncio
3
+ import contextlib
4
4
  from pathlib import Path
5
5
  from typing import Optional
6
6
 
7
7
  from textual.app import App, ComposeResult
8
8
  from textual.binding import Binding
9
9
 
10
- from .screens.main_screen import MainScreen
11
- from .screens.configuration_screen import ConfigurationScreen
10
+ from gitflow_analytics.config import Config, ConfigLoader
11
+
12
12
  from .screens.analysis_progress_screen import AnalysisProgressScreen
13
- from .screens.results_screen import ResultsScreen
13
+ from .screens.configuration_screen import ConfigurationScreen
14
14
  from .screens.loading_screen import InitializationLoadingScreen
15
- from gitflow_analytics.config import ConfigLoader, Config
15
+ from .screens.main_screen import MainScreen
16
16
 
17
17
 
18
18
  class GitFlowAnalyticsApp(App):
19
19
  """
20
20
  Main Terminal User Interface application for GitFlow Analytics.
21
-
21
+
22
22
  WHY: Provides a comprehensive TUI that guides users through the entire
23
23
  analytics workflow from configuration to results analysis. Designed to
24
24
  be more user-friendly than command-line interface while maintaining
25
25
  the power and flexibility of the core analysis engine.
26
-
26
+
27
27
  DESIGN DECISION: Uses a screen-based navigation model where each major
28
28
  workflow step (configuration, analysis, results) has its own dedicated
29
29
  screen. This provides clear context separation and allows for complex
30
30
  interactions within each workflow step.
31
31
  """
32
-
32
+
33
33
  TITLE = "GitFlow Analytics"
34
34
  SUB_TITLE = "Developer Productivity Analysis"
35
-
35
+
36
36
  CSS = """
37
37
  /* Global styles */
38
38
  .screen-title {
@@ -265,26 +265,26 @@ class GitFlowAnalyticsApp(App):
265
265
  color: $warning;
266
266
  }
267
267
  """
268
-
268
+
269
269
  BINDINGS = [
270
270
  Binding("ctrl+q", "quit", "Quit", priority=True),
271
271
  Binding("ctrl+c", "quit", "Quit", priority=True),
272
272
  Binding("f1", "help", "Help"),
273
273
  Binding("ctrl+d", "toggle_dark", "Toggle Dark Mode"),
274
274
  ]
275
-
275
+
276
276
  def __init__(self) -> None:
277
277
  super().__init__()
278
278
  self.config: Optional[Config] = None
279
279
  self.config_path: Optional[Path] = None
280
280
  self.initialization_complete = False
281
281
  self._nlp_engine = None
282
-
282
+
283
283
  def compose(self) -> ComposeResult:
284
284
  """
285
285
  Compose the main application. We don't yield screens here during startup
286
286
  to avoid screen stack management issues.
287
-
287
+
288
288
  WHY: Instead of yielding screens in compose, we handle the initial screen
289
289
  setup in on_mount to ensure proper screen stack management and avoid
290
290
  IndexError when transitioning between screens.
@@ -292,11 +292,11 @@ class GitFlowAnalyticsApp(App):
292
292
  # Don't yield screens here - handle in on_mount for proper screen management
293
293
  # Return empty to avoid NoneType iteration error
294
294
  return iter([])
295
-
295
+
296
296
  def on_mount(self) -> None:
297
297
  """
298
298
  Handle application startup with loading screen.
299
-
299
+
300
300
  WHY: Shows loading screen immediately while heavy initialization
301
301
  happens in the background, providing better user experience.
302
302
  Using push_screen instead of compose to avoid screen stack issues.
@@ -304,29 +304,29 @@ class GitFlowAnalyticsApp(App):
304
304
  # Set up application title
305
305
  self.title = "GitFlow Analytics"
306
306
  self.sub_title = "Developer Productivity Analysis"
307
-
307
+
308
308
  # Start with loading screen during initialization
309
309
  if not self.initialization_complete:
310
310
  loading_screen = InitializationLoadingScreen(
311
311
  config_loader_func=self._load_default_config,
312
312
  nlp_init_func=self._initialize_nlp_engine,
313
313
  loading_message="Initializing GitFlow Analytics...",
314
- id="loading-screen"
314
+ id="loading-screen",
315
315
  )
316
316
  self.push_screen(loading_screen)
317
317
  else:
318
318
  # Show main screen if already initialized
319
319
  main_screen = MainScreen(self.config, self.config_path, id="main-screen")
320
320
  self.push_screen(main_screen)
321
-
321
+
322
322
  def _load_default_config(self) -> Optional[tuple]:
323
323
  """
324
324
  Attempt to load configuration from default locations.
325
-
325
+
326
326
  WHY: Provides automatic configuration discovery to reduce setup
327
327
  friction for users who have configurations in standard locations.
328
328
  This version is designed to be called from the loading screen.
329
-
329
+
330
330
  @return: Tuple of (config, config_path) if found, None otherwise
331
331
  """
332
332
  default_config_paths = [
@@ -335,176 +335,179 @@ class GitFlowAnalyticsApp(App):
335
335
  Path(".gitflow/config.yaml"),
336
336
  Path("~/.gitflow/config.yaml").expanduser(),
337
337
  ]
338
-
338
+
339
339
  for config_path in default_config_paths:
340
340
  if config_path.exists():
341
341
  try:
342
342
  config = ConfigLoader.load(config_path)
343
343
  return (config, config_path)
344
-
345
- except Exception as e:
344
+
345
+ except Exception:
346
346
  # Log error but continue trying other paths
347
347
  continue
348
-
348
+
349
349
  return None
350
-
350
+
351
351
  def _initialize_nlp_engine(self, config: Optional[Config] = None) -> Optional[object]:
352
352
  """
353
353
  Initialize the NLP engine for qualitative analysis.
354
-
354
+
355
355
  WHY: spaCy model loading can be slow and should happen during
356
356
  the loading screen to provide user feedback. This method handles
357
357
  the heavy NLP initialization work.
358
-
358
+
359
359
  @param config: Configuration object with qualitative settings
360
360
  @return: Initialized NLP engine object or None if not needed/failed
361
361
  """
362
- if not config or not getattr(config, 'qualitative', None):
362
+ if not config or not getattr(config, "qualitative", None):
363
363
  return None
364
-
364
+
365
365
  if not config.qualitative.enabled:
366
366
  return None
367
-
367
+
368
368
  try:
369
369
  # Import here to avoid slow imports at module level
370
370
  from gitflow_analytics.qualitative.core.nlp_engine import NLPEngine
371
-
371
+
372
372
  # Initialize the NLP engine (this loads spaCy models)
373
373
  nlp_engine = NLPEngine(config.qualitative.nlp)
374
-
374
+
375
375
  # Validate the setup
376
376
  is_valid, issues = nlp_engine.validate_setup()
377
377
  if not is_valid:
378
378
  # Return None if validation fails, but don't raise exception
379
379
  return None
380
-
380
+
381
381
  return nlp_engine
382
-
382
+
383
383
  except ImportError:
384
384
  # Qualitative analysis dependencies not available
385
385
  return None
386
386
  except Exception:
387
387
  # Other initialization errors
388
388
  return None
389
-
389
+
390
390
  def on_initialization_loading_screen_initialization_complete(
391
391
  self, message: InitializationLoadingScreen.InitializationComplete
392
392
  ) -> None:
393
393
  """
394
394
  Handle completion of initialization loading.
395
-
395
+
396
396
  WHY: When the loading screen completes initialization, we need to
397
397
  update the app state and transition to the main screen. Using pop_screen
398
398
  and push_screen for proper screen stack management.
399
399
  """
400
400
  initialization_data = message.data
401
-
401
+
402
402
  # Update app state with loaded configuration
403
- config_result = initialization_data.get('config')
403
+ config_result = initialization_data.get("config")
404
404
  if config_result:
405
405
  self.config, self.config_path = config_result
406
-
406
+
407
407
  # Store NLP engine for later use
408
- self._nlp_engine = initialization_data.get('nlp')
409
-
408
+ self._nlp_engine = initialization_data.get("nlp")
409
+
410
410
  # Mark initialization as complete
411
411
  self.initialization_complete = True
412
-
412
+
413
413
  # Transition to main screen by popping loading screen and pushing main screen
414
- try:
414
+ with contextlib.suppress(Exception):
415
415
  self.pop_screen() # Remove loading screen
416
- except:
417
- pass # Ignore if no screen to pop
418
-
416
+
419
417
  main_screen = MainScreen(self.config, self.config_path, id="main-screen")
420
418
  self.push_screen(main_screen)
421
-
419
+
422
420
  # Show success notification if config was loaded
423
421
  if self.config and self.config_path:
424
422
  self.notify(f"Loaded configuration from {self.config_path}", severity="info")
425
-
423
+
426
424
  def on_initialization_loading_screen_loading_cancelled(
427
425
  self, message: InitializationLoadingScreen.LoadingCancelled
428
426
  ) -> None:
429
427
  """
430
428
  Handle cancellation of initialization loading.
431
-
429
+
432
430
  WHY: If user cancels loading, we should still show the main screen
433
431
  but without the loaded configuration. Using pop_screen and push_screen
434
432
  for proper screen stack management.
435
433
  """
436
434
  # Mark initialization as complete (even if cancelled)
437
435
  self.initialization_complete = True
438
-
436
+
439
437
  # Transition to main screen without configuration
440
- try:
438
+ with contextlib.suppress(Exception):
441
439
  self.pop_screen() # Remove loading screen
442
- except:
443
- pass # Ignore if no screen to pop
444
-
440
+
445
441
  main_screen = MainScreen(None, None, id="main-screen")
446
442
  self.push_screen(main_screen)
447
-
448
- self.notify("Initialization cancelled. You can load configuration manually.", severity="warning")
449
-
450
- def on_main_screen_new_analysis_requested(self, message: MainScreen.NewAnalysisRequested) -> None:
443
+
444
+ self.notify(
445
+ "Initialization cancelled. You can load configuration manually.", severity="warning"
446
+ )
447
+
448
+ def on_main_screen_new_analysis_requested(
449
+ self, message: MainScreen.NewAnalysisRequested
450
+ ) -> None:
451
451
  """Handle new analysis request from main screen."""
452
452
  if not self.config:
453
453
  self.notify("Please load or create a configuration first", severity="error")
454
454
  return
455
-
455
+
456
456
  # Launch analysis progress screen
457
457
  analysis_screen = AnalysisProgressScreen(
458
458
  config=self.config,
459
459
  weeks=12, # TODO: Get from config or user preference
460
- enable_qualitative=getattr(self.config, 'qualitative', None) and self.config.qualitative.enabled
460
+ enable_qualitative=getattr(self.config, "qualitative", None)
461
+ and self.config.qualitative.enabled,
461
462
  )
462
-
463
+
463
464
  self.push_screen(analysis_screen)
464
-
465
- def on_main_screen_configuration_requested(self, message: MainScreen.ConfigurationRequested) -> None:
465
+
466
+ def on_main_screen_configuration_requested(
467
+ self, message: MainScreen.ConfigurationRequested
468
+ ) -> None:
466
469
  """Handle configuration request from main screen."""
467
- config_screen = ConfigurationScreen(
468
- config_path=self.config_path,
469
- config=self.config
470
- )
471
-
470
+ config_screen = ConfigurationScreen(config_path=self.config_path, config=self.config)
471
+
472
472
  def handle_config_result(config: Optional[Config]) -> None:
473
473
  if config:
474
474
  self.config = config
475
475
  # TODO: Set config_path if provided
476
-
476
+
477
477
  # Update main screen
478
478
  main_screen = self.query_one("#main-screen", MainScreen)
479
479
  main_screen.update_config(self.config, self.config_path)
480
-
480
+
481
481
  self.notify("Configuration updated successfully", severity="success")
482
-
482
+
483
483
  self.push_screen(config_screen, handle_config_result)
484
-
485
- def on_main_screen_cache_status_requested(self, message: MainScreen.CacheStatusRequested) -> None:
484
+
485
+ def on_main_screen_cache_status_requested(
486
+ self, message: MainScreen.CacheStatusRequested
487
+ ) -> None:
486
488
  """Handle cache status request from main screen."""
487
489
  if not self.config:
488
490
  self.notify("No configuration loaded", severity="error")
489
491
  return
490
-
492
+
491
493
  try:
492
494
  from gitflow_analytics.core.cache import GitAnalysisCache
493
-
495
+
494
496
  cache = GitAnalysisCache(self.config.cache.directory)
495
497
  stats = cache.get_cache_stats()
496
-
498
+
497
499
  # Calculate cache size
498
500
  import os
501
+
499
502
  cache_size = 0
500
503
  try:
501
504
  for root, _dirs, files in os.walk(self.config.cache.directory):
502
505
  for f in files:
503
506
  cache_size += os.path.getsize(os.path.join(root, f))
504
507
  cache_size_mb = cache_size / 1024 / 1024
505
- except:
508
+ except Exception:
506
509
  cache_size_mb = 0
507
-
510
+
508
511
  message_text = f"""Cache Statistics:
509
512
  • Location: {self.config.cache.directory}
510
513
  • Cached commits: {stats['cached_commits']:,}
@@ -513,38 +516,42 @@ class GitFlowAnalyticsApp(App):
513
516
  • Stale entries: {stats['stale_commits']:,}
514
517
  • Cache size: {cache_size_mb:.1f} MB
515
518
  • TTL: {self.config.cache.ttl_hours} hours"""
516
-
519
+
517
520
  self.notify(message_text, severity="info")
518
-
521
+
519
522
  except Exception as e:
520
523
  self.notify(f"Failed to get cache statistics: {e}", severity="error")
521
-
522
- def on_main_screen_identity_management_requested(self, message: MainScreen.IdentityManagementRequested) -> None:
524
+
525
+ def on_main_screen_identity_management_requested(
526
+ self, message: MainScreen.IdentityManagementRequested
527
+ ) -> None:
523
528
  """Handle identity management request from main screen."""
524
529
  if not self.config:
525
530
  self.notify("No configuration loaded", severity="error")
526
531
  return
527
-
532
+
528
533
  try:
529
534
  from gitflow_analytics.core.identity import DeveloperIdentityResolver
530
-
535
+
531
536
  identity_resolver = DeveloperIdentityResolver(
532
537
  self.config.cache.directory / "identities.db"
533
538
  )
534
-
539
+
535
540
  developers = identity_resolver.get_developer_stats()
536
-
541
+
537
542
  if not developers:
538
543
  self.notify("No developer identities found. Run analysis first.", severity="info")
539
544
  return
540
-
545
+
541
546
  # Show top developers
542
- top_devs = sorted(developers, key=lambda d: d['total_commits'], reverse=True)[:10]
543
-
547
+ top_devs = sorted(developers, key=lambda d: d["total_commits"], reverse=True)[:10]
548
+
544
549
  dev_list = []
545
550
  for dev in top_devs:
546
- dev_list.append(f"• {dev['primary_name']}: {dev['total_commits']} commits, {dev['alias_count']} aliases")
547
-
551
+ dev_list.append(
552
+ f"• {dev['primary_name']}: {dev['total_commits']} commits, {dev['alias_count']} aliases"
553
+ )
554
+
548
555
  message_text = f"""Developer Identity Statistics:
549
556
  • Total unique developers: {len(developers)}
550
557
  • Manual mappings: {len(self.config.analysis.manual_identity_mappings) if self.config.analysis.manual_identity_mappings else 0}
@@ -553,12 +560,12 @@ Top Contributors:
553
560
  {chr(10).join(dev_list)}
554
561
 
555
562
  Use the CLI 'merge-identity' command to merge duplicate identities."""
556
-
563
+
557
564
  self.notify(message_text, severity="info")
558
-
565
+
559
566
  except Exception as e:
560
567
  self.notify(f"Failed to get identity information: {e}", severity="error")
561
-
568
+
562
569
  def on_main_screen_help_requested(self, message: MainScreen.HelpRequested) -> None:
563
570
  """Handle help request from main screen."""
564
571
  help_text = """GitFlow Analytics - Terminal UI Help
@@ -598,13 +605,13 @@ Use the CLI 'merge-identity' command to merge duplicate identities."""
598
605
  • Review identity mappings for accurate attribution
599
606
 
600
607
  For more information: https://github.com/bobmatnyc/gitflow-analytics"""
601
-
608
+
602
609
  self.notify(help_text, severity="info")
603
-
610
+
604
611
  def action_quit(self) -> None:
605
612
  """
606
613
  Quit the application with confirmation if analysis is running.
607
-
614
+
608
615
  WHY: Provides safe exit that checks for running operations to
609
616
  prevent data loss or corruption from incomplete analysis.
610
617
  """
@@ -614,93 +621,89 @@ For more information: https://github.com/bobmatnyc/gitflow-analytics"""
614
621
  if analysis_screen:
615
622
  # TODO: Show confirmation dialog
616
623
  pass
617
- except:
624
+ except Exception:
618
625
  pass
619
-
626
+
620
627
  self.exit()
621
-
628
+
622
629
  def action_help(self) -> None:
623
630
  """Show application help."""
624
631
  # Trigger help from main screen if available
625
632
  try:
626
633
  main_screen = self.query_one("#main-screen", MainScreen)
627
634
  main_screen.action_help()
628
- except:
635
+ except Exception:
629
636
  # Fallback to direct help
630
637
  self.on_main_screen_help_requested(MainScreen.HelpRequested())
631
-
638
+
632
639
  def action_toggle_dark(self) -> None:
633
640
  """
634
641
  Toggle between dark and light themes.
635
-
642
+
636
643
  WHY: Provides theme flexibility for different user preferences
637
644
  and working environments (bright vs dim lighting conditions).
638
645
  """
639
646
  self.dark = not self.dark
640
647
  theme = "dark" if self.dark else "light"
641
648
  self.notify(f"Switched to {theme} theme", severity="info")
642
-
649
+
643
650
  def get_current_config(self) -> Optional[Config]:
644
651
  """Get the currently loaded configuration."""
645
652
  return self.config
646
-
653
+
647
654
  def get_current_config_path(self) -> Optional[Path]:
648
655
  """Get the path of the currently loaded configuration."""
649
656
  return self.config_path
650
-
657
+
651
658
  def get_nlp_engine(self) -> Optional[object]:
652
659
  """
653
660
  Get the initialized NLP engine for qualitative analysis.
654
-
661
+
655
662
  WHY: Provides access to the pre-loaded NLP engine to avoid
656
663
  re-initialization overhead during analysis operations.
657
-
664
+
658
665
  @return: Initialized NLP engine or None if not available
659
666
  """
660
667
  return self._nlp_engine
661
-
668
+
662
669
  def update_config(self, config: Config, config_path: Optional[Path] = None) -> None:
663
670
  """
664
671
  Update the application configuration and refresh relevant screens.
665
-
672
+
666
673
  WHY: Provides centralized configuration updates that can be called
667
674
  from any screen to ensure all parts of the application stay in sync.
668
675
  """
669
676
  self.config = config
670
677
  self.config_path = config_path
671
-
678
+
672
679
  # Update main screen if visible
673
680
  try:
674
681
  main_screen = self.query_one("#main-screen", MainScreen)
675
682
  main_screen.update_config(config, config_path)
676
- except:
683
+ except Exception:
677
684
  pass
678
-
685
+
679
686
  async def run_analysis_async(
680
- self,
681
- weeks: int = 12,
682
- enable_qualitative: bool = True
687
+ self, weeks: int = 12, enable_qualitative: bool = True
683
688
  ) -> Optional[dict]:
684
689
  """
685
690
  Run analysis asynchronously and return results.
686
-
691
+
687
692
  WHY: Provides programmatic access to analysis functionality
688
693
  for integration with other systems or automated workflows.
689
-
694
+
690
695
  @param weeks: Number of weeks to analyze
691
696
  @param enable_qualitative: Whether to enable qualitative analysis
692
697
  @return: Analysis results dictionary or None if failed
693
698
  """
694
699
  if not self.config:
695
700
  raise ValueError("No configuration loaded")
696
-
701
+
697
702
  # Create analysis screen
698
- analysis_screen = AnalysisProgressScreen(
699
- config=self.config,
700
- weeks=weeks,
701
- enable_qualitative=enable_qualitative
703
+ AnalysisProgressScreen(
704
+ config=self.config, weeks=weeks, enable_qualitative=enable_qualitative
702
705
  )
703
-
706
+
704
707
  # Run analysis (this would need to be implemented properly)
705
708
  # For now, return None to indicate not implemented
706
709
  return None
@@ -709,7 +712,7 @@ For more information: https://github.com/bobmatnyc/gitflow-analytics"""
709
712
  def main() -> None:
710
713
  """
711
714
  Main entry point for the TUI application.
712
-
715
+
713
716
  WHY: Provides a clean entry point that can be called from the CLI
714
717
  or used as a standalone application launcher.
715
718
  """
@@ -718,4 +721,4 @@ def main() -> None:
718
721
 
719
722
 
720
723
  if __name__ == "__main__":
721
- main()
724
+ main()
@@ -1,8 +1,8 @@
1
1
  """TUI screens for GitFlow Analytics."""
2
2
 
3
- from .main_screen import MainScreen
4
- from .configuration_screen import ConfigurationScreen
5
3
  from .analysis_progress_screen import AnalysisProgressScreen
4
+ from .configuration_screen import ConfigurationScreen
5
+ from .main_screen import MainScreen
6
6
  from .results_screen import ResultsScreen
7
7
 
8
- __all__ = ["MainScreen", "ConfigurationScreen", "AnalysisProgressScreen", "ResultsScreen"]
8
+ __all__ = ["MainScreen", "ConfigurationScreen", "AnalysisProgressScreen", "ResultsScreen"]