xai-review 0.18.0__tar.gz → 0.20.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.

Files changed (194) hide show
  1. {xai_review-0.18.0 → xai_review-0.20.0}/PKG-INFO +24 -9
  2. {xai_review-0.18.0 → xai_review-0.20.0}/README.md +13 -7
  3. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/github/pr/client.py +13 -7
  4. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/github/pr/schema/pull_request.py +6 -6
  5. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/gitlab/mr/client.py +29 -20
  6. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/gitlab/mr/schema/changes.py +5 -5
  7. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/gitlab/mr/schema/discussions.py +1 -4
  8. xai_review-0.20.0/ai_review/clients/gitlab/mr/schema/notes.py +19 -0
  9. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/llm/factory.py +1 -1
  10. xai_review-0.20.0/ai_review/services/prompt/adapter.py +25 -0
  11. xai_review-0.20.0/ai_review/services/prompt/schema.py +52 -0
  12. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/review/service.py +45 -42
  13. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/vcs/factory.py +1 -1
  14. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/vcs/github/client.py +52 -34
  15. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/vcs/gitlab/client.py +62 -44
  16. xai_review-0.20.0/ai_review/services/vcs/types.py +64 -0
  17. xai_review-0.20.0/ai_review/tests/suites/services/cost/test_schema.py +124 -0
  18. xai_review-0.20.0/ai_review/tests/suites/services/cost/test_service.py +99 -0
  19. xai_review-0.20.0/ai_review/tests/suites/services/prompt/test_adapter.py +59 -0
  20. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/services/prompt/test_schema.py +18 -18
  21. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/services/prompt/test_service.py +13 -11
  22. xai_review-0.20.0/ai_review/tests/suites/services/review/summary/__init__.py +0 -0
  23. {xai_review-0.18.0 → xai_review-0.20.0}/pyproject.toml +15 -3
  24. {xai_review-0.18.0 → xai_review-0.20.0}/xai_review.egg-info/PKG-INFO +24 -9
  25. {xai_review-0.18.0 → xai_review-0.20.0}/xai_review.egg-info/SOURCES.txt +5 -1
  26. {xai_review-0.18.0 → xai_review-0.20.0}/xai_review.egg-info/requires.txt +3 -1
  27. xai_review-0.18.0/ai_review/clients/gitlab/mr/schema/comments.py +0 -19
  28. xai_review-0.18.0/ai_review/services/prompt/adapter.py +0 -25
  29. xai_review-0.18.0/ai_review/services/prompt/schema.py +0 -52
  30. xai_review-0.18.0/ai_review/services/vcs/types.py +0 -55
  31. {xai_review-0.18.0 → xai_review-0.20.0}/LICENSE +0 -0
  32. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/__init__.py +0 -0
  33. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/cli/__init__.py +0 -0
  34. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/cli/commands/__init__.py +0 -0
  35. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/cli/commands/run_context_review.py +0 -0
  36. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/cli/commands/run_inline_review.py +0 -0
  37. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/cli/commands/run_review.py +0 -0
  38. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/cli/commands/run_summary_review.py +0 -0
  39. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/cli/main.py +0 -0
  40. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/__init__.py +0 -0
  41. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/claude/__init__.py +0 -0
  42. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/claude/client.py +0 -0
  43. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/claude/schema.py +0 -0
  44. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/gemini/__init__.py +0 -0
  45. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/gemini/client.py +0 -0
  46. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/gemini/schema.py +0 -0
  47. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/github/__init__.py +0 -0
  48. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/github/client.py +0 -0
  49. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/github/pr/__init__.py +0 -0
  50. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/github/pr/schema/__init__.py +0 -0
  51. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/github/pr/schema/comments.py +0 -0
  52. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/github/pr/schema/files.py +0 -0
  53. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/github/pr/schema/reviews.py +0 -0
  54. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/gitlab/__init__.py +0 -0
  55. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/gitlab/client.py +0 -0
  56. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/gitlab/mr/__init__.py +0 -0
  57. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/gitlab/mr/schema/__init__.py +0 -0
  58. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/openai/__init__.py +0 -0
  59. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/openai/client.py +0 -0
  60. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/clients/openai/schema.py +0 -0
  61. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/config.py +0 -0
  62. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/__init__.py +0 -0
  63. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/asynchronous/__init__.py +0 -0
  64. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/asynchronous/gather.py +0 -0
  65. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/config/__init__.py +0 -0
  66. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/config/artifacts.py +0 -0
  67. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/config/base.py +0 -0
  68. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/config/claude.py +0 -0
  69. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/config/gemini.py +0 -0
  70. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/config/github.py +0 -0
  71. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/config/gitlab.py +0 -0
  72. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/config/http.py +0 -0
  73. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/config/llm.py +0 -0
  74. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/config/logger.py +0 -0
  75. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/config/openai.py +0 -0
  76. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/config/prompt.py +0 -0
  77. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/config/review.py +0 -0
  78. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/config/vcs.py +0 -0
  79. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/constants/__init__.py +0 -0
  80. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/constants/llm_provider.py +0 -0
  81. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/constants/vcs_provider.py +0 -0
  82. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/diff/__init__.py +0 -0
  83. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/diff/models.py +0 -0
  84. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/diff/parser.py +0 -0
  85. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/diff/tools.py +0 -0
  86. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/http/__init__.py +0 -0
  87. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/http/client.py +0 -0
  88. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/http/event_hooks/__init__.py +0 -0
  89. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/http/event_hooks/base.py +0 -0
  90. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/http/event_hooks/logger.py +0 -0
  91. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/http/handlers.py +0 -0
  92. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/http/transports/__init__.py +0 -0
  93. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/http/transports/retry.py +0 -0
  94. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/json.py +0 -0
  95. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/logger.py +0 -0
  96. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/resources.py +0 -0
  97. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/template/__init__.py +0 -0
  98. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/libs/template/render.py +0 -0
  99. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/prompts/__init__.py +0 -0
  100. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/prompts/default_context.md +0 -0
  101. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/prompts/default_inline.md +0 -0
  102. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/prompts/default_summary.md +0 -0
  103. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/prompts/default_system_context.md +0 -0
  104. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/prompts/default_system_inline.md +0 -0
  105. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/prompts/default_system_summary.md +0 -0
  106. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/resources/__init__.py +0 -0
  107. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/resources/pricing.yaml +0 -0
  108. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/__init__.py +0 -0
  109. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/artifacts/__init__.py +0 -0
  110. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/artifacts/schema.py +0 -0
  111. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/artifacts/service.py +0 -0
  112. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/artifacts/tools.py +0 -0
  113. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/cost/__init__.py +0 -0
  114. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/cost/schema.py +0 -0
  115. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/cost/service.py +0 -0
  116. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/diff/__init__.py +0 -0
  117. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/diff/renderers.py +0 -0
  118. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/diff/schema.py +0 -0
  119. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/diff/service.py +0 -0
  120. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/diff/tools.py +0 -0
  121. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/git/__init__.py +0 -0
  122. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/git/service.py +0 -0
  123. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/git/types.py +0 -0
  124. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/llm/__init__.py +0 -0
  125. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/llm/claude/__init__.py +0 -0
  126. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/llm/claude/client.py +0 -0
  127. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/llm/gemini/__init__.py +0 -0
  128. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/llm/gemini/client.py +0 -0
  129. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/llm/openai/__init__.py +0 -0
  130. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/llm/openai/client.py +0 -0
  131. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/llm/types.py +0 -0
  132. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/prompt/__init__.py +0 -0
  133. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/prompt/service.py +0 -0
  134. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/prompt/tools.py +0 -0
  135. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/review/__init__.py +0 -0
  136. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/review/inline/__init__.py +0 -0
  137. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/review/inline/schema.py +0 -0
  138. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/review/inline/service.py +0 -0
  139. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/review/policy/__init__.py +0 -0
  140. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/review/policy/service.py +0 -0
  141. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/review/summary/__init__.py +0 -0
  142. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/review/summary/schema.py +0 -0
  143. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/review/summary/service.py +0 -0
  144. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/vcs/__init__.py +0 -0
  145. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/vcs/github/__init__.py +0 -0
  146. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/services/vcs/gitlab/__init__.py +0 -0
  147. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/__init__.py +0 -0
  148. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/fixtures/__init__.py +0 -0
  149. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/fixtures/git.py +0 -0
  150. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/__init__.py +0 -0
  151. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/clients/__init__.py +0 -0
  152. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/clients/claude/__init__.py +0 -0
  153. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/clients/claude/test_client.py +0 -0
  154. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/clients/claude/test_schema.py +0 -0
  155. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/clients/gemini/__init__.py +0 -0
  156. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/clients/gemini/test_client.py +0 -0
  157. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/clients/gemini/test_schema.py +0 -0
  158. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/clients/github/__init__.py +0 -0
  159. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/clients/github/test_client.py +0 -0
  160. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/clients/gitlab/__init__.py +0 -0
  161. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/clients/gitlab/test_client.py +0 -0
  162. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/clients/openai/__init__.py +0 -0
  163. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/clients/openai/test_client.py +0 -0
  164. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/clients/openai/test_schema.py +0 -0
  165. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/libs/__init__.py +0 -0
  166. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/libs/config/__init__.py +0 -0
  167. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/libs/config/test_prompt.py +0 -0
  168. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/libs/diff/__init__.py +0 -0
  169. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/libs/diff/test_models.py +0 -0
  170. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/libs/diff/test_parser.py +0 -0
  171. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/libs/diff/test_tools.py +0 -0
  172. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/libs/template/__init__.py +0 -0
  173. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/libs/template/test_render.py +0 -0
  174. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/libs/test_json.py +0 -0
  175. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/services/__init__.py +0 -0
  176. {xai_review-0.18.0/ai_review/tests/suites/services/diff → xai_review-0.20.0/ai_review/tests/suites/services/cost}/__init__.py +0 -0
  177. {xai_review-0.18.0/ai_review/tests/suites/services/prompt → xai_review-0.20.0/ai_review/tests/suites/services/diff}/__init__.py +0 -0
  178. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/services/diff/test_renderers.py +0 -0
  179. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/services/diff/test_service.py +0 -0
  180. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/services/diff/test_tools.py +0 -0
  181. {xai_review-0.18.0/ai_review/tests/suites/services/review → xai_review-0.20.0/ai_review/tests/suites/services/prompt}/__init__.py +0 -0
  182. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/services/prompt/test_tools.py +0 -0
  183. {xai_review-0.18.0/ai_review/tests/suites/services/review/inline → xai_review-0.20.0/ai_review/tests/suites/services/review}/__init__.py +0 -0
  184. {xai_review-0.18.0/ai_review/tests/suites/services/review/policy → xai_review-0.20.0/ai_review/tests/suites/services/review/inline}/__init__.py +0 -0
  185. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/services/review/inline/test_schema.py +0 -0
  186. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/services/review/inline/test_service.py +0 -0
  187. {xai_review-0.18.0/ai_review/tests/suites/services/review/summary → xai_review-0.20.0/ai_review/tests/suites/services/review/policy}/__init__.py +0 -0
  188. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/services/review/policy/test_service.py +0 -0
  189. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/services/review/summary/test_schema.py +0 -0
  190. {xai_review-0.18.0 → xai_review-0.20.0}/ai_review/tests/suites/services/review/summary/test_service.py +0 -0
  191. {xai_review-0.18.0 → xai_review-0.20.0}/setup.cfg +0 -0
  192. {xai_review-0.18.0 → xai_review-0.20.0}/xai_review.egg-info/dependency_links.txt +0 -0
  193. {xai_review-0.18.0 → xai_review-0.20.0}/xai_review.egg-info/entry_points.txt +0 -0
  194. {xai_review-0.18.0 → xai_review-0.20.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.18.0
3
+ Version: 0.20.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
@@ -73,13 +82,13 @@ final decision to human reviewers.
73
82
  Curious how **AI Review** works in practice? Here are three real Pull Requests reviewed entirely by the tool — one per
74
83
  mode:
75
84
 
76
- | Mode | Description | Live Example |
77
- |------------|----------------------------------------------------|-------------------------------------------------------------------------------|
78
- | 🧩 Inline | Adds line-by-line comments directly in the diff | [Try AI Review (inline)](https://github.com/Nikita-Filonov/ai-review/pull/4) |
79
- | 📄 Summary | Posts a concise summary review with key highlights | [Try AI Review (context)](https://github.com/Nikita-Filonov/ai-review/pull/5) |
80
- | 🧠 Context | Performs broader analysis across multiple files | [Try AI Review (summary)](https://github.com/Nikita-Filonov/ai-review/pull/6) |
85
+ | Mode | Description | 🐙 GitHub | 🦊 GitLab |
86
+ |------------|----------------------------------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------------|
87
+ | 🧩 Inline | Adds line-by-line comments directly in the diff | [View on GitHub](https://github.com/Nikita-Filonov/ai-review/pull/4) | [View on GitLab](https://gitlab.com/core8332439/review/-/merge_requests/2) |
88
+ | 🧠 Context | Performs broader analysis across multiple files | [View on GitHub](https://github.com/Nikita-Filonov/ai-review/pull/5) | [View on GitLab](https://gitlab.com/core8332439/review/-/merge_requests/3) |
89
+ | 📄 Summary | Posts a concise summary review with key highlights | [View on GitHub](https://github.com/Nikita-Filonov/ai-review/pull/6) | [View on GitLab](https://gitlab.com/core8332439/review/-/merge_requests/4) |
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.18.0
210
+ - uses: Nikita-Filonov/ai-review@v0.20.0
196
211
  with:
197
212
  review-command: ${{ inputs.review-command }}
198
213
  env:
@@ -49,13 +49,13 @@ final decision to human reviewers.
49
49
  Curious how **AI Review** works in practice? Here are three real Pull Requests reviewed entirely by the tool — one per
50
50
  mode:
51
51
 
52
- | Mode | Description | Live Example |
53
- |------------|----------------------------------------------------|-------------------------------------------------------------------------------|
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
- | 📄 Summary | Posts a concise summary review with key highlights | [Try AI Review (context)](https://github.com/Nikita-Filonov/ai-review/pull/5) |
56
- | 🧠 Context | Performs broader analysis across multiple files | [Try AI Review (summary)](https://github.com/Nikita-Filonov/ai-review/pull/6) |
52
+ | Mode | Description | 🐙 GitHub | 🦊 GitLab |
53
+ |------------|----------------------------------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------------|
54
+ | 🧩 Inline | Adds line-by-line comments directly in the diff | [View on GitHub](https://github.com/Nikita-Filonov/ai-review/pull/4) | [View on GitLab](https://gitlab.com/core8332439/review/-/merge_requests/2) |
55
+ | 🧠 Context | Performs broader analysis across multiple files | [View on GitHub](https://github.com/Nikita-Filonov/ai-review/pull/5) | [View on GitLab](https://gitlab.com/core8332439/review/-/merge_requests/3) |
56
+ | 📄 Summary | Posts a concise summary review with key highlights | [View on GitHub](https://github.com/Nikita-Filonov/ai-review/pull/6) | [View on GitLab](https://gitlab.com/core8332439/review/-/merge_requests/4) |
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.18.0
177
+ - uses: Nikita-Filonov/ai-review@v0.20.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
- body: str,
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 GitLabMergeRequestsHTTPClient(HTTPClient):
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
- async def get_comments_api(self, project_id: str, merge_request_id: str) -> Response:
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
- async def create_comment_api(
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: GitLabCreateMRCommentRequestSchema,
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 get_comments(
69
+ async def get_notes(
61
70
  self,
62
71
  project_id: str,
63
72
  merge_request_id: str
64
- ) -> GitLabGetMRCommentsResponseSchema:
65
- response = await self.get_comments_api(project_id, merge_request_id)
66
- return GitLabGetMRCommentsResponseSchema.model_validate_json(response.text)
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 create_comment(
85
+ async def create_note(
77
86
  self,
78
- comment: str,
87
+ body: str,
79
88
  project_id: str,
80
89
  merge_request_id: str,
81
- ) -> GitLabCreateMRCommentResponseSchema:
82
- request = GitLabCreateMRCommentRequestSchema(body=comment)
83
- response = await self.create_comment_api(
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 GitLabCreateMRCommentResponseSchema.model_validate_json(response.text)
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
@@ -1,9 +1,6 @@
1
1
  from pydantic import BaseModel, RootModel
2
2
 
3
-
4
- class GitLabNoteSchema(BaseModel):
5
- id: int
6
- body: str
3
+ from ai_review.clients.gitlab.mr.schema.notes import GitLabNoteSchema
7
4
 
8
5
 
9
6
  class GitLabDiscussionSchema(BaseModel):
@@ -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
@@ -15,4 +15,4 @@ def get_llm_client() -> LLMClient:
15
15
  case LLMProvider.CLAUDE:
16
16
  return ClaudeLLMClient()
17
17
  case _:
18
- raise ValueError(f"Unsupported provider: {settings.llm.provider}")
18
+ raise ValueError(f"Unsupported LLM provider: {settings.llm.provider}")
@@ -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)