gitpr-cli 1.6.0__tar.gz → 1.7.0__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.
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/PKG-INFO +2 -2
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/README.md +1 -1
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/gitpr_cli.egg-info/PKG-INFO +2 -2
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/gitpr_cli.egg-info/SOURCES.txt +2 -0
- gitpr_cli-1.7.0/src/ascii_chart.py +248 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/config.py +2 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/config_schema.py +110 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/core.py +8 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/config_app.py +1 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/metrics_app.py +118 -21
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/updater.py +2 -2
- gitpr_cli-1.7.0/tests/test_ascii_chart.py +166 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_config_schema.py +33 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_full_diff_encoding.py +24 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_metrics.py +13 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_smart_excludes.py +1 -1
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/LICENSE +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/gitpr_cli.egg-info/dependency_links.txt +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/gitpr_cli.egg-info/entry_points.txt +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/gitpr_cli.egg-info/requires.txt +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/gitpr_cli.egg-info/top_level.txt +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/pyproject.toml +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/setup.cfg +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ai_providers.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/activate_policy_pack.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/baseline_gate.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/baseline_summary.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/calculate_risk.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/compare_against_baseline.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/create_baseline.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/generate_mentor_explanation.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/generate_pr_explanation.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/generate_test_file.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/install_policy_pack.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/publish_pull_request.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/resolve_effective_policy.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/run_check.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/suppress_finding.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/application/use_cases/validate_policy_pack.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/blame_engine.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/branding/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/branding/badge_builder.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/branding/badge_data.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/cache.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/changelog_builder.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/chat_memory.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/commit_classifier.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/demo/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/demo/demo_runner.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/demo/fake_ai_provider.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/demo/scenarios/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/demo/scenarios/laravel_bug_fix.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/demo/scenarios/security_issue.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/diff_parser.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/doc_links.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/baseline/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/baseline/baseline_comparator.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/baseline/baseline_fingerprint.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/baseline/baseline_manifest.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/baseline/baseline_types.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/baseline/suppression_policy.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/check/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/check/check_config.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/check/check_output.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/check/check_types.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/finding/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/finding/finding_types.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/linter/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/linter/sast_finding_mapper.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/mentor/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/mentor/mentor_explanation_builder.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/policy/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/policy/policy_checksum.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/policy/policy_compatibility.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/policy/policy_manifest.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/policy/policy_resolver.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/policy/policy_types.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/pr/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/pr/explain_section_builder.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/risk/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/risk/risk_calculator.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/risk/risk_explanation.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/risk/risk_rules.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/risk/risk_types.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/tests_generation/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/tests_generation/framework_detector.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/tests_generation/test_content_types.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/domain/tests_generation/test_scaffold_builder.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/fix/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/fix/apply_fix.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/fix/fix_history.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/fix/patch_applier.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/fix/patch_extractor.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/fix/patch_provenance.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/fix/patch_safety_classifier.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/fix/rollback_fix.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/github_api.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/i18n.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/baseline/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/baseline/local_baseline_repository.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/ci/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/ci/junit_builder.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/ci/masking.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/ci/sarif_builder.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/git/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/git/identity.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/git/patch_applier.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/git/risk_history_reader.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/git/selective_stager.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/git/test_matcher.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/linter/external/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/linter/external/bandit_bridge.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/linter/external/base_bridge.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/linter/external/gitleaks_bridge.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/linter/external/semgrep_bridge.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/policy/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/policy/local_policy_repository.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/policy/policy_pack_loader.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/scm/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/scm/azure_devops_provider.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/scm/base.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/scm/bitbucket_provider.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/scm/factory.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/scm/github_provider.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/scm/gitlab_provider.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/infrastructure/scm/web_links.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/issue_autoclose/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/issue_autoclose/insert_close_line.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/issue_autoclose/issue_registry.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/issue_autoclose/register_created_issue.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/issue_autoclose/resolve_close_issue.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/issue_engine.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ledger.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/linter_engine.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/linter_wizard.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/main.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/mcp_server.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/metrics.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/net.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/policy_packs/laravel-quality/CHANGELOG.md +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/policy_packs/laravel-quality/README.md +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/policy_packs/laravel-quality/linter.yml +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/policy_packs/laravel-quality/policy.yml +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/policy_packs/node-quality/README.md +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/policy_packs/node-quality/linter.yml +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/policy_packs/node-quality/policy.yml +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/policy_packs/php-security/README.md +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/policy_packs/php-security/linter.yml +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/policy_packs/php-security/policy.yml +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/policy_packs/vue-quality/README.md +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/policy_packs/vue-quality/linter.yml +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/policy_packs/vue-quality/policy.yml +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/project_config.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/release_engine.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/review/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/review/diff_normalizer.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/review/diff_source.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/review/remote_pr.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/review/render.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/reviewer_resolution.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/reviewer_suggestion.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/scm_settings.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/security.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/security_ruleset.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/skill_frontmatter.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/spinner.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/split/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/split/apply_split_plan.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/split/generate_split_plan.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/split/hunk_grouper.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/split/hunk_parser.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/split/split_plan.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/suggest_reviewers.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/tui_issue.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/chat_app.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/commit_app.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/demo/__init__.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/demo/demo_app.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/demo/demo_help_screen.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/demo/demo_screens.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/help_screen.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/issue_app.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/linter_app.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/metrics_migration_app.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/pr_base_app.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/pr_publish_app.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/ui/pr_publish_help.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/usage_log.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/src/version_bump.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_baseline_cli.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_blame_engine_ranges.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_blame_metrics.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_changelog_builder.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_chat_backend.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_check_cli.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_cli_help.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_commit_app.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_commit_classifier.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_config_app.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_config_cli.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_config_issue_autoclose.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_config_store.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_config_suggest_reviewers.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_config_validation.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_core.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_diff_failure_cli.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_diff_parser.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_doc_links.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_explain_command.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_external_linters.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_i18n.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_install_wizard.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_issue_engine.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_linter_metrics.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_linter_presets.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_main_suggest_reviewers.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_mcp_prompts.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_mcp_server.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_mcp_server_e2e.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_mentor_cli.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_net_timeouts.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_plugins.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_policy_cli.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_pr_base_app.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_pr_publish_app.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_pr_publish_linter_modal.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_pre_save.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_project_config.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_project_config_cli.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_release_cli.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_release_engine.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_reviewer_resolution.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_reviewer_suggestion.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_risk_cli.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_security_ruleset.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_seed_command.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_skill_command.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_skill_context.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_skill_frontmatter.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_skill_templates.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_suggest_reviewers.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_tests_command.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_thinking_words.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_tui_issue_token.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_updater.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_usage_log.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_version_bump.py +0 -0
- {gitpr_cli-1.6.0 → gitpr_cli-1.7.0}/tests/test_web_links.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitpr-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: AI-powered PR automation, commit messages, and code review (Gemini, DeepSeek, and Ollama)
|
|
5
5
|
Author-email: Natan Fiuza <contato@natanfiuza.dev.br>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -499,7 +499,7 @@ This is useful for scripting, CI/CD pipelines, and one-off queries where you don
|
|
|
499
499
|
|
|
500
500
|
📖 **Full documentation:** [gitpr.natanfiuza.dev.br/docs/mcp-integration](https://gitpr.natanfiuza.dev.br/docs/mcp-integration) — available in 5 languages (EN, PT-BR, PT-PT, ES, FR).
|
|
501
501
|
|
|
502
|
-
> 🧩 **Official VS Code & Cursor Extension** — Prefer native editor commands, SCM toolbar buttons, and inline `Problems` diagnostics?
|
|
502
|
+
> 🧩 **Official VS Code & Cursor Extension** — Prefer native editor commands, SCM toolbar buttons, and inline `Problems` diagnostics? Download the ready-to-use `.vsix` from [GitHub Releases](https://github.com/natanfiuza/gitpr/releases) or see the [Editor Extension Guide](https://gitpr.natanfiuza.dev.br/docs/editor-extension) for 1-click install in VS Code and Cursor.
|
|
503
503
|
|
|
504
504
|
> 💬 **MCP Prompts** — GitPR also exposes 7 pre-defined message templates (prompts) for common flows like "Review PR", "Generate Commit Message", and "Create Issue from Diff". See the [MCP Prompts guide](https://gitpr.natanfiuza.dev.br/docs/mcp-prompts) for the full list.
|
|
505
505
|
|
|
@@ -479,7 +479,7 @@ This is useful for scripting, CI/CD pipelines, and one-off queries where you don
|
|
|
479
479
|
|
|
480
480
|
📖 **Full documentation:** [gitpr.natanfiuza.dev.br/docs/mcp-integration](https://gitpr.natanfiuza.dev.br/docs/mcp-integration) — available in 5 languages (EN, PT-BR, PT-PT, ES, FR).
|
|
481
481
|
|
|
482
|
-
> 🧩 **Official VS Code & Cursor Extension** — Prefer native editor commands, SCM toolbar buttons, and inline `Problems` diagnostics?
|
|
482
|
+
> 🧩 **Official VS Code & Cursor Extension** — Prefer native editor commands, SCM toolbar buttons, and inline `Problems` diagnostics? Download the ready-to-use `.vsix` from [GitHub Releases](https://github.com/natanfiuza/gitpr/releases) or see the [Editor Extension Guide](https://gitpr.natanfiuza.dev.br/docs/editor-extension) for 1-click install in VS Code and Cursor.
|
|
483
483
|
|
|
484
484
|
> 💬 **MCP Prompts** — GitPR also exposes 7 pre-defined message templates (prompts) for common flows like "Review PR", "Generate Commit Message", and "Create Issue from Diff". See the [MCP Prompts guide](https://gitpr.natanfiuza.dev.br/docs/mcp-prompts) for the full list.
|
|
485
485
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitpr-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: AI-powered PR automation, commit messages, and code review (Gemini, DeepSeek, and Ollama)
|
|
5
5
|
Author-email: Natan Fiuza <contato@natanfiuza.dev.br>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -499,7 +499,7 @@ This is useful for scripting, CI/CD pipelines, and one-off queries where you don
|
|
|
499
499
|
|
|
500
500
|
📖 **Full documentation:** [gitpr.natanfiuza.dev.br/docs/mcp-integration](https://gitpr.natanfiuza.dev.br/docs/mcp-integration) — available in 5 languages (EN, PT-BR, PT-PT, ES, FR).
|
|
501
501
|
|
|
502
|
-
> 🧩 **Official VS Code & Cursor Extension** — Prefer native editor commands, SCM toolbar buttons, and inline `Problems` diagnostics?
|
|
502
|
+
> 🧩 **Official VS Code & Cursor Extension** — Prefer native editor commands, SCM toolbar buttons, and inline `Problems` diagnostics? Download the ready-to-use `.vsix` from [GitHub Releases](https://github.com/natanfiuza/gitpr/releases) or see the [Editor Extension Guide](https://gitpr.natanfiuza.dev.br/docs/editor-extension) for 1-click install in VS Code and Cursor.
|
|
503
503
|
|
|
504
504
|
> 💬 **MCP Prompts** — GitPR also exposes 7 pre-defined message templates (prompts) for common flows like "Review PR", "Generate Commit Message", and "Create Issue from Diff". See the [MCP Prompts guide](https://gitpr.natanfiuza.dev.br/docs/mcp-prompts) for the full list.
|
|
505
505
|
|
|
@@ -9,6 +9,7 @@ gitpr_cli.egg-info/requires.txt
|
|
|
9
9
|
gitpr_cli.egg-info/top_level.txt
|
|
10
10
|
src/__init__.py
|
|
11
11
|
src/ai_providers.py
|
|
12
|
+
src/ascii_chart.py
|
|
12
13
|
src/blame_engine.py
|
|
13
14
|
src/cache.py
|
|
14
15
|
src/changelog_builder.py
|
|
@@ -184,6 +185,7 @@ src/ui/demo/__init__.py
|
|
|
184
185
|
src/ui/demo/demo_app.py
|
|
185
186
|
src/ui/demo/demo_help_screen.py
|
|
186
187
|
src/ui/demo/demo_screens.py
|
|
188
|
+
tests/test_ascii_chart.py
|
|
187
189
|
tests/test_baseline_cli.py
|
|
188
190
|
tests/test_blame_engine_ranges.py
|
|
189
191
|
tests/test_blame_metrics.py
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ASCII / Unicode 2D Chart Rendering Engine for GitPR Metrics.
|
|
3
|
+
|
|
4
|
+
Provides pure functions to render 2D terminal charts (vertical histograms,
|
|
5
|
+
time-series charts) without external dependencies or UI framework coupling.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import math
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _fmt_tokens(val: float) -> str:
|
|
13
|
+
"""Format token count in human-readable compact form (e.g. 1.2M, 45.0k, 500)."""
|
|
14
|
+
if val >= 1_000_000:
|
|
15
|
+
return f"{val / 1_000_000:.1f}M"
|
|
16
|
+
if val >= 1_000:
|
|
17
|
+
return f"{val / 1_000:.1f}k"
|
|
18
|
+
return str(int(round(val)))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _duration_ms_label(ms: Optional[float]) -> str:
|
|
22
|
+
"""Format duration in milliseconds into ms, s, or min."""
|
|
23
|
+
if ms is None:
|
|
24
|
+
return "0 ms"
|
|
25
|
+
if ms < 1000:
|
|
26
|
+
return f"{ms:.0f} ms"
|
|
27
|
+
seconds = ms / 1000.0
|
|
28
|
+
if seconds < 60:
|
|
29
|
+
return f"{seconds:.1f} s"
|
|
30
|
+
return f"{seconds / 60.0:.1f} min"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def render_empty_card(title: str, message: str, width: int = 60) -> str:
|
|
34
|
+
"""Render a framed empty-state banner with informative text."""
|
|
35
|
+
w = max(40, width)
|
|
36
|
+
inner_w = w - 4
|
|
37
|
+
top_border = f"┌{'─' * (w - 2)}┐"
|
|
38
|
+
bottom_border = f"└{'─' * (w - 2)}┘"
|
|
39
|
+
empty_line = f"│ {' ' * inner_w} │"
|
|
40
|
+
|
|
41
|
+
title_line = f"│ [bold cyan]{title:<{inner_w}}[/bold cyan] │"
|
|
42
|
+
msg_line = f"│ [yellow]{message:<{inner_w}}[/yellow] │"
|
|
43
|
+
|
|
44
|
+
return "\n".join([top_border, title_line, empty_line, msg_line, bottom_border])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def render_vertical_histogram(
|
|
48
|
+
latency: Optional[Dict[str, Any]],
|
|
49
|
+
height: int = 7,
|
|
50
|
+
col_width: int = 7,
|
|
51
|
+
) -> str:
|
|
52
|
+
"""Render a 2D vertical ASCII histogram for latency distributions.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
latency: Dict from latency_report() containing 'count', 'average_ms',
|
|
56
|
+
'median_ms', 'p95_ms', and 'buckets'.
|
|
57
|
+
height: Height in lines for the plotting area.
|
|
58
|
+
col_width: Character width of each column bar.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Formatted multi-line string containing the 2D chart.
|
|
62
|
+
"""
|
|
63
|
+
if not latency or not latency.get("count") or not latency.get("buckets"):
|
|
64
|
+
return render_empty_card(
|
|
65
|
+
"📊 Latency Distribution",
|
|
66
|
+
"No timed executions recorded in the selected window.",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
buckets: List[Dict[str, Any]] = latency["buckets"]
|
|
70
|
+
counts = [b["count"] for b in buckets]
|
|
71
|
+
max_count = max(counts) if counts else 0
|
|
72
|
+
if max_count <= 0:
|
|
73
|
+
max_count = 1
|
|
74
|
+
|
|
75
|
+
count_total = latency.get("count", 0)
|
|
76
|
+
avg_lbl = _duration_ms_label(latency.get("average_ms"))
|
|
77
|
+
med_lbl = _duration_ms_label(latency.get("median_ms"))
|
|
78
|
+
p95_lbl = _duration_ms_label(latency.get("p95_ms"))
|
|
79
|
+
|
|
80
|
+
lines: List[str] = []
|
|
81
|
+
|
|
82
|
+
# Title & Summary header
|
|
83
|
+
lines.append("[bold cyan]📊 Latency Histogram[/bold cyan]")
|
|
84
|
+
lines.append(
|
|
85
|
+
f" Timed runs: [bold]{count_total}[/bold] │ "
|
|
86
|
+
f"Average: [cyan]{avg_lbl}[/cyan] │ "
|
|
87
|
+
f"Median (P50): [green]{med_lbl}[/green] │ "
|
|
88
|
+
f"P95: [yellow]{p95_lbl}[/yellow]"
|
|
89
|
+
)
|
|
90
|
+
lines.append("")
|
|
91
|
+
|
|
92
|
+
# Y-axis label formatting width
|
|
93
|
+
y_label_width = max(len(str(max_count)), 3)
|
|
94
|
+
|
|
95
|
+
# Plot rows from top (height) down to 1
|
|
96
|
+
for r in range(height, 0, -1):
|
|
97
|
+
# Y-axis tick mark and label
|
|
98
|
+
ratio = r / height
|
|
99
|
+
level_val = round(ratio * max_count)
|
|
100
|
+
if r == height:
|
|
101
|
+
y_tick = f"{max_count:>{y_label_width}} ┤"
|
|
102
|
+
elif r == math.ceil(height / 2):
|
|
103
|
+
half_val = round(max_count / 2)
|
|
104
|
+
y_tick = f"{half_val:>{y_label_width}} ┤"
|
|
105
|
+
else:
|
|
106
|
+
y_tick = f"{' ' * y_label_width} │"
|
|
107
|
+
|
|
108
|
+
# Build column cells
|
|
109
|
+
cells = []
|
|
110
|
+
for b_count in counts:
|
|
111
|
+
# Thresholds for full block and half block
|
|
112
|
+
val_ratio = b_count / max_count
|
|
113
|
+
if val_ratio >= (r / height):
|
|
114
|
+
# Full block
|
|
115
|
+
bar_chars = "█" * col_width
|
|
116
|
+
elif val_ratio >= ((r - 0.5) / height):
|
|
117
|
+
# Half block
|
|
118
|
+
bar_chars = "▄" * col_width
|
|
119
|
+
else:
|
|
120
|
+
bar_chars = " " * col_width
|
|
121
|
+
|
|
122
|
+
cells.append(bar_chars)
|
|
123
|
+
|
|
124
|
+
row_content = " ".join(cells)
|
|
125
|
+
lines.append(f" {y_tick} {row_content}")
|
|
126
|
+
|
|
127
|
+
# X-axis Baseline
|
|
128
|
+
base_col = "─" * col_width
|
|
129
|
+
x_axis_line = "───".join([base_col] * len(buckets))
|
|
130
|
+
lines.append(f" {0:>{y_label_width}} ┴─{x_axis_line}─")
|
|
131
|
+
|
|
132
|
+
# Bucket Labels
|
|
133
|
+
label_cells = []
|
|
134
|
+
for b in buckets:
|
|
135
|
+
lbl = b.get("label", "")
|
|
136
|
+
label_cells.append(f"{lbl:^{col_width}}")
|
|
137
|
+
lines.append(f" {' ' * y_label_width} " + " ".join(label_cells))
|
|
138
|
+
|
|
139
|
+
# Counts and percentages below labels
|
|
140
|
+
stat_cells = []
|
|
141
|
+
for b in buckets:
|
|
142
|
+
c = b.get("count", 0)
|
|
143
|
+
rate = b.get("rate", 0.0)
|
|
144
|
+
txt = f"{c} ({rate:.0%})"
|
|
145
|
+
stat_cells.append(f"{txt:^{col_width}}")
|
|
146
|
+
lines.append(f" {' ' * y_label_width} " + " ".join(stat_cells))
|
|
147
|
+
|
|
148
|
+
return "\n".join(lines)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def render_token_timeseries(
|
|
152
|
+
trend: Optional[Dict[str, Any]],
|
|
153
|
+
height: int = 7,
|
|
154
|
+
col_width: int = 5,
|
|
155
|
+
) -> str:
|
|
156
|
+
"""Render a 2D continuous ASCII time-series chart for daily token trends.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
trend: Dict from token_trend_report() containing 'days', 'total_tokens',
|
|
160
|
+
'peak_tokens', 'average_tokens', and 'sparkline'.
|
|
161
|
+
height: Height in lines for the plotting area.
|
|
162
|
+
col_width: Character width allocated per day column.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Formatted multi-line string containing the 2D chart.
|
|
166
|
+
"""
|
|
167
|
+
if not trend or not trend.get("days") or not trend.get("total_tokens"):
|
|
168
|
+
return render_empty_card(
|
|
169
|
+
"📈 Token Trend & Consumption",
|
|
170
|
+
"No token consumption recorded in the selected window.",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
days: List[Dict[str, Any]] = trend["days"]
|
|
174
|
+
peak_tokens = trend.get("peak_tokens", 0)
|
|
175
|
+
total_tokens = trend.get("total_tokens", 0)
|
|
176
|
+
avg_tokens = trend.get("average_tokens", 0.0)
|
|
177
|
+
sparkline = trend.get("sparkline", "")
|
|
178
|
+
|
|
179
|
+
if peak_tokens <= 0:
|
|
180
|
+
peak_tokens = 1
|
|
181
|
+
|
|
182
|
+
lines: List[str] = []
|
|
183
|
+
|
|
184
|
+
# Title & Summary header
|
|
185
|
+
lines.append("[bold cyan]📈 Token Consumption Trend[/bold cyan]")
|
|
186
|
+
lines.append(
|
|
187
|
+
f" Total: [bold]{total_tokens:,}[/bold] │ "
|
|
188
|
+
f"Peak: [yellow]{peak_tokens:,}[/yellow] │ "
|
|
189
|
+
f"Daily Avg: [green]{avg_tokens:,.0f}[/green] │ "
|
|
190
|
+
f"Sparkline: {sparkline}"
|
|
191
|
+
)
|
|
192
|
+
lines.append("")
|
|
193
|
+
|
|
194
|
+
y_label_top = _fmt_tokens(peak_tokens)
|
|
195
|
+
y_label_mid = _fmt_tokens(peak_tokens / 2)
|
|
196
|
+
y_label_width = max(len(y_label_top), len(y_label_mid), 4)
|
|
197
|
+
|
|
198
|
+
# Plot matrix from top down
|
|
199
|
+
for r in range(height, 0, -1):
|
|
200
|
+
if r == height:
|
|
201
|
+
y_tick = f"{y_label_top:>{y_label_width}} ┼"
|
|
202
|
+
grid_char = "┈"
|
|
203
|
+
elif r == math.ceil(height / 2):
|
|
204
|
+
y_tick = f"{y_label_mid:>{y_label_width}} ┼"
|
|
205
|
+
grid_char = "┈"
|
|
206
|
+
else:
|
|
207
|
+
y_tick = f"{' ' * y_label_width} │"
|
|
208
|
+
grid_char = " "
|
|
209
|
+
|
|
210
|
+
day_cells = []
|
|
211
|
+
for d in days:
|
|
212
|
+
tokens = d.get("tokens", 0)
|
|
213
|
+
ratio = tokens / peak_tokens
|
|
214
|
+
if ratio >= (r / height):
|
|
215
|
+
cell_str = "█" * col_width
|
|
216
|
+
elif ratio >= ((r - 0.5) / height):
|
|
217
|
+
cell_str = "▄" * col_width
|
|
218
|
+
elif grid_char != " ":
|
|
219
|
+
cell_str = grid_char * col_width
|
|
220
|
+
else:
|
|
221
|
+
cell_str = " " * col_width
|
|
222
|
+
day_cells.append(cell_str)
|
|
223
|
+
|
|
224
|
+
lines.append(f" {y_tick} " + " ".join(day_cells))
|
|
225
|
+
|
|
226
|
+
# X-axis Baseline
|
|
227
|
+
base_col = "─" * col_width
|
|
228
|
+
x_axis_line = "─".join([base_col] * len(days))
|
|
229
|
+
lines.append(f" {'0':>{y_label_width}} ┴─{x_axis_line}─")
|
|
230
|
+
|
|
231
|
+
# Date labels (MM-DD)
|
|
232
|
+
date_cells = []
|
|
233
|
+
for d in days:
|
|
234
|
+
raw_day = d.get("day", "")
|
|
235
|
+
# Format YYYY-MM-DD -> MM-DD
|
|
236
|
+
short_date = raw_day[5:] if len(raw_day) >= 10 else raw_day
|
|
237
|
+
date_cells.append(f"{short_date:^{col_width}}")
|
|
238
|
+
lines.append(f" {' ' * y_label_width} " + " ".join(date_cells))
|
|
239
|
+
|
|
240
|
+
# Compact token labels below dates
|
|
241
|
+
tok_cells = []
|
|
242
|
+
for d in days:
|
|
243
|
+
t = d.get("tokens", 0)
|
|
244
|
+
tok_str = _fmt_tokens(t) if t > 0 else "·"
|
|
245
|
+
tok_cells.append(f"{tok_str:^{col_width}}")
|
|
246
|
+
lines.append(f" {' ' * y_label_width} " + " ".join(tok_cells))
|
|
247
|
+
|
|
248
|
+
return "\n".join(lines)
|
|
@@ -291,6 +291,8 @@ DEFAULT_MODEL_PRICES = {
|
|
|
291
291
|
"deepseek-v4-pro": (0.435, 0.87),
|
|
292
292
|
"gemini-2.5-pro": (1.25, 10.00),
|
|
293
293
|
"gemini-2.5-flash-lite": (0.10, 0.40),
|
|
294
|
+
"gemini-pro-latest": (1.25, 10.00),
|
|
295
|
+
"gemini-flash-lite-latest": (0.10, 0.40),
|
|
294
296
|
}
|
|
295
297
|
|
|
296
298
|
# Providers that run on this machine. There is no invoice behind them, so their
|
|
@@ -22,6 +22,7 @@ from src.i18n import __
|
|
|
22
22
|
# Widget kinds. Each one maps to a different control in ui/config_app.py.
|
|
23
23
|
KIND_BOOL = "bool"
|
|
24
24
|
KIND_INT = "int"
|
|
25
|
+
KIND_FLOAT = "float"
|
|
25
26
|
KIND_STR = "str"
|
|
26
27
|
KIND_ENUM = "enum"
|
|
27
28
|
KIND_TEMPLATE = "template"
|
|
@@ -39,6 +40,7 @@ KIND_BRANCH = "branch"
|
|
|
39
40
|
KINDS = (
|
|
40
41
|
KIND_BOOL,
|
|
41
42
|
KIND_INT,
|
|
43
|
+
KIND_FLOAT,
|
|
42
44
|
KIND_STR,
|
|
43
45
|
KIND_ENUM,
|
|
44
46
|
KIND_TEMPLATE,
|
|
@@ -395,6 +397,16 @@ FIELDS = (
|
|
|
395
397
|
default="180",
|
|
396
398
|
seeded=True,
|
|
397
399
|
),
|
|
400
|
+
ConfigField(
|
|
401
|
+
key="GITPR_METRICS_CURRENCY",
|
|
402
|
+
label=__("Metrics Currency"),
|
|
403
|
+
description=__(
|
|
404
|
+
"Currency code used in telemetry and cost reports (default USD)."
|
|
405
|
+
),
|
|
406
|
+
category="ai",
|
|
407
|
+
kind=KIND_STR,
|
|
408
|
+
default="",
|
|
409
|
+
),
|
|
398
410
|
ConfigField(
|
|
399
411
|
key="GEMINI_API_MODEL_PRIMARY",
|
|
400
412
|
label=__("Gemini Advanced Model"),
|
|
@@ -405,6 +417,28 @@ FIELDS = (
|
|
|
405
417
|
show_if=(("DEFAULT_AI_PROVIDER", ("gemini",)),),
|
|
406
418
|
seeded=True,
|
|
407
419
|
),
|
|
420
|
+
ConfigField(
|
|
421
|
+
key="GITPR_METRICS_PRICE_GEMINI_PRO_LATEST_INPUT",
|
|
422
|
+
label=__("Gemini Advanced Model - Input Price"),
|
|
423
|
+
description=__(
|
|
424
|
+
"Cost per million input tokens for the advanced model."
|
|
425
|
+
),
|
|
426
|
+
category="ai",
|
|
427
|
+
kind=KIND_FLOAT,
|
|
428
|
+
default="",
|
|
429
|
+
show_if=(("DEFAULT_AI_PROVIDER", ("gemini",)),),
|
|
430
|
+
),
|
|
431
|
+
ConfigField(
|
|
432
|
+
key="GITPR_METRICS_PRICE_GEMINI_PRO_LATEST_OUTPUT",
|
|
433
|
+
label=__("Gemini Advanced Model - Output Price"),
|
|
434
|
+
description=__(
|
|
435
|
+
"Cost per million completion tokens for the advanced model."
|
|
436
|
+
),
|
|
437
|
+
category="ai",
|
|
438
|
+
kind=KIND_FLOAT,
|
|
439
|
+
default="",
|
|
440
|
+
show_if=(("DEFAULT_AI_PROVIDER", ("gemini",)),),
|
|
441
|
+
),
|
|
408
442
|
ConfigField(
|
|
409
443
|
key="GEMINI_API_MODEL_SECONDARY",
|
|
410
444
|
label=__("Gemini Fast Model"),
|
|
@@ -415,6 +449,28 @@ FIELDS = (
|
|
|
415
449
|
show_if=(("DEFAULT_AI_PROVIDER", ("gemini",)),),
|
|
416
450
|
seeded=True,
|
|
417
451
|
),
|
|
452
|
+
ConfigField(
|
|
453
|
+
key="GITPR_METRICS_PRICE_GEMINI_FLASH_LITE_LATEST_INPUT",
|
|
454
|
+
label=__("Gemini Fast Model - Input Price"),
|
|
455
|
+
description=__(
|
|
456
|
+
"Cost per million input tokens for the fast model."
|
|
457
|
+
),
|
|
458
|
+
category="ai",
|
|
459
|
+
kind=KIND_FLOAT,
|
|
460
|
+
default="",
|
|
461
|
+
show_if=(("DEFAULT_AI_PROVIDER", ("gemini",)),),
|
|
462
|
+
),
|
|
463
|
+
ConfigField(
|
|
464
|
+
key="GITPR_METRICS_PRICE_GEMINI_FLASH_LITE_LATEST_OUTPUT",
|
|
465
|
+
label=__("Gemini Fast Model - Output Price"),
|
|
466
|
+
description=__(
|
|
467
|
+
"Cost per million completion tokens for the fast model."
|
|
468
|
+
),
|
|
469
|
+
category="ai",
|
|
470
|
+
kind=KIND_FLOAT,
|
|
471
|
+
default="",
|
|
472
|
+
show_if=(("DEFAULT_AI_PROVIDER", ("gemini",)),),
|
|
473
|
+
),
|
|
418
474
|
ConfigField(
|
|
419
475
|
key="GEMINI_API_KEY_ENCRYPTED",
|
|
420
476
|
label=__("Gemini API Key"),
|
|
@@ -437,6 +493,28 @@ FIELDS = (
|
|
|
437
493
|
show_if=(("DEFAULT_AI_PROVIDER", ("deepseek",)),),
|
|
438
494
|
seeded=True,
|
|
439
495
|
),
|
|
496
|
+
ConfigField(
|
|
497
|
+
key="GITPR_METRICS_PRICE_DEEPSEEK_V4_PRO_INPUT",
|
|
498
|
+
label=__("DeepSeek Advanced Model - Input Price"),
|
|
499
|
+
description=__(
|
|
500
|
+
"Cost per million input tokens for the advanced model."
|
|
501
|
+
),
|
|
502
|
+
category="ai",
|
|
503
|
+
kind=KIND_FLOAT,
|
|
504
|
+
default="",
|
|
505
|
+
show_if=(("DEFAULT_AI_PROVIDER", ("deepseek",)),),
|
|
506
|
+
),
|
|
507
|
+
ConfigField(
|
|
508
|
+
key="GITPR_METRICS_PRICE_DEEPSEEK_V4_PRO_OUTPUT",
|
|
509
|
+
label=__("DeepSeek Advanced Model - Output Price"),
|
|
510
|
+
description=__(
|
|
511
|
+
"Cost per million completion tokens for the advanced model."
|
|
512
|
+
),
|
|
513
|
+
category="ai",
|
|
514
|
+
kind=KIND_FLOAT,
|
|
515
|
+
default="",
|
|
516
|
+
show_if=(("DEFAULT_AI_PROVIDER", ("deepseek",)),),
|
|
517
|
+
),
|
|
440
518
|
ConfigField(
|
|
441
519
|
key="DEEPSEEK_API_MODEL_SECONDARY",
|
|
442
520
|
label=__("DeepSeek Fast Model"),
|
|
@@ -447,6 +525,28 @@ FIELDS = (
|
|
|
447
525
|
show_if=(("DEFAULT_AI_PROVIDER", ("deepseek",)),),
|
|
448
526
|
seeded=True,
|
|
449
527
|
),
|
|
528
|
+
ConfigField(
|
|
529
|
+
key="GITPR_METRICS_PRICE_DEEPSEEK_V4_FLASH_INPUT",
|
|
530
|
+
label=__("DeepSeek Fast Model - Input Price"),
|
|
531
|
+
description=__(
|
|
532
|
+
"Cost per million input tokens for the fast model."
|
|
533
|
+
),
|
|
534
|
+
category="ai",
|
|
535
|
+
kind=KIND_FLOAT,
|
|
536
|
+
default="",
|
|
537
|
+
show_if=(("DEFAULT_AI_PROVIDER", ("deepseek",)),),
|
|
538
|
+
),
|
|
539
|
+
ConfigField(
|
|
540
|
+
key="GITPR_METRICS_PRICE_DEEPSEEK_V4_FLASH_OUTPUT",
|
|
541
|
+
label=__("DeepSeek Fast Model - Output Price"),
|
|
542
|
+
description=__(
|
|
543
|
+
"Cost per million completion tokens for the fast model."
|
|
544
|
+
),
|
|
545
|
+
category="ai",
|
|
546
|
+
kind=KIND_FLOAT,
|
|
547
|
+
default="",
|
|
548
|
+
show_if=(("DEFAULT_AI_PROVIDER", ("deepseek",)),),
|
|
549
|
+
),
|
|
450
550
|
ConfigField(
|
|
451
551
|
key="DEEPSEEK_API_KEY_ENCRYPTED",
|
|
452
552
|
label=__("DeepSeek API Key"),
|
|
@@ -1595,6 +1695,16 @@ def validate_field_value(field, value):
|
|
|
1595
1695
|
if parsed <= 0:
|
|
1596
1696
|
return __("Use a whole number greater than zero.")
|
|
1597
1697
|
|
|
1698
|
+
elif field.kind == KIND_FLOAT:
|
|
1699
|
+
if not value:
|
|
1700
|
+
return ""
|
|
1701
|
+
try:
|
|
1702
|
+
parsed = float(value)
|
|
1703
|
+
except ValueError:
|
|
1704
|
+
return __("Use a number greater than or equal to zero.")
|
|
1705
|
+
if parsed < 0:
|
|
1706
|
+
return __("Use a number greater than or equal to zero.")
|
|
1707
|
+
|
|
1598
1708
|
elif field.kind == KIND_ENUM:
|
|
1599
1709
|
if value not in field.choices:
|
|
1600
1710
|
return __("Choose one of: {choices}", choices=", ".join(field.choices))
|
|
@@ -170,6 +170,10 @@ _FALLBACK_SMART_EXCLUDES = [
|
|
|
170
170
|
"*.pth",
|
|
171
171
|
"*.onnx",
|
|
172
172
|
"*.safetensors",
|
|
173
|
+
"*.arrow",
|
|
174
|
+
"*.gguf",
|
|
175
|
+
"*.tflite",
|
|
176
|
+
"*.keras",
|
|
173
177
|
"*.bin",
|
|
174
178
|
"*.dat",
|
|
175
179
|
"*.p12",
|
|
@@ -1156,6 +1160,10 @@ def generate_pr_content(
|
|
|
1156
1160
|
)
|
|
1157
1161
|
return None
|
|
1158
1162
|
|
|
1163
|
+
# Defensive Binary hunk guard: collapse any U+FFFD-heavy binary hunks that
|
|
1164
|
+
# slipped past path excludes or were passed as a raw diff by an external caller.
|
|
1165
|
+
diff_text = collapse_binary_hunks(diff_text)
|
|
1166
|
+
|
|
1159
1167
|
t_start = time.perf_counter()
|
|
1160
1168
|
|
|
1161
1169
|
# Cache folder configuration — THE map for the whole project. It used to
|