xai-review 0.18.0__tar.gz → 0.19.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.
Potentially problematic release.
This version of xai-review might be problematic. Click here for more details.
- {xai_review-0.18.0 → xai_review-0.19.0}/PKG-INFO +21 -6
- {xai_review-0.18.0 → xai_review-0.19.0}/README.md +10 -4
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/github/pr/client.py +13 -7
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/github/pr/schema/pull_request.py +6 -6
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/gitlab/mr/client.py +29 -20
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/gitlab/mr/schema/changes.py +5 -5
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/gitlab/mr/schema/discussions.py +1 -4
- xai_review-0.19.0/ai_review/clients/gitlab/mr/schema/notes.py +19 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/llm/factory.py +1 -1
- xai_review-0.19.0/ai_review/services/prompt/adapter.py +25 -0
- xai_review-0.19.0/ai_review/services/prompt/schema.py +52 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/review/service.py +45 -42
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/vcs/factory.py +1 -1
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/vcs/github/client.py +52 -34
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/vcs/gitlab/client.py +62 -44
- xai_review-0.19.0/ai_review/services/vcs/types.py +64 -0
- xai_review-0.19.0/ai_review/tests/suites/services/cost/test_schema.py +124 -0
- xai_review-0.19.0/ai_review/tests/suites/services/cost/test_service.py +99 -0
- xai_review-0.19.0/ai_review/tests/suites/services/prompt/test_adapter.py +59 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/services/prompt/test_schema.py +18 -18
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/services/prompt/test_service.py +13 -11
- xai_review-0.19.0/ai_review/tests/suites/services/review/summary/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/pyproject.toml +15 -3
- {xai_review-0.18.0 → xai_review-0.19.0}/xai_review.egg-info/PKG-INFO +21 -6
- {xai_review-0.18.0 → xai_review-0.19.0}/xai_review.egg-info/SOURCES.txt +5 -1
- {xai_review-0.18.0 → xai_review-0.19.0}/xai_review.egg-info/requires.txt +3 -1
- xai_review-0.18.0/ai_review/clients/gitlab/mr/schema/comments.py +0 -19
- xai_review-0.18.0/ai_review/services/prompt/adapter.py +0 -25
- xai_review-0.18.0/ai_review/services/prompt/schema.py +0 -52
- xai_review-0.18.0/ai_review/services/vcs/types.py +0 -55
- {xai_review-0.18.0 → xai_review-0.19.0}/LICENSE +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/cli/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/cli/commands/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/cli/commands/run_context_review.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/cli/commands/run_inline_review.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/cli/commands/run_review.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/cli/commands/run_summary_review.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/cli/main.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/claude/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/claude/client.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/claude/schema.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/gemini/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/gemini/client.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/gemini/schema.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/github/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/github/client.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/github/pr/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/github/pr/schema/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/github/pr/schema/comments.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/github/pr/schema/files.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/github/pr/schema/reviews.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/gitlab/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/gitlab/client.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/gitlab/mr/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/gitlab/mr/schema/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/openai/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/openai/client.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/clients/openai/schema.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/config.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/asynchronous/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/asynchronous/gather.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/config/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/config/artifacts.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/config/base.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/config/claude.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/config/gemini.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/config/github.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/config/gitlab.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/config/http.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/config/llm.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/config/logger.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/config/openai.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/config/prompt.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/config/review.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/config/vcs.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/constants/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/constants/llm_provider.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/constants/vcs_provider.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/diff/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/diff/models.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/diff/parser.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/diff/tools.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/http/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/http/client.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/http/event_hooks/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/http/event_hooks/base.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/http/event_hooks/logger.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/http/handlers.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/http/transports/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/http/transports/retry.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/json.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/logger.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/resources.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/template/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/libs/template/render.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/prompts/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/prompts/default_context.md +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/prompts/default_inline.md +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/prompts/default_summary.md +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/prompts/default_system_context.md +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/prompts/default_system_inline.md +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/prompts/default_system_summary.md +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/resources/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/resources/pricing.yaml +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/artifacts/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/artifacts/schema.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/artifacts/service.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/artifacts/tools.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/cost/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/cost/schema.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/cost/service.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/diff/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/diff/renderers.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/diff/schema.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/diff/service.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/diff/tools.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/git/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/git/service.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/git/types.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/llm/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/llm/claude/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/llm/claude/client.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/llm/gemini/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/llm/gemini/client.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/llm/openai/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/llm/openai/client.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/llm/types.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/prompt/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/prompt/service.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/prompt/tools.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/review/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/review/inline/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/review/inline/schema.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/review/inline/service.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/review/policy/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/review/policy/service.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/review/summary/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/review/summary/schema.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/review/summary/service.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/vcs/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/vcs/github/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/services/vcs/gitlab/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/fixtures/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/fixtures/git.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/clients/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/clients/claude/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/clients/claude/test_client.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/clients/claude/test_schema.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/clients/gemini/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/clients/gemini/test_client.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/clients/gemini/test_schema.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/clients/github/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/clients/github/test_client.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/clients/gitlab/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/clients/gitlab/test_client.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/clients/openai/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/clients/openai/test_client.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/clients/openai/test_schema.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/libs/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/libs/config/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/libs/config/test_prompt.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/libs/diff/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/libs/diff/test_models.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/libs/diff/test_parser.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/libs/diff/test_tools.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/libs/template/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/libs/template/test_render.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/libs/test_json.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/services/__init__.py +0 -0
- {xai_review-0.18.0/ai_review/tests/suites/services/diff → xai_review-0.19.0/ai_review/tests/suites/services/cost}/__init__.py +0 -0
- {xai_review-0.18.0/ai_review/tests/suites/services/prompt → xai_review-0.19.0/ai_review/tests/suites/services/diff}/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/services/diff/test_renderers.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/services/diff/test_service.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/services/diff/test_tools.py +0 -0
- {xai_review-0.18.0/ai_review/tests/suites/services/review → xai_review-0.19.0/ai_review/tests/suites/services/prompt}/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/services/prompt/test_tools.py +0 -0
- {xai_review-0.18.0/ai_review/tests/suites/services/review/inline → xai_review-0.19.0/ai_review/tests/suites/services/review}/__init__.py +0 -0
- {xai_review-0.18.0/ai_review/tests/suites/services/review/policy → xai_review-0.19.0/ai_review/tests/suites/services/review/inline}/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/services/review/inline/test_schema.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/services/review/inline/test_service.py +0 -0
- {xai_review-0.18.0/ai_review/tests/suites/services/review/summary → xai_review-0.19.0/ai_review/tests/suites/services/review/policy}/__init__.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/services/review/policy/test_service.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/services/review/summary/test_schema.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/ai_review/tests/suites/services/review/summary/test_service.py +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/setup.cfg +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/xai_review.egg-info/dependency_links.txt +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/xai_review.egg-info/entry_points.txt +0 -0
- {xai_review-0.18.0 → xai_review-0.19.0}/xai_review.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xai-review
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.0
|
|
4
4
|
Summary: AI-powered code review tool
|
|
5
5
|
Author-email: Nikita Filonov <nikita.filonov@example.com>
|
|
6
6
|
Maintainer-email: Nikita Filonov <nikita.filonov@example.com>
|
|
@@ -9,17 +9,26 @@ Project-URL: Issues, https://github.com/Nikita-Filonov/ai-review/issues
|
|
|
9
9
|
Project-URL: Homepage, https://github.com/Nikita-Filonov/ai-review
|
|
10
10
|
Project-URL: Repository, https://github.com/Nikita-Filonov/ai-review
|
|
11
11
|
Keywords: ai,code review,llm,openai,claude,gemini
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
17
|
+
Classifier: Topic :: Software Development :: Testing
|
|
18
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
12
20
|
Requires-Python: >=3.11
|
|
13
21
|
Description-Content-Type: text/markdown
|
|
14
22
|
License-File: LICENSE
|
|
15
23
|
Requires-Dist: typer
|
|
16
24
|
Requires-Dist: httpx
|
|
17
25
|
Requires-Dist: pyyaml
|
|
18
|
-
Requires-Dist: pytest
|
|
19
26
|
Requires-Dist: loguru
|
|
20
27
|
Requires-Dist: aiofiles
|
|
21
28
|
Requires-Dist: pydantic
|
|
22
29
|
Requires-Dist: pydantic-settings
|
|
30
|
+
Provides-Extra: test
|
|
31
|
+
Requires-Dist: pytest; extra == "test"
|
|
23
32
|
Dynamic: license-file
|
|
24
33
|
|
|
25
34
|
# AI Review
|
|
@@ -76,10 +85,10 @@ mode:
|
|
|
76
85
|
| Mode | Description | Live Example |
|
|
77
86
|
|------------|----------------------------------------------------|-------------------------------------------------------------------------------|
|
|
78
87
|
| 🧩 Inline | Adds line-by-line comments directly in the diff | [Try AI Review (inline)](https://github.com/Nikita-Filonov/ai-review/pull/4) |
|
|
79
|
-
|
|
|
80
|
-
|
|
|
88
|
+
| 🧠 Context | Performs broader analysis across multiple files | [Try AI Review (context)](https://github.com/Nikita-Filonov/ai-review/pull/5) |
|
|
89
|
+
| 📄 Summary | Posts a concise summary review with key highlights | [Try AI Review (summary)](https://github.com/Nikita-Filonov/ai-review/pull/6) |
|
|
81
90
|
|
|
82
|
-
Each review was generated automatically via GitHub Actions using the corresponding mode:
|
|
91
|
+
👉 Each review was generated automatically via GitHub Actions using the corresponding mode:
|
|
83
92
|
|
|
84
93
|
```bash
|
|
85
94
|
ai-review run-inline
|
|
@@ -97,12 +106,18 @@ Install via **pip**:
|
|
|
97
106
|
pip install xai-review
|
|
98
107
|
```
|
|
99
108
|
|
|
109
|
+
📦 Available on [PyPI](https://pypi.org/project/xai-review/)
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
100
113
|
Or run directly via Docker:
|
|
101
114
|
|
|
102
115
|
```bash
|
|
103
116
|
docker run --rm -v $(pwd):/app nikitafilonov/ai-review:latest run-summary
|
|
104
117
|
```
|
|
105
118
|
|
|
119
|
+
🐳 Pull from [DockerHub](https://hub.docker.com/r/nikitafilonov/ai-review)
|
|
120
|
+
|
|
106
121
|
👉 Before running, create a basic configuration file [.ai-review.yaml](./docs/configs/.ai-review.yaml) in the root of
|
|
107
122
|
your project:
|
|
108
123
|
|
|
@@ -192,7 +207,7 @@ jobs:
|
|
|
192
207
|
runs-on: ubuntu-latest
|
|
193
208
|
steps:
|
|
194
209
|
- uses: actions/checkout@v4
|
|
195
|
-
- uses: Nikita-Filonov/ai-review@v0.
|
|
210
|
+
- uses: Nikita-Filonov/ai-review@v0.19.0
|
|
196
211
|
with:
|
|
197
212
|
review-command: ${{ inputs.review-command }}
|
|
198
213
|
env:
|
|
@@ -52,10 +52,10 @@ mode:
|
|
|
52
52
|
| Mode | Description | Live Example |
|
|
53
53
|
|------------|----------------------------------------------------|-------------------------------------------------------------------------------|
|
|
54
54
|
| 🧩 Inline | Adds line-by-line comments directly in the diff | [Try AI Review (inline)](https://github.com/Nikita-Filonov/ai-review/pull/4) |
|
|
55
|
-
|
|
|
56
|
-
|
|
|
55
|
+
| 🧠 Context | Performs broader analysis across multiple files | [Try AI Review (context)](https://github.com/Nikita-Filonov/ai-review/pull/5) |
|
|
56
|
+
| 📄 Summary | Posts a concise summary review with key highlights | [Try AI Review (summary)](https://github.com/Nikita-Filonov/ai-review/pull/6) |
|
|
57
57
|
|
|
58
|
-
Each review was generated automatically via GitHub Actions using the corresponding mode:
|
|
58
|
+
👉 Each review was generated automatically via GitHub Actions using the corresponding mode:
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
61
|
ai-review run-inline
|
|
@@ -73,12 +73,18 @@ Install via **pip**:
|
|
|
73
73
|
pip install xai-review
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
📦 Available on [PyPI](https://pypi.org/project/xai-review/)
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
76
80
|
Or run directly via Docker:
|
|
77
81
|
|
|
78
82
|
```bash
|
|
79
83
|
docker run --rm -v $(pwd):/app nikitafilonov/ai-review:latest run-summary
|
|
80
84
|
```
|
|
81
85
|
|
|
86
|
+
🐳 Pull from [DockerHub](https://hub.docker.com/r/nikitafilonov/ai-review)
|
|
87
|
+
|
|
82
88
|
👉 Before running, create a basic configuration file [.ai-review.yaml](./docs/configs/.ai-review.yaml) in the root of
|
|
83
89
|
your project:
|
|
84
90
|
|
|
@@ -168,7 +174,7 @@ jobs:
|
|
|
168
174
|
runs-on: ubuntu-latest
|
|
169
175
|
steps:
|
|
170
176
|
- uses: actions/checkout@v4
|
|
171
|
-
- uses: Nikita-Filonov/ai-review@v0.
|
|
177
|
+
- uses: Nikita-Filonov/ai-review@v0.19.0
|
|
172
178
|
with:
|
|
173
179
|
review-command: ${{ inputs.review-command }}
|
|
174
180
|
env:
|
|
@@ -11,21 +11,31 @@ from ai_review.clients.github.pr.schema.files import GitHubGetPRFilesResponseSch
|
|
|
11
11
|
from ai_review.clients.github.pr.schema.pull_request import GitHubGetPRResponseSchema
|
|
12
12
|
from ai_review.clients.github.pr.schema.reviews import GitHubGetPRReviewsResponseSchema
|
|
13
13
|
from ai_review.libs.http.client import HTTPClient
|
|
14
|
+
from ai_review.libs.http.handlers import HTTPClientError, handle_http_error
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GitHubPullRequestsHTTPClientError(HTTPClientError):
|
|
18
|
+
pass
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
class GitHubPullRequestsHTTPClient(HTTPClient):
|
|
22
|
+
@handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
|
|
17
23
|
async def get_pull_request_api(self, owner: str, repo: str, pull_number: str) -> Response:
|
|
18
24
|
return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}")
|
|
19
25
|
|
|
26
|
+
@handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
|
|
20
27
|
async def get_files_api(self, owner: str, repo: str, pull_number: str) -> Response:
|
|
21
28
|
return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}/files")
|
|
22
29
|
|
|
30
|
+
@handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
|
|
23
31
|
async def get_issue_comments_api(self, owner: str, repo: str, issue_number: str) -> Response:
|
|
24
32
|
return await self.get(f"/repos/{owner}/{repo}/issues/{issue_number}/comments")
|
|
25
33
|
|
|
34
|
+
@handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
|
|
26
35
|
async def get_review_comments_api(self, owner: str, repo: str, pull_number: str) -> Response:
|
|
27
36
|
return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}/comments")
|
|
28
37
|
|
|
38
|
+
@handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
|
|
29
39
|
async def create_review_comment_api(
|
|
30
40
|
self,
|
|
31
41
|
owner: str,
|
|
@@ -38,6 +48,7 @@ class GitHubPullRequestsHTTPClient(HTTPClient):
|
|
|
38
48
|
json=request.model_dump(),
|
|
39
49
|
)
|
|
40
50
|
|
|
51
|
+
@handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
|
|
41
52
|
async def create_issue_comment_api(
|
|
42
53
|
self,
|
|
43
54
|
owner: str,
|
|
@@ -50,6 +61,7 @@ class GitHubPullRequestsHTTPClient(HTTPClient):
|
|
|
50
61
|
json=request.model_dump(),
|
|
51
62
|
)
|
|
52
63
|
|
|
64
|
+
@handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
|
|
53
65
|
async def get_reviews_api(self, owner: str, repo: str, pull_number: str) -> Response:
|
|
54
66
|
return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}/reviews")
|
|
55
67
|
|
|
@@ -78,14 +90,8 @@ class GitHubPullRequestsHTTPClient(HTTPClient):
|
|
|
78
90
|
owner: str,
|
|
79
91
|
repo: str,
|
|
80
92
|
pull_number: str,
|
|
81
|
-
|
|
82
|
-
commit_id: str,
|
|
83
|
-
path: str,
|
|
84
|
-
line: int,
|
|
93
|
+
request: GitHubCreateReviewCommentRequestSchema
|
|
85
94
|
) -> GitHubCreateReviewCommentResponseSchema:
|
|
86
|
-
request = GitHubCreateReviewCommentRequestSchema(
|
|
87
|
-
body=body, commit_id=commit_id, path=path, line=line
|
|
88
|
-
)
|
|
89
95
|
response = await self.create_review_comment_api(owner, repo, pull_number, request)
|
|
90
96
|
return GitHubCreateReviewCommentResponseSchema.model_validate_json(response.text)
|
|
91
97
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from pydantic import BaseModel
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class GitHubUserSchema(BaseModel):
|
|
@@ -8,13 +8,13 @@ class GitHubUserSchema(BaseModel):
|
|
|
8
8
|
|
|
9
9
|
class GitHubLabelSchema(BaseModel):
|
|
10
10
|
id: int
|
|
11
|
-
name: str
|
|
11
|
+
name: str | None = None
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class GitHubBranchSchema(BaseModel):
|
|
15
15
|
ref: str
|
|
16
16
|
sha: str
|
|
17
|
-
label: str
|
|
17
|
+
label: str | None = None
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class GitHubGetPRResponseSchema(BaseModel):
|
|
@@ -23,8 +23,8 @@ class GitHubGetPRResponseSchema(BaseModel):
|
|
|
23
23
|
title: str
|
|
24
24
|
body: str | None = None
|
|
25
25
|
user: GitHubUserSchema
|
|
26
|
-
labels: list[GitHubLabelSchema]
|
|
27
|
-
assignees: list[GitHubUserSchema] =
|
|
28
|
-
requested_reviewers: list[GitHubUserSchema] =
|
|
26
|
+
labels: list[GitHubLabelSchema] = Field(default_factory=list)
|
|
27
|
+
assignees: list[GitHubUserSchema] = Field(default_factory=list)
|
|
28
|
+
requested_reviewers: list[GitHubUserSchema] = Field(default_factory=list)
|
|
29
29
|
base: GitHubBranchSchema
|
|
30
30
|
head: GitHubBranchSchema
|
|
@@ -1,47 +1,56 @@
|
|
|
1
1
|
from httpx import Response
|
|
2
2
|
|
|
3
3
|
from ai_review.clients.gitlab.mr.schema.changes import GitLabGetMRChangesResponseSchema
|
|
4
|
-
from ai_review.clients.gitlab.mr.schema.comments import (
|
|
5
|
-
GitLabGetMRCommentsResponseSchema,
|
|
6
|
-
GitLabCreateMRCommentRequestSchema,
|
|
7
|
-
GitLabCreateMRCommentResponseSchema,
|
|
8
|
-
)
|
|
9
4
|
from ai_review.clients.gitlab.mr.schema.discussions import (
|
|
10
5
|
GitLabGetMRDiscussionsResponseSchema,
|
|
11
6
|
GitLabCreateMRDiscussionRequestSchema,
|
|
12
7
|
GitLabCreateMRDiscussionResponseSchema
|
|
13
8
|
)
|
|
9
|
+
from ai_review.clients.gitlab.mr.schema.notes import (
|
|
10
|
+
GitLabGetMRNotesResponseSchema,
|
|
11
|
+
GitLabCreateMRNoteRequestSchema,
|
|
12
|
+
GitLabCreateMRNoteResponseSchema,
|
|
13
|
+
)
|
|
14
14
|
from ai_review.libs.http.client import HTTPClient
|
|
15
|
+
from ai_review.libs.http.handlers import handle_http_error, HTTPClientError
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
class
|
|
18
|
+
class GitLabMergeRequestsHTTPClientError(HTTPClientError):
|
|
19
|
+
pass
|
|
18
20
|
|
|
21
|
+
|
|
22
|
+
class GitLabMergeRequestsHTTPClient(HTTPClient):
|
|
23
|
+
@handle_http_error(client="GitLabMergeRequestsHTTPClient", exception=GitLabMergeRequestsHTTPClientError)
|
|
19
24
|
async def get_changes_api(self, project_id: str, merge_request_id: str) -> Response:
|
|
20
25
|
return await self.get(
|
|
21
26
|
f"/api/v4/projects/{project_id}/merge_requests/{merge_request_id}/changes"
|
|
22
27
|
)
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
@handle_http_error(client="GitLabMergeRequestsHTTPClient", exception=GitLabMergeRequestsHTTPClientError)
|
|
30
|
+
async def get_notes_api(self, project_id: str, merge_request_id: str) -> Response:
|
|
25
31
|
return await self.get(
|
|
26
32
|
f"/api/v4/projects/{project_id}/merge_requests/{merge_request_id}/notes"
|
|
27
33
|
)
|
|
28
34
|
|
|
35
|
+
@handle_http_error(client="GitLabMergeRequestsHTTPClient", exception=GitLabMergeRequestsHTTPClientError)
|
|
29
36
|
async def get_discussions_api(self, project_id: str, merge_request_id: str) -> Response:
|
|
30
37
|
return await self.get(
|
|
31
38
|
f"/api/v4/projects/{project_id}/merge_requests/{merge_request_id}/discussions"
|
|
32
39
|
)
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
@handle_http_error(client="GitLabMergeRequestsHTTPClient", exception=GitLabMergeRequestsHTTPClientError)
|
|
42
|
+
async def create_note_api(
|
|
35
43
|
self,
|
|
36
44
|
project_id: str,
|
|
37
45
|
merge_request_id: str,
|
|
38
|
-
request:
|
|
46
|
+
request: GitLabCreateMRNoteRequestSchema,
|
|
39
47
|
) -> Response:
|
|
40
48
|
return await self.post(
|
|
41
49
|
f"/api/v4/projects/{project_id}/merge_requests/{merge_request_id}/notes",
|
|
42
50
|
json=request.model_dump(),
|
|
43
51
|
)
|
|
44
52
|
|
|
53
|
+
@handle_http_error(client="GitLabMergeRequestsHTTPClient", exception=GitLabMergeRequestsHTTPClientError)
|
|
45
54
|
async def create_discussion_api(
|
|
46
55
|
self,
|
|
47
56
|
project_id: str,
|
|
@@ -57,13 +66,13 @@ class GitLabMergeRequestsHTTPClient(HTTPClient):
|
|
|
57
66
|
response = await self.get_changes_api(project_id, merge_request_id)
|
|
58
67
|
return GitLabGetMRChangesResponseSchema.model_validate_json(response.text)
|
|
59
68
|
|
|
60
|
-
async def
|
|
69
|
+
async def get_notes(
|
|
61
70
|
self,
|
|
62
71
|
project_id: str,
|
|
63
72
|
merge_request_id: str
|
|
64
|
-
) ->
|
|
65
|
-
response = await self.
|
|
66
|
-
return
|
|
73
|
+
) -> GitLabGetMRNotesResponseSchema:
|
|
74
|
+
response = await self.get_notes_api(project_id, merge_request_id)
|
|
75
|
+
return GitLabGetMRNotesResponseSchema.model_validate_json(response.text)
|
|
67
76
|
|
|
68
77
|
async def get_discussions(
|
|
69
78
|
self,
|
|
@@ -73,26 +82,26 @@ class GitLabMergeRequestsHTTPClient(HTTPClient):
|
|
|
73
82
|
response = await self.get_discussions_api(project_id, merge_request_id)
|
|
74
83
|
return GitLabGetMRDiscussionsResponseSchema.model_validate_json(response.text)
|
|
75
84
|
|
|
76
|
-
async def
|
|
85
|
+
async def create_note(
|
|
77
86
|
self,
|
|
78
|
-
|
|
87
|
+
body: str,
|
|
79
88
|
project_id: str,
|
|
80
89
|
merge_request_id: str,
|
|
81
|
-
) ->
|
|
82
|
-
request =
|
|
83
|
-
response = await self.
|
|
90
|
+
) -> GitLabCreateMRNoteResponseSchema:
|
|
91
|
+
request = GitLabCreateMRNoteRequestSchema(body=body)
|
|
92
|
+
response = await self.create_note_api(
|
|
84
93
|
request=request,
|
|
85
94
|
project_id=project_id,
|
|
86
95
|
merge_request_id=merge_request_id
|
|
87
96
|
)
|
|
88
|
-
return
|
|
97
|
+
return GitLabCreateMRNoteResponseSchema.model_validate_json(response.text)
|
|
89
98
|
|
|
90
99
|
async def create_discussion(
|
|
91
100
|
self,
|
|
92
101
|
project_id: str,
|
|
93
102
|
merge_request_id: str,
|
|
94
103
|
request: GitLabCreateMRDiscussionRequestSchema
|
|
95
|
-
):
|
|
104
|
+
) -> GitLabCreateMRDiscussionResponseSchema:
|
|
96
105
|
response = await self.create_discussion_api(
|
|
97
106
|
request=request,
|
|
98
107
|
project_id=project_id,
|
|
@@ -14,9 +14,9 @@ class GitLabDiffRefsSchema(BaseModel):
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class GitLabMRChangeSchema(BaseModel):
|
|
17
|
-
diff: str
|
|
18
|
-
old_path: str
|
|
19
|
-
new_path: str
|
|
17
|
+
diff: str | None = None
|
|
18
|
+
old_path: str | None = None
|
|
19
|
+
new_path: str | None = None
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class GitLabGetMRChangesResponseSchema(BaseModel):
|
|
@@ -24,12 +24,12 @@ class GitLabGetMRChangesResponseSchema(BaseModel):
|
|
|
24
24
|
iid: int
|
|
25
25
|
title: str
|
|
26
26
|
author: GitLabUserSchema
|
|
27
|
-
labels: list[str] =
|
|
27
|
+
labels: list[str] = Field(default_factory=list)
|
|
28
28
|
changes: list[GitLabMRChangeSchema]
|
|
29
29
|
assignees: list[GitLabUserSchema] = Field(default_factory=list)
|
|
30
30
|
reviewers: list[GitLabUserSchema] = Field(default_factory=list)
|
|
31
31
|
diff_refs: GitLabDiffRefsSchema
|
|
32
32
|
project_id: int
|
|
33
|
-
description: str
|
|
33
|
+
description: str | None = None
|
|
34
34
|
source_branch: str
|
|
35
35
|
target_branch: str
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from pydantic import BaseModel, RootModel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class GitLabNoteSchema(BaseModel):
|
|
5
|
+
id: int
|
|
6
|
+
body: str
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GitLabGetMRNotesResponseSchema(RootModel[list[GitLabNoteSchema]]):
|
|
10
|
+
root: list[GitLabNoteSchema]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class GitLabCreateMRNoteRequestSchema(BaseModel):
|
|
14
|
+
body: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GitLabCreateMRNoteResponseSchema(BaseModel):
|
|
18
|
+
id: int
|
|
19
|
+
body: str
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from ai_review.services.prompt.schema import PromptContextSchema
|
|
2
|
+
from ai_review.services.vcs.types import ReviewInfoSchema
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def build_prompt_context_from_mr_info(review: ReviewInfoSchema) -> PromptContextSchema:
|
|
6
|
+
return PromptContextSchema(
|
|
7
|
+
review_title=review.title,
|
|
8
|
+
review_description=review.description,
|
|
9
|
+
|
|
10
|
+
review_author_name=review.author.name,
|
|
11
|
+
review_author_username=review.author.username,
|
|
12
|
+
|
|
13
|
+
review_reviewers=[user.name for user in review.reviewers],
|
|
14
|
+
review_reviewers_usernames=[user.username for user in review.reviewers],
|
|
15
|
+
review_reviewer=review.reviewers[0].name if review.reviewers else "",
|
|
16
|
+
|
|
17
|
+
review_assignees=[user.name for user in review.assignees],
|
|
18
|
+
review_assignees_usernames=[user.username for user in review.assignees],
|
|
19
|
+
|
|
20
|
+
source_branch=review.source_branch.ref,
|
|
21
|
+
target_branch=review.target_branch.ref,
|
|
22
|
+
|
|
23
|
+
labels=review.labels,
|
|
24
|
+
changed_files=review.changed_files,
|
|
25
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
from ai_review.config import settings
|
|
4
|
+
from ai_review.libs.template.render import render_template
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PromptContextSchema(BaseModel):
|
|
8
|
+
review_title: str = ""
|
|
9
|
+
review_description: str = ""
|
|
10
|
+
|
|
11
|
+
review_author_name: str = ""
|
|
12
|
+
review_author_username: str = ""
|
|
13
|
+
|
|
14
|
+
review_reviewer: str = ""
|
|
15
|
+
review_reviewers: list[str] = Field(default_factory=list)
|
|
16
|
+
review_reviewers_usernames: list[str] = Field(default_factory=list)
|
|
17
|
+
|
|
18
|
+
review_assignees: list[str] = Field(default_factory=list)
|
|
19
|
+
review_assignees_usernames: list[str] = Field(default_factory=list)
|
|
20
|
+
|
|
21
|
+
source_branch: str = ""
|
|
22
|
+
target_branch: str = ""
|
|
23
|
+
|
|
24
|
+
labels: list[str] = Field(default_factory=list)
|
|
25
|
+
changed_files: list[str] = Field(default_factory=list)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def render_values(self) -> dict[str, str]:
|
|
29
|
+
return {
|
|
30
|
+
"review_title": self.review_title,
|
|
31
|
+
"review_description": self.review_description,
|
|
32
|
+
|
|
33
|
+
"review_author_name": self.review_author_name,
|
|
34
|
+
"review_author_username": self.review_author_username,
|
|
35
|
+
|
|
36
|
+
"review_reviewer": self.review_reviewer,
|
|
37
|
+
"review_reviewers": ", ".join(self.review_reviewers),
|
|
38
|
+
"review_reviewers_usernames": ", ".join(self.review_reviewers_usernames),
|
|
39
|
+
|
|
40
|
+
"review_assignees": ", ".join(self.review_assignees),
|
|
41
|
+
"review_assignees_usernames": ", ".join(self.review_assignees_usernames),
|
|
42
|
+
|
|
43
|
+
"source_branch": self.source_branch,
|
|
44
|
+
"target_branch": self.target_branch,
|
|
45
|
+
|
|
46
|
+
"labels": ", ".join(self.labels),
|
|
47
|
+
"changed_files": ", ".join(self.changed_files),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
def apply_format(self, prompt: str) -> str:
|
|
51
|
+
values = {**self.render_values, **settings.prompt.context}
|
|
52
|
+
return render_template(prompt, values, settings.prompt.context_placeholder)
|
|
@@ -15,7 +15,7 @@ from ai_review.services.review.inline.service import InlineCommentService
|
|
|
15
15
|
from ai_review.services.review.policy.service import ReviewPolicyService
|
|
16
16
|
from ai_review.services.review.summary.service import SummaryCommentService
|
|
17
17
|
from ai_review.services.vcs.factory import get_vcs_client
|
|
18
|
-
from ai_review.services.vcs.types import
|
|
18
|
+
from ai_review.services.vcs.types import ReviewInfoSchema
|
|
19
19
|
|
|
20
20
|
logger = get_logger("REVIEW_SERVICE")
|
|
21
21
|
|
|
@@ -52,37 +52,43 @@ class ReviewService:
|
|
|
52
52
|
logger.exception(f"LLM request failed: {error}")
|
|
53
53
|
raise
|
|
54
54
|
|
|
55
|
-
async def
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
settings.review.inline_tag in
|
|
59
|
-
for
|
|
60
|
-
for note in discussion.notes
|
|
55
|
+
async def has_existing_inline_comments(self) -> bool:
|
|
56
|
+
comments = await self.vcs.get_inline_comments()
|
|
57
|
+
has_comments = any(
|
|
58
|
+
settings.review.inline_tag in comment.body
|
|
59
|
+
for comment in comments
|
|
61
60
|
)
|
|
62
|
-
if
|
|
63
|
-
logger.info("Skipping inline review: AI inline
|
|
61
|
+
if has_comments:
|
|
62
|
+
logger.info("Skipping inline review: AI inline comments already exist")
|
|
64
63
|
|
|
65
|
-
return
|
|
64
|
+
return has_comments
|
|
66
65
|
|
|
67
66
|
async def has_existing_summary_comments(self) -> bool:
|
|
68
|
-
comments = await self.vcs.
|
|
69
|
-
has_comments = any(
|
|
67
|
+
comments = await self.vcs.get_general_comments()
|
|
68
|
+
has_comments = any(
|
|
69
|
+
settings.review.summary_tag in comment.body for comment in comments
|
|
70
|
+
)
|
|
70
71
|
if has_comments:
|
|
71
72
|
logger.info("Skipping summary review: AI summary comment already exists")
|
|
72
73
|
|
|
73
74
|
return has_comments
|
|
74
75
|
|
|
75
|
-
async def
|
|
76
|
+
async def process_inline_comments(
|
|
77
|
+
self,
|
|
78
|
+
flow: Literal["inline", "context"],
|
|
79
|
+
comments: InlineCommentListSchema
|
|
80
|
+
) -> None:
|
|
76
81
|
results = await bounded_gather([
|
|
77
|
-
self.vcs.
|
|
82
|
+
self.vcs.create_inline_comment(
|
|
78
83
|
file=comment.file,
|
|
79
84
|
line=comment.line,
|
|
80
85
|
message=comment.body_with_tag
|
|
81
86
|
)
|
|
82
87
|
for comment in comments.root
|
|
83
88
|
])
|
|
89
|
+
|
|
84
90
|
fallbacks = [
|
|
85
|
-
self.vcs.
|
|
91
|
+
self.vcs.create_general_comment(comment.fallback_body_with_tag)
|
|
86
92
|
for comment, result in zip(comments.root, results)
|
|
87
93
|
if isinstance(result, Exception)
|
|
88
94
|
]
|
|
@@ -90,19 +96,19 @@ class ReviewService:
|
|
|
90
96
|
logger.warning(f"Falling back to {len(fallbacks)} general comments ({flow} review)")
|
|
91
97
|
await bounded_gather(fallbacks)
|
|
92
98
|
|
|
93
|
-
async def process_file_inline(self, file: str,
|
|
94
|
-
raw_diff = self.git.get_diff_for_file(
|
|
99
|
+
async def process_file_inline(self, file: str, review_info: ReviewInfoSchema) -> None:
|
|
100
|
+
raw_diff = self.git.get_diff_for_file(review_info.base_sha, review_info.head_sha, file)
|
|
95
101
|
if not raw_diff.strip():
|
|
96
102
|
logger.debug(f"No diff for {file}, skipping")
|
|
97
103
|
return
|
|
98
104
|
|
|
99
105
|
rendered_file = self.diff.render_file(
|
|
100
106
|
file=file,
|
|
101
|
-
base_sha=
|
|
102
|
-
head_sha=
|
|
107
|
+
base_sha=review_info.base_sha,
|
|
108
|
+
head_sha=review_info.head_sha,
|
|
103
109
|
raw_diff=raw_diff,
|
|
104
110
|
)
|
|
105
|
-
prompt_context = build_prompt_context_from_mr_info(
|
|
111
|
+
prompt_context = build_prompt_context_from_mr_info(review_info)
|
|
106
112
|
prompt = self.prompt.build_inline_request(rendered_file, prompt_context)
|
|
107
113
|
prompt_system = self.prompt.build_system_inline_request(prompt_context)
|
|
108
114
|
prompt_result = await self.ask_llm(prompt, prompt_system)
|
|
@@ -114,29 +120,27 @@ class ReviewService:
|
|
|
114
120
|
return
|
|
115
121
|
|
|
116
122
|
logger.info(f"Posting {len(comments.root)} inline comments to {file}")
|
|
117
|
-
await self.
|
|
123
|
+
await self.process_inline_comments(flow="inline", comments=comments)
|
|
118
124
|
|
|
119
125
|
async def run_inline_review(self) -> None:
|
|
120
|
-
if await self.
|
|
126
|
+
if await self.has_existing_inline_comments():
|
|
121
127
|
return
|
|
122
128
|
|
|
123
|
-
|
|
129
|
+
review_info = await self.vcs.get_review_info()
|
|
130
|
+
logger.info(f"Starting inline review: {len(review_info.changed_files)} files changed")
|
|
124
131
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
changed_files = self.policy.apply_for_files(mr_info.changed_files)
|
|
132
|
+
changed_files = self.policy.apply_for_files(review_info.changed_files)
|
|
128
133
|
await bounded_gather([
|
|
129
|
-
self.process_file_inline(changed_file,
|
|
134
|
+
self.process_file_inline(changed_file, review_info)
|
|
130
135
|
for changed_file in changed_files
|
|
131
136
|
])
|
|
132
137
|
|
|
133
138
|
async def run_context_review(self) -> None:
|
|
134
|
-
if await self.
|
|
139
|
+
if await self.has_existing_inline_comments():
|
|
135
140
|
return
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
changed_files = self.policy.apply_for_files(
|
|
139
|
-
|
|
142
|
+
review_info = await self.vcs.get_review_info()
|
|
143
|
+
changed_files = self.policy.apply_for_files(review_info.changed_files)
|
|
140
144
|
if not changed_files:
|
|
141
145
|
logger.info("No files to review for context review")
|
|
142
146
|
return
|
|
@@ -146,10 +150,10 @@ class ReviewService:
|
|
|
146
150
|
rendered_files = self.diff.render_files(
|
|
147
151
|
git=self.git,
|
|
148
152
|
files=changed_files,
|
|
149
|
-
base_sha=
|
|
150
|
-
head_sha=
|
|
153
|
+
base_sha=review_info.base_sha,
|
|
154
|
+
head_sha=review_info.head_sha,
|
|
151
155
|
)
|
|
152
|
-
prompt_context = build_prompt_context_from_mr_info(
|
|
156
|
+
prompt_context = build_prompt_context_from_mr_info(review_info)
|
|
153
157
|
prompt = self.prompt.build_context_request(rendered_files, prompt_context)
|
|
154
158
|
prompt_system = self.prompt.build_system_context_request(prompt_context)
|
|
155
159
|
prompt_result = await self.ask_llm(prompt, prompt_system)
|
|
@@ -161,15 +165,14 @@ class ReviewService:
|
|
|
161
165
|
return
|
|
162
166
|
|
|
163
167
|
logger.info(f"Posting {len(comments.root)} inline comments (context review)")
|
|
164
|
-
await self.
|
|
168
|
+
await self.process_inline_comments(flow="context", comments=comments)
|
|
165
169
|
|
|
166
170
|
async def run_summary_review(self) -> None:
|
|
167
171
|
if await self.has_existing_summary_comments():
|
|
168
172
|
return
|
|
169
173
|
|
|
170
|
-
|
|
171
|
-
changed_files = self.policy.apply_for_files(
|
|
172
|
-
|
|
174
|
+
review_info = await self.vcs.get_review_info()
|
|
175
|
+
changed_files = self.policy.apply_for_files(review_info.changed_files)
|
|
173
176
|
if not changed_files:
|
|
174
177
|
logger.info("No files to review for summary")
|
|
175
178
|
return
|
|
@@ -179,10 +182,10 @@ class ReviewService:
|
|
|
179
182
|
rendered_files = self.diff.render_files(
|
|
180
183
|
git=self.git,
|
|
181
184
|
files=changed_files,
|
|
182
|
-
base_sha=
|
|
183
|
-
head_sha=
|
|
185
|
+
base_sha=review_info.base_sha,
|
|
186
|
+
head_sha=review_info.head_sha,
|
|
184
187
|
)
|
|
185
|
-
prompt_context = build_prompt_context_from_mr_info(
|
|
188
|
+
prompt_context = build_prompt_context_from_mr_info(review_info)
|
|
186
189
|
prompt = self.prompt.build_summary_request(rendered_files, prompt_context)
|
|
187
190
|
prompt_system = self.prompt.build_system_summary_request(prompt_context)
|
|
188
191
|
prompt_result = await self.ask_llm(prompt, prompt_system)
|
|
@@ -193,7 +196,7 @@ class ReviewService:
|
|
|
193
196
|
return
|
|
194
197
|
|
|
195
198
|
logger.info(f"Posting summary review comment ({len(summary.text)} chars)")
|
|
196
|
-
await self.vcs.
|
|
199
|
+
await self.vcs.create_general_comment(summary.body_with_tag)
|
|
197
200
|
|
|
198
201
|
def report_total_cost(self):
|
|
199
202
|
total_report = self.cost.aggregate()
|