gitflow-analytics 3.13.0__py3-none-any.whl → 3.13.5__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/cli.py +143 -77
- gitflow_analytics/cli_wizards/menu.py +147 -6
- gitflow_analytics/config/loader.py +3 -1
- gitflow_analytics/config/profiles.py +1 -2
- gitflow_analytics/core/data_fetcher.py +0 -2
- gitflow_analytics/extractors/tickets.py +3 -1
- gitflow_analytics/integrations/github_integration.py +1 -1
- gitflow_analytics/integrations/jira_integration.py +1 -1
- gitflow_analytics/qualitative/chatgpt_analyzer.py +15 -15
- gitflow_analytics/qualitative/classifiers/llm/prompts.py +1 -1
- gitflow_analytics/qualitative/core/processor.py +1 -2
- gitflow_analytics/qualitative/enhanced_analyzer.py +24 -8
- gitflow_analytics/ui/progress_display.py +14 -6
- gitflow_analytics/verify_activity.py +1 -1
- {gitflow_analytics-3.13.0.dist-info → gitflow_analytics-3.13.5.dist-info}/METADATA +37 -1
- {gitflow_analytics-3.13.0.dist-info → gitflow_analytics-3.13.5.dist-info}/RECORD +21 -21
- {gitflow_analytics-3.13.0.dist-info → gitflow_analytics-3.13.5.dist-info}/WHEEL +0 -0
- {gitflow_analytics-3.13.0.dist-info → gitflow_analytics-3.13.5.dist-info}/entry_points.txt +0 -0
- {gitflow_analytics-3.13.0.dist-info → gitflow_analytics-3.13.5.dist-info}/licenses/LICENSE +0 -0
- {gitflow_analytics-3.13.0.dist-info → gitflow_analytics-3.13.5.dist-info}/top_level.txt +0 -0
gitflow_analytics/_version.py
CHANGED
gitflow_analytics/cli.py
CHANGED
|
@@ -429,8 +429,7 @@ def cli(ctx: click.Context) -> None:
|
|
|
429
429
|
"--use-batch-classification/--use-legacy-classification",
|
|
430
430
|
default=True,
|
|
431
431
|
help=(
|
|
432
|
-
"Use batch LLM classification on pre-fetched data (Step 2 of 2) - "
|
|
433
|
-
"now the default behavior"
|
|
432
|
+
"Use batch LLM classification on pre-fetched data (Step 2 of 2) - now the default behavior"
|
|
434
433
|
),
|
|
435
434
|
)
|
|
436
435
|
@click.option(
|
|
@@ -1213,8 +1212,7 @@ def analyze(
|
|
|
1213
1212
|
display.update_progress_task(
|
|
1214
1213
|
"main",
|
|
1215
1214
|
description=(
|
|
1216
|
-
f"🔍 Discovering repositories from organization: "
|
|
1217
|
-
f"{cfg.github.organization}"
|
|
1215
|
+
f"🔍 Discovering repositories from organization: {cfg.github.organization}"
|
|
1218
1216
|
),
|
|
1219
1217
|
completed=15,
|
|
1220
1218
|
)
|
|
@@ -1457,8 +1455,7 @@ def analyze(
|
|
|
1457
1455
|
display.update_progress_task(
|
|
1458
1456
|
"repos",
|
|
1459
1457
|
description=(
|
|
1460
|
-
f"Step 1: Fetching data for "
|
|
1461
|
-
f"{len(repos_needing_analysis)} repositories"
|
|
1458
|
+
f"Step 1: Fetching data for {len(repos_needing_analysis)} repositories"
|
|
1462
1459
|
),
|
|
1463
1460
|
completed=0,
|
|
1464
1461
|
)
|
|
@@ -1532,7 +1529,6 @@ def analyze(
|
|
|
1532
1529
|
description="Processing repositories",
|
|
1533
1530
|
unit="repos",
|
|
1534
1531
|
) as repos_progress_ctx:
|
|
1535
|
-
|
|
1536
1532
|
for idx, repo_config in enumerate(repos_needing_analysis, 1):
|
|
1537
1533
|
try:
|
|
1538
1534
|
repo_path = Path(repo_config.path)
|
|
@@ -2285,11 +2281,9 @@ def analyze(
|
|
|
2285
2281
|
f" 🗃️ Database state: {pre_classification_commits} commits, "
|
|
2286
2282
|
f"{pre_classification_batches} batches"
|
|
2287
2283
|
)
|
|
2288
|
-
click.echo(
|
|
2289
|
-
" 💡 Commits exist but no daily batches - " "batch creation failed"
|
|
2290
|
-
)
|
|
2284
|
+
click.echo(" 💡 Commits exist but no daily batches - batch creation failed")
|
|
2291
2285
|
raise click.ClickException(
|
|
2292
|
-
"No batches available for classification -
|
|
2286
|
+
"No batches available for classification - batch creation process failed"
|
|
2293
2287
|
)
|
|
2294
2288
|
|
|
2295
2289
|
if display:
|
|
@@ -2717,7 +2711,9 @@ def analyze(
|
|
|
2717
2711
|
)
|
|
2718
2712
|
commit["canonical_id"] = canonical_id
|
|
2719
2713
|
# Also add canonical display name for reports
|
|
2720
|
-
commit["canonical_name"] = identity_resolver.get_canonical_name(
|
|
2714
|
+
commit["canonical_name"] = identity_resolver.get_canonical_name(
|
|
2715
|
+
canonical_id
|
|
2716
|
+
)
|
|
2721
2717
|
|
|
2722
2718
|
all_commits.extend(commits)
|
|
2723
2719
|
if display:
|
|
@@ -2934,9 +2930,9 @@ def analyze(
|
|
|
2934
2930
|
):
|
|
2935
2931
|
existing_mappings.append(new_mapping)
|
|
2936
2932
|
|
|
2937
|
-
config_data["analysis"]["identity"][
|
|
2938
|
-
|
|
2939
|
-
|
|
2933
|
+
config_data["analysis"]["identity"]["manual_mappings"] = (
|
|
2934
|
+
existing_mappings
|
|
2935
|
+
)
|
|
2940
2936
|
|
|
2941
2937
|
# Apply bot exclusions
|
|
2942
2938
|
if suggested_config.get("exclude", {}).get("authors"):
|
|
@@ -3382,7 +3378,7 @@ def analyze(
|
|
|
3382
3378
|
# Weekly metrics report (only if CSV generation is enabled)
|
|
3383
3379
|
if generate_csv:
|
|
3384
3380
|
weekly_report = (
|
|
3385
|
-
output / f
|
|
3381
|
+
output / f"weekly_metrics_{datetime.now(timezone.utc).strftime('%Y%m%d')}.csv"
|
|
3386
3382
|
)
|
|
3387
3383
|
try:
|
|
3388
3384
|
logger.debug("Starting weekly metrics report generation")
|
|
@@ -3402,7 +3398,7 @@ def analyze(
|
|
|
3402
3398
|
if generate_csv:
|
|
3403
3399
|
activity_summary_report = (
|
|
3404
3400
|
output
|
|
3405
|
-
/ f
|
|
3401
|
+
/ f"developer_activity_summary_{datetime.now(timezone.utc).strftime('%Y%m%d')}.csv"
|
|
3406
3402
|
)
|
|
3407
3403
|
try:
|
|
3408
3404
|
logger.debug("Starting developer activity summary report generation")
|
|
@@ -3429,7 +3425,7 @@ def analyze(
|
|
|
3429
3425
|
|
|
3430
3426
|
# Summary report (only if CSV generation is enabled)
|
|
3431
3427
|
if generate_csv:
|
|
3432
|
-
summary_report = output / f
|
|
3428
|
+
summary_report = output / f"summary_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
3433
3429
|
try:
|
|
3434
3430
|
report_gen.generate_summary_report(
|
|
3435
3431
|
all_commits,
|
|
@@ -3453,7 +3449,7 @@ def analyze(
|
|
|
3453
3449
|
|
|
3454
3450
|
# Developer report (only if CSV generation is enabled)
|
|
3455
3451
|
if generate_csv:
|
|
3456
|
-
developer_report = output / f
|
|
3452
|
+
developer_report = output / f"developers_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
3457
3453
|
try:
|
|
3458
3454
|
report_gen.generate_developer_report(developer_stats, developer_report)
|
|
3459
3455
|
generated_reports.append(developer_report.name)
|
|
@@ -3471,7 +3467,7 @@ def analyze(
|
|
|
3471
3467
|
# Untracked commits report (only if CSV generation is enabled)
|
|
3472
3468
|
if generate_csv:
|
|
3473
3469
|
untracked_commits_report = (
|
|
3474
|
-
output / f
|
|
3470
|
+
output / f"untracked_commits_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
3475
3471
|
)
|
|
3476
3472
|
try:
|
|
3477
3473
|
report_gen.generate_untracked_commits_report(
|
|
@@ -3492,7 +3488,7 @@ def analyze(
|
|
|
3492
3488
|
# Weekly Categorization report (only if CSV generation is enabled)
|
|
3493
3489
|
if generate_csv:
|
|
3494
3490
|
weekly_categorization_report = (
|
|
3495
|
-
output / f
|
|
3491
|
+
output / f"weekly_categorization_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
3496
3492
|
)
|
|
3497
3493
|
try:
|
|
3498
3494
|
logger.debug("Starting weekly categorization report generation")
|
|
@@ -3510,7 +3506,7 @@ def analyze(
|
|
|
3510
3506
|
# PM Correlations report (if PM data is available and CSV generation is enabled)
|
|
3511
3507
|
if aggregated_pm_data and generate_csv:
|
|
3512
3508
|
pm_correlations_report = (
|
|
3513
|
-
output / f
|
|
3509
|
+
output / f"pm_correlations_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
3514
3510
|
)
|
|
3515
3511
|
try:
|
|
3516
3512
|
report_gen.generate_pm_correlations_report(
|
|
@@ -3525,7 +3521,7 @@ def analyze(
|
|
|
3525
3521
|
# Story Point Correlation report (only if CSV generation is enabled)
|
|
3526
3522
|
if generate_csv:
|
|
3527
3523
|
story_point_correlation_report = (
|
|
3528
|
-
output / f
|
|
3524
|
+
output / f"story_point_correlation_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
3529
3525
|
)
|
|
3530
3526
|
try:
|
|
3531
3527
|
logger.debug("Starting story point correlation report generation")
|
|
@@ -3541,7 +3537,7 @@ def analyze(
|
|
|
3541
3537
|
click.echo(f" ⚠️ Warning: Story point correlation report failed: {e}")
|
|
3542
3538
|
|
|
3543
3539
|
# Activity distribution report (always generate data, optionally write CSV)
|
|
3544
|
-
activity_report = output / f
|
|
3540
|
+
activity_report = output / f"activity_distribution_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
3545
3541
|
try:
|
|
3546
3542
|
logger.debug("Starting activity distribution report generation")
|
|
3547
3543
|
analytics_gen.generate_activity_distribution_report(
|
|
@@ -3565,7 +3561,7 @@ def analyze(
|
|
|
3565
3561
|
raise
|
|
3566
3562
|
|
|
3567
3563
|
# Developer focus report (always generate data, optionally write CSV)
|
|
3568
|
-
focus_report = output / f
|
|
3564
|
+
focus_report = output / f"developer_focus_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
3569
3565
|
try:
|
|
3570
3566
|
logger.debug("Starting developer focus report generation")
|
|
3571
3567
|
analytics_gen.generate_developer_focus_report(
|
|
@@ -3589,7 +3585,7 @@ def analyze(
|
|
|
3589
3585
|
raise
|
|
3590
3586
|
|
|
3591
3587
|
# Qualitative insights report (always generate data, optionally write CSV)
|
|
3592
|
-
insights_report = output / f
|
|
3588
|
+
insights_report = output / f"qualitative_insights_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
3593
3589
|
try:
|
|
3594
3590
|
logger.debug("Starting qualitative insights report generation")
|
|
3595
3591
|
analytics_gen.generate_qualitative_insights_report(
|
|
@@ -3609,7 +3605,7 @@ def analyze(
|
|
|
3609
3605
|
|
|
3610
3606
|
branch_health_gen = BranchHealthReportGenerator()
|
|
3611
3607
|
|
|
3612
|
-
branch_health_report = output / f
|
|
3608
|
+
branch_health_report = output / f"branch_health_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
3613
3609
|
try:
|
|
3614
3610
|
logger.debug("Starting branch health report generation")
|
|
3615
3611
|
branch_health_gen.generate_csv_report(branch_health_metrics, branch_health_report)
|
|
@@ -3623,7 +3619,7 @@ def analyze(
|
|
|
3623
3619
|
|
|
3624
3620
|
# Detailed branch report
|
|
3625
3621
|
detailed_branch_report = (
|
|
3626
|
-
output / f
|
|
3622
|
+
output / f"branch_details_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
3627
3623
|
)
|
|
3628
3624
|
try:
|
|
3629
3625
|
branch_health_gen.generate_detailed_branch_report(
|
|
@@ -3663,7 +3659,7 @@ def analyze(
|
|
|
3663
3659
|
|
|
3664
3660
|
# Weekly trends report (includes developer and project trends) (only if CSV generation is enabled)
|
|
3665
3661
|
if generate_csv:
|
|
3666
|
-
trends_report = output / f
|
|
3662
|
+
trends_report = output / f"weekly_trends_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
3667
3663
|
try:
|
|
3668
3664
|
logger.debug("Starting weekly trends report generation")
|
|
3669
3665
|
analytics_gen.generate_weekly_trends_report(
|
|
@@ -3739,7 +3735,7 @@ def analyze(
|
|
|
3739
3735
|
# Weekly velocity report (only if CSV generation is enabled)
|
|
3740
3736
|
if generate_csv:
|
|
3741
3737
|
weekly_velocity_report = (
|
|
3742
|
-
output / f
|
|
3738
|
+
output / f"weekly_velocity_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
3743
3739
|
)
|
|
3744
3740
|
try:
|
|
3745
3741
|
logger.debug("Starting weekly velocity report generation")
|
|
@@ -3765,7 +3761,7 @@ def analyze(
|
|
|
3765
3761
|
# Weekly DORA metrics report (only if CSV generation is enabled)
|
|
3766
3762
|
if generate_csv:
|
|
3767
3763
|
weekly_dora_report = (
|
|
3768
|
-
output / f
|
|
3764
|
+
output / f"weekly_dora_metrics_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
3769
3765
|
)
|
|
3770
3766
|
try:
|
|
3771
3767
|
logger.debug("Starting weekly DORA metrics report generation")
|
|
@@ -3983,7 +3979,7 @@ def analyze(
|
|
|
3983
3979
|
logger.debug("Starting comprehensive JSON export generation")
|
|
3984
3980
|
click.echo(" 🔄 Generating comprehensive JSON export...")
|
|
3985
3981
|
json_report = (
|
|
3986
|
-
output / f
|
|
3982
|
+
output / f"comprehensive_export_{datetime.now().strftime('%Y%m%d')}.json"
|
|
3987
3983
|
)
|
|
3988
3984
|
|
|
3989
3985
|
# Initialize comprehensive JSON exporter
|
|
@@ -5178,7 +5174,7 @@ def identities(config: Path, weeks: int, apply: bool) -> None:
|
|
|
5178
5174
|
|
|
5179
5175
|
# Run analysis
|
|
5180
5176
|
identity_report_path = (
|
|
5181
|
-
cfg.cache.directory / f
|
|
5177
|
+
cfg.cache.directory / f"identity_analysis_{datetime.now().strftime('%Y%m%d')}.yaml"
|
|
5182
5178
|
)
|
|
5183
5179
|
identity_result = analysis_pass.run_analysis(
|
|
5184
5180
|
all_commits, output_path=identity_report_path, apply_to_config=False
|
|
@@ -5525,7 +5521,9 @@ def aliases_command(
|
|
|
5525
5521
|
confidence_color = (
|
|
5526
5522
|
"green"
|
|
5527
5523
|
if alias.confidence >= 0.9
|
|
5528
|
-
else "yellow"
|
|
5524
|
+
else "yellow"
|
|
5525
|
+
if alias.confidence >= 0.8
|
|
5526
|
+
else "red"
|
|
5529
5527
|
)
|
|
5530
5528
|
click.echo(" Confidence: ", nl=False)
|
|
5531
5529
|
click.secho(f"{alias.confidence:.0%}", fg=confidence_color)
|
|
@@ -5941,12 +5939,10 @@ def create_alias_interactive(config: Path, output: Optional[Path]) -> None:
|
|
|
5941
5939
|
)
|
|
5942
5940
|
@click.option(
|
|
5943
5941
|
"--old-name",
|
|
5944
|
-
required=True,
|
|
5945
5942
|
help="Current canonical name to rename (must match a name in manual_mappings)",
|
|
5946
5943
|
)
|
|
5947
5944
|
@click.option(
|
|
5948
5945
|
"--new-name",
|
|
5949
|
-
required=True,
|
|
5950
5946
|
help="New canonical display name to use in reports",
|
|
5951
5947
|
)
|
|
5952
5948
|
@click.option(
|
|
@@ -5959,12 +5955,19 @@ def create_alias_interactive(config: Path, output: Optional[Path]) -> None:
|
|
|
5959
5955
|
is_flag=True,
|
|
5960
5956
|
help="Show what would be changed without applying changes",
|
|
5961
5957
|
)
|
|
5958
|
+
@click.option(
|
|
5959
|
+
"--interactive",
|
|
5960
|
+
"-i",
|
|
5961
|
+
is_flag=True,
|
|
5962
|
+
help="Interactive mode: select developer from numbered list",
|
|
5963
|
+
)
|
|
5962
5964
|
def alias_rename(
|
|
5963
5965
|
config: Path,
|
|
5964
5966
|
old_name: str,
|
|
5965
5967
|
new_name: str,
|
|
5966
5968
|
update_cache: bool,
|
|
5967
5969
|
dry_run: bool,
|
|
5970
|
+
interactive: bool,
|
|
5968
5971
|
) -> None:
|
|
5969
5972
|
"""Rename a developer's canonical display name.
|
|
5970
5973
|
|
|
@@ -5975,6 +5978,9 @@ def alias_rename(
|
|
|
5975
5978
|
|
|
5976
5979
|
\b
|
|
5977
5980
|
EXAMPLES:
|
|
5981
|
+
# Interactive mode: select from numbered list
|
|
5982
|
+
gitflow-analytics alias-rename -c config.yaml --interactive
|
|
5983
|
+
|
|
5978
5984
|
# Rename with dry-run to see changes
|
|
5979
5985
|
gitflow-analytics alias-rename -c config.yaml \\
|
|
5980
5986
|
--old-name "bianco-zaelot" \\
|
|
@@ -6001,27 +6007,11 @@ def alias_rename(
|
|
|
6001
6007
|
try:
|
|
6002
6008
|
from .core.identity import DeveloperIdentityResolver
|
|
6003
6009
|
|
|
6004
|
-
# Validate inputs
|
|
6005
|
-
if not old_name.strip():
|
|
6006
|
-
click.echo("❌ Error: --old-name cannot be empty", err=True)
|
|
6007
|
-
sys.exit(1)
|
|
6008
|
-
|
|
6009
|
-
if not new_name.strip():
|
|
6010
|
-
click.echo("❌ Error: --new-name cannot be empty", err=True)
|
|
6011
|
-
sys.exit(1)
|
|
6012
|
-
|
|
6013
|
-
old_name = old_name.strip()
|
|
6014
|
-
new_name = new_name.strip()
|
|
6015
|
-
|
|
6016
|
-
if old_name == new_name:
|
|
6017
|
-
click.echo("❌ Error: old-name and new-name are identical", err=True)
|
|
6018
|
-
sys.exit(1)
|
|
6019
|
-
|
|
6020
6010
|
# Load the YAML config file
|
|
6021
6011
|
click.echo(f"\n📋 Loading configuration from {config}...")
|
|
6022
6012
|
|
|
6023
6013
|
try:
|
|
6024
|
-
with open(config,
|
|
6014
|
+
with open(config, encoding="utf-8") as f:
|
|
6025
6015
|
config_data = yaml.safe_load(f)
|
|
6026
6016
|
except Exception as e:
|
|
6027
6017
|
click.echo(f"❌ Error loading config file: {e}", err=True)
|
|
@@ -6037,7 +6027,9 @@ def alias_rename(
|
|
|
6037
6027
|
sys.exit(1)
|
|
6038
6028
|
|
|
6039
6029
|
if "manual_mappings" not in config_data["analysis"]["identity"]:
|
|
6040
|
-
click.echo(
|
|
6030
|
+
click.echo(
|
|
6031
|
+
"❌ Error: 'analysis.identity.manual_mappings' not found in config", err=True
|
|
6032
|
+
)
|
|
6041
6033
|
sys.exit(1)
|
|
6042
6034
|
|
|
6043
6035
|
manual_mappings = config_data["analysis"]["identity"]["manual_mappings"]
|
|
@@ -6046,6 +6038,62 @@ def alias_rename(
|
|
|
6046
6038
|
click.echo("❌ Error: manual_mappings is empty", err=True)
|
|
6047
6039
|
sys.exit(1)
|
|
6048
6040
|
|
|
6041
|
+
# Interactive mode: display numbered list and prompt for selection
|
|
6042
|
+
if interactive or not old_name or not new_name:
|
|
6043
|
+
click.echo("\n" + "=" * 60)
|
|
6044
|
+
click.echo(click.style("Current Developers:", fg="cyan", bold=True))
|
|
6045
|
+
click.echo("=" * 60 + "\n")
|
|
6046
|
+
|
|
6047
|
+
developer_names = []
|
|
6048
|
+
for idx, mapping in enumerate(manual_mappings, 1):
|
|
6049
|
+
name = mapping.get("name", "Unknown")
|
|
6050
|
+
email = mapping.get("primary_email", "N/A")
|
|
6051
|
+
alias_count = len(mapping.get("aliases", []))
|
|
6052
|
+
|
|
6053
|
+
developer_names.append(name)
|
|
6054
|
+
click.echo(f" {idx}. {click.style(name, fg='green')}")
|
|
6055
|
+
click.echo(f" Email: {email}")
|
|
6056
|
+
click.echo(f" Aliases: {alias_count} email(s)")
|
|
6057
|
+
click.echo()
|
|
6058
|
+
|
|
6059
|
+
# Prompt for selection
|
|
6060
|
+
try:
|
|
6061
|
+
selection = click.prompt(
|
|
6062
|
+
"Select developer number to rename (or 0 to cancel)",
|
|
6063
|
+
type=click.IntRange(0, len(developer_names)),
|
|
6064
|
+
)
|
|
6065
|
+
except click.Abort:
|
|
6066
|
+
click.echo("\n👋 Cancelled by user.")
|
|
6067
|
+
sys.exit(0)
|
|
6068
|
+
|
|
6069
|
+
if selection == 0:
|
|
6070
|
+
click.echo("\n👋 Cancelled.")
|
|
6071
|
+
sys.exit(0)
|
|
6072
|
+
|
|
6073
|
+
# Get selected developer name
|
|
6074
|
+
old_name = developer_names[selection - 1]
|
|
6075
|
+
click.echo(f"\n📝 Selected: {click.style(old_name, fg='green')}")
|
|
6076
|
+
|
|
6077
|
+
# Prompt for new name if not provided
|
|
6078
|
+
if not new_name:
|
|
6079
|
+
new_name = click.prompt("Enter new canonical name", type=str)
|
|
6080
|
+
|
|
6081
|
+
# Validate inputs
|
|
6082
|
+
if not old_name or not old_name.strip():
|
|
6083
|
+
click.echo("❌ Error: --old-name cannot be empty", err=True)
|
|
6084
|
+
sys.exit(1)
|
|
6085
|
+
|
|
6086
|
+
if not new_name or not new_name.strip():
|
|
6087
|
+
click.echo("❌ Error: --new-name cannot be empty", err=True)
|
|
6088
|
+
sys.exit(1)
|
|
6089
|
+
|
|
6090
|
+
old_name = old_name.strip()
|
|
6091
|
+
new_name = new_name.strip()
|
|
6092
|
+
|
|
6093
|
+
if old_name == new_name:
|
|
6094
|
+
click.echo("❌ Error: old-name and new-name are identical", err=True)
|
|
6095
|
+
sys.exit(1)
|
|
6096
|
+
|
|
6049
6097
|
# Find the matching entry
|
|
6050
6098
|
matching_entry = None
|
|
6051
6099
|
matching_index = None
|
|
@@ -6065,24 +6113,30 @@ def alias_rename(
|
|
|
6065
6113
|
sys.exit(1)
|
|
6066
6114
|
|
|
6067
6115
|
# Display what will be changed
|
|
6068
|
-
click.echo(
|
|
6116
|
+
click.echo("\n🔍 Found matching entry:")
|
|
6069
6117
|
click.echo(f" Current name: {old_name}")
|
|
6070
6118
|
click.echo(f" New name: {new_name}")
|
|
6071
6119
|
click.echo(f" Email: {matching_entry.get('primary_email', 'N/A')}")
|
|
6072
6120
|
click.echo(f" Aliases: {len(matching_entry.get('aliases', []))} email(s)")
|
|
6073
6121
|
|
|
6074
6122
|
if dry_run:
|
|
6075
|
-
click.echo(
|
|
6123
|
+
click.echo("\n🔎 DRY RUN - No changes will be made")
|
|
6076
6124
|
|
|
6077
6125
|
# Update the config file
|
|
6078
6126
|
if not dry_run:
|
|
6079
|
-
click.echo(
|
|
6127
|
+
click.echo("\n📝 Updating configuration file...")
|
|
6080
6128
|
manual_mappings[matching_index]["name"] = new_name
|
|
6081
6129
|
|
|
6082
6130
|
try:
|
|
6083
6131
|
with open(config, "w", encoding="utf-8") as f:
|
|
6084
|
-
yaml.dump(
|
|
6085
|
-
|
|
6132
|
+
yaml.dump(
|
|
6133
|
+
config_data,
|
|
6134
|
+
f,
|
|
6135
|
+
default_flow_style=False,
|
|
6136
|
+
allow_unicode=True,
|
|
6137
|
+
sort_keys=False,
|
|
6138
|
+
)
|
|
6139
|
+
click.echo("✅ Configuration file updated")
|
|
6086
6140
|
except Exception as e:
|
|
6087
6141
|
click.echo(f"❌ Error writing config file: {e}", err=True)
|
|
6088
6142
|
sys.exit(1)
|
|
@@ -6091,7 +6145,7 @@ def alias_rename(
|
|
|
6091
6145
|
|
|
6092
6146
|
# Update database cache if requested
|
|
6093
6147
|
if update_cache:
|
|
6094
|
-
click.echo(
|
|
6148
|
+
click.echo("\n💾 Checking database cache...")
|
|
6095
6149
|
|
|
6096
6150
|
# Load config to get cache directory
|
|
6097
6151
|
cfg = ConfigLoader.load(config)
|
|
@@ -6099,7 +6153,7 @@ def alias_rename(
|
|
|
6099
6153
|
|
|
6100
6154
|
if not identity_db_path.exists():
|
|
6101
6155
|
click.echo(f"⚠️ Warning: Identity database not found at {identity_db_path}")
|
|
6102
|
-
click.echo(
|
|
6156
|
+
click.echo(" Skipping cache update")
|
|
6103
6157
|
else:
|
|
6104
6158
|
# Initialize identity resolver to access database
|
|
6105
6159
|
identity_resolver = DeveloperIdentityResolver(
|
|
@@ -6113,15 +6167,17 @@ def alias_rename(
|
|
|
6113
6167
|
with identity_resolver.get_session() as session:
|
|
6114
6168
|
# Count developer_identities records
|
|
6115
6169
|
result = session.execute(
|
|
6116
|
-
text(
|
|
6117
|
-
|
|
6170
|
+
text(
|
|
6171
|
+
"SELECT COUNT(*) FROM developer_identities WHERE primary_name = :old_name"
|
|
6172
|
+
),
|
|
6173
|
+
{"old_name": old_name},
|
|
6118
6174
|
)
|
|
6119
6175
|
identity_count = result.scalar()
|
|
6120
6176
|
|
|
6121
6177
|
# Count developer_aliases records
|
|
6122
6178
|
result = session.execute(
|
|
6123
6179
|
text("SELECT COUNT(*) FROM developer_aliases WHERE name = :old_name"),
|
|
6124
|
-
{"old_name": old_name}
|
|
6180
|
+
{"old_name": old_name},
|
|
6125
6181
|
)
|
|
6126
6182
|
alias_count = result.scalar()
|
|
6127
6183
|
|
|
@@ -6129,28 +6185,34 @@ def alias_rename(
|
|
|
6129
6185
|
click.echo(f" Found {alias_count} alias record(s)")
|
|
6130
6186
|
|
|
6131
6187
|
if identity_count == 0 and alias_count == 0:
|
|
6132
|
-
click.echo(
|
|
6188
|
+
click.echo(" ℹ️ No database records to update")
|
|
6133
6189
|
elif not dry_run:
|
|
6134
|
-
click.echo(
|
|
6190
|
+
click.echo(" Updating database records...")
|
|
6135
6191
|
|
|
6136
6192
|
with identity_resolver.get_session() as session:
|
|
6137
6193
|
# Update developer_identities
|
|
6138
6194
|
if identity_count > 0:
|
|
6139
6195
|
session.execute(
|
|
6140
|
-
text(
|
|
6141
|
-
|
|
6196
|
+
text(
|
|
6197
|
+
"UPDATE developer_identities SET primary_name = :new_name WHERE primary_name = :old_name"
|
|
6198
|
+
),
|
|
6199
|
+
{"new_name": new_name, "old_name": old_name},
|
|
6142
6200
|
)
|
|
6143
6201
|
|
|
6144
6202
|
# Update developer_aliases
|
|
6145
6203
|
if alias_count > 0:
|
|
6146
6204
|
session.execute(
|
|
6147
|
-
text(
|
|
6148
|
-
|
|
6205
|
+
text(
|
|
6206
|
+
"UPDATE developer_aliases SET name = :new_name WHERE name = :old_name"
|
|
6207
|
+
),
|
|
6208
|
+
{"new_name": new_name, "old_name": old_name},
|
|
6149
6209
|
)
|
|
6150
6210
|
|
|
6151
|
-
click.echo(
|
|
6211
|
+
click.echo(" ✅ Database updated")
|
|
6152
6212
|
else:
|
|
6153
|
-
click.echo(
|
|
6213
|
+
click.echo(
|
|
6214
|
+
f" [Would update {identity_count + alias_count} database record(s)]"
|
|
6215
|
+
)
|
|
6154
6216
|
|
|
6155
6217
|
# Summary
|
|
6156
6218
|
click.echo(f"\n{'🔎 DRY RUN SUMMARY' if dry_run else '✅ RENAME COMPLETE'}")
|
|
@@ -6160,14 +6222,14 @@ def alias_rename(
|
|
|
6160
6222
|
if update_cache:
|
|
6161
6223
|
click.echo(f" Cache: {'Would update' if dry_run else 'Updated'}")
|
|
6162
6224
|
else:
|
|
6163
|
-
click.echo(
|
|
6225
|
+
click.echo(" Cache: Skipped (use --update-cache to update)")
|
|
6164
6226
|
|
|
6165
6227
|
if dry_run:
|
|
6166
|
-
click.echo(
|
|
6228
|
+
click.echo("\n💡 Run without --dry-run to apply changes")
|
|
6167
6229
|
else:
|
|
6168
|
-
click.echo(
|
|
6230
|
+
click.echo("\n💡 Next steps:")
|
|
6169
6231
|
click.echo(f" - Review the updated config file: {config}")
|
|
6170
|
-
click.echo(
|
|
6232
|
+
click.echo(" - Re-run analysis to see updated reports with new name")
|
|
6171
6233
|
|
|
6172
6234
|
except KeyboardInterrupt:
|
|
6173
6235
|
click.echo("\n\n👋 Interrupted by user. Exiting.")
|
|
@@ -6175,6 +6237,7 @@ def alias_rename(
|
|
|
6175
6237
|
except Exception as e:
|
|
6176
6238
|
click.echo(f"❌ Unexpected error: {e}", err=True)
|
|
6177
6239
|
import traceback
|
|
6240
|
+
|
|
6178
6241
|
traceback.print_exc()
|
|
6179
6242
|
sys.exit(1)
|
|
6180
6243
|
|
|
@@ -6750,7 +6813,10 @@ def training_statistics(config: Path) -> None:
|
|
|
6750
6813
|
|
|
6751
6814
|
# Initialize trainer to access statistics
|
|
6752
6815
|
trainer = CommitClassificationTrainer(
|
|
6753
|
-
config=cfg,
|
|
6816
|
+
config=cfg,
|
|
6817
|
+
cache=cache,
|
|
6818
|
+
orchestrator=None,
|
|
6819
|
+
training_config={}, # Not needed for stats
|
|
6754
6820
|
)
|
|
6755
6821
|
|
|
6756
6822
|
stats = trainer.get_training_statistics()
|
|
@@ -5,6 +5,7 @@ is run without arguments, offering options for configuration, alias management,
|
|
|
5
5
|
analysis execution, and more.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import contextlib
|
|
8
9
|
import logging
|
|
9
10
|
import os
|
|
10
11
|
import subprocess
|
|
@@ -98,10 +99,8 @@ def _atomic_yaml_write(config_path: Path, config_data: dict) -> None:
|
|
|
98
99
|
except Exception as e:
|
|
99
100
|
# Cleanup temp file on error
|
|
100
101
|
if temp_fd is not None:
|
|
101
|
-
|
|
102
|
+
with contextlib.suppress(Exception):
|
|
102
103
|
os.close(temp_fd)
|
|
103
|
-
except Exception:
|
|
104
|
-
pass
|
|
105
104
|
|
|
106
105
|
if temp_path and temp_path.exists():
|
|
107
106
|
temp_path.unlink(missing_ok=True)
|
|
@@ -242,7 +241,7 @@ def validate_config(config_path: Path) -> bool:
|
|
|
242
241
|
|
|
243
242
|
# Line before
|
|
244
243
|
if mark.line > 0:
|
|
245
|
-
click.echo(f" {mark.line}: {lines[mark.line-1].rstrip()}", err=True)
|
|
244
|
+
click.echo(f" {mark.line}: {lines[mark.line - 1].rstrip()}", err=True)
|
|
246
245
|
|
|
247
246
|
# Problematic line (highlighted)
|
|
248
247
|
click.echo(
|
|
@@ -256,7 +255,7 @@ def validate_config(config_path: Path) -> bool:
|
|
|
256
255
|
|
|
257
256
|
# Line after
|
|
258
257
|
if mark.line + 1 < len(lines):
|
|
259
|
-
click.echo(f" {mark.line + 2}: {lines[mark.line+1].rstrip()}", err=True)
|
|
258
|
+
click.echo(f" {mark.line + 2}: {lines[mark.line + 1].rstrip()}", err=True)
|
|
260
259
|
except Exception:
|
|
261
260
|
# If we can't read file, just skip context
|
|
262
261
|
pass
|
|
@@ -562,6 +561,145 @@ def run_full_analysis(config_path: Path) -> bool:
|
|
|
562
561
|
return success
|
|
563
562
|
|
|
564
563
|
|
|
564
|
+
def rename_developer_alias(config_path: Path) -> bool:
|
|
565
|
+
"""Interactive interface for renaming developer aliases.
|
|
566
|
+
|
|
567
|
+
Args:
|
|
568
|
+
config_path: Path to config.yaml file
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
True if rename succeeded, False otherwise.
|
|
572
|
+
"""
|
|
573
|
+
click.echo("\n" + "=" * 60)
|
|
574
|
+
click.echo(click.style("Rename Developer Alias", fg="cyan", bold=True))
|
|
575
|
+
click.echo("=" * 60 + "\n")
|
|
576
|
+
|
|
577
|
+
click.echo("Update a developer's canonical display name in reports.")
|
|
578
|
+
click.echo("This updates the configuration file and optionally the cache.\n")
|
|
579
|
+
|
|
580
|
+
try:
|
|
581
|
+
# Load config to get manual_mappings
|
|
582
|
+
with open(config_path) as f:
|
|
583
|
+
config_data = yaml.safe_load(f)
|
|
584
|
+
|
|
585
|
+
# Navigate to manual_mappings
|
|
586
|
+
manual_mappings = (
|
|
587
|
+
config_data.get("analysis", {}).get("identity", {}).get("manual_mappings", [])
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
if not manual_mappings:
|
|
591
|
+
click.echo(
|
|
592
|
+
click.style(
|
|
593
|
+
"❌ No manual_mappings found in config. Please add developers first.", fg="red"
|
|
594
|
+
),
|
|
595
|
+
err=True,
|
|
596
|
+
)
|
|
597
|
+
return False
|
|
598
|
+
|
|
599
|
+
# Display numbered list of developers
|
|
600
|
+
click.echo(click.style("Current Developers:", fg="cyan", bold=True))
|
|
601
|
+
click.echo()
|
|
602
|
+
|
|
603
|
+
developer_names = []
|
|
604
|
+
for idx, mapping in enumerate(manual_mappings, 1):
|
|
605
|
+
name = mapping.get("name", "Unknown")
|
|
606
|
+
email = mapping.get("primary_email", "N/A")
|
|
607
|
+
alias_count = len(mapping.get("aliases", []))
|
|
608
|
+
|
|
609
|
+
developer_names.append(name)
|
|
610
|
+
click.echo(f" {idx}. {click.style(name, fg='green')}")
|
|
611
|
+
click.echo(f" Email: {email}")
|
|
612
|
+
click.echo(f" Aliases: {alias_count} email(s)")
|
|
613
|
+
click.echo()
|
|
614
|
+
|
|
615
|
+
# Prompt for selection
|
|
616
|
+
try:
|
|
617
|
+
selection = click.prompt(
|
|
618
|
+
"Select developer number to rename (or 0 to cancel)",
|
|
619
|
+
type=click.IntRange(0, len(developer_names)),
|
|
620
|
+
)
|
|
621
|
+
except click.Abort:
|
|
622
|
+
click.echo(click.style("\n❌ Cancelled", fg="yellow"))
|
|
623
|
+
return False
|
|
624
|
+
|
|
625
|
+
if selection == 0:
|
|
626
|
+
click.echo(click.style("\n❌ Cancelled", fg="yellow"))
|
|
627
|
+
return False
|
|
628
|
+
|
|
629
|
+
# Get selected developer name
|
|
630
|
+
old_name = developer_names[selection - 1]
|
|
631
|
+
click.echo(f"\n📝 Selected: {click.style(old_name, fg='green')}")
|
|
632
|
+
|
|
633
|
+
# Prompt for new name
|
|
634
|
+
new_name = click.prompt("Enter new canonical name", type=str)
|
|
635
|
+
|
|
636
|
+
# Validate new name
|
|
637
|
+
new_name = new_name.strip()
|
|
638
|
+
if not new_name:
|
|
639
|
+
click.echo(click.style("❌ New name cannot be empty", fg="red"), err=True)
|
|
640
|
+
return False
|
|
641
|
+
|
|
642
|
+
if new_name == old_name:
|
|
643
|
+
click.echo(click.style("❌ New name is identical to current name", fg="yellow"))
|
|
644
|
+
return False
|
|
645
|
+
|
|
646
|
+
# Ask about cache update
|
|
647
|
+
update_cache = click.confirm("\nAlso update database cache?", default=True)
|
|
648
|
+
|
|
649
|
+
# Show what will be done
|
|
650
|
+
click.echo("\n" + "=" * 60)
|
|
651
|
+
click.echo(click.style("Summary", fg="yellow", bold=True))
|
|
652
|
+
click.echo("=" * 60)
|
|
653
|
+
click.echo(f" Old name: {old_name}")
|
|
654
|
+
click.echo(f" New name: {new_name}")
|
|
655
|
+
click.echo(f" Update cache: {'Yes' if update_cache else 'No'}")
|
|
656
|
+
click.echo()
|
|
657
|
+
|
|
658
|
+
# Confirm
|
|
659
|
+
if not click.confirm("Proceed with rename?", default=True):
|
|
660
|
+
click.echo(click.style("\n❌ Cancelled", fg="yellow"))
|
|
661
|
+
return False
|
|
662
|
+
|
|
663
|
+
except Exception as e:
|
|
664
|
+
click.echo(click.style(f"❌ Error reading config: {e}", fg="red"), err=True)
|
|
665
|
+
logger.error(f"Config read error: {type(e).__name__}: {e}")
|
|
666
|
+
return False
|
|
667
|
+
|
|
668
|
+
try:
|
|
669
|
+
# Validate config path
|
|
670
|
+
_validate_subprocess_path(config_path)
|
|
671
|
+
except ValueError as e:
|
|
672
|
+
click.echo(click.style(f"❌ Invalid config path: {e}", fg="red"), err=True)
|
|
673
|
+
logger.error(f"Config path validation failed: {e}")
|
|
674
|
+
return False
|
|
675
|
+
|
|
676
|
+
# Build command
|
|
677
|
+
cmd = [
|
|
678
|
+
sys.executable,
|
|
679
|
+
"-m",
|
|
680
|
+
"gitflow_analytics.cli",
|
|
681
|
+
"alias-rename",
|
|
682
|
+
"-c",
|
|
683
|
+
str(config_path),
|
|
684
|
+
"--old-name",
|
|
685
|
+
old_name,
|
|
686
|
+
"--new-name",
|
|
687
|
+
new_name,
|
|
688
|
+
]
|
|
689
|
+
|
|
690
|
+
if update_cache:
|
|
691
|
+
cmd.append("--update-cache")
|
|
692
|
+
|
|
693
|
+
# Run with timeout
|
|
694
|
+
success = _run_subprocess_safely(cmd, operation_name="Alias Rename", timeout=60)
|
|
695
|
+
|
|
696
|
+
if success:
|
|
697
|
+
click.echo(click.style("\n✅ Rename completed successfully!", fg="green"))
|
|
698
|
+
click.echo(f"Future reports will show '{new_name}' instead of '{old_name}'")
|
|
699
|
+
|
|
700
|
+
return success
|
|
701
|
+
|
|
702
|
+
|
|
565
703
|
def show_main_menu(config_path: Optional[Path] = None) -> None:
|
|
566
704
|
"""Display main interactive menu.
|
|
567
705
|
|
|
@@ -597,13 +735,14 @@ def show_main_menu(config_path: Optional[Path] = None) -> None:
|
|
|
597
735
|
click.echo(" 3. Re-pull Data (Re-run Analysis)")
|
|
598
736
|
click.echo(" 4. Set Number of Weeks")
|
|
599
737
|
click.echo(" 5. Run Full Analysis")
|
|
738
|
+
click.echo(" 6. Rename Developer Alias")
|
|
600
739
|
click.echo(" 0. Exit")
|
|
601
740
|
|
|
602
741
|
# Get user choice
|
|
603
742
|
click.echo()
|
|
604
743
|
choice = click.prompt(
|
|
605
744
|
click.style("Enter your choice", fg="yellow"),
|
|
606
|
-
type=click.Choice(["0", "1", "2", "3", "4", "5"], case_sensitive=False),
|
|
745
|
+
type=click.Choice(["0", "1", "2", "3", "4", "5", "6"], case_sensitive=False),
|
|
607
746
|
show_choices=False,
|
|
608
747
|
)
|
|
609
748
|
|
|
@@ -623,6 +762,8 @@ def show_main_menu(config_path: Optional[Path] = None) -> None:
|
|
|
623
762
|
success = set_weeks(config_path)
|
|
624
763
|
elif choice == "5":
|
|
625
764
|
success = run_full_analysis(config_path)
|
|
765
|
+
elif choice == "6":
|
|
766
|
+
success = rename_developer_alias(config_path)
|
|
626
767
|
|
|
627
768
|
# Show warning if operation failed
|
|
628
769
|
if not success and choice != "0":
|
|
@@ -968,7 +968,9 @@ class ConfigLoader:
|
|
|
968
968
|
(
|
|
969
969
|
cls._resolve_env_var(item)
|
|
970
970
|
if isinstance(item, str)
|
|
971
|
-
else cls._resolve_config_dict(item)
|
|
971
|
+
else cls._resolve_config_dict(item)
|
|
972
|
+
if isinstance(item, dict)
|
|
973
|
+
else item
|
|
972
974
|
)
|
|
973
975
|
for item in value
|
|
974
976
|
]
|
|
@@ -234,8 +234,7 @@ class ProfileManager:
|
|
|
234
234
|
if not profile_class:
|
|
235
235
|
available = ", ".join(cls._profiles.keys())
|
|
236
236
|
raise ValueError(
|
|
237
|
-
f"Unknown configuration profile: {profile_name}. "
|
|
238
|
-
f"Available profiles: {available}"
|
|
237
|
+
f"Unknown configuration profile: {profile_name}. Available profiles: {available}"
|
|
239
238
|
)
|
|
240
239
|
|
|
241
240
|
profile_settings = profile_class.get_settings()
|
|
@@ -192,7 +192,6 @@ class GitDataFetcher:
|
|
|
192
192
|
description=f"📊 Processing repository: {project_key}",
|
|
193
193
|
unit="steps",
|
|
194
194
|
) as repo_progress_ctx:
|
|
195
|
-
|
|
196
195
|
# Step 1: Fetch commits
|
|
197
196
|
progress.set_description(repo_progress_ctx, f"🔍 {project_key}: Fetching commits")
|
|
198
197
|
daily_commits = self._fetch_commits_by_day(
|
|
@@ -538,7 +537,6 @@ class GitDataFetcher:
|
|
|
538
537
|
unit="days",
|
|
539
538
|
nested=True,
|
|
540
539
|
) as day_progress_ctx:
|
|
541
|
-
|
|
542
540
|
for day_date in days_to_process:
|
|
543
541
|
# Update description to show current repository and day clearly
|
|
544
542
|
day_str = day_date.strftime("%Y-%m-%d")
|
|
@@ -503,7 +503,9 @@ class TicketExtractor:
|
|
|
503
503
|
:100
|
|
504
504
|
], # Increased from 60 to 100
|
|
505
505
|
"full_message": commit.get("message", ""),
|
|
506
|
-
"author": commit.get(
|
|
506
|
+
"author": commit.get(
|
|
507
|
+
"canonical_name", commit.get("author_name", "Unknown")
|
|
508
|
+
),
|
|
507
509
|
"author_email": commit.get("author_email", ""),
|
|
508
510
|
"canonical_id": commit.get("canonical_id", commit.get("author_email", "")),
|
|
509
511
|
"timestamp": commit.get("timestamp"),
|
|
@@ -101,7 +101,7 @@ class GitHubIntegration:
|
|
|
101
101
|
|
|
102
102
|
if cache_hits > 0 or cache_misses > 0:
|
|
103
103
|
print(
|
|
104
|
-
f" 📊 GitHub PR cache: {cache_hits} hits, {cache_misses} misses ({cache_hits/(cache_hits+cache_misses)*100:.1f}% hit rate)"
|
|
104
|
+
f" 📊 GitHub PR cache: {cache_hits} hits, {cache_misses} misses ({cache_hits / (cache_hits + cache_misses) * 100:.1f}% hit rate)"
|
|
105
105
|
if (cache_hits + cache_misses) > 0
|
|
106
106
|
else ""
|
|
107
107
|
)
|
|
@@ -186,7 +186,7 @@ class JIRAIntegration:
|
|
|
186
186
|
|
|
187
187
|
if cache_hits > 0 or cache_misses > 0:
|
|
188
188
|
print(
|
|
189
|
-
f" 📊 JIRA cache: {cache_hits} hits, {cache_misses} misses ({cache_hits/(cache_hits+cache_misses)*100:.1f}% hit rate)"
|
|
189
|
+
f" 📊 JIRA cache: {cache_hits} hits, {cache_misses} misses ({cache_hits / (cache_hits + cache_misses) * 100:.1f}% hit rate)"
|
|
190
190
|
)
|
|
191
191
|
|
|
192
192
|
# Fetch missing tickets from JIRA
|
|
@@ -169,16 +169,16 @@ class ChatGPTQualitativeAnalyzer:
|
|
|
169
169
|
def _create_executive_summary_prompt(self, summary_data: dict[str, Any]) -> str:
|
|
170
170
|
"""Create the prompt for ChatGPT."""
|
|
171
171
|
|
|
172
|
-
prompt = f"""Based on the following GitFlow Analytics data from the past {summary_data[
|
|
172
|
+
prompt = f"""Based on the following GitFlow Analytics data from the past {summary_data["period_weeks"]} weeks, provide a comprehensive executive summary with qualitative insights:
|
|
173
173
|
|
|
174
174
|
## Key Metrics:
|
|
175
|
-
- Total Commits: {summary_data[
|
|
176
|
-
- Active Developers: {summary_data[
|
|
177
|
-
- Lines Changed: {summary_data[
|
|
178
|
-
- Story Points Delivered: {summary_data[
|
|
179
|
-
- Ticket Coverage: {summary_data[
|
|
180
|
-
- Team Health Score: {summary_data[
|
|
181
|
-
- Velocity Trend: {summary_data[
|
|
175
|
+
- Total Commits: {summary_data["total_commits"]:,}
|
|
176
|
+
- Active Developers: {summary_data["total_developers"]}
|
|
177
|
+
- Lines Changed: {summary_data["lines_changed"]:,}
|
|
178
|
+
- Story Points Delivered: {summary_data["story_points"]}
|
|
179
|
+
- Ticket Coverage: {summary_data["ticket_coverage"]:.1f}%
|
|
180
|
+
- Team Health Score: {summary_data["team_health_score"]:.1f}/100 ({summary_data["team_health_rating"]})
|
|
181
|
+
- Velocity Trend: {summary_data["velocity_trend"]}
|
|
182
182
|
|
|
183
183
|
## Top Contributors:
|
|
184
184
|
"""
|
|
@@ -222,18 +222,18 @@ Report only statistical patterns, measurable trends, and process gaps. Use factu
|
|
|
222
222
|
|
|
223
223
|
return f"""## Executive Summary
|
|
224
224
|
|
|
225
|
-
Over the past {summary_data[
|
|
225
|
+
Over the past {summary_data["period_weeks"]} weeks, the development team generated {summary_data["total_commits"]:,} commits across {summary_data["total_developers"]} active developers.
|
|
226
226
|
|
|
227
|
-
The team health score measured {summary_data[
|
|
227
|
+
The team health score measured {summary_data["team_health_score"]:.1f}/100 ({summary_data["team_health_rating"]}). Ticket coverage reached {summary_data["ticket_coverage"]:.1f}% of total commits with trackable references.
|
|
228
228
|
|
|
229
229
|
### Measured Outputs:
|
|
230
|
-
- Code changes: {summary_data[
|
|
231
|
-
- Story points completed: {summary_data[
|
|
232
|
-
- Velocity trend: {summary_data[
|
|
230
|
+
- Code changes: {summary_data["lines_changed"]:,} lines modified
|
|
231
|
+
- Story points completed: {summary_data["story_points"]}
|
|
232
|
+
- Velocity trend: {summary_data["velocity_trend"]}
|
|
233
233
|
|
|
234
234
|
### Process Recommendations:
|
|
235
|
-
1. {
|
|
236
|
-
2. {
|
|
235
|
+
1. {"Maintain current output rate" if summary_data["velocity_trend"] == "increasing" else "Analyze velocity decline factors"}
|
|
236
|
+
2. {"Sustain current tracking rate" if summary_data["ticket_coverage"] > 60 else "Increase commit-ticket linking to reach 70% coverage target"}
|
|
237
237
|
3. Review projects with health scores below 60/100 for process gaps
|
|
238
238
|
|
|
239
239
|
*Note: This is a fallback summary. For detailed analysis, configure ChatGPT integration.*
|
|
@@ -353,7 +353,7 @@ Response (format: CATEGORY confidence reasoning):""",
|
|
|
353
353
|
"""
|
|
354
354
|
formatted = []
|
|
355
355
|
for i, example in enumerate(examples, 1):
|
|
356
|
-
formatted.append(f
|
|
356
|
+
formatted.append(f'{i}. Message: "{example["message"]}"')
|
|
357
357
|
formatted.append(f" Response: {example['response']}")
|
|
358
358
|
return "\n".join(formatted)
|
|
359
359
|
|
|
@@ -577,8 +577,7 @@ class QualitativeProcessor:
|
|
|
577
577
|
llm_pct = (llm_processed / total_commits) * 100 if total_commits > 0 else 0
|
|
578
578
|
|
|
579
579
|
self.logger.info(
|
|
580
|
-
f"Processing breakdown: {cache_pct:.1f}% cached, "
|
|
581
|
-
f"{nlp_pct:.1f}% NLP, {llm_pct:.1f}% LLM"
|
|
580
|
+
f"Processing breakdown: {cache_pct:.1f}% cached, {nlp_pct:.1f}% NLP, {llm_pct:.1f}% LLM"
|
|
582
581
|
)
|
|
583
582
|
|
|
584
583
|
def _should_optimize_cache(self) -> bool:
|
|
@@ -906,7 +906,9 @@ class EnhancedQualitativeAnalyzer:
|
|
|
906
906
|
"status": (
|
|
907
907
|
"excellent"
|
|
908
908
|
if activity_score >= 80
|
|
909
|
-
else "good"
|
|
909
|
+
else "good"
|
|
910
|
+
if activity_score >= 60
|
|
911
|
+
else "needs_improvement"
|
|
910
912
|
),
|
|
911
913
|
},
|
|
912
914
|
"contributor_diversity": {
|
|
@@ -915,7 +917,9 @@ class EnhancedQualitativeAnalyzer:
|
|
|
915
917
|
"status": (
|
|
916
918
|
"excellent"
|
|
917
919
|
if len(contributors) >= 4
|
|
918
|
-
else "good"
|
|
920
|
+
else "good"
|
|
921
|
+
if len(contributors) >= 2
|
|
922
|
+
else "concerning"
|
|
919
923
|
),
|
|
920
924
|
},
|
|
921
925
|
"pr_velocity": {
|
|
@@ -929,7 +933,9 @@ class EnhancedQualitativeAnalyzer:
|
|
|
929
933
|
"status": (
|
|
930
934
|
"excellent"
|
|
931
935
|
if ticket_coverage >= 80
|
|
932
|
-
else "good"
|
|
936
|
+
else "good"
|
|
937
|
+
if ticket_coverage >= 60
|
|
938
|
+
else "needs_improvement"
|
|
933
939
|
),
|
|
934
940
|
},
|
|
935
941
|
}
|
|
@@ -948,7 +954,9 @@ class EnhancedQualitativeAnalyzer:
|
|
|
948
954
|
"status": (
|
|
949
955
|
"excellent"
|
|
950
956
|
if overall_score >= 80
|
|
951
|
-
else "good"
|
|
957
|
+
else "good"
|
|
958
|
+
if overall_score >= 60
|
|
959
|
+
else "needs_improvement"
|
|
952
960
|
),
|
|
953
961
|
}
|
|
954
962
|
|
|
@@ -1918,7 +1926,9 @@ class EnhancedQualitativeAnalyzer:
|
|
|
1918
1926
|
"status": (
|
|
1919
1927
|
"excellent"
|
|
1920
1928
|
if ticket_coverage >= 80
|
|
1921
|
-
else "good"
|
|
1929
|
+
else "good"
|
|
1930
|
+
if ticket_coverage >= 60
|
|
1931
|
+
else "needs_improvement"
|
|
1922
1932
|
),
|
|
1923
1933
|
},
|
|
1924
1934
|
"message_quality": {
|
|
@@ -1926,7 +1936,9 @@ class EnhancedQualitativeAnalyzer:
|
|
|
1926
1936
|
"status": (
|
|
1927
1937
|
"excellent"
|
|
1928
1938
|
if message_quality >= 80
|
|
1929
|
-
else "good"
|
|
1939
|
+
else "good"
|
|
1940
|
+
if message_quality >= 60
|
|
1941
|
+
else "needs_improvement"
|
|
1930
1942
|
),
|
|
1931
1943
|
},
|
|
1932
1944
|
"commit_size_compliance": {
|
|
@@ -1934,7 +1946,9 @@ class EnhancedQualitativeAnalyzer:
|
|
|
1934
1946
|
"status": (
|
|
1935
1947
|
"excellent"
|
|
1936
1948
|
if size_compliance >= 80
|
|
1937
|
-
else "good"
|
|
1949
|
+
else "good"
|
|
1950
|
+
if size_compliance >= 60
|
|
1951
|
+
else "needs_improvement"
|
|
1938
1952
|
),
|
|
1939
1953
|
},
|
|
1940
1954
|
"pr_approval_rate": {"score": pr_approval_rate, "status": "good"}, # Placeholder
|
|
@@ -1986,7 +2000,9 @@ class EnhancedQualitativeAnalyzer:
|
|
|
1986
2000
|
"collaboration_level": (
|
|
1987
2001
|
"high"
|
|
1988
2002
|
if collaboration_score >= 70
|
|
1989
|
-
else "medium"
|
|
2003
|
+
else "medium"
|
|
2004
|
+
if collaboration_score >= 40
|
|
2005
|
+
else "low"
|
|
1990
2006
|
),
|
|
1991
2007
|
"patterns": {
|
|
1992
2008
|
"multi_project_engagement": cross_collaboration_rate >= 50,
|
|
@@ -457,12 +457,16 @@ class RichProgressDisplay:
|
|
|
457
457
|
mem_icon = (
|
|
458
458
|
"🟢"
|
|
459
459
|
if self.statistics.memory_usage < 500
|
|
460
|
-
else "🟡"
|
|
460
|
+
else "🟡"
|
|
461
|
+
if self.statistics.memory_usage < 1000
|
|
462
|
+
else "🔴"
|
|
461
463
|
)
|
|
462
464
|
cpu_icon = (
|
|
463
465
|
"🟢"
|
|
464
466
|
if self.statistics.cpu_percent < 50
|
|
465
|
-
else "🟡"
|
|
467
|
+
else "🟡"
|
|
468
|
+
if self.statistics.cpu_percent < 80
|
|
469
|
+
else "🔴"
|
|
466
470
|
)
|
|
467
471
|
system_stats.append(f"{mem_icon} Memory: {self.statistics.memory_usage:.0f} MB")
|
|
468
472
|
system_stats.append(f"{cpu_icon} CPU: {self.statistics.cpu_percent:.1f}%")
|
|
@@ -471,7 +475,9 @@ class RichProgressDisplay:
|
|
|
471
475
|
speed_icon = (
|
|
472
476
|
"🚀"
|
|
473
477
|
if self.statistics.processing_speed > 100
|
|
474
|
-
else "⚡"
|
|
478
|
+
else "⚡"
|
|
479
|
+
if self.statistics.processing_speed > 50
|
|
480
|
+
else "🐢"
|
|
475
481
|
)
|
|
476
482
|
system_stats.append(
|
|
477
483
|
f"{speed_icon} Speed: {self.statistics.processing_speed:.1f} commits/s"
|
|
@@ -484,7 +490,9 @@ class RichProgressDisplay:
|
|
|
484
490
|
phase_indicator = (
|
|
485
491
|
"⚙️"
|
|
486
492
|
if "Processing" in self.statistics.current_phase
|
|
487
|
-
else "🔍"
|
|
493
|
+
else "🔍"
|
|
494
|
+
if "Analyzing" in self.statistics.current_phase
|
|
495
|
+
else "✨"
|
|
488
496
|
)
|
|
489
497
|
phase_text = f"{phase_indicator} [bold green]{self.statistics.current_phase}[/bold green]"
|
|
490
498
|
elapsed_text = f"⏱️ [bold blue]{self.statistics.get_elapsed_time()}[/bold blue]"
|
|
@@ -1250,9 +1258,9 @@ class SimpleProgressDisplay:
|
|
|
1250
1258
|
# Compatibility methods for CLI interface
|
|
1251
1259
|
def show_header(self):
|
|
1252
1260
|
"""Display header - compatibility method for CLI."""
|
|
1253
|
-
print(f"\n{'='*60}")
|
|
1261
|
+
print(f"\n{'=' * 60}")
|
|
1254
1262
|
print(f"GitFlow Analytics v{self.version}")
|
|
1255
|
-
print(f"{'='*60}\n")
|
|
1263
|
+
print(f"{'=' * 60}\n")
|
|
1256
1264
|
|
|
1257
1265
|
def start_live_display(self):
|
|
1258
1266
|
"""Start live display - compatibility wrapper for start()."""
|
|
@@ -636,7 +636,7 @@ class ActivityVerifier:
|
|
|
636
636
|
# Group consecutive days
|
|
637
637
|
lines.append(f"Found {len(zero_activity_days)} days with no activity:")
|
|
638
638
|
for i in range(0, len(zero_activity_days), 7):
|
|
639
|
-
lines.append(f" {', '.join(zero_activity_days[i:i+7])}")
|
|
639
|
+
lines.append(f" {', '.join(zero_activity_days[i : i + 7])}")
|
|
640
640
|
else:
|
|
641
641
|
lines.append("No days with zero activity found!")
|
|
642
642
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitflow-analytics
|
|
3
|
-
Version: 3.13.
|
|
3
|
+
Version: 3.13.5
|
|
4
4
|
Summary: Analyze Git repositories for developer productivity insights
|
|
5
5
|
Author-email: Bob Matyas <bobmatnyc@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -1183,6 +1183,42 @@ gitflow-analytics analyze -c config.yaml --debug
|
|
|
1183
1183
|
|
|
1184
1184
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
1185
1185
|
|
|
1186
|
+
### Development Setup
|
|
1187
|
+
|
|
1188
|
+
```bash
|
|
1189
|
+
# Clone the repository
|
|
1190
|
+
git clone https://github.com/bobmatnyc/gitflow-analytics.git
|
|
1191
|
+
cd gitflow-analytics
|
|
1192
|
+
|
|
1193
|
+
# Install development dependencies
|
|
1194
|
+
make install-dev
|
|
1195
|
+
|
|
1196
|
+
# Run tests
|
|
1197
|
+
make test
|
|
1198
|
+
|
|
1199
|
+
# Format code
|
|
1200
|
+
make format
|
|
1201
|
+
|
|
1202
|
+
# Run all quality checks
|
|
1203
|
+
make quality-gate
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1206
|
+
### Release Workflow
|
|
1207
|
+
|
|
1208
|
+
This project uses a Makefile-based release workflow for simplicity and transparency. See [RELEASE.md](RELEASE.md) for detailed documentation.
|
|
1209
|
+
|
|
1210
|
+
**Quick Reference:**
|
|
1211
|
+
```bash
|
|
1212
|
+
make release-patch # Bug fixes (3.13.1 → 3.13.2)
|
|
1213
|
+
make release-minor # New features (3.13.1 → 3.14.0)
|
|
1214
|
+
make release-major # Breaking changes (3.13.1 → 4.0.0)
|
|
1215
|
+
```
|
|
1216
|
+
|
|
1217
|
+
For more details, see:
|
|
1218
|
+
- [RELEASE.md](RELEASE.md) - Comprehensive release guide
|
|
1219
|
+
- [RELEASE_QUICKREF.md](RELEASE_QUICKREF.md) - Quick reference card
|
|
1220
|
+
- `make help` - All available commands
|
|
1221
|
+
|
|
1186
1222
|
## License
|
|
1187
1223
|
|
|
1188
1224
|
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
gitflow_analytics/__init__.py,sha256=W3Jaey5wuT1nBPehVLTIRkVIyBa5jgYOlBKc_UFfh-4,773
|
|
2
|
-
gitflow_analytics/_version.py,sha256
|
|
3
|
-
gitflow_analytics/cli.py,sha256=
|
|
2
|
+
gitflow_analytics/_version.py,sha256=-dJJFHKImkdT2L9Kw0gRXFj8dh26CFKPTsdrAlg1aTg,138
|
|
3
|
+
gitflow_analytics/cli.py,sha256=KdnuqiUodiqmImsDfMv15MuOlLrI_2zTv3x4s1BV0U8,302532
|
|
4
4
|
gitflow_analytics/config.py,sha256=XRuxvzLWyn_ML7mDCcuZ9-YFNAEsnt33vIuWxQQ_jxg,1033
|
|
5
5
|
gitflow_analytics/constants.py,sha256=GXEncUJS9ijOI5KWtQCTANwdqxPfXpw-4lNjhaWTKC4,2488
|
|
6
|
-
gitflow_analytics/verify_activity.py,sha256=
|
|
6
|
+
gitflow_analytics/verify_activity.py,sha256=q82VnU8FhHEPlnupYMvh1XtyaDJfIPPg-AI8cSM0PIk,27054
|
|
7
7
|
gitflow_analytics/classification/__init__.py,sha256=p8shPUZpGaw7-ivhfAVrPDbSP2LrpvWC1WEsBJIg-PI,969
|
|
8
8
|
gitflow_analytics/classification/batch_classifier.py,sha256=wR1hwYOB4JbV2h5fQrs-UHlf4XwCPZSJUjKFWyD4Qv0,37696
|
|
9
9
|
gitflow_analytics/classification/classifier.py,sha256=U1vpdiMXqGdHR8iHWf_wPdrJxxNRB5By94BDpck8R9g,17750
|
|
@@ -12,13 +12,13 @@ gitflow_analytics/classification/linguist_analyzer.py,sha256=HjLx9mM7hGXtrvMba6o
|
|
|
12
12
|
gitflow_analytics/classification/model.py,sha256=2KbmFh9MpyvHMcNHbqwUTAAVLHHu3MiTfFIPyZSGa-8,16356
|
|
13
13
|
gitflow_analytics/cli_wizards/__init__.py,sha256=iSCVYkwAnyPweZixLtFnNa7pB8DRLAj_sJrUPYesdn8,432
|
|
14
14
|
gitflow_analytics/cli_wizards/install_wizard.py,sha256=gz5c1NYeGLCzs-plL6ju7GXn7VldF7VyMw8MO4CzUGk,70345
|
|
15
|
-
gitflow_analytics/cli_wizards/menu.py,sha256=
|
|
15
|
+
gitflow_analytics/cli_wizards/menu.py,sha256=Jcz4aTimVQ2kt1z9yC3I8uWUrmxitLvCvvSgem_nRpI,26106
|
|
16
16
|
gitflow_analytics/cli_wizards/run_launcher.py,sha256=J6G_C7IqxPg7_GhAfbV99D1dIIWwb1s_qmHC7Iv2iGI,15038
|
|
17
17
|
gitflow_analytics/config/__init__.py,sha256=KziRIbBJctB5LOLcKLzELWA1rXwjS6-C2_DeM_hT9rM,1133
|
|
18
18
|
gitflow_analytics/config/aliases.py,sha256=z9F0X6qbbF544Tw7sHlOoBj5mpRSddMkCpoKLzvVzDU,10960
|
|
19
19
|
gitflow_analytics/config/errors.py,sha256=IBKhAIwJ4gscZFnLDyE3jEp03wn2stPR7JQJXNSIfok,10386
|
|
20
|
-
gitflow_analytics/config/loader.py,sha256=
|
|
21
|
-
gitflow_analytics/config/profiles.py,sha256=
|
|
20
|
+
gitflow_analytics/config/loader.py,sha256=khhxlt14TE_J-q-07cuhGpvmatU9Ttii0oMcKnsFpMA,38084
|
|
21
|
+
gitflow_analytics/config/profiles.py,sha256=61lGoRScui3kBE63Bb9CSA442ISVjD_TupCEK-Yh7Yk,7947
|
|
22
22
|
gitflow_analytics/config/repository.py,sha256=u7JHcKvqmXOl3i7EmNUfJ6wtjzElxPMyXRkATnVyQ0I,4685
|
|
23
23
|
gitflow_analytics/config/schema.py,sha256=ETxxWUwpAAwMXiXFkawoYcwJvvSo9D6zK0uHU-JLyS0,17270
|
|
24
24
|
gitflow_analytics/config/validator.py,sha256=l7AHjXYJ8wEmyA1rn2WiItZXtAiRb9YBLjFCDl53qKM,5907
|
|
@@ -26,7 +26,7 @@ gitflow_analytics/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
|
|
|
26
26
|
gitflow_analytics/core/analyzer.py,sha256=apLbRFAOGDPCNnBTNOG_eXaVXh_QglO07t6p5sINnKo,59924
|
|
27
27
|
gitflow_analytics/core/branch_mapper.py,sha256=1L1ctrhTEqMZ61eS1nZRkcyaarLipeQgotw4HdXcSmM,7407
|
|
28
28
|
gitflow_analytics/core/cache.py,sha256=2SBzry3FoLCJyhu-I-AgNTSzN_MkA-DunzOAxq_lyTw,69152
|
|
29
|
-
gitflow_analytics/core/data_fetcher.py,sha256=
|
|
29
|
+
gitflow_analytics/core/data_fetcher.py,sha256=KI0lGxrKvjOHf2UjnGytmcy9GSnSsA28c5mysOH7q1o,106944
|
|
30
30
|
gitflow_analytics/core/git_auth.py,sha256=QP7U5_Mi9J-hEtoEhdjoMBl61nCukOGlL8PYXYSyN3g,6369
|
|
31
31
|
gitflow_analytics/core/git_timeout_wrapper.py,sha256=14K8PHKSOonW4hJpLigB5XQNSWxmFbMFbrpu8cT1h-M,12534
|
|
32
32
|
gitflow_analytics/core/identity.py,sha256=CTjxpM5BeeMyGQ8QbtSCsUmuzMmU7vhBwrdQctjI7Z0,31397
|
|
@@ -38,14 +38,14 @@ gitflow_analytics/extractors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
|
38
38
|
gitflow_analytics/extractors/base.py,sha256=AKbYkFiMhNxVj7zfNzsJfh0rpyTdNr4Faea3bcZPPBo,1168
|
|
39
39
|
gitflow_analytics/extractors/ml_tickets.py,sha256=js5OFmbZt9JHy5r_crehhuB1MxrkdfrPj2u_4B6K35c,43304
|
|
40
40
|
gitflow_analytics/extractors/story_points.py,sha256=IggP-Ei832oV9aD08a3li08kmjF3BqyU9i8EgAZcpfs,5324
|
|
41
|
-
gitflow_analytics/extractors/tickets.py,sha256=
|
|
41
|
+
gitflow_analytics/extractors/tickets.py,sha256=vNOUGyUSHiaBguPOWwg1gzB5gJ4RJtZs4HdLAJqvI0k,43883
|
|
42
42
|
gitflow_analytics/identity_llm/__init__.py,sha256=tpWDwapm6zIyb8LxLO8A6pHlE3wNorT_fBL-Yp9-XnU,250
|
|
43
43
|
gitflow_analytics/identity_llm/analysis_pass.py,sha256=FJF1BEGekHRY4i5jasgxxL_UWFGYP5kBkvn8hAtMorY,9728
|
|
44
44
|
gitflow_analytics/identity_llm/analyzer.py,sha256=-a7lUJt_Dlgx9aNOH1YlFqPe7BSxtwY2RoGruIzwrzs,17932
|
|
45
45
|
gitflow_analytics/identity_llm/models.py,sha256=F1RN6g8og9esj-m4TPY_928Ci9TA43G9NFNHYf4zHHQ,2677
|
|
46
46
|
gitflow_analytics/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
|
-
gitflow_analytics/integrations/github_integration.py,sha256=
|
|
48
|
-
gitflow_analytics/integrations/jira_integration.py,sha256
|
|
47
|
+
gitflow_analytics/integrations/github_integration.py,sha256=4hTV8I1ACY9ELRIbl9vikamtPkiUyVN1ualtkBvrWPE,13330
|
|
48
|
+
gitflow_analytics/integrations/jira_integration.py,sha256=-Rutft6uQG7hUAcbUck0iHJZ2dbKcToN9zhjd9aIWng,28767
|
|
49
49
|
gitflow_analytics/integrations/orchestrator.py,sha256=u3FKZF2yD5g5HhNFm6nIJe69ZKfU1QLni4S14GDRIrY,13205
|
|
50
50
|
gitflow_analytics/metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
51
|
gitflow_analytics/metrics/activity_scoring.py,sha256=lSrdeH9SVNzeSR80vvzdWBuFcD3BkHsOdSZu97q6Bdg,13171
|
|
@@ -61,8 +61,8 @@ gitflow_analytics/pm_framework/registry.py,sha256=ggUHS3WFsKXifaYPZgY15r2vGZEKyx
|
|
|
61
61
|
gitflow_analytics/pm_framework/adapters/__init__.py,sha256=vS5btB-yIwVHZfoFYacWxHk3HszxIMWLnvBUgVDdNDU,1756
|
|
62
62
|
gitflow_analytics/pm_framework/adapters/jira_adapter.py,sha256=E5-NuKHFDGkqObjhWvXqoEsVVnLXrnAiF2v81hTYQ7A,72527
|
|
63
63
|
gitflow_analytics/qualitative/__init__.py,sha256=fwlb_xrv7Gatjylk5wclzckZxyss8K5cdZhhTHMWfYw,1184
|
|
64
|
-
gitflow_analytics/qualitative/chatgpt_analyzer.py,sha256=
|
|
65
|
-
gitflow_analytics/qualitative/enhanced_analyzer.py,sha256=
|
|
64
|
+
gitflow_analytics/qualitative/chatgpt_analyzer.py,sha256=nQDk1Rf2z2svpsnXoz0mxbwLXytFo3EgImbegg53FvI,11816
|
|
65
|
+
gitflow_analytics/qualitative/enhanced_analyzer.py,sha256=js27aVVxt9ZUYEcbJwYdmpt7JZTRbLHcWCkO2LrxA9E,92733
|
|
66
66
|
gitflow_analytics/qualitative/example_enhanced_usage.py,sha256=pKKhAjOCwmBaJPzZ8RDl8R4uG23NwFXUUeAqv6oYM2E,14924
|
|
67
67
|
gitflow_analytics/qualitative/classifiers/__init__.py,sha256=lgabpW-_aub_O-1CVbmgeUVEo2jf5O-DK0Y2dF-WrZc,346
|
|
68
68
|
gitflow_analytics/qualitative/classifiers/change_type.py,sha256=3glCIkNxTQAbk0s0Urp4nLp9OXtYzF0-I8SzOEpx9JE,23291
|
|
@@ -76,13 +76,13 @@ gitflow_analytics/qualitative/classifiers/llm/batch_processor.py,sha256=oSlJPYOA
|
|
|
76
76
|
gitflow_analytics/qualitative/classifiers/llm/cache.py,sha256=5UWRMgz0bOc_GRShE5gvYEzLhxB-VBDkhZKRECNmjOI,16930
|
|
77
77
|
gitflow_analytics/qualitative/classifiers/llm/cost_tracker.py,sha256=2mqWPHo9SrhwZQ0Q_FNViI_M83Rl18sJVg3p1lwkcBM,14902
|
|
78
78
|
gitflow_analytics/qualitative/classifiers/llm/openai_client.py,sha256=pkLvXpAR9tK2VLPwpUoK3GYmThMb8VcekRj2qpvVrNw,15255
|
|
79
|
-
gitflow_analytics/qualitative/classifiers/llm/prompts.py,sha256=
|
|
79
|
+
gitflow_analytics/qualitative/classifiers/llm/prompts.py,sha256=JCA2_Ecswugk3L6Kw0GB7NFsBpEvhsG3FSnG8x1T5-c,12904
|
|
80
80
|
gitflow_analytics/qualitative/classifiers/llm/response_parser.py,sha256=AyGfTpmvpx4QvRKJDWGS3CbylLm2SOAD0nSZVrkGTrM,9931
|
|
81
81
|
gitflow_analytics/qualitative/core/__init__.py,sha256=22tZJDPyYE0k5-9lx_84R2SsZN8PRc_1I1L6prSkoSE,315
|
|
82
82
|
gitflow_analytics/qualitative/core/llm_fallback.py,sha256=q6KijFgi7PMgmWdgKEhFV3JSskbrs4PrD27npVSTDz4,25572
|
|
83
83
|
gitflow_analytics/qualitative/core/nlp_engine.py,sha256=c-R0chjKmCif5ilBl3JIURNushVNw5musc8INJhL3cc,14490
|
|
84
84
|
gitflow_analytics/qualitative/core/pattern_cache.py,sha256=H_t759ftWGJC3QQy6dKqdt3Mf2TojWfV6C41eVdPZTo,18537
|
|
85
|
-
gitflow_analytics/qualitative/core/processor.py,sha256=
|
|
85
|
+
gitflow_analytics/qualitative/core/processor.py,sha256=uLeE2qHo4Py54UNJQFMlMubMUmC4NuXK6SivFEB5p8U,27434
|
|
86
86
|
gitflow_analytics/qualitative/models/__init__.py,sha256=Ro_lAXyt3jfL29xgZ6jn_DvDRv3PZ6myzsrXq_ctqRQ,457
|
|
87
87
|
gitflow_analytics/qualitative/models/schemas.py,sha256=9GRvp_mFOtIRiAbaNjzxq5Lo8PzD-r4F_-sqoyOjN3w,10670
|
|
88
88
|
gitflow_analytics/qualitative/utils/__init__.py,sha256=YGLGiP4WWFO-KnZERJ6uj8M3uJsmizsSeoR1tsoGK0c,319
|
|
@@ -124,12 +124,12 @@ gitflow_analytics/training/pipeline.py,sha256=PQegTk_-OsPexVyRDfiy-3Df-7pcs25C4v
|
|
|
124
124
|
gitflow_analytics/types/__init__.py,sha256=v31ysjqF7jgCUkqAKaj9gqV3RDjL74sJRzX3uh7NxZA,156
|
|
125
125
|
gitflow_analytics/types/commit_types.py,sha256=Ub7Nyh5ajGQW_YVoVV_iQ1Y05aEHZd-YGA4xjOSHElc,1684
|
|
126
126
|
gitflow_analytics/ui/__init__.py,sha256=UBhYhZMvwlSrCuGWjkIdoP2zNbiQxOHOli-I8mqIZUE,441
|
|
127
|
-
gitflow_analytics/ui/progress_display.py,sha256=
|
|
127
|
+
gitflow_analytics/ui/progress_display.py,sha256=omCS86mCQR0QeMoM0YnsV3Gf2oALsDLu8u7XseQU6lk,59306
|
|
128
128
|
gitflow_analytics/utils/__init__.py,sha256=YE3E5Mx-LmVRqLIgUUwDmbstm6gkpeavYHrQmVjwR3o,197
|
|
129
129
|
gitflow_analytics/utils/commit_utils.py,sha256=TBgrWW73EODGOegGCF79ch0L0e5R6gpydNWutiQOa14,1356
|
|
130
|
-
gitflow_analytics-3.13.
|
|
131
|
-
gitflow_analytics-3.13.
|
|
132
|
-
gitflow_analytics-3.13.
|
|
133
|
-
gitflow_analytics-3.13.
|
|
134
|
-
gitflow_analytics-3.13.
|
|
135
|
-
gitflow_analytics-3.13.
|
|
130
|
+
gitflow_analytics-3.13.5.dist-info/licenses/LICENSE,sha256=xwvSwY1GYXpRpmbnFvvnbmMwpobnrdN9T821sGvjOY0,1066
|
|
131
|
+
gitflow_analytics-3.13.5.dist-info/METADATA,sha256=iB3vdWqPASbljtKC1rORHcIbiP1X21egIORMtIIGFws,40374
|
|
132
|
+
gitflow_analytics-3.13.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
133
|
+
gitflow_analytics-3.13.5.dist-info/entry_points.txt,sha256=ZOsX0GLsnMysp5FWPOfP_qyoS7WJ8IgcaDFDxWBYl1g,98
|
|
134
|
+
gitflow_analytics-3.13.5.dist-info/top_level.txt,sha256=CQyxZXjKvpSB1kgqqtuE0PCRqfRsXZJL8JrYpJKtkrk,18
|
|
135
|
+
gitflow_analytics-3.13.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|