gitpr-cli 1.7.0__tar.gz → 1.8.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.7.0 → gitpr_cli-1.8.0}/PKG-INFO +2 -2
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/README.md +1 -1
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/gitpr_cli.egg-info/PKG-INFO +2 -2
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/gitpr_cli.egg-info/SOURCES.txt +1 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/config.py +53 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/config_schema.py +66 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/core.py +193 -9
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/scm/factory.py +9 -6
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/issue_engine.py +6 -1
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/review/remote_pr.py +7 -1
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/scm_settings.py +12 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/updater.py +2 -2
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_commit_app.py +6 -0
- gitpr_cli-1.8.0/tests/test_map_reduce_chunking.py +162 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/LICENSE +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/gitpr_cli.egg-info/dependency_links.txt +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/gitpr_cli.egg-info/entry_points.txt +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/gitpr_cli.egg-info/requires.txt +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/gitpr_cli.egg-info/top_level.txt +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/pyproject.toml +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/setup.cfg +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ai_providers.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/activate_policy_pack.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/baseline_gate.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/baseline_summary.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/calculate_risk.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/compare_against_baseline.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/create_baseline.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/generate_mentor_explanation.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/generate_pr_explanation.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/generate_test_file.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/install_policy_pack.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/publish_pull_request.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/resolve_effective_policy.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/run_check.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/suppress_finding.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/application/use_cases/validate_policy_pack.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ascii_chart.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/blame_engine.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/branding/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/branding/badge_builder.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/branding/badge_data.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/cache.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/changelog_builder.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/chat_memory.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/commit_classifier.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/demo/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/demo/demo_runner.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/demo/fake_ai_provider.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/demo/scenarios/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/demo/scenarios/laravel_bug_fix.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/demo/scenarios/security_issue.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/diff_parser.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/doc_links.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/baseline/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/baseline/baseline_comparator.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/baseline/baseline_fingerprint.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/baseline/baseline_manifest.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/baseline/baseline_types.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/baseline/suppression_policy.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/check/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/check/check_config.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/check/check_output.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/check/check_types.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/finding/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/finding/finding_types.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/linter/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/linter/sast_finding_mapper.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/mentor/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/mentor/mentor_explanation_builder.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/policy/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/policy/policy_checksum.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/policy/policy_compatibility.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/policy/policy_manifest.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/policy/policy_resolver.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/policy/policy_types.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/pr/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/pr/explain_section_builder.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/risk/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/risk/risk_calculator.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/risk/risk_explanation.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/risk/risk_rules.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/risk/risk_types.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/tests_generation/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/tests_generation/framework_detector.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/tests_generation/test_content_types.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/domain/tests_generation/test_scaffold_builder.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/fix/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/fix/apply_fix.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/fix/fix_history.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/fix/patch_applier.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/fix/patch_extractor.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/fix/patch_provenance.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/fix/patch_safety_classifier.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/fix/rollback_fix.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/github_api.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/i18n.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/baseline/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/baseline/local_baseline_repository.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/ci/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/ci/junit_builder.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/ci/masking.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/ci/sarif_builder.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/git/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/git/identity.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/git/patch_applier.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/git/risk_history_reader.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/git/selective_stager.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/git/test_matcher.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/linter/external/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/linter/external/bandit_bridge.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/linter/external/base_bridge.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/linter/external/gitleaks_bridge.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/linter/external/semgrep_bridge.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/policy/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/policy/local_policy_repository.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/policy/policy_pack_loader.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/scm/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/scm/azure_devops_provider.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/scm/base.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/scm/bitbucket_provider.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/scm/github_provider.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/scm/gitlab_provider.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/infrastructure/scm/web_links.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/issue_autoclose/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/issue_autoclose/insert_close_line.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/issue_autoclose/issue_registry.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/issue_autoclose/register_created_issue.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/issue_autoclose/resolve_close_issue.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ledger.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/linter_engine.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/linter_wizard.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/main.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/mcp_server.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/metrics.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/net.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/policy_packs/laravel-quality/CHANGELOG.md +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/policy_packs/laravel-quality/README.md +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/policy_packs/laravel-quality/linter.yml +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/policy_packs/laravel-quality/policy.yml +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/policy_packs/node-quality/README.md +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/policy_packs/node-quality/linter.yml +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/policy_packs/node-quality/policy.yml +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/policy_packs/php-security/README.md +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/policy_packs/php-security/linter.yml +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/policy_packs/php-security/policy.yml +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/policy_packs/vue-quality/README.md +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/policy_packs/vue-quality/linter.yml +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/policy_packs/vue-quality/policy.yml +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/project_config.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/release_engine.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/review/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/review/diff_normalizer.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/review/diff_source.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/review/render.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/reviewer_resolution.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/reviewer_suggestion.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/security.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/security_ruleset.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/skill_frontmatter.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/spinner.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/split/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/split/apply_split_plan.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/split/generate_split_plan.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/split/hunk_grouper.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/split/hunk_parser.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/split/split_plan.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/suggest_reviewers.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/tui_issue.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/chat_app.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/commit_app.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/config_app.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/demo/__init__.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/demo/demo_app.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/demo/demo_help_screen.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/demo/demo_screens.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/help_screen.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/issue_app.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/linter_app.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/metrics_app.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/metrics_migration_app.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/pr_base_app.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/pr_publish_app.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/ui/pr_publish_help.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/usage_log.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/src/version_bump.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_ascii_chart.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_baseline_cli.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_blame_engine_ranges.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_blame_metrics.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_changelog_builder.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_chat_backend.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_check_cli.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_cli_help.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_commit_classifier.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_config_app.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_config_cli.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_config_issue_autoclose.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_config_schema.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_config_store.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_config_suggest_reviewers.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_config_validation.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_core.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_diff_failure_cli.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_diff_parser.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_doc_links.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_explain_command.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_external_linters.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_full_diff_encoding.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_i18n.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_install_wizard.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_issue_engine.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_linter_metrics.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_linter_presets.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_main_suggest_reviewers.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_mcp_prompts.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_mcp_server.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_mcp_server_e2e.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_mentor_cli.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_metrics.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_net_timeouts.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_plugins.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_policy_cli.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_pr_base_app.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_pr_publish_app.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_pr_publish_linter_modal.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_pre_save.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_project_config.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_project_config_cli.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_release_cli.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_release_engine.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_reviewer_resolution.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_reviewer_suggestion.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_risk_cli.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_security_ruleset.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_seed_command.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_skill_command.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_skill_context.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_skill_frontmatter.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_skill_templates.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_smart_excludes.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_suggest_reviewers.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_tests_command.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_thinking_words.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_tui_issue_token.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_updater.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_usage_log.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.0}/tests/test_version_bump.py +0 -0
- {gitpr_cli-1.7.0 → gitpr_cli-1.8.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.8.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? 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.
|
|
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. Maintainers can run `scripts/marketplace-setup-wizard.sh` to configure and publish to Visual Studio Marketplace and Open VSX Registry.
|
|
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? 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.
|
|
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. Maintainers can run `scripts/marketplace-setup-wizard.sh` to configure and publish to Visual Studio Marketplace and Open VSX Registry.
|
|
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.8.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? 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.
|
|
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. Maintainers can run `scripts/marketplace-setup-wizard.sh` to configure and publish to Visual Studio Marketplace and Open VSX Registry.
|
|
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
|
|
|
@@ -215,6 +215,7 @@ tests/test_issue_engine.py
|
|
|
215
215
|
tests/test_linter_metrics.py
|
|
216
216
|
tests/test_linter_presets.py
|
|
217
217
|
tests/test_main_suggest_reviewers.py
|
|
218
|
+
tests/test_map_reduce_chunking.py
|
|
218
219
|
tests/test_mcp_prompts.py
|
|
219
220
|
tests/test_mcp_server.py
|
|
220
221
|
tests/test_mcp_server_e2e.py
|
|
@@ -249,6 +249,59 @@ def get_linter_timeout():
|
|
|
249
249
|
return _positive_float(os.getenv("GITPR_LINTER_TIMEOUT"), _DEFAULT_LINTER_TIMEOUT)
|
|
250
250
|
|
|
251
251
|
|
|
252
|
+
DEFAULT_MAP_REDUCE_MAX_TOKENS = 90000
|
|
253
|
+
DEFAULT_MAP_REDUCE_MAX_PART_TOKENS = 50000
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _positive_int_from_raw(raw, fallback):
|
|
257
|
+
"""Parses raw as a positive int, falling back on non-positive or invalid input."""
|
|
258
|
+
if raw is None:
|
|
259
|
+
return fallback
|
|
260
|
+
try:
|
|
261
|
+
val = int(str(raw).strip())
|
|
262
|
+
return val if val > 0 else fallback
|
|
263
|
+
except (TypeError, ValueError):
|
|
264
|
+
return fallback
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def get_map_reduce_thresholds(provider=None):
|
|
268
|
+
"""Returns (max_tokens, max_part_tokens) for Map-Reduce chunking.
|
|
269
|
+
|
|
270
|
+
Resolution hierarchy:
|
|
271
|
+
1. Provider-specific: GITPR_MAP_REDUCE_MAX_TOKENS_<PROVIDER>
|
|
272
|
+
2. Generic: GITPR_MAP_REDUCE_MAX_TOKENS
|
|
273
|
+
3. Baseline default: 90,000 tokens
|
|
274
|
+
|
|
275
|
+
For part tokens:
|
|
276
|
+
1. Provider-specific: GITPR_MAP_REDUCE_MAX_PART_TOKENS_<PROVIDER>
|
|
277
|
+
2. Generic: GITPR_MAP_REDUCE_MAX_PART_TOKENS
|
|
278
|
+
3. Baseline default: 50,000 tokens
|
|
279
|
+
|
|
280
|
+
Guarantees max_part_tokens <= max_tokens.
|
|
281
|
+
"""
|
|
282
|
+
load_dotenv(ENV_FILE)
|
|
283
|
+
prov_key = (provider or "").strip().upper()
|
|
284
|
+
|
|
285
|
+
trigger_val = None
|
|
286
|
+
if prov_key:
|
|
287
|
+
trigger_val = os.getenv(f"GITPR_MAP_REDUCE_MAX_TOKENS_{prov_key}")
|
|
288
|
+
if trigger_val is None or not str(trigger_val).strip():
|
|
289
|
+
trigger_val = os.getenv("GITPR_MAP_REDUCE_MAX_TOKENS")
|
|
290
|
+
max_tokens = _positive_int_from_raw(trigger_val, DEFAULT_MAP_REDUCE_MAX_TOKENS)
|
|
291
|
+
|
|
292
|
+
part_val = None
|
|
293
|
+
if prov_key:
|
|
294
|
+
part_val = os.getenv(f"GITPR_MAP_REDUCE_MAX_PART_TOKENS_{prov_key}")
|
|
295
|
+
if part_val is None or not str(part_val).strip():
|
|
296
|
+
part_val = os.getenv("GITPR_MAP_REDUCE_MAX_PART_TOKENS")
|
|
297
|
+
max_part_tokens = _positive_int_from_raw(part_val, DEFAULT_MAP_REDUCE_MAX_PART_TOKENS)
|
|
298
|
+
|
|
299
|
+
if max_part_tokens > max_tokens:
|
|
300
|
+
max_part_tokens = max_tokens
|
|
301
|
+
|
|
302
|
+
return max_tokens, max_part_tokens
|
|
303
|
+
|
|
304
|
+
|
|
252
305
|
# Metrics pricing.
|
|
253
306
|
#
|
|
254
307
|
# Two layers, the second overriding the first per model:
|
|
@@ -483,6 +483,28 @@ FIELDS = (
|
|
|
483
483
|
validator=VALIDATOR_AI_KEY,
|
|
484
484
|
show_if=(("DEFAULT_AI_PROVIDER", ("gemini",)),),
|
|
485
485
|
),
|
|
486
|
+
ConfigField(
|
|
487
|
+
key="GITPR_MAP_REDUCE_MAX_TOKENS_GEMINI",
|
|
488
|
+
label=__("Gemini Map-Reduce Trigger Tokens"),
|
|
489
|
+
description=__(
|
|
490
|
+
"Maximum total diff tokens before triggering Map-Reduce processing for Gemini."
|
|
491
|
+
),
|
|
492
|
+
category="ai",
|
|
493
|
+
kind=KIND_INT,
|
|
494
|
+
default="90000",
|
|
495
|
+
show_if=(("DEFAULT_AI_PROVIDER", ("gemini",)),),
|
|
496
|
+
),
|
|
497
|
+
ConfigField(
|
|
498
|
+
key="GITPR_MAP_REDUCE_MAX_PART_TOKENS_GEMINI",
|
|
499
|
+
label=__("Gemini Map-Reduce Part Size"),
|
|
500
|
+
description=__(
|
|
501
|
+
"Maximum tokens per batch chunk during Gemini Map-Reduce processing."
|
|
502
|
+
),
|
|
503
|
+
category="ai",
|
|
504
|
+
kind=KIND_INT,
|
|
505
|
+
default="50000",
|
|
506
|
+
show_if=(("DEFAULT_AI_PROVIDER", ("gemini",)),),
|
|
507
|
+
),
|
|
486
508
|
ConfigField(
|
|
487
509
|
key="DEEPSEEK_API_MODEL_PRIMARY",
|
|
488
510
|
label=__("DeepSeek Advanced Model"),
|
|
@@ -559,6 +581,28 @@ FIELDS = (
|
|
|
559
581
|
validator=VALIDATOR_AI_KEY,
|
|
560
582
|
show_if=(("DEFAULT_AI_PROVIDER", ("deepseek",)),),
|
|
561
583
|
),
|
|
584
|
+
ConfigField(
|
|
585
|
+
key="GITPR_MAP_REDUCE_MAX_TOKENS_DEEPSEEK",
|
|
586
|
+
label=__("DeepSeek Map-Reduce Trigger Tokens"),
|
|
587
|
+
description=__(
|
|
588
|
+
"Maximum total diff tokens before triggering Map-Reduce processing for DeepSeek."
|
|
589
|
+
),
|
|
590
|
+
category="ai",
|
|
591
|
+
kind=KIND_INT,
|
|
592
|
+
default="90000",
|
|
593
|
+
show_if=(("DEFAULT_AI_PROVIDER", ("deepseek",)),),
|
|
594
|
+
),
|
|
595
|
+
ConfigField(
|
|
596
|
+
key="GITPR_MAP_REDUCE_MAX_PART_TOKENS_DEEPSEEK",
|
|
597
|
+
label=__("DeepSeek Map-Reduce Part Size"),
|
|
598
|
+
description=__(
|
|
599
|
+
"Maximum tokens per batch chunk during DeepSeek Map-Reduce processing."
|
|
600
|
+
),
|
|
601
|
+
category="ai",
|
|
602
|
+
kind=KIND_INT,
|
|
603
|
+
default="50000",
|
|
604
|
+
show_if=(("DEFAULT_AI_PROVIDER", ("deepseek",)),),
|
|
605
|
+
),
|
|
562
606
|
ConfigField(
|
|
563
607
|
key="OLLAMA_API_MODEL_PRIMARY",
|
|
564
608
|
label=__("Ollama Advanced Model"),
|
|
@@ -579,6 +623,28 @@ FIELDS = (
|
|
|
579
623
|
show_if=(("DEFAULT_AI_PROVIDER", ("ollama",)),),
|
|
580
624
|
seeded=True,
|
|
581
625
|
),
|
|
626
|
+
ConfigField(
|
|
627
|
+
key="GITPR_MAP_REDUCE_MAX_TOKENS_OLLAMA",
|
|
628
|
+
label=__("Ollama Map-Reduce Trigger Tokens"),
|
|
629
|
+
description=__(
|
|
630
|
+
"Maximum total diff tokens before triggering Map-Reduce processing for Ollama."
|
|
631
|
+
),
|
|
632
|
+
category="ai",
|
|
633
|
+
kind=KIND_INT,
|
|
634
|
+
default="90000",
|
|
635
|
+
show_if=(("DEFAULT_AI_PROVIDER", ("ollama",)),),
|
|
636
|
+
),
|
|
637
|
+
ConfigField(
|
|
638
|
+
key="GITPR_MAP_REDUCE_MAX_PART_TOKENS_OLLAMA",
|
|
639
|
+
label=__("Ollama Map-Reduce Part Size"),
|
|
640
|
+
description=__(
|
|
641
|
+
"Maximum tokens per batch chunk during Ollama Map-Reduce processing."
|
|
642
|
+
),
|
|
643
|
+
category="ai",
|
|
644
|
+
kind=KIND_INT,
|
|
645
|
+
default="50000",
|
|
646
|
+
show_if=(("DEFAULT_AI_PROVIDER", ("ollama",)),),
|
|
647
|
+
),
|
|
582
648
|
# ----------------------------------------------------------- Pull request
|
|
583
649
|
ConfigField(
|
|
584
650
|
key="OUTPUT_FILE_NAME",
|
|
@@ -1099,20 +1099,136 @@ def estimate_token_count(text):
|
|
|
1099
1099
|
return len(text) // 4
|
|
1100
1100
|
|
|
1101
1101
|
|
|
1102
|
-
def
|
|
1103
|
-
"""Splits
|
|
1102
|
+
def _split_line_chunk(text, max_tokens):
|
|
1103
|
+
"""Splits arbitrary text by lines (and character slices if needed) keeping each part <= max_tokens."""
|
|
1104
|
+
chunks = []
|
|
1105
|
+
current = ""
|
|
1106
|
+
for line in text.splitlines(keepends=True):
|
|
1107
|
+
if estimate_token_count(line) > max_tokens:
|
|
1108
|
+
if current:
|
|
1109
|
+
chunks.append(current)
|
|
1110
|
+
current = ""
|
|
1111
|
+
step = max_tokens * 4
|
|
1112
|
+
for start in range(0, len(line), step):
|
|
1113
|
+
slice_str = line[start : start + step]
|
|
1114
|
+
chunks.append(slice_str)
|
|
1115
|
+
elif estimate_token_count(current + line) > max_tokens and current:
|
|
1116
|
+
chunks.append(current)
|
|
1117
|
+
current = line
|
|
1118
|
+
else:
|
|
1119
|
+
current += line
|
|
1120
|
+
if current:
|
|
1121
|
+
chunks.append(current)
|
|
1122
|
+
return chunks or [text]
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
def _split_giant_file(file_diff, max_part_tokens):
|
|
1126
|
+
"""Splits a single file diff that exceeds max_part_tokens into smaller parts."""
|
|
1127
|
+
fn_match = re.search(r"^diff --git a/(\S+) b/\S+", file_diff, flags=re.MULTILINE)
|
|
1128
|
+
file_name = fn_match.group(1) if fn_match else "file"
|
|
1129
|
+
|
|
1130
|
+
hunk_match = re.search(
|
|
1131
|
+
r"^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@", file_diff, flags=re.MULTILINE
|
|
1132
|
+
)
|
|
1133
|
+
if hunk_match:
|
|
1134
|
+
header = file_diff[: hunk_match.start()]
|
|
1135
|
+
body = file_diff[hunk_match.start() :]
|
|
1136
|
+
else:
|
|
1137
|
+
header = ""
|
|
1138
|
+
body = file_diff
|
|
1139
|
+
|
|
1140
|
+
hunk_parts = re.split(
|
|
1141
|
+
r"(^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@.*)", body, flags=re.MULTILINE
|
|
1142
|
+
)
|
|
1143
|
+
raw_hunks = []
|
|
1144
|
+
if len(hunk_parts) > 1:
|
|
1145
|
+
if hunk_parts[0].strip():
|
|
1146
|
+
raw_hunks.append(hunk_parts[0])
|
|
1147
|
+
for j in range(1, len(hunk_parts), 2):
|
|
1148
|
+
hunk_content = hunk_parts[j] + (
|
|
1149
|
+
hunk_parts[j + 1] if j + 1 < len(hunk_parts) else ""
|
|
1150
|
+
)
|
|
1151
|
+
raw_hunks.append(hunk_content)
|
|
1152
|
+
else:
|
|
1153
|
+
raw_hunks = [body]
|
|
1154
|
+
|
|
1155
|
+
sub_chunks = []
|
|
1156
|
+
current_sub = ""
|
|
1157
|
+
header_clean = header.strip()
|
|
1158
|
+
|
|
1159
|
+
def _format_part(sub_content, idx):
|
|
1160
|
+
prefix = (
|
|
1161
|
+
f"{header_clean}\n# [{file_name} - part {idx}]\n\n"
|
|
1162
|
+
if header_clean
|
|
1163
|
+
else f"# [{file_name} - part {idx}]\n\n"
|
|
1164
|
+
)
|
|
1165
|
+
return prefix + sub_content
|
|
1166
|
+
|
|
1167
|
+
part_idx = 1
|
|
1168
|
+
for hunk in raw_hunks:
|
|
1169
|
+
if estimate_token_count(hunk) > max_part_tokens:
|
|
1170
|
+
if current_sub:
|
|
1171
|
+
sub_chunks.append(_format_part(current_sub, part_idx))
|
|
1172
|
+
part_idx += 1
|
|
1173
|
+
current_sub = ""
|
|
1174
|
+
line_pieces = _split_line_chunk(hunk, max_part_tokens)
|
|
1175
|
+
for piece in line_pieces:
|
|
1176
|
+
sub_chunks.append(_format_part(piece, part_idx))
|
|
1177
|
+
part_idx += 1
|
|
1178
|
+
elif (
|
|
1179
|
+
estimate_token_count(current_sub + hunk) > max_part_tokens
|
|
1180
|
+
and current_sub
|
|
1181
|
+
):
|
|
1182
|
+
sub_chunks.append(_format_part(current_sub, part_idx))
|
|
1183
|
+
part_idx += 1
|
|
1184
|
+
current_sub = hunk
|
|
1185
|
+
else:
|
|
1186
|
+
current_sub += hunk
|
|
1187
|
+
|
|
1188
|
+
if current_sub:
|
|
1189
|
+
sub_chunks.append(_format_part(current_sub, part_idx))
|
|
1190
|
+
|
|
1191
|
+
return sub_chunks or [file_diff]
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
def split_diff_into_chunks(diff_text, max_tokens=90000, max_part_tokens=None):
|
|
1195
|
+
"""Splits the diff based on the token limit while preserving file header integrity.
|
|
1196
|
+
|
|
1197
|
+
- max_tokens: Diff size threshold that triggers Map-Reduce (default 90,000).
|
|
1198
|
+
- max_part_tokens: Maximum tokens per batch chunk (default 50,000).
|
|
1199
|
+
"""
|
|
1200
|
+
if max_part_tokens is None:
|
|
1201
|
+
max_part_tokens = 50000
|
|
1202
|
+
if max_part_tokens > max_tokens:
|
|
1203
|
+
max_part_tokens = max_tokens
|
|
1204
|
+
|
|
1104
1205
|
if estimate_token_count(diff_text) <= max_tokens:
|
|
1105
1206
|
return [diff_text]
|
|
1106
1207
|
|
|
1107
|
-
parts = re.split(r"(^diff --git
|
|
1108
|
-
|
|
1109
|
-
|
|
1208
|
+
parts = re.split(r"(^diff --git (?:[abciwos12]/|\S+ ))", diff_text, flags=re.MULTILINE)
|
|
1209
|
+
if len(parts) <= 1:
|
|
1210
|
+
return _split_line_chunk(diff_text, max_part_tokens)
|
|
1110
1211
|
|
|
1212
|
+
file_diffs = []
|
|
1213
|
+
if parts[0].strip():
|
|
1214
|
+
file_diffs.append(parts[0])
|
|
1111
1215
|
for i in range(1, len(parts), 2):
|
|
1112
|
-
|
|
1216
|
+
file_d = parts[i] + (parts[i + 1] if i + 1 < len(parts) else "")
|
|
1217
|
+
file_diffs.append(file_d)
|
|
1218
|
+
|
|
1219
|
+
chunks = []
|
|
1220
|
+
current_chunk = ""
|
|
1113
1221
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1222
|
+
for file_diff in file_diffs:
|
|
1223
|
+
file_tokens = estimate_token_count(file_diff)
|
|
1224
|
+
if file_tokens > max_part_tokens:
|
|
1225
|
+
if current_chunk:
|
|
1226
|
+
chunks.append(current_chunk)
|
|
1227
|
+
current_chunk = ""
|
|
1228
|
+
file_sub_chunks = _split_giant_file(file_diff, max_part_tokens)
|
|
1229
|
+
chunks.extend(file_sub_chunks)
|
|
1230
|
+
elif (
|
|
1231
|
+
estimate_token_count(current_chunk + file_diff) > max_part_tokens
|
|
1116
1232
|
and current_chunk
|
|
1117
1233
|
):
|
|
1118
1234
|
chunks.append(current_chunk)
|
|
@@ -1369,7 +1485,11 @@ def generate_pr_content(
|
|
|
1369
1485
|
fg="cyan",
|
|
1370
1486
|
)
|
|
1371
1487
|
|
|
1372
|
-
|
|
1488
|
+
from src.config import get_map_reduce_thresholds
|
|
1489
|
+
max_tokens, max_part_tokens = get_map_reduce_thresholds(provider)
|
|
1490
|
+
chunks = split_diff_into_chunks(
|
|
1491
|
+
diff_text, max_tokens=max_tokens, max_part_tokens=max_part_tokens
|
|
1492
|
+
)
|
|
1373
1493
|
total_meta = {
|
|
1374
1494
|
"prompt_tokens": 0,
|
|
1375
1495
|
"completion_tokens": 0,
|
|
@@ -1479,6 +1599,70 @@ def generate_pr_content(
|
|
|
1479
1599
|
)
|
|
1480
1600
|
diff_unificado = "\n\n".join(resumos_parciais)
|
|
1481
1601
|
|
|
1602
|
+
if estimate_token_count(diff_unificado) > max_tokens:
|
|
1603
|
+
click.secho(
|
|
1604
|
+
__("🔄 Large summary set detected! Running intermediate reduction..."),
|
|
1605
|
+
fg="cyan",
|
|
1606
|
+
dim=True,
|
|
1607
|
+
)
|
|
1608
|
+
intermediate_summaries = []
|
|
1609
|
+
curr_batch = []
|
|
1610
|
+
curr_tokens = 0
|
|
1611
|
+
for item in resumos_parciais:
|
|
1612
|
+
item_tok = estimate_token_count(item)
|
|
1613
|
+
if curr_tokens + item_tok > max_part_tokens and curr_batch:
|
|
1614
|
+
batch_str = "\n\n".join(curr_batch)
|
|
1615
|
+
inter_prompt = (
|
|
1616
|
+
__(
|
|
1617
|
+
"Generate ONLY a JSON object in the format {json_format} consolidating these technical summaries into a unified summary:\n",
|
|
1618
|
+
json_format='{"resumo": "..."}',
|
|
1619
|
+
)
|
|
1620
|
+
+ batch_str
|
|
1621
|
+
)
|
|
1622
|
+
inter_res = call_ai_model(
|
|
1623
|
+
provider,
|
|
1624
|
+
api_key,
|
|
1625
|
+
api_model,
|
|
1626
|
+
inter_prompt,
|
|
1627
|
+
instrucao_sistema,
|
|
1628
|
+
quiet=True,
|
|
1629
|
+
action=f"{action_folder}_inter_reduce",
|
|
1630
|
+
)
|
|
1631
|
+
if inter_res and "resumo" in inter_res:
|
|
1632
|
+
intermediate_summaries.append(inter_res["resumo"])
|
|
1633
|
+
_aggregate_meta(inter_res.pop("_telemetry_meta", None))
|
|
1634
|
+
else:
|
|
1635
|
+
intermediate_summaries.append(batch_str)
|
|
1636
|
+
curr_batch = [item]
|
|
1637
|
+
curr_tokens = item_tok
|
|
1638
|
+
else:
|
|
1639
|
+
curr_batch.append(item)
|
|
1640
|
+
curr_tokens += item_tok
|
|
1641
|
+
if curr_batch:
|
|
1642
|
+
batch_str = "\n\n".join(curr_batch)
|
|
1643
|
+
inter_prompt = (
|
|
1644
|
+
__(
|
|
1645
|
+
"Generate ONLY a JSON object in the format {json_format} consolidating these technical summaries into a unified summary:\n",
|
|
1646
|
+
json_format='{"resumo": "..."}',
|
|
1647
|
+
)
|
|
1648
|
+
+ batch_str
|
|
1649
|
+
)
|
|
1650
|
+
inter_res = call_ai_model(
|
|
1651
|
+
provider,
|
|
1652
|
+
api_key,
|
|
1653
|
+
api_model,
|
|
1654
|
+
inter_prompt,
|
|
1655
|
+
instrucao_sistema,
|
|
1656
|
+
quiet=True,
|
|
1657
|
+
action=f"{action_folder}_inter_reduce",
|
|
1658
|
+
)
|
|
1659
|
+
if inter_res and "resumo" in inter_res:
|
|
1660
|
+
intermediate_summaries.append(inter_res["resumo"])
|
|
1661
|
+
_aggregate_meta(inter_res.pop("_telemetry_meta", None))
|
|
1662
|
+
else:
|
|
1663
|
+
intermediate_summaries.append(batch_str)
|
|
1664
|
+
diff_unificado = "\n\n".join(intermediate_summaries)
|
|
1665
|
+
|
|
1482
1666
|
if action_type == "commit":
|
|
1483
1667
|
prompt = (
|
|
1484
1668
|
__(
|
|
@@ -70,10 +70,13 @@ def resolve_scm_provider(config: dict) -> ScmProvider:
|
|
|
70
70
|
def detect_provider_from_remote(remote_url: str, default: Optional[str] = "github") -> Optional[str]:
|
|
71
71
|
"""Guess the forge from a git remote URL (case-insensitive substrings).
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
behavior, while ``default=None`` lets a
|
|
76
|
-
apart from "not recognised at all"
|
|
73
|
+
The named forges are checked first, so a URL that mentions one of them wins
|
|
74
|
+
even when it also contains "github". Anything unknown returns *default*:
|
|
75
|
+
"github" reproduces the historical behavior, while ``default=None`` lets a
|
|
76
|
+
caller tell "recognised as GitHub" apart from "not recognised at all"
|
|
77
|
+
(self-hosted hosts need the latter). For that reason the GitHub check runs
|
|
78
|
+
before the ``None`` return: ``github.com`` is a recognised forge, not an
|
|
79
|
+
unknown host.
|
|
77
80
|
"""
|
|
78
81
|
url = (remote_url or "").lower()
|
|
79
82
|
if "gitlab" in url:
|
|
@@ -82,10 +85,10 @@ def detect_provider_from_remote(remote_url: str, default: Optional[str] = "githu
|
|
|
82
85
|
return "bitbucket"
|
|
83
86
|
if "dev.azure.com" in url or "visualstudio.com" in url:
|
|
84
87
|
return "azure_devops"
|
|
85
|
-
if default is None:
|
|
86
|
-
return None
|
|
87
88
|
if "github" in url:
|
|
88
89
|
return "github"
|
|
90
|
+
if default is None:
|
|
91
|
+
return None
|
|
89
92
|
return default
|
|
90
93
|
|
|
91
94
|
|
|
@@ -170,7 +170,12 @@ def generate_issue_content(context_text, context_type="diff"):
|
|
|
170
170
|
total_meta["duration_ms"] += new_meta.get("duration_ms", 0)
|
|
171
171
|
|
|
172
172
|
if context_type == "diff":
|
|
173
|
-
|
|
173
|
+
from src.config import get_map_reduce_thresholds
|
|
174
|
+
|
|
175
|
+
max_tokens, max_part_tokens = get_map_reduce_thresholds(provider)
|
|
176
|
+
chunks = split_diff_into_chunks(
|
|
177
|
+
context_text, max_tokens=max_tokens, max_part_tokens=max_part_tokens
|
|
178
|
+
)
|
|
174
179
|
else:
|
|
175
180
|
chunks = [context_text]
|
|
176
181
|
|
|
@@ -342,9 +342,15 @@ def _chunk_count(diff_text):
|
|
|
342
342
|
back, and the callers that need it (the MCP tool especially) have no
|
|
343
343
|
terminal to read the "processing in N batches" line from.
|
|
344
344
|
"""
|
|
345
|
+
from src.config import get_map_reduce_thresholds
|
|
345
346
|
from src.core import split_diff_into_chunks
|
|
346
347
|
|
|
347
348
|
try:
|
|
348
|
-
|
|
349
|
+
max_tokens, max_part_tokens = get_map_reduce_thresholds()
|
|
350
|
+
return len(
|
|
351
|
+
split_diff_into_chunks(
|
|
352
|
+
diff_text, max_tokens=max_tokens, max_part_tokens=max_part_tokens
|
|
353
|
+
)
|
|
354
|
+
)
|
|
349
355
|
except Exception:
|
|
350
356
|
return 1
|
|
@@ -366,6 +366,13 @@ def _ignored_provider_warning(choice) -> str:
|
|
|
366
366
|
)
|
|
367
367
|
|
|
368
368
|
|
|
369
|
+
def _default_provider_warning() -> str:
|
|
370
|
+
return __(
|
|
371
|
+
"⚠️ The origin remote does not name a forge, so GitPR is using GitHub. "
|
|
372
|
+
"Run 'gitpr --init' to choose the right forge for this repository."
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
|
|
369
376
|
def resolve_scm_settings(
|
|
370
377
|
start_dir=None, remote_url=None, config_path=None, warnings=None
|
|
371
378
|
) -> ScmSettings:
|
|
@@ -390,6 +397,11 @@ def resolve_scm_settings(
|
|
|
390
397
|
choice = resolve_provider_choice(project_file.provider, remote)
|
|
391
398
|
if choice.ignored:
|
|
392
399
|
collected.append(_ignored_provider_warning(choice))
|
|
400
|
+
if remote and choice.source == SOURCE_DEFAULT:
|
|
401
|
+
# An existing remote that names no forge: the repository is about to be
|
|
402
|
+
# driven as GitHub unless someone records an override. The absent-remote
|
|
403
|
+
# case has its own error at the call sites.
|
|
404
|
+
collected.append(_default_provider_warning())
|
|
393
405
|
|
|
394
406
|
token = _token_for(choice.key)
|
|
395
407
|
if os.getenv(RAW_TOKEN_KEY):
|
|
@@ -9,8 +9,8 @@ from packaging.version import InvalidVersion, Version
|
|
|
9
9
|
# Canonical Single Source of Truth (SSOT) for GitPR package version (PyPI distribution)
|
|
10
10
|
# ── Must stay ABOVE the i18n import: i18n.py lazily imports __lang_version__
|
|
11
11
|
# from here, and pyproject.toml reads __version__ via setuptools attr:.
|
|
12
|
-
__version__ = "1.
|
|
13
|
-
__lang_version__ = "v0.0.
|
|
12
|
+
__version__ = "1.8.0" # GitPR current package version (Canonical SSOT)
|
|
13
|
+
__lang_version__ = "v0.0.43" # Language dictionary version control (asset, independent of package version)
|
|
14
14
|
__scripts_version__ = (
|
|
15
15
|
"v0.0.4" # Git Hook scripts version control (independent from __lang_version__)
|
|
16
16
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from click.testing import CliRunner
|
|
2
2
|
from src.main import cli
|
|
3
|
+
import sys
|
|
3
4
|
import asyncio
|
|
4
5
|
from unittest.mock import MagicMock, patch
|
|
5
6
|
import pytest
|
|
@@ -177,6 +178,10 @@ class TestCommitAppUnit:
|
|
|
177
178
|
class TestCommitAppHeadlessFlow:
|
|
178
179
|
"""Headless integration tests using Textual run_test()."""
|
|
179
180
|
|
|
181
|
+
@pytest.mark.skipif(
|
|
182
|
+
sys.platform == "win32",
|
|
183
|
+
reason="Textual app.run_test pilot.pause blocks on Windows headless without a real TTY",
|
|
184
|
+
)
|
|
180
185
|
def test_headless_full_flow(self):
|
|
181
186
|
async def _test():
|
|
182
187
|
unstaged = [("src/foo.py", "M")]
|
|
@@ -223,6 +228,7 @@ class TestCliCommitRouting:
|
|
|
223
228
|
runner = CliRunner()
|
|
224
229
|
with patch("src.main._may_ask_the_user", return_value=True), \
|
|
225
230
|
patch("src.main.setup_environment"), \
|
|
231
|
+
patch("src.main.check_internet_connection", return_value=True), \
|
|
226
232
|
patch("src.ui.commit_app.CommitApp.run") as mock_run:
|
|
227
233
|
result = runner.invoke(cli, ["-c"])
|
|
228
234
|
assert mock_run.called
|