xai-review 0.11.0__tar.gz → 0.12.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 (172) hide show
  1. {xai_review-0.11.0 → xai_review-0.12.0}/PKG-INFO +1 -1
  2. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/config/prompt.py +1 -0
  3. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/prompt/service.py +19 -18
  4. xai_review-0.12.0/ai_review/services/prompt/tools.py +24 -0
  5. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/prompt/test_service.py +33 -0
  6. xai_review-0.12.0/ai_review/tests/suites/services/prompt/test_tools.py +72 -0
  7. {xai_review-0.11.0 → xai_review-0.12.0}/pyproject.toml +1 -1
  8. {xai_review-0.11.0 → xai_review-0.12.0}/xai_review.egg-info/PKG-INFO +1 -1
  9. {xai_review-0.11.0 → xai_review-0.12.0}/xai_review.egg-info/SOURCES.txt +2 -0
  10. {xai_review-0.11.0 → xai_review-0.12.0}/LICENSE +0 -0
  11. {xai_review-0.11.0 → xai_review-0.12.0}/README.md +0 -0
  12. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/__init__.py +0 -0
  13. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/cli/__init__.py +0 -0
  14. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/cli/commands/__init__.py +0 -0
  15. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/cli/commands/run_context_review.py +0 -0
  16. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/cli/commands/run_inline_review.py +0 -0
  17. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/cli/commands/run_review.py +0 -0
  18. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/cli/commands/run_summary_review.py +0 -0
  19. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/cli/main.py +0 -0
  20. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/__init__.py +0 -0
  21. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/claude/__init__.py +0 -0
  22. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/claude/client.py +0 -0
  23. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/claude/schema.py +0 -0
  24. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/gemini/__init__.py +0 -0
  25. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/gemini/client.py +0 -0
  26. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/gemini/schema.py +0 -0
  27. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/gitlab/__init__.py +0 -0
  28. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/gitlab/client.py +0 -0
  29. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/gitlab/mr/__init__.py +0 -0
  30. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/gitlab/mr/client.py +0 -0
  31. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/gitlab/mr/schema/__init__.py +0 -0
  32. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/gitlab/mr/schema/changes.py +0 -0
  33. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/gitlab/mr/schema/comments.py +0 -0
  34. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/gitlab/mr/schema/discussions.py +0 -0
  35. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/openai/__init__.py +0 -0
  36. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/openai/client.py +0 -0
  37. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/clients/openai/schema.py +0 -0
  38. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/config.py +0 -0
  39. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/__init__.py +0 -0
  40. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/asynchronous/__init__.py +0 -0
  41. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/asynchronous/gather.py +0 -0
  42. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/config/__init__.py +0 -0
  43. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/config/artifacts.py +0 -0
  44. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/config/base.py +0 -0
  45. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/config/claude.py +0 -0
  46. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/config/gemini.py +0 -0
  47. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/config/gitlab.py +0 -0
  48. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/config/http.py +0 -0
  49. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/config/llm.py +0 -0
  50. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/config/logger.py +0 -0
  51. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/config/openai.py +0 -0
  52. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/config/review.py +0 -0
  53. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/config/vcs.py +0 -0
  54. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/constants/__init__.py +0 -0
  55. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/constants/llm_provider.py +0 -0
  56. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/constants/vcs_provider.py +0 -0
  57. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/diff/__init__.py +0 -0
  58. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/diff/models.py +0 -0
  59. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/diff/parser.py +0 -0
  60. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/diff/tools.py +0 -0
  61. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/http/__init__.py +0 -0
  62. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/http/client.py +0 -0
  63. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/http/event_hooks/__init__.py +0 -0
  64. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/http/event_hooks/base.py +0 -0
  65. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/http/event_hooks/logger.py +0 -0
  66. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/http/handlers.py +0 -0
  67. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/http/transports/__init__.py +0 -0
  68. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/http/transports/retry.py +0 -0
  69. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/json.py +0 -0
  70. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/logger.py +0 -0
  71. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/resources.py +0 -0
  72. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/template/__init__.py +0 -0
  73. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/libs/template/render.py +0 -0
  74. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/prompts/__init__.py +0 -0
  75. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/prompts/default_context.md +0 -0
  76. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/prompts/default_inline.md +0 -0
  77. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/prompts/default_summary.md +0 -0
  78. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/prompts/default_system_context.md +0 -0
  79. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/prompts/default_system_inline.md +0 -0
  80. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/prompts/default_system_summary.md +0 -0
  81. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/resources/__init__.py +0 -0
  82. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/resources/pricing.yaml +0 -0
  83. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/__init__.py +0 -0
  84. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/artifacts/__init__.py +0 -0
  85. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/artifacts/schema.py +0 -0
  86. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/artifacts/service.py +0 -0
  87. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/artifacts/tools.py +0 -0
  88. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/cost/__init__.py +0 -0
  89. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/cost/schema.py +0 -0
  90. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/cost/service.py +0 -0
  91. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/diff/__init__.py +0 -0
  92. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/diff/renderers.py +0 -0
  93. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/diff/schema.py +0 -0
  94. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/diff/service.py +0 -0
  95. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/diff/tools.py +0 -0
  96. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/git/__init__.py +0 -0
  97. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/git/service.py +0 -0
  98. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/git/types.py +0 -0
  99. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/llm/__init__.py +0 -0
  100. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/llm/claude/__init__.py +0 -0
  101. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/llm/claude/client.py +0 -0
  102. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/llm/factory.py +0 -0
  103. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/llm/gemini/__init__.py +0 -0
  104. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/llm/gemini/client.py +0 -0
  105. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/llm/openai/__init__.py +0 -0
  106. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/llm/openai/client.py +0 -0
  107. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/llm/types.py +0 -0
  108. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/prompt/__init__.py +0 -0
  109. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/prompt/adapter.py +0 -0
  110. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/prompt/schema.py +0 -0
  111. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/review/__init__.py +0 -0
  112. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/review/inline/__init__.py +0 -0
  113. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/review/inline/schema.py +0 -0
  114. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/review/inline/service.py +0 -0
  115. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/review/policy/__init__.py +0 -0
  116. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/review/policy/service.py +0 -0
  117. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/review/service.py +0 -0
  118. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/review/summary/__init__.py +0 -0
  119. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/review/summary/schema.py +0 -0
  120. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/review/summary/service.py +0 -0
  121. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/vcs/__init__.py +0 -0
  122. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/vcs/factory.py +0 -0
  123. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/vcs/gitlab/__init__.py +0 -0
  124. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/vcs/gitlab/client.py +0 -0
  125. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/services/vcs/types.py +0 -0
  126. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/__init__.py +0 -0
  127. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/fixtures/__init__.py +0 -0
  128. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/fixtures/git.py +0 -0
  129. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/__init__.py +0 -0
  130. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/clients/__init__.py +0 -0
  131. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/clients/claude/__init__.py +0 -0
  132. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/clients/claude/test_client.py +0 -0
  133. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/clients/claude/test_schema.py +0 -0
  134. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/clients/gemini/__init__.py +0 -0
  135. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/clients/gemini/test_client.py +0 -0
  136. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/clients/gemini/test_schema.py +0 -0
  137. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/clients/gitlab/__init__.py +0 -0
  138. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/clients/gitlab/test_client.py +0 -0
  139. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/clients/openai/__init__.py +0 -0
  140. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/clients/openai/test_client.py +0 -0
  141. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/clients/openai/test_schema.py +0 -0
  142. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/libs/__init__.py +0 -0
  143. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/libs/config/__init__.py +0 -0
  144. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/libs/config/test_prompt.py +0 -0
  145. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/libs/diff/__init__.py +0 -0
  146. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/libs/diff/test_models.py +0 -0
  147. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/libs/diff/test_parser.py +0 -0
  148. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/libs/diff/test_tools.py +0 -0
  149. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/libs/template/__init__.py +0 -0
  150. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/libs/template/test_render.py +0 -0
  151. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/libs/test_json.py +0 -0
  152. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/__init__.py +0 -0
  153. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/diff/__init__.py +0 -0
  154. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/diff/test_renderers.py +0 -0
  155. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/diff/test_service.py +0 -0
  156. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/diff/test_tools.py +0 -0
  157. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/prompt/__init__.py +0 -0
  158. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/prompt/test_schema.py +0 -0
  159. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/review/__init__.py +0 -0
  160. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/review/inline/__init__.py +0 -0
  161. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/review/inline/test_schema.py +0 -0
  162. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/review/inline/test_service.py +0 -0
  163. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/review/policy/__init__.py +0 -0
  164. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/review/policy/test_service.py +0 -0
  165. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/review/summary/__init__.py +0 -0
  166. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/review/summary/test_schema.py +0 -0
  167. {xai_review-0.11.0 → xai_review-0.12.0}/ai_review/tests/suites/services/review/summary/test_service.py +0 -0
  168. {xai_review-0.11.0 → xai_review-0.12.0}/setup.cfg +0 -0
  169. {xai_review-0.11.0 → xai_review-0.12.0}/xai_review.egg-info/dependency_links.txt +0 -0
  170. {xai_review-0.11.0 → xai_review-0.12.0}/xai_review.egg-info/entry_points.txt +0 -0
  171. {xai_review-0.11.0 → xai_review-0.12.0}/xai_review.egg-info/requires.txt +0 -0
  172. {xai_review-0.11.0 → xai_review-0.12.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.11.0
3
+ Version: 0.12.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>
@@ -8,6 +8,7 @@ from ai_review.libs.resources import load_resource
8
8
 
9
9
  class PromptConfig(BaseModel):
10
10
  context: dict[str, str] = Field(default_factory=dict)
11
+ normalize_prompts: bool = True
11
12
  context_placeholder: str = "<<{value}>>"
12
13
  inline_prompt_files: list[FilePath] | None = None
13
14
  context_prompt_files: list[FilePath] | None = None
@@ -1,56 +1,57 @@
1
1
  from ai_review.config import settings
2
2
  from ai_review.services.diff.schema import DiffFileSchema
3
3
  from ai_review.services.prompt.schema import PromptContextSchema
4
+ from ai_review.services.prompt.tools import normalize_prompt, format_file
4
5
 
5
6
 
6
- def format_file(diff: DiffFileSchema) -> str:
7
- return f"# File: {diff.file}\n{diff.diff}\n"
7
+ class PromptService:
8
+ @classmethod
9
+ def prepare_prompt(cls, prompts: list[str], context: PromptContextSchema) -> str:
10
+ prompt = "\n\n".join(prompts)
11
+ prompt = context.apply_format(prompt)
8
12
 
13
+ if settings.prompt.normalize_prompts:
14
+ prompt = normalize_prompt(prompt)
15
+
16
+ return prompt
9
17
 
10
- class PromptService:
11
18
  @classmethod
12
19
  def build_inline_request(cls, diff: DiffFileSchema, context: PromptContextSchema) -> str:
13
- inline_prompts = "\n\n".join(settings.prompt.load_inline())
14
- inline_prompts = context.apply_format(inline_prompts)
20
+ prompt = cls.prepare_prompt(settings.prompt.load_inline(), context)
15
21
  return (
16
- f"{inline_prompts}\n\n"
22
+ f"{prompt}\n\n"
17
23
  f"## Diff\n\n"
18
24
  f"{format_file(diff)}"
19
25
  )
20
26
 
21
27
  @classmethod
22
28
  def build_summary_request(cls, diffs: list[DiffFileSchema], context: PromptContextSchema) -> str:
29
+ prompt = cls.prepare_prompt(settings.prompt.load_summary(), context)
23
30
  changes = "\n\n".join(map(format_file, diffs))
24
- summary_prompts = "\n\n".join(settings.prompt.load_summary())
25
- summary_prompts = context.apply_format(summary_prompts)
26
31
  return (
27
- f"{summary_prompts}\n\n"
32
+ f"{prompt}\n\n"
28
33
  f"## Changes\n\n"
29
34
  f"{changes}\n"
30
35
  )
31
36
 
32
37
  @classmethod
33
38
  def build_context_request(cls, diffs: list[DiffFileSchema], context: PromptContextSchema) -> str:
39
+ prompt = cls.prepare_prompt(settings.prompt.load_context(), context)
34
40
  changes = "\n\n".join(map(format_file, diffs))
35
- inline_prompts = "\n\n".join(settings.prompt.load_context())
36
- inline_prompts = context.apply_format(inline_prompts)
37
41
  return (
38
- f"{inline_prompts}\n\n"
42
+ f"{prompt}\n\n"
39
43
  f"## Diff\n\n"
40
44
  f"{changes}\n"
41
45
  )
42
46
 
43
47
  @classmethod
44
48
  def build_system_inline_request(cls, context: PromptContextSchema) -> str:
45
- prompt = "\n\n".join(settings.prompt.load_system_inline())
46
- return context.apply_format(prompt)
49
+ return cls.prepare_prompt(settings.prompt.load_system_inline(), context)
47
50
 
48
51
  @classmethod
49
52
  def build_system_context_request(cls, context: PromptContextSchema) -> str:
50
- prompt = "\n\n".join(settings.prompt.load_system_context())
51
- return context.apply_format(prompt)
53
+ return cls.prepare_prompt(settings.prompt.load_system_context(), context)
52
54
 
53
55
  @classmethod
54
56
  def build_system_summary_request(cls, context: PromptContextSchema) -> str:
55
- prompt = "\n\n".join(settings.prompt.load_system_summary())
56
- return context.apply_format(prompt)
57
+ return cls.prepare_prompt(settings.prompt.load_system_summary(), context)
@@ -0,0 +1,24 @@
1
+ import re
2
+
3
+ from ai_review.libs.logger import get_logger
4
+ from ai_review.services.diff.schema import DiffFileSchema
5
+
6
+ logger = get_logger("PROMPT_TOOLS")
7
+
8
+
9
+ def format_file(diff: DiffFileSchema) -> str:
10
+ return f"# File: {diff.file}\n{diff.diff}\n"
11
+
12
+
13
+ def normalize_prompt(text: str) -> str:
14
+ tails_stripped = [re.sub(r"[ \t]+$", "", line) for line in text.splitlines()]
15
+ text = "\n".join(tails_stripped)
16
+
17
+ text = re.sub(r"\n{3,}", "\n\n", text)
18
+
19
+ result = text.strip()
20
+ if len(text) > len(result):
21
+ logger.info(f"Prompt has been normalized from {len(text)} to {len(result)}")
22
+ return result
23
+
24
+ return text
@@ -1,5 +1,6 @@
1
1
  import pytest
2
2
 
3
+ from ai_review.config import settings
3
4
  from ai_review.libs.config.prompt import PromptConfig
4
5
  from ai_review.services.diff.schema import DiffFileSchema
5
6
  from ai_review.services.prompt.schema import PromptContextSchema
@@ -134,3 +135,35 @@ def test_diff_placeholders_are_not_replaced(dummy_context: PromptContextSchema)
134
135
 
135
136
  assert "<<merge_request_title>>" in result
136
137
  assert "Fix login bug" not in result
138
+
139
+
140
+ def test_prepare_prompt_basic_substitution(dummy_context: PromptContextSchema) -> None:
141
+ prompts = ["Hello", "MR title: <<merge_request_title>>"]
142
+ result = PromptService.prepare_prompt(prompts, dummy_context)
143
+ assert "Hello" in result
144
+ assert "MR title: Fix login bug" in result
145
+
146
+
147
+ def test_prepare_prompt_applies_normalization(
148
+ monkeypatch: pytest.MonkeyPatch,
149
+ dummy_context: PromptContextSchema
150
+ ) -> None:
151
+ monkeypatch.setattr(settings.prompt, "normalize_prompts", True)
152
+ prompts = ["Line with space ", "", "", "Next line"]
153
+ result = PromptService.prepare_prompt(prompts, dummy_context)
154
+
155
+ assert "Line with space" in result
156
+ assert "Next line" in result
157
+ assert "\n\n\n" not in result
158
+
159
+
160
+ def test_prepare_prompt_skips_normalization(
161
+ monkeypatch: pytest.MonkeyPatch,
162
+ dummy_context: PromptContextSchema
163
+ ) -> None:
164
+ monkeypatch.setattr(settings.prompt, "normalize_prompts", False)
165
+ prompts = ["Line with space ", "", "", "Next line"]
166
+ result = PromptService.prepare_prompt(prompts, dummy_context)
167
+
168
+ assert "Line with space " in result
169
+ assert "\n\n\n" in result
@@ -0,0 +1,72 @@
1
+ from ai_review.services.diff.schema import DiffFileSchema
2
+ from ai_review.services.prompt.tools import format_file, normalize_prompt
3
+
4
+
5
+ def test_format_file_basic():
6
+ diff = DiffFileSchema(file="main.py", diff="+ print('hello')")
7
+ result = format_file(diff)
8
+ assert result == "# File: main.py\n+ print('hello')\n"
9
+
10
+
11
+ def test_format_file_empty_diff():
12
+ diff = DiffFileSchema(file="empty.py", diff="")
13
+ result = format_file(diff)
14
+ assert result == "# File: empty.py\n\n"
15
+
16
+
17
+ def test_format_file_multiline_diff():
18
+ diff = DiffFileSchema(
19
+ file="utils/helpers.py",
20
+ diff="- old line\n+ new line\n+ another line"
21
+ )
22
+ result = format_file(diff)
23
+ expected = (
24
+ "# File: utils/helpers.py\n"
25
+ "- old line\n"
26
+ "+ new line\n"
27
+ "+ another line\n"
28
+ )
29
+ assert result == expected
30
+
31
+
32
+ def test_format_file_filename_with_path():
33
+ diff = DiffFileSchema(file="src/app/models/user.py", diff="+ class User:")
34
+ result = format_file(diff)
35
+ assert result.startswith("# File: src/app/models/user.py\n")
36
+ assert result.endswith("+ class User:\n")
37
+
38
+
39
+ def test_trailing_spaces_are_removed():
40
+ text = "hello \nworld\t\t"
41
+ result = normalize_prompt(text)
42
+ assert result == "hello\nworld"
43
+
44
+
45
+ def test_multiple_empty_lines_collapsed():
46
+ text = "line1\n\n\n\nline2"
47
+ result = normalize_prompt(text)
48
+ assert result == "line1\n\nline2"
49
+
50
+
51
+ def test_leading_and_trailing_whitespace_removed():
52
+ text = "\n\n hello\nworld \n\n"
53
+ result = normalize_prompt(text)
54
+ assert result == "hello\nworld"
55
+
56
+
57
+ def test_internal_spaces_preserved():
58
+ text = "foo bar\nbaz\t\tqux"
59
+ result = normalize_prompt(text)
60
+ assert result == "foo bar\nbaz\t\tqux"
61
+
62
+
63
+ def test_only_whitespace_string():
64
+ text = " \n \n"
65
+ result = normalize_prompt(text)
66
+ assert result == ""
67
+
68
+
69
+ def test_no_changes_when_already_clean():
70
+ text = "line1\nline2"
71
+ result = normalize_prompt(text)
72
+ assert result == text
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "xai-review"
7
7
  readme = "README.md"
8
- version = "0.11.0"
8
+ version = "0.12.0"
9
9
  license = { text = "Apache-2.0" }
10
10
  authors = [
11
11
  { name = "Nikita Filonov", email = "nikita.filonov@example.com" }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xai-review
3
- Version: 0.11.0
3
+ Version: 0.12.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>
@@ -102,6 +102,7 @@ ai_review/services/prompt/__init__.py
102
102
  ai_review/services/prompt/adapter.py
103
103
  ai_review/services/prompt/schema.py
104
104
  ai_review/services/prompt/service.py
105
+ ai_review/services/prompt/tools.py
105
106
  ai_review/services/review/__init__.py
106
107
  ai_review/services/review/service.py
107
108
  ai_review/services/review/inline/__init__.py
@@ -151,6 +152,7 @@ ai_review/tests/suites/services/diff/test_tools.py
151
152
  ai_review/tests/suites/services/prompt/__init__.py
152
153
  ai_review/tests/suites/services/prompt/test_schema.py
153
154
  ai_review/tests/suites/services/prompt/test_service.py
155
+ ai_review/tests/suites/services/prompt/test_tools.py
154
156
  ai_review/tests/suites/services/review/__init__.py
155
157
  ai_review/tests/suites/services/review/inline/__init__.py
156
158
  ai_review/tests/suites/services/review/inline/test_schema.py
File without changes
File without changes
File without changes