gitflow-analytics 3.13.5__tar.gz → 3.13.7__tar.gz
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-3.13.5 → gitflow_analytics-3.13.7}/PKG-INFO +1 -1
- gitflow_analytics-3.13.7/docs/research/csv-generation-narrative-bug-20251222.md +412 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/pyproject.toml +3 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/_version.py +1 -1
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/cli.py +135 -2
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/core/git_auth.py +74 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/core/git_timeout_wrapper.py +8 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics.egg-info/PKG-INFO +1 -1
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics.egg-info/SOURCES.txt +1 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_reports.py +0 -1
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/CHANGELOG.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/CLAUDE.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/LICENSE +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/MANIFEST.in +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/README.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/DOCUMENTATION-STANDARDS.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/README.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/SECURITY.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/STRUCTURE.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/README.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/analysis-files/ewtn-critical-security-report.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/agent_engineer_20250915_085239_236.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/agent_engineer_20250915_114233_697.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/agent_engineer_20250915_115330_511.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/agent_engineer_20250915_120516_669.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/agent_engineer_20250915_124906_951.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/agent_engineer_20250915_175540_671.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/agent_engineer_20250915_214023_300.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/agent_engineer_20250916_130622_510.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/agent_ops_20250915_123323_419.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/agent_ops_20250915_134446_128.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/agent_ops_20250915_184656_725.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/agent_research_20250915_091233_023.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/agent_research_20250915_100404_339.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/agent_research_20250915_110606_413.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/system_prompt_20250915_084833_641.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/system_prompt_20250915_090830_660.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-logs/prompts/system_prompt_20250916_130545_822.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-reports/output/database_qualitative_report_20250630_20250824.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-reports/output/narrative_report_20250630_20250824.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-reports/reports-24week/database_qualitative_report_20250303_20250817.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-reports/reports-24week/narrative_report_20250303_20250817.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-reports/test-ewtn-reports/narrative_report_20250810.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-reports/test-reports/database_qualitative_report_20250908_20250914.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-reports/test-reports/narrative_report_20250908_20250914.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-reports/test-weekly-reports/database_qualitative_report_20250623_20250817.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/old-reports/test-weekly-reports/narrative_report_20250623_20250817.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/temp-files/CHANGELOG_INTERACTIVE_LAUNCHER.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/temp-files/CLEANUP_SUMMARY-20250929.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/temp-files/FEATURES_READY_FOR_TESTING.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/temp-files/FIX_SUMMARY.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/temp-files/IMPLEMENTATION_SUMMARY.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/temp-files/PROGRESS_TRACKING_FIXES.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/temp-files/PROJECT_CLEANUP_REPORT-20250929.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/temp-files/README.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/temp-files/STORY_POINTS_CONFIG_SUMMARY-archived.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/temp-files/SYNTAX_ERROR_FIX.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/temp-files/TEST_REPORT_GIT_CLONING.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/temp-files/TIMEZONE_BUG_RESOLUTION.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/temp-files/USAGE_EXAMPLES-archived.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/_archive/temp-files/code_analysis_report.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/architecture/README.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/architecture/branch-analysis-optimization.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/architecture/caching-strategy.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/architecture/llm-classifier-refactoring.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/architecture/ml-pipeline.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/configuration/configuration.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/deployment/README.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/design/README.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/design/circuit-breaker-implementation.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/design/commit-classification-design.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/design/git_pm_correlation_design.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/design/platform-agnostic-pm-framework.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/design/qualitative_data_extraction.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/developer/README.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/developer/contributing.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/developer/development-setup.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/developer/project-organization.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/developer/refactoring-guide.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/developer/training-guide.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/examples/README.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/examples/interactive-launcher-examples.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/examples/story-points-configuration.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/getting-started/README.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/getting-started/first-analysis.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/getting-started/installation.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/getting-started/quickstart.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/guides/LLM_CLASSIFICATION_GUIDE.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/guides/README.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/guides/chatgpt-setup.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/guides/identity-resolution-enhanced.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/guides/interactive-launcher.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/guides/managing-aliases.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/guides/ml-categorization.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/guides/pm-platform-setup.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/guides/troubleshooting.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/quick-reference/launcher-and-identity.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/reference/README.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/reference/cache-system.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/reference/cli-commands.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/reference/configuration-schema.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/docs/reference/json-export-schema.md +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/setup.cfg +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/classification/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/classification/batch_classifier.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/classification/classifier.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/classification/feature_extractor.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/classification/linguist_analyzer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/classification/model.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/cli_wizards/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/cli_wizards/install_wizard.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/cli_wizards/menu.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/cli_wizards/run_launcher.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/config/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/config/aliases.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/config/errors.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/config/loader.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/config/profiles.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/config/repository.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/config/schema.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/config/validator.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/config.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/constants.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/core/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/core/analyzer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/core/branch_mapper.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/core/cache.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/core/data_fetcher.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/core/identity.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/core/metrics_storage.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/core/progress.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/core/schema_version.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/core/subprocess_git.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/extractors/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/extractors/base.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/extractors/ml_tickets.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/extractors/story_points.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/extractors/tickets.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/identity_llm/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/identity_llm/analysis_pass.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/identity_llm/analyzer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/identity_llm/models.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/integrations/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/integrations/github_integration.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/integrations/jira_integration.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/integrations/orchestrator.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/metrics/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/metrics/activity_scoring.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/metrics/branch_health.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/metrics/dora.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/models/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/models/database.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/pm_framework/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/pm_framework/adapters/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/pm_framework/adapters/jira_adapter.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/pm_framework/base.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/pm_framework/models.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/pm_framework/orchestrator.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/pm_framework/registry.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/chatgpt_analyzer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/classifiers/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/classifiers/change_type.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/classifiers/domain_classifier.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/classifiers/intent_analyzer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/classifiers/llm/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/classifiers/llm/base.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/classifiers/llm/batch_processor.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/classifiers/llm/cache.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/classifiers/llm/cost_tracker.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/classifiers/llm/openai_client.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/classifiers/llm/prompts.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/classifiers/llm/response_parser.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/classifiers/llm_commit_classifier.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/classifiers/risk_analyzer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/core/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/core/llm_fallback.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/core/nlp_engine.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/core/pattern_cache.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/core/processor.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/enhanced_analyzer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/example_enhanced_usage.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/models/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/models/schemas.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/utils/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/utils/batch_processor.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/utils/cost_tracker.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/utils/metrics.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/qualitative/utils/text_processing.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/analytics_writer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/base.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/branch_health_writer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/classification_writer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/cli_integration.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/csv_writer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/data_models.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/database_report_generator.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/example_usage.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/factory.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/formatters.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/html_generator.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/interfaces.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/json_exporter.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/narrative_writer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/story_point_correlation.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/reports/weekly_trends_writer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/security/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/security/config.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/security/extractors/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/security/extractors/dependency_checker.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/security/extractors/secret_detector.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/security/extractors/vulnerability_scanner.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/security/llm_analyzer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/security/reports/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/security/reports/security_report.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/security/security_analyzer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/training/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/training/model_loader.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/training/pipeline.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/types/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/types/commit_types.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/ui/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/ui/progress_display.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/utils/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/utils/commit_utils.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/verify_activity.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics.egg-info/dependency_links.txt +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics.egg-info/entry_points.txt +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics.egg-info/requires.txt +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics.egg-info/top_level.txt +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/conftest.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/core/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/core/test_analyzer.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/core/test_analyzer_merge_stats.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/core/test_cache.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/core/test_data_fetcher.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/core/test_data_fetcher_merge_stats.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/core/test_identity.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/core/test_merge_commit_detection.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/core/test_progress.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/debug_bulk_exists.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/debug_commit_story_points.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/debug_database_storage.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/debug_jira_enrichment.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/debug_story_points.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/extractors/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/integration/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/integration/conftest.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/integration/test_merge_exclusion_workflow.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/integrations/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/metrics/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/models/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/qualitative/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/qualitative/test_basic_integration.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/reports/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/run_all_tests.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/run_security_all_repos.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/run_security_analysis.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test-qa-install/test_error_handling.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test-qa-install/test_wizard_automated.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_atomic_caching.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_cache_invalidation.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_classification_system.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_cli.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_cli_wizards/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_cli_wizards/test_menu.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_config.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_config_extends.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_config_profiles.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_config_story_points.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_jira_connection.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_llm_commit_classification.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_march_2025_comparison.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_metrics.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_ml_accuracy.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_ml_components.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_ml_comprehensive.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_ml_integration.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_pm_env_resolution.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_report_abstraction.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_story_points_analysis.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_training_pipeline.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/test_two_step_process.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/utils/__init__.py +0 -0
- {gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/tests/utils/test_commit_utils.py +0 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
# Bug Analysis: CSV Generation Disabled but Narrative Report Still Tries to Read CSV
|
|
2
|
+
|
|
3
|
+
**Date**: 2025-12-22
|
|
4
|
+
**Analyst**: Research Agent
|
|
5
|
+
**Priority**: High
|
|
6
|
+
**Type**: Logic Error / Missing Conditional Check
|
|
7
|
+
|
|
8
|
+
## Executive Summary
|
|
9
|
+
|
|
10
|
+
When qualitative analysis runs with "CSV generation disabled", the code still unconditionally tries to read `qualitative_insights_20251222.csv`, causing a `FileNotFoundError`. The root cause is that CSV files are **always generated** regardless of the `generate_csv` flag, but the narrative report generation assumes these CSVs exist and attempts to read them without checking if CSV generation was enabled.
|
|
11
|
+
|
|
12
|
+
## Bug Details
|
|
13
|
+
|
|
14
|
+
### Symptom
|
|
15
|
+
```
|
|
16
|
+
Error: FileNotFoundError: [Errno 2] No such file or directory: 'qualitative_insights_20251222.csv'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This occurs when:
|
|
20
|
+
1. User runs analysis with CSV generation disabled (`--no-csv` flag or config setting)
|
|
21
|
+
2. Narrative report generation is enabled (`markdown` in output formats)
|
|
22
|
+
3. The code tries to load CSV files that were never written to disk
|
|
23
|
+
|
|
24
|
+
### Root Cause Analysis
|
|
25
|
+
|
|
26
|
+
**The Fundamental Problem**: The comment "always generate data, optionally write CSV" is misleading. The CSV files ARE written to disk unconditionally, but when `generate_csv=False`, they are deleted or not added to the reports list.
|
|
27
|
+
|
|
28
|
+
#### Code Flow
|
|
29
|
+
|
|
30
|
+
**Step 1: CSV Report Generation (Lines 3663-3725)**
|
|
31
|
+
|
|
32
|
+
All three CSV reports follow the same flawed pattern:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# Activity distribution report (always generate data, optionally write CSV)
|
|
36
|
+
activity_report = output / f"activity_distribution_{datetime.now().strftime('%Y%m%d')}.csv"
|
|
37
|
+
try:
|
|
38
|
+
logger.debug("Starting activity distribution report generation")
|
|
39
|
+
analytics_gen.generate_activity_distribution_report(
|
|
40
|
+
all_commits, developer_stats, activity_report
|
|
41
|
+
)
|
|
42
|
+
logger.debug("Activity distribution report completed successfully")
|
|
43
|
+
if generate_csv: # ⚠️ Only controls whether report is LISTED, not whether it's WRITTEN
|
|
44
|
+
generated_reports.append(activity_report.name)
|
|
45
|
+
if not display:
|
|
46
|
+
click.echo(f" ✅ Activity distribution: {activity_report}")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**What Actually Happens**:
|
|
50
|
+
- `generate_activity_distribution_report()` is called unconditionally
|
|
51
|
+
- Inside `analytics_writer.py:316-346`, the CSV is **written to disk** (line 344: `df.to_csv(output_path, index=False)`)
|
|
52
|
+
- The `if generate_csv` check only controls:
|
|
53
|
+
- Whether the filename is added to `generated_reports` list
|
|
54
|
+
- Whether a success message is printed
|
|
55
|
+
|
|
56
|
+
**The Same Pattern for All Three CSVs**:
|
|
57
|
+
1. **activity_report** (lines 3663-3685)
|
|
58
|
+
2. **focus_report** (lines 3687-3709)
|
|
59
|
+
3. **insights_report** (lines 3711-3725)
|
|
60
|
+
|
|
61
|
+
All three reports:
|
|
62
|
+
- Define the CSV path
|
|
63
|
+
- Call the generator function (which writes CSV to disk)
|
|
64
|
+
- Only conditionally add to `generated_reports` list if `generate_csv=True`
|
|
65
|
+
|
|
66
|
+
**Step 2: Narrative Report Generation (Lines 3911-4039)**
|
|
67
|
+
|
|
68
|
+
The narrative report generation **unconditionally reads all three CSVs**:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
# Generate markdown reports if enabled
|
|
72
|
+
if "markdown" in cfg.output.formats:
|
|
73
|
+
# Calculate date range for consistent filename formatting across all markdown reports
|
|
74
|
+
date_range = f"{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}"
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
logger.debug("Starting narrative report generation")
|
|
78
|
+
narrative_gen = NarrativeReportGenerator()
|
|
79
|
+
|
|
80
|
+
# Lazy import pandas - only needed for CSV reading in narrative generation
|
|
81
|
+
import pandas as pd
|
|
82
|
+
|
|
83
|
+
# Load activity distribution data
|
|
84
|
+
logger.debug("Loading activity distribution data")
|
|
85
|
+
activity_df = pd.read_csv(activity_report) # ⚠️ ASSUMES CSV EXISTS
|
|
86
|
+
activity_data = cast(list[dict[str, Any]], activity_df.to_dict("records"))
|
|
87
|
+
|
|
88
|
+
# Load focus data
|
|
89
|
+
logger.debug("Loading focus data")
|
|
90
|
+
focus_df = pd.read_csv(focus_report) # ⚠️ ASSUMES CSV EXISTS
|
|
91
|
+
focus_data = cast(list[dict[str, Any]], focus_df.to_dict("records"))
|
|
92
|
+
|
|
93
|
+
# Load insights data
|
|
94
|
+
logger.debug("Loading insights data")
|
|
95
|
+
insights_df = pd.read_csv(insights_report) # ⚠️ ASSUMES CSV EXISTS
|
|
96
|
+
insights_data = cast(list[dict[str, Any]], insights_df.to_dict("records"))
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Problem**: The narrative report generation has NO conditional check for `generate_csv`. It assumes the CSV files exist on disk.
|
|
100
|
+
|
|
101
|
+
### Variables Involved
|
|
102
|
+
|
|
103
|
+
| Variable | Definition Line | Purpose | Current Behavior |
|
|
104
|
+
|----------|----------------|---------|------------------|
|
|
105
|
+
| `generate_csv` | Function parameter | Controls whether CSVs should be generated | Only controls whether CSVs are listed in `generated_reports`, NOT whether they're written |
|
|
106
|
+
| `activity_report` | Line 3664 | Path to activity CSV | Path is defined, CSV is written, but may not be listed |
|
|
107
|
+
| `focus_report` | Line 3688 | Path to focus CSV | Path is defined, CSV is written, but may not be listed |
|
|
108
|
+
| `insights_report` | Line 3712 | Path to insights CSV | Path is defined, CSV is written, but may not be listed |
|
|
109
|
+
| `generated_reports` | List variable | Tracks which reports to display | Only includes CSVs if `generate_csv=True` |
|
|
110
|
+
|
|
111
|
+
### The Contradiction
|
|
112
|
+
|
|
113
|
+
**Comment Says**: "always generate data, optionally write CSV"
|
|
114
|
+
|
|
115
|
+
**Reality Is**:
|
|
116
|
+
- CSV is **always written** to disk (line 344 in `analytics_writer.py`)
|
|
117
|
+
- The `generate_csv` flag only controls reporting/listing
|
|
118
|
+
- BUT: If CSV generation is disabled in the future (e.g., by wrapping the generator call in `if generate_csv:`), the narrative report will break
|
|
119
|
+
|
|
120
|
+
**Why This Works Sometimes**:
|
|
121
|
+
Currently, the bug doesn't manifest because CSVs ARE always written. The bug will appear if:
|
|
122
|
+
1. Someone "fixes" the CSV generation to respect the `generate_csv` flag
|
|
123
|
+
2. The CSV file gets deleted between generation and narrative report
|
|
124
|
+
3. File permissions prevent CSV writing but don't raise an exception
|
|
125
|
+
|
|
126
|
+
## Impact Analysis
|
|
127
|
+
|
|
128
|
+
### Current State
|
|
129
|
+
- **Low Impact**: CSVs are always written, so narrative report succeeds
|
|
130
|
+
- **Confusion**: Users see "CSV generation disabled" but CSVs are still created
|
|
131
|
+
- **Technical Debt**: Misleading code comments and variable names
|
|
132
|
+
|
|
133
|
+
### If CSV Generation is "Fixed"
|
|
134
|
+
- **High Impact**: Narrative report will crash with FileNotFoundError
|
|
135
|
+
- **User Experience**: Broken feature when CSV generation is disabled
|
|
136
|
+
- **Data Loss**: No way to generate narrative reports without CSV files
|
|
137
|
+
|
|
138
|
+
## Recommended Fix Approach
|
|
139
|
+
|
|
140
|
+
### Option 1: Skip Narrative Report When CSV Disabled (Conservative)
|
|
141
|
+
|
|
142
|
+
**Rationale**: If CSV generation is disabled, don't generate narrative report at all.
|
|
143
|
+
|
|
144
|
+
**Change Location**: `cli.py` lines 3911-3039
|
|
145
|
+
|
|
146
|
+
**Implementation**:
|
|
147
|
+
```python
|
|
148
|
+
# Generate markdown reports if enabled
|
|
149
|
+
if "markdown" in cfg.output.formats and generate_csv: # ⚠️ ADD generate_csv CHECK
|
|
150
|
+
# Calculate date range for consistent filename formatting across all markdown reports
|
|
151
|
+
date_range = f"{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}"
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
logger.debug("Starting narrative report generation")
|
|
155
|
+
# ... rest of the code unchanged ...
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Pros**:
|
|
159
|
+
- Minimal code change
|
|
160
|
+
- Clear logic: no CSVs = no narrative report
|
|
161
|
+
- No risk of file not found errors
|
|
162
|
+
|
|
163
|
+
**Cons**:
|
|
164
|
+
- Users lose narrative reports when CSV generation is disabled
|
|
165
|
+
- Doesn't address the underlying architectural issue
|
|
166
|
+
|
|
167
|
+
### Option 2: Generate In-Memory Data Without Writing CSVs (Optimal)
|
|
168
|
+
|
|
169
|
+
**Rationale**: Separate data generation from CSV writing. Generate data in memory, optionally write CSVs, always pass data to narrative report.
|
|
170
|
+
|
|
171
|
+
**Change Locations**:
|
|
172
|
+
1. `analytics_writer.py` lines 316-346 (and similar methods)
|
|
173
|
+
2. `cli.py` lines 3663-3725
|
|
174
|
+
|
|
175
|
+
**Implementation**:
|
|
176
|
+
|
|
177
|
+
**Step 1**: Modify report generators to return data AND optionally write CSV:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
def generate_qualitative_insights_report(
|
|
181
|
+
self,
|
|
182
|
+
commits: List[Dict[str, Any]],
|
|
183
|
+
developer_stats: List[Dict[str, Any]],
|
|
184
|
+
ticket_analysis: Dict[str, Any],
|
|
185
|
+
output_path: Path | None = None # ⚠️ Make optional
|
|
186
|
+
) -> tuple[pd.DataFrame, Path | None]: # ⚠️ Return data AND path
|
|
187
|
+
"""Generate qualitative insights and patterns report."""
|
|
188
|
+
# Apply exclusion filtering in Phase 2
|
|
189
|
+
commits = self._filter_excluded_authors(commits)
|
|
190
|
+
developer_stats = self._filter_excluded_authors(developer_stats)
|
|
191
|
+
insights = []
|
|
192
|
+
|
|
193
|
+
# Analyze commit patterns
|
|
194
|
+
commit_insights = self._analyze_commit_patterns(commits)
|
|
195
|
+
insights.extend(commit_insights)
|
|
196
|
+
|
|
197
|
+
# ... other analysis steps ...
|
|
198
|
+
|
|
199
|
+
# Create DataFrame
|
|
200
|
+
df = pd.DataFrame(insights)
|
|
201
|
+
|
|
202
|
+
# Write CSV only if path provided
|
|
203
|
+
written_path = None
|
|
204
|
+
if output_path is not None:
|
|
205
|
+
df.to_csv(output_path, index=False)
|
|
206
|
+
written_path = output_path
|
|
207
|
+
|
|
208
|
+
return df, written_path # ⚠️ Return both data and path
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Step 2**: Update CLI to use returned data:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
# Qualitative insights report (always generate data, optionally write CSV)
|
|
215
|
+
insights_report_path = output / f"qualitative_insights_{datetime.now().strftime('%Y%m%d')}.csv" if generate_csv else None
|
|
216
|
+
try:
|
|
217
|
+
logger.debug("Starting qualitative insights report generation")
|
|
218
|
+
insights_df, insights_written_path = analytics_gen.generate_qualitative_insights_report(
|
|
219
|
+
all_commits, developer_stats, ticket_analysis, insights_report_path
|
|
220
|
+
)
|
|
221
|
+
insights_data = cast(list[dict[str, Any]], insights_df.to_dict("records"))
|
|
222
|
+
logger.debug("Qualitative insights report completed successfully")
|
|
223
|
+
if insights_written_path:
|
|
224
|
+
generated_reports.append(insights_written_path.name)
|
|
225
|
+
if not display:
|
|
226
|
+
click.echo(f" ✅ Qualitative insights: {insights_written_path}")
|
|
227
|
+
except Exception as e:
|
|
228
|
+
logger.error(f"Error in qualitative insights report generation: {e}")
|
|
229
|
+
raise
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Step 3**: Update narrative report to use in-memory data:
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
# Generate markdown reports if enabled
|
|
236
|
+
if "markdown" in cfg.output.formats:
|
|
237
|
+
# ... narrative generation setup ...
|
|
238
|
+
|
|
239
|
+
# Use in-memory data instead of reading CSVs
|
|
240
|
+
narrative_gen.generate_narrative_report(
|
|
241
|
+
all_commits,
|
|
242
|
+
all_prs,
|
|
243
|
+
developer_stats,
|
|
244
|
+
activity_data, # ⚠️ Use in-memory data from Step 2
|
|
245
|
+
focus_data, # ⚠️ Use in-memory data from Step 2
|
|
246
|
+
insights_data, # ⚠️ Use in-memory data from Step 2
|
|
247
|
+
ticket_analysis,
|
|
248
|
+
pr_metrics,
|
|
249
|
+
narrative_report,
|
|
250
|
+
weeks,
|
|
251
|
+
aggregated_pm_data,
|
|
252
|
+
chatgpt_summary,
|
|
253
|
+
branch_health_metrics,
|
|
254
|
+
cfg.analysis.exclude_authors,
|
|
255
|
+
analysis_start_date=start_date,
|
|
256
|
+
analysis_end_date=end_date,
|
|
257
|
+
)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Pros**:
|
|
261
|
+
- Clean separation of concerns
|
|
262
|
+
- Narrative reports work with or without CSV generation
|
|
263
|
+
- No unnecessary disk I/O
|
|
264
|
+
- Prevents FileNotFoundError completely
|
|
265
|
+
- More testable (can test data generation without file I/O)
|
|
266
|
+
|
|
267
|
+
**Cons**:
|
|
268
|
+
- More extensive refactoring required
|
|
269
|
+
- Need to update all three report generators
|
|
270
|
+
- Need to update narrative report call
|
|
271
|
+
|
|
272
|
+
### Option 3: Check File Existence Before Reading (Quick Fix)
|
|
273
|
+
|
|
274
|
+
**Rationale**: Add defensive checks before reading CSVs.
|
|
275
|
+
|
|
276
|
+
**Change Location**: `cli.py` lines 3920-3936
|
|
277
|
+
|
|
278
|
+
**Implementation**:
|
|
279
|
+
```python
|
|
280
|
+
# Generate markdown reports if enabled
|
|
281
|
+
if "markdown" in cfg.output.formats:
|
|
282
|
+
# Calculate date range for consistent filename formatting across all markdown reports
|
|
283
|
+
date_range = f"{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}"
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
logger.debug("Starting narrative report generation")
|
|
287
|
+
narrative_gen = NarrativeReportGenerator()
|
|
288
|
+
|
|
289
|
+
# Lazy import pandas - only needed for CSV reading in narrative generation
|
|
290
|
+
import pandas as pd
|
|
291
|
+
|
|
292
|
+
# ⚠️ CHECK FILE EXISTENCE BEFORE READING
|
|
293
|
+
if not (activity_report.exists() and focus_report.exists() and insights_report.exists()):
|
|
294
|
+
logger.warning("CSV files not found, skipping narrative report generation")
|
|
295
|
+
if not display:
|
|
296
|
+
click.echo(" ⚠️ Narrative report skipped: CSV generation was disabled")
|
|
297
|
+
# Skip to next section
|
|
298
|
+
else:
|
|
299
|
+
# Load activity distribution data
|
|
300
|
+
logger.debug("Loading activity distribution data")
|
|
301
|
+
activity_df = pd.read_csv(activity_report)
|
|
302
|
+
# ... rest of the code unchanged ...
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**Pros**:
|
|
306
|
+
- Quick fix
|
|
307
|
+
- Prevents crash
|
|
308
|
+
- Minimal code change
|
|
309
|
+
|
|
310
|
+
**Cons**:
|
|
311
|
+
- Doesn't solve the architectural issue
|
|
312
|
+
- Silent failure mode (narrative report silently skipped)
|
|
313
|
+
- Still relies on CSV files existing on disk
|
|
314
|
+
|
|
315
|
+
## Recommendation
|
|
316
|
+
|
|
317
|
+
**Primary Recommendation**: **Option 2** (Generate In-Memory Data Without Writing CSVs)
|
|
318
|
+
|
|
319
|
+
This is the most robust solution because:
|
|
320
|
+
1. Eliminates the file dependency completely
|
|
321
|
+
2. Improves performance (no unnecessary disk I/O)
|
|
322
|
+
3. Makes the code more testable
|
|
323
|
+
4. Prevents future FileNotFoundError scenarios
|
|
324
|
+
5. Aligns with the original intent ("always generate data, optionally write CSV")
|
|
325
|
+
|
|
326
|
+
**Fallback Recommendation**: **Option 1** (Skip Narrative Report When CSV Disabled)
|
|
327
|
+
|
|
328
|
+
If Option 2 is too complex for immediate implementation:
|
|
329
|
+
1. Apply Option 1 as a quick fix
|
|
330
|
+
2. File a technical debt ticket for Option 2
|
|
331
|
+
3. Add a warning message to users when narrative report is skipped
|
|
332
|
+
|
|
333
|
+
**Not Recommended**: Option 3 (defensive file checks) - This is a band-aid that doesn't address the root cause.
|
|
334
|
+
|
|
335
|
+
## Test Cases Required
|
|
336
|
+
|
|
337
|
+
After implementing the fix, verify:
|
|
338
|
+
|
|
339
|
+
1. **CSV Generation Enabled + Narrative Enabled**
|
|
340
|
+
- CSVs written to disk ✓
|
|
341
|
+
- Narrative report generated ✓
|
|
342
|
+
- All data consistent ✓
|
|
343
|
+
|
|
344
|
+
2. **CSV Generation Disabled + Narrative Enabled**
|
|
345
|
+
- CSVs NOT written to disk ✓
|
|
346
|
+
- Narrative report generated (Option 2) or skipped with warning (Option 1) ✓
|
|
347
|
+
- No FileNotFoundError ✓
|
|
348
|
+
|
|
349
|
+
3. **CSV Generation Enabled + Narrative Disabled**
|
|
350
|
+
- CSVs written to disk ✓
|
|
351
|
+
- Narrative report NOT generated ✓
|
|
352
|
+
|
|
353
|
+
4. **CSV Generation Disabled + Narrative Disabled**
|
|
354
|
+
- CSVs NOT written to disk ✓
|
|
355
|
+
- Narrative report NOT generated ✓
|
|
356
|
+
|
|
357
|
+
## Files Requiring Changes
|
|
358
|
+
|
|
359
|
+
### Option 1 (Conservative Fix)
|
|
360
|
+
- `/Users/masa/Projects/gitflow-analytics/src/gitflow_analytics/cli.py` (line 3912)
|
|
361
|
+
|
|
362
|
+
### Option 2 (Optimal Fix)
|
|
363
|
+
- `/Users/masa/Projects/gitflow-analytics/src/gitflow_analytics/reports/analytics_writer.py` (lines 316-346, and similar methods)
|
|
364
|
+
- `/Users/masa/Projects/gitflow-analytics/src/gitflow_analytics/cli.py` (lines 3663-3725, 3911-4039)
|
|
365
|
+
|
|
366
|
+
### Option 3 (Quick Fix)
|
|
367
|
+
- `/Users/masa/Projects/gitflow-analytics/src/gitflow_analytics/cli.py` (lines 3920-3936)
|
|
368
|
+
|
|
369
|
+
## Additional Notes
|
|
370
|
+
|
|
371
|
+
### Related Code Patterns
|
|
372
|
+
|
|
373
|
+
The same pattern exists for all three CSV reports:
|
|
374
|
+
1. **Activity Distribution** (lines 3663-3685)
|
|
375
|
+
2. **Developer Focus** (lines 3687-3709)
|
|
376
|
+
3. **Qualitative Insights** (lines 3711-3725)
|
|
377
|
+
|
|
378
|
+
All three need the same fix applied consistently.
|
|
379
|
+
|
|
380
|
+
### Configuration Context
|
|
381
|
+
|
|
382
|
+
The `generate_csv` flag comes from:
|
|
383
|
+
- CLI flag: `--no-csv` or `--csv`
|
|
384
|
+
- Config file: `output.formats` list (presence of "csv")
|
|
385
|
+
|
|
386
|
+
The narrative report is controlled by:
|
|
387
|
+
- Config file: `output.formats` list (presence of "markdown")
|
|
388
|
+
|
|
389
|
+
Currently, there's no dependency between these two settings, but they should be coupled or at least documented clearly.
|
|
390
|
+
|
|
391
|
+
### Performance Implications
|
|
392
|
+
|
|
393
|
+
**Option 2 Benefits**:
|
|
394
|
+
- Reduces disk I/O when CSV generation is disabled
|
|
395
|
+
- Faster execution (no write → read cycle)
|
|
396
|
+
- Lower memory pressure (no duplicate data structures)
|
|
397
|
+
|
|
398
|
+
**Current Inefficiency**:
|
|
399
|
+
1. Generate data in memory → write to CSV
|
|
400
|
+
2. Read CSV back into memory → convert to dict
|
|
401
|
+
3. Pass dict to narrative report
|
|
402
|
+
|
|
403
|
+
**Option 2 Flow**:
|
|
404
|
+
1. Generate data in memory
|
|
405
|
+
2. Optionally write to CSV (if enabled)
|
|
406
|
+
3. Pass data directly to narrative report
|
|
407
|
+
|
|
408
|
+
This eliminates the write → read round-trip entirely.
|
|
409
|
+
|
|
410
|
+
## Conclusion
|
|
411
|
+
|
|
412
|
+
The bug is a logical error where the `generate_csv` flag is not properly checked before attempting to read CSV files in the narrative report generation. The recommended fix (Option 2) involves refactoring the report generators to separate data generation from CSV writing, allowing the narrative report to use in-memory data regardless of CSV generation settings. This provides a more robust, performant, and maintainable solution.
|
|
@@ -96,6 +96,9 @@ target-version = "py39"
|
|
|
96
96
|
select = ["E", "F", "UP", "B", "SIM", "I"]
|
|
97
97
|
ignore = ["E501"] # Line too long - handled by formatter
|
|
98
98
|
|
|
99
|
+
[tool.ruff.lint.per-file-ignores]
|
|
100
|
+
"tests/**/*.py" = ["E402", "E722", "F401", "SIM102"] # Allow test-specific patterns
|
|
101
|
+
|
|
99
102
|
[tool.black]
|
|
100
103
|
line-length = 100
|
|
101
104
|
target-version = ['py39']
|
|
@@ -1590,6 +1590,68 @@ def analyze(
|
|
|
1590
1590
|
total_commits += result["stats"]["total_commits"]
|
|
1591
1591
|
total_tickets += result["stats"]["unique_tickets"]
|
|
1592
1592
|
|
|
1593
|
+
# Fetch and enrich with GitHub PRs after data collection
|
|
1594
|
+
if repo_config.github_repo:
|
|
1595
|
+
try:
|
|
1596
|
+
if display:
|
|
1597
|
+
display.print_status(
|
|
1598
|
+
" 📥 Fetching pull requests from GitHub...",
|
|
1599
|
+
"info",
|
|
1600
|
+
)
|
|
1601
|
+
|
|
1602
|
+
# Load commits that were just fetched from cache
|
|
1603
|
+
with cache.get_session() as session:
|
|
1604
|
+
from gitflow_analytics.models.database import CachedCommit
|
|
1605
|
+
|
|
1606
|
+
cached_commits = (
|
|
1607
|
+
session.query(CachedCommit)
|
|
1608
|
+
.filter(
|
|
1609
|
+
CachedCommit.repo_path == str(repo_path),
|
|
1610
|
+
CachedCommit.timestamp >= start_date,
|
|
1611
|
+
CachedCommit.timestamp <= end_date,
|
|
1612
|
+
)
|
|
1613
|
+
.all()
|
|
1614
|
+
)
|
|
1615
|
+
|
|
1616
|
+
# Convert to dict format for enrichment
|
|
1617
|
+
commits_for_enrichment = []
|
|
1618
|
+
for cached_commit in cached_commits:
|
|
1619
|
+
commit_dict = {
|
|
1620
|
+
"hash": cached_commit.commit_hash,
|
|
1621
|
+
"author_name": cached_commit.author_name,
|
|
1622
|
+
"author_email": cached_commit.author_email,
|
|
1623
|
+
"date": cached_commit.timestamp,
|
|
1624
|
+
"message": cached_commit.message,
|
|
1625
|
+
}
|
|
1626
|
+
commits_for_enrichment.append(commit_dict)
|
|
1627
|
+
|
|
1628
|
+
# Enrich with GitHub PR data
|
|
1629
|
+
enrichment = orchestrator.enrich_repository_data(
|
|
1630
|
+
repo_config, commits_for_enrichment, start_date
|
|
1631
|
+
)
|
|
1632
|
+
|
|
1633
|
+
if enrichment["prs"]:
|
|
1634
|
+
pr_count = len(enrichment["prs"])
|
|
1635
|
+
if display:
|
|
1636
|
+
display.print_status(
|
|
1637
|
+
f" ✅ Found {pr_count} pull requests",
|
|
1638
|
+
"success",
|
|
1639
|
+
)
|
|
1640
|
+
else:
|
|
1641
|
+
click.echo(f" ✅ Found {pr_count} pull requests")
|
|
1642
|
+
|
|
1643
|
+
except Exception as e:
|
|
1644
|
+
logger.warning(
|
|
1645
|
+
f"Failed to fetch PRs for {repo_config.github_repo}: {e}"
|
|
1646
|
+
)
|
|
1647
|
+
if display:
|
|
1648
|
+
display.print_status(
|
|
1649
|
+
f" ⚠️ Could not fetch PRs: {e}",
|
|
1650
|
+
"warning",
|
|
1651
|
+
)
|
|
1652
|
+
else:
|
|
1653
|
+
click.echo(f" ⚠️ Could not fetch PRs: {e}")
|
|
1654
|
+
|
|
1593
1655
|
# Collect unique developers if available
|
|
1594
1656
|
if "developers" in result["stats"]:
|
|
1595
1657
|
total_developers.update(result["stats"]["developers"])
|
|
@@ -2088,6 +2150,68 @@ def analyze(
|
|
|
2088
2150
|
total_commits += result["stats"]["total_commits"]
|
|
2089
2151
|
total_tickets += result["stats"]["unique_tickets"]
|
|
2090
2152
|
|
|
2153
|
+
# Fetch and enrich with GitHub PRs after data collection
|
|
2154
|
+
if repo_config.github_repo:
|
|
2155
|
+
try:
|
|
2156
|
+
if display:
|
|
2157
|
+
display.print_status(
|
|
2158
|
+
" 📥 Fetching pull requests from GitHub...",
|
|
2159
|
+
"info",
|
|
2160
|
+
)
|
|
2161
|
+
|
|
2162
|
+
# Load commits that were just fetched from cache
|
|
2163
|
+
with cache.get_session() as session:
|
|
2164
|
+
from gitflow_analytics.models.database import CachedCommit
|
|
2165
|
+
|
|
2166
|
+
cached_commits = (
|
|
2167
|
+
session.query(CachedCommit)
|
|
2168
|
+
.filter(
|
|
2169
|
+
CachedCommit.repo_path == str(repo_path),
|
|
2170
|
+
CachedCommit.timestamp >= start_date,
|
|
2171
|
+
CachedCommit.timestamp <= end_date,
|
|
2172
|
+
)
|
|
2173
|
+
.all()
|
|
2174
|
+
)
|
|
2175
|
+
|
|
2176
|
+
# Convert to dict format for enrichment
|
|
2177
|
+
commits_for_enrichment = []
|
|
2178
|
+
for cached_commit in cached_commits:
|
|
2179
|
+
commit_dict = {
|
|
2180
|
+
"hash": cached_commit.commit_hash,
|
|
2181
|
+
"author_name": cached_commit.author_name,
|
|
2182
|
+
"author_email": cached_commit.author_email,
|
|
2183
|
+
"date": cached_commit.timestamp,
|
|
2184
|
+
"message": cached_commit.message,
|
|
2185
|
+
}
|
|
2186
|
+
commits_for_enrichment.append(commit_dict)
|
|
2187
|
+
|
|
2188
|
+
# Enrich with GitHub PR data
|
|
2189
|
+
enrichment = orchestrator.enrich_repository_data(
|
|
2190
|
+
repo_config, commits_for_enrichment, start_date
|
|
2191
|
+
)
|
|
2192
|
+
|
|
2193
|
+
if enrichment["prs"]:
|
|
2194
|
+
pr_count = len(enrichment["prs"])
|
|
2195
|
+
if display:
|
|
2196
|
+
display.print_status(
|
|
2197
|
+
f" ✅ Found {pr_count} pull requests",
|
|
2198
|
+
"success",
|
|
2199
|
+
)
|
|
2200
|
+
else:
|
|
2201
|
+
click.echo(f" ✅ Found {pr_count} pull requests")
|
|
2202
|
+
|
|
2203
|
+
except Exception as e:
|
|
2204
|
+
logger.warning(
|
|
2205
|
+
f"Failed to fetch PRs for {repo_config.github_repo}: {e}"
|
|
2206
|
+
)
|
|
2207
|
+
if display:
|
|
2208
|
+
display.print_status(
|
|
2209
|
+
f" ⚠️ Could not fetch PRs: {e}",
|
|
2210
|
+
"warning",
|
|
2211
|
+
)
|
|
2212
|
+
else:
|
|
2213
|
+
click.echo(f" ⚠️ Could not fetch PRs: {e}")
|
|
2214
|
+
|
|
2091
2215
|
# Collect unique developers if available
|
|
2092
2216
|
if "developers" in result["stats"]:
|
|
2093
2217
|
total_developers.update(result["stats"]["developers"])
|
|
@@ -3784,8 +3908,8 @@ def analyze(
|
|
|
3784
3908
|
traceback.print_exc()
|
|
3785
3909
|
raise
|
|
3786
3910
|
|
|
3787
|
-
# Generate markdown reports if enabled
|
|
3788
|
-
if "markdown" in cfg.output.formats:
|
|
3911
|
+
# Generate markdown reports if enabled (requires CSV files)
|
|
3912
|
+
if "markdown" in cfg.output.formats and generate_csv:
|
|
3789
3913
|
# Calculate date range for consistent filename formatting across all markdown reports
|
|
3790
3914
|
date_range = f"{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}"
|
|
3791
3915
|
|
|
@@ -3915,6 +4039,15 @@ def analyze(
|
|
|
3915
4039
|
|
|
3916
4040
|
traceback.print_exc()
|
|
3917
4041
|
raise
|
|
4042
|
+
elif "markdown" in cfg.output.formats and not generate_csv:
|
|
4043
|
+
# Narrative report requires CSV files, but CSV generation is disabled
|
|
4044
|
+
logger.info(
|
|
4045
|
+
"Skipping narrative report generation - CSV files required but CSV generation is disabled"
|
|
4046
|
+
)
|
|
4047
|
+
if not display:
|
|
4048
|
+
click.echo(
|
|
4049
|
+
" ℹ️ Narrative report skipped (requires CSV files - enable with --csv flag)"
|
|
4050
|
+
)
|
|
3918
4051
|
|
|
3919
4052
|
# Generate database-backed qualitative report
|
|
3920
4053
|
if "markdown" in cfg.output.formats:
|
{gitflow_analytics-3.13.5 → gitflow_analytics-3.13.7}/src/gitflow_analytics/core/git_auth.py
RENAMED
|
@@ -112,6 +112,80 @@ def setup_git_credentials(token: str, username: str = "git") -> bool:
|
|
|
112
112
|
return False
|
|
113
113
|
|
|
114
114
|
|
|
115
|
+
def ensure_remote_url_has_token(repo_path: Path, token: str) -> bool:
|
|
116
|
+
"""Embed GitHub token in remote URL for HTTPS authentication.
|
|
117
|
+
|
|
118
|
+
This is needed because subprocess git operations may not have access
|
|
119
|
+
to the credential helper store due to environment variable restrictions
|
|
120
|
+
(GIT_CREDENTIAL_HELPER="" and GIT_ASKPASS="/bin/echo" in git_timeout_wrapper).
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
repo_path: Path to the git repository
|
|
124
|
+
token: GitHub personal access token
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
True if URL was updated with token, False if already has token,
|
|
128
|
+
not applicable (SSH URL), or operation failed
|
|
129
|
+
"""
|
|
130
|
+
if not token:
|
|
131
|
+
logger.debug("No token provided, skipping remote URL update")
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
# Get current origin remote URL
|
|
136
|
+
result = subprocess.run(
|
|
137
|
+
["git", "remote", "get-url", "origin"],
|
|
138
|
+
cwd=repo_path,
|
|
139
|
+
capture_output=True,
|
|
140
|
+
text=True,
|
|
141
|
+
check=True,
|
|
142
|
+
)
|
|
143
|
+
current_url = result.stdout.strip()
|
|
144
|
+
|
|
145
|
+
if not current_url:
|
|
146
|
+
logger.debug(f"No origin remote found for {repo_path}")
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
# Check if it's an HTTPS GitHub URL without embedded token
|
|
150
|
+
if current_url.startswith("https://github.com/"):
|
|
151
|
+
# URL format: https://github.com/org/repo.git
|
|
152
|
+
# New format: https://git:TOKEN@github.com/org/repo.git
|
|
153
|
+
new_url = current_url.replace("https://github.com/", f"https://git:{token}@github.com/")
|
|
154
|
+
|
|
155
|
+
# Update the remote URL
|
|
156
|
+
subprocess.run(
|
|
157
|
+
["git", "remote", "set-url", "origin", new_url],
|
|
158
|
+
cwd=repo_path,
|
|
159
|
+
capture_output=True,
|
|
160
|
+
text=True,
|
|
161
|
+
check=True,
|
|
162
|
+
)
|
|
163
|
+
logger.debug(f"Updated remote URL with embedded token for {repo_path.name}")
|
|
164
|
+
return True
|
|
165
|
+
|
|
166
|
+
elif "@github.com" in current_url:
|
|
167
|
+
# Already has authentication embedded (either token or SSH)
|
|
168
|
+
logger.debug(f"Remote URL already has authentication for {repo_path.name}")
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
elif current_url.startswith("git@github.com:"):
|
|
172
|
+
# SSH URL, no need to modify
|
|
173
|
+
logger.debug(f"Using SSH authentication for {repo_path.name}")
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
else:
|
|
177
|
+
# Unknown URL format
|
|
178
|
+
logger.debug(f"Unknown URL format for {repo_path.name}: {current_url}")
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
except subprocess.CalledProcessError as e:
|
|
182
|
+
logger.warning(f"Could not update remote URL for {repo_path.name}: {e.stderr}")
|
|
183
|
+
return False
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.warning(f"Unexpected error updating remote URL for {repo_path.name}: {e}")
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
|
|
115
189
|
def preflight_git_authentication(config: dict) -> bool:
|
|
116
190
|
"""Run pre-flight checks for git authentication and setup credentials.
|
|
117
191
|
|