gitflow-analytics 1.0.3__py3-none-any.whl → 1.3.11__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/__init__.py +31 -0
- gitflow_analytics/classification/batch_classifier.py +752 -0
- gitflow_analytics/classification/classifier.py +464 -0
- gitflow_analytics/classification/feature_extractor.py +725 -0
- gitflow_analytics/classification/linguist_analyzer.py +574 -0
- gitflow_analytics/classification/model.py +455 -0
- gitflow_analytics/cli.py +4158 -350
- gitflow_analytics/cli_rich.py +198 -48
- gitflow_analytics/config/__init__.py +43 -0
- gitflow_analytics/config/errors.py +261 -0
- gitflow_analytics/config/loader.py +905 -0
- gitflow_analytics/config/profiles.py +264 -0
- gitflow_analytics/config/repository.py +124 -0
- gitflow_analytics/config/schema.py +444 -0
- gitflow_analytics/config/validator.py +154 -0
- gitflow_analytics/config.py +44 -508
- gitflow_analytics/core/analyzer.py +1209 -98
- gitflow_analytics/core/cache.py +1337 -29
- gitflow_analytics/core/data_fetcher.py +1285 -0
- gitflow_analytics/core/identity.py +363 -14
- gitflow_analytics/core/metrics_storage.py +526 -0
- gitflow_analytics/core/progress.py +372 -0
- gitflow_analytics/core/schema_version.py +269 -0
- gitflow_analytics/extractors/ml_tickets.py +1100 -0
- gitflow_analytics/extractors/story_points.py +8 -1
- gitflow_analytics/extractors/tickets.py +749 -11
- gitflow_analytics/identity_llm/__init__.py +6 -0
- gitflow_analytics/identity_llm/analysis_pass.py +231 -0
- gitflow_analytics/identity_llm/analyzer.py +464 -0
- gitflow_analytics/identity_llm/models.py +76 -0
- gitflow_analytics/integrations/github_integration.py +175 -11
- gitflow_analytics/integrations/jira_integration.py +461 -24
- gitflow_analytics/integrations/orchestrator.py +124 -1
- gitflow_analytics/metrics/activity_scoring.py +322 -0
- gitflow_analytics/metrics/branch_health.py +470 -0
- gitflow_analytics/metrics/dora.py +379 -20
- gitflow_analytics/models/database.py +843 -53
- gitflow_analytics/pm_framework/__init__.py +115 -0
- gitflow_analytics/pm_framework/adapters/__init__.py +50 -0
- gitflow_analytics/pm_framework/adapters/jira_adapter.py +1845 -0
- gitflow_analytics/pm_framework/base.py +406 -0
- gitflow_analytics/pm_framework/models.py +211 -0
- gitflow_analytics/pm_framework/orchestrator.py +652 -0
- gitflow_analytics/pm_framework/registry.py +333 -0
- gitflow_analytics/qualitative/__init__.py +9 -10
- gitflow_analytics/qualitative/chatgpt_analyzer.py +259 -0
- gitflow_analytics/qualitative/classifiers/__init__.py +3 -3
- gitflow_analytics/qualitative/classifiers/change_type.py +518 -244
- gitflow_analytics/qualitative/classifiers/domain_classifier.py +272 -165
- gitflow_analytics/qualitative/classifiers/intent_analyzer.py +321 -222
- gitflow_analytics/qualitative/classifiers/llm/__init__.py +35 -0
- gitflow_analytics/qualitative/classifiers/llm/base.py +193 -0
- gitflow_analytics/qualitative/classifiers/llm/batch_processor.py +383 -0
- gitflow_analytics/qualitative/classifiers/llm/cache.py +479 -0
- gitflow_analytics/qualitative/classifiers/llm/cost_tracker.py +435 -0
- gitflow_analytics/qualitative/classifiers/llm/openai_client.py +403 -0
- gitflow_analytics/qualitative/classifiers/llm/prompts.py +373 -0
- gitflow_analytics/qualitative/classifiers/llm/response_parser.py +287 -0
- gitflow_analytics/qualitative/classifiers/llm_commit_classifier.py +607 -0
- gitflow_analytics/qualitative/classifiers/risk_analyzer.py +215 -189
- gitflow_analytics/qualitative/core/__init__.py +4 -4
- gitflow_analytics/qualitative/core/llm_fallback.py +239 -235
- gitflow_analytics/qualitative/core/nlp_engine.py +157 -148
- gitflow_analytics/qualitative/core/pattern_cache.py +214 -192
- gitflow_analytics/qualitative/core/processor.py +381 -248
- gitflow_analytics/qualitative/enhanced_analyzer.py +2236 -0
- gitflow_analytics/qualitative/example_enhanced_usage.py +420 -0
- gitflow_analytics/qualitative/models/__init__.py +7 -7
- gitflow_analytics/qualitative/models/schemas.py +155 -121
- gitflow_analytics/qualitative/utils/__init__.py +4 -4
- gitflow_analytics/qualitative/utils/batch_processor.py +136 -123
- gitflow_analytics/qualitative/utils/cost_tracker.py +142 -140
- gitflow_analytics/qualitative/utils/metrics.py +172 -158
- gitflow_analytics/qualitative/utils/text_processing.py +146 -104
- gitflow_analytics/reports/__init__.py +100 -0
- gitflow_analytics/reports/analytics_writer.py +539 -14
- gitflow_analytics/reports/base.py +648 -0
- gitflow_analytics/reports/branch_health_writer.py +322 -0
- gitflow_analytics/reports/classification_writer.py +924 -0
- gitflow_analytics/reports/cli_integration.py +427 -0
- gitflow_analytics/reports/csv_writer.py +1676 -212
- gitflow_analytics/reports/data_models.py +504 -0
- gitflow_analytics/reports/database_report_generator.py +427 -0
- gitflow_analytics/reports/example_usage.py +344 -0
- gitflow_analytics/reports/factory.py +499 -0
- gitflow_analytics/reports/formatters.py +698 -0
- gitflow_analytics/reports/html_generator.py +1116 -0
- gitflow_analytics/reports/interfaces.py +489 -0
- gitflow_analytics/reports/json_exporter.py +2770 -0
- gitflow_analytics/reports/narrative_writer.py +2287 -158
- gitflow_analytics/reports/story_point_correlation.py +1144 -0
- gitflow_analytics/reports/weekly_trends_writer.py +389 -0
- gitflow_analytics/training/__init__.py +5 -0
- gitflow_analytics/training/model_loader.py +377 -0
- gitflow_analytics/training/pipeline.py +550 -0
- gitflow_analytics/tui/__init__.py +1 -1
- gitflow_analytics/tui/app.py +129 -126
- gitflow_analytics/tui/screens/__init__.py +3 -3
- gitflow_analytics/tui/screens/analysis_progress_screen.py +188 -179
- gitflow_analytics/tui/screens/configuration_screen.py +154 -178
- gitflow_analytics/tui/screens/loading_screen.py +100 -110
- gitflow_analytics/tui/screens/main_screen.py +89 -72
- gitflow_analytics/tui/screens/results_screen.py +305 -281
- gitflow_analytics/tui/widgets/__init__.py +2 -2
- gitflow_analytics/tui/widgets/data_table.py +67 -69
- gitflow_analytics/tui/widgets/export_modal.py +76 -76
- gitflow_analytics/tui/widgets/progress_widget.py +41 -46
- gitflow_analytics-1.3.11.dist-info/METADATA +1015 -0
- gitflow_analytics-1.3.11.dist-info/RECORD +122 -0
- gitflow_analytics-1.0.3.dist-info/METADATA +0 -490
- gitflow_analytics-1.0.3.dist-info/RECORD +0 -62
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/WHEEL +0 -0
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/entry_points.txt +0 -0
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/licenses/LICENSE +0 -0
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/top_level.txt +0 -0
gitflow_analytics/tui/app.py
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
"""Main TUI application for GitFlow Analytics."""
|
|
2
2
|
|
|
3
|
-
import
|
|
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 .
|
|
11
|
-
|
|
10
|
+
from gitflow_analytics.config import Config, ConfigLoader
|
|
11
|
+
|
|
12
12
|
from .screens.analysis_progress_screen import AnalysisProgressScreen
|
|
13
|
-
from .screens.
|
|
13
|
+
from .screens.configuration_screen import ConfigurationScreen
|
|
14
14
|
from .screens.loading_screen import InitializationLoadingScreen
|
|
15
|
-
from
|
|
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
|
|
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,
|
|
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(
|
|
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(
|
|
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
|
-
|
|
414
|
+
with contextlib.suppress(Exception):
|
|
415
415
|
self.pop_screen() # Remove loading screen
|
|
416
|
-
|
|
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
|
-
|
|
438
|
+
with contextlib.suppress(Exception):
|
|
441
439
|
self.pop_screen() # Remove loading screen
|
|
442
|
-
|
|
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(
|
|
449
|
-
|
|
450
|
-
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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[
|
|
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(
|
|
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
|
-
|
|
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"]
|