xai-review 0.20.0__py3-none-any.whl → 0.21.0__py3-none-any.whl

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 (54) hide show
  1. ai_review/services/artifacts/service.py +2 -1
  2. ai_review/services/artifacts/types.py +20 -0
  3. ai_review/services/cost/service.py +2 -1
  4. ai_review/services/cost/types.py +12 -0
  5. ai_review/services/diff/service.py +2 -1
  6. ai_review/services/diff/types.py +28 -0
  7. ai_review/services/hook/__init__.py +5 -0
  8. ai_review/services/hook/constants.py +24 -0
  9. ai_review/services/hook/service.py +162 -0
  10. ai_review/services/hook/types.py +28 -0
  11. ai_review/services/llm/claude/client.py +2 -2
  12. ai_review/services/llm/factory.py +2 -2
  13. ai_review/services/llm/gemini/client.py +2 -2
  14. ai_review/services/llm/openai/client.py +2 -2
  15. ai_review/services/llm/types.py +1 -1
  16. ai_review/services/prompt/service.py +2 -1
  17. ai_review/services/prompt/types.py +27 -0
  18. ai_review/services/review/gateway/__init__.py +0 -0
  19. ai_review/services/review/gateway/comment.py +65 -0
  20. ai_review/services/review/gateway/llm.py +40 -0
  21. ai_review/services/review/inline/schema.py +2 -2
  22. ai_review/services/review/inline/service.py +2 -1
  23. ai_review/services/review/inline/types.py +11 -0
  24. ai_review/services/review/service.py +23 -74
  25. ai_review/services/review/summary/service.py +2 -1
  26. ai_review/services/review/summary/types.py +8 -0
  27. ai_review/services/vcs/factory.py +2 -2
  28. ai_review/services/vcs/github/client.py +4 -2
  29. ai_review/services/vcs/gitlab/client.py +4 -2
  30. ai_review/services/vcs/types.py +1 -1
  31. ai_review/tests/fixtures/artifacts.py +51 -0
  32. ai_review/tests/fixtures/cost.py +48 -0
  33. ai_review/tests/fixtures/diff.py +46 -0
  34. ai_review/tests/fixtures/git.py +11 -5
  35. ai_review/tests/fixtures/llm.py +26 -0
  36. ai_review/tests/fixtures/prompt.py +43 -0
  37. ai_review/tests/fixtures/review/__init__.py +0 -0
  38. ai_review/tests/fixtures/review/inline.py +25 -0
  39. ai_review/tests/fixtures/review/summary.py +19 -0
  40. ai_review/tests/fixtures/vcs.py +49 -0
  41. ai_review/tests/suites/services/diff/test_service.py +3 -3
  42. ai_review/tests/suites/services/diff/test_tools.py +9 -9
  43. ai_review/tests/suites/services/hook/__init__.py +0 -0
  44. ai_review/tests/suites/services/hook/test_service.py +93 -0
  45. ai_review/tests/suites/services/review/inline/test_schema.py +10 -9
  46. ai_review/tests/suites/services/review/summary/test_schema.py +0 -1
  47. ai_review/tests/suites/services/review/summary/test_service.py +10 -7
  48. ai_review/tests/suites/services/review/test_service.py +126 -0
  49. {xai_review-0.20.0.dist-info → xai_review-0.21.0.dist-info}/METADATA +5 -2
  50. {xai_review-0.20.0.dist-info → xai_review-0.21.0.dist-info}/RECORD +54 -29
  51. {xai_review-0.20.0.dist-info → xai_review-0.21.0.dist-info}/WHEEL +0 -0
  52. {xai_review-0.20.0.dist-info → xai_review-0.21.0.dist-info}/entry_points.txt +0 -0
  53. {xai_review-0.20.0.dist-info → xai_review-0.21.0.dist-info}/licenses/LICENSE +0 -0
  54. {xai_review-0.20.0.dist-info → xai_review-0.21.0.dist-info}/top_level.txt +0 -0
@@ -55,23 +55,23 @@ def test_find_diff_file_not_found_returns_none() -> None:
55
55
 
56
56
  # ---------- read_snapshot ----------
57
57
 
58
- def test_read_snapshot_prefers_git(monkeypatch: pytest.MonkeyPatch, fake_git: FakeGitService) -> None:
59
- fake_git.responses["get_file_at_commit"] = "from git"
60
- monkeypatch.setattr(tools, "GitService", lambda: fake_git)
58
+ def test_read_snapshot_prefers_git(monkeypatch: pytest.MonkeyPatch, fake_git_service: FakeGitService) -> None:
59
+ fake_git_service.responses["get_file_at_commit"] = "from git"
60
+ monkeypatch.setattr(tools, "GitService", lambda: fake_git_service)
61
61
 
62
62
  assert tools.read_snapshot("foo.py", head_sha="HEAD") == "from git"
63
63
 
64
64
 
65
65
  def test_read_snapshot_fallback_to_filesystem(
66
66
  tmp_path: Path,
67
- fake_git: FakeGitService,
68
67
  monkeypatch: pytest.MonkeyPatch,
68
+ fake_git_service: FakeGitService,
69
69
  ) -> None:
70
70
  file = tmp_path / "file.txt"
71
71
  file.write_text("hello")
72
72
 
73
- fake_git.responses["get_file_at_commit"] = None
74
- monkeypatch.setattr(tools, "GitService", lambda: fake_git)
73
+ fake_git_service.responses["get_file_at_commit"] = None
74
+ monkeypatch.setattr(tools, "GitService", lambda: fake_git_service)
75
75
 
76
76
  result = tools.read_snapshot(str(file))
77
77
  assert result == "hello"
@@ -79,11 +79,11 @@ def test_read_snapshot_fallback_to_filesystem(
79
79
 
80
80
  def test_read_snapshot_returns_none_if_missing(
81
81
  tmp_path: Path,
82
- fake_git: FakeGitService,
83
82
  monkeypatch: pytest.MonkeyPatch,
83
+ fake_git_service: FakeGitService,
84
84
  ) -> None:
85
- fake_git.responses["get_file_at_commit"] = None
86
- monkeypatch.setattr(tools, "GitService", lambda: fake_git)
85
+ fake_git_service.responses["get_file_at_commit"] = None
86
+ monkeypatch.setattr(tools, "GitService", lambda: fake_git_service)
87
87
 
88
88
  assert tools.read_snapshot(str(tmp_path / "nope.txt")) is None
89
89
 
File without changes
@@ -0,0 +1,93 @@
1
+ import pytest
2
+
3
+ from ai_review.services.cost.schema import CostReportSchema
4
+ from ai_review.services.hook.constants import HookType
5
+ from ai_review.services.hook.service import HookService
6
+
7
+
8
+ @pytest.fixture
9
+ def hook_service() -> HookService:
10
+ """Return a fresh HookService instance for each test."""
11
+ return HookService()
12
+
13
+
14
+ @pytest.mark.asyncio
15
+ async def test_inject_and_emit_simple(hook_service: HookService):
16
+ """
17
+ Should register hook and invoke it with emitted args.
18
+ """
19
+ results = []
20
+
21
+ async def sample_hook(arg1: str, arg2: int):
22
+ results.append((arg1, arg2))
23
+
24
+ hook_service.inject_hook(HookType.ON_CHAT_START, sample_hook)
25
+ await hook_service.emit(HookType.ON_CHAT_START, "hi", 42)
26
+
27
+ assert results == [("hi", 42)]
28
+
29
+
30
+ @pytest.mark.asyncio
31
+ async def test_emit_without_hooks_does_nothing(hook_service: HookService):
32
+ """
33
+ If no hooks are registered, emit should silently return.
34
+ """
35
+ await hook_service.emit(HookType.ON_CHAT_COMPLETE, "text")
36
+
37
+
38
+ @pytest.mark.asyncio
39
+ async def test_emit_handles_hook_exception(monkeypatch: pytest.MonkeyPatch, hook_service: HookService):
40
+ """
41
+ Should catch exceptions in hook and log them, without breaking flow.
42
+ """
43
+ errors = []
44
+
45
+ async def failing_hook():
46
+ raise ValueError("Boom!")
47
+
48
+ def fake_logger_exception(message: str):
49
+ errors.append(message)
50
+
51
+ monkeypatch.setattr("ai_review.services.hook.service.logger.exception", fake_logger_exception)
52
+ hook_service.inject_hook(HookType.ON_CHAT_COMPLETE, failing_hook)
53
+
54
+ await hook_service.emit(HookType.ON_CHAT_COMPLETE)
55
+ assert any("Boom!" in message for message in errors)
56
+
57
+
58
+ @pytest.mark.asyncio
59
+ async def test_on_chat_start_decorator_registers_hook(hook_service: HookService):
60
+ """
61
+ Using @on_chat_start should register the callback.
62
+ """
63
+ results = []
64
+
65
+ @hook_service.on_chat_start
66
+ async def chat_start_hook(prompt: str, prompt_system: str):
67
+ results.append((prompt, prompt_system))
68
+
69
+ await hook_service.emit_chat_start("Hello", "SYS")
70
+ assert results == [("Hello", "SYS")]
71
+
72
+
73
+ @pytest.mark.asyncio
74
+ async def test_on_chat_complete_decorator_registers_hook(hook_service: HookService):
75
+ """
76
+ Using @on_chat_complete should register and trigger hook.
77
+ """
78
+ results = []
79
+
80
+ @hook_service.on_chat_complete
81
+ async def chat_complete_hook(result: str, report: CostReportSchema | None):
82
+ results.append((result, report))
83
+
84
+ cost_report = CostReportSchema(
85
+ model="gpt",
86
+ prompt_tokens=10,
87
+ completion_tokens=100,
88
+ total_cost=26,
89
+ input_cost=10.5,
90
+ output_cost=15.5
91
+ )
92
+ await hook_service.emit_chat_complete("done", cost_report)
93
+ assert results == [("done", cost_report)]
@@ -1,3 +1,5 @@
1
+ import pytest
2
+
1
3
  from ai_review.config import settings
2
4
  from ai_review.services.review.inline.schema import (
3
5
  InlineCommentSchema,
@@ -7,14 +9,14 @@ from ai_review.services.review.inline.schema import (
7
9
 
8
10
  def test_normalize_file_and_message():
9
11
  comment = InlineCommentSchema(file=" \\src\\main.py ", line=10, message=" fix bug ")
10
- assert comment.file == "src/main.py" # нормализуется и слеши, и пробелы
11
- assert comment.message == "fix bug" # пробелы убраны
12
+ assert comment.file == "src/main.py"
13
+ assert comment.message == "fix bug"
12
14
 
13
15
 
14
16
  def test_body_without_suggestion():
15
17
  comment = InlineCommentSchema(file="a.py", line=1, message="use f-string")
16
18
  assert comment.body == "use f-string"
17
- assert settings.review.inline_tag not in comment.body # тег ещё не добавлен
19
+ assert settings.review.inline_tag not in comment.body
18
20
 
19
21
 
20
22
  def test_body_with_suggestion():
@@ -31,18 +33,17 @@ def test_body_with_suggestion():
31
33
  assert comment.body == expected
32
34
 
33
35
 
34
- def test_body_with_tag(monkeypatch):
36
+ def test_body_with_tag(monkeypatch: pytest.MonkeyPatch):
35
37
  monkeypatch.setattr(settings.review, "inline_tag", "#ai-inline")
36
38
  comment = InlineCommentSchema(file="a.py", line=3, message="something")
37
39
  assert comment.body_with_tag.endswith("\n\n#ai-inline")
40
+ assert settings.review.inline_tag not in comment.body
38
41
 
39
42
 
40
- def test_fallback_body_with_tag(monkeypatch):
43
+ def test_fallback_body(monkeypatch: pytest.MonkeyPatch):
41
44
  monkeypatch.setattr(settings.review, "inline_tag", "#ai-inline")
42
45
  comment = InlineCommentSchema(file="a.py", line=42, message="missing check")
43
- body = comment.fallback_body_with_tag
44
- assert body.startswith("**a.py:42** — missing check")
45
- assert "#ai-inline" in body
46
+ assert comment.fallback_body.startswith("**a.py:42** — missing check")
46
47
 
47
48
 
48
49
  def test_dedup_key_differs_on_message_and_suggestion():
@@ -53,7 +54,7 @@ def test_dedup_key_differs_on_message_and_suggestion():
53
54
 
54
55
  def test_list_dedupe_removes_duplicates():
55
56
  c1 = InlineCommentSchema(file="a.py", line=1, message="msg one")
56
- c2 = InlineCommentSchema(file="a.py", line=1, message="msg one") # дубликат
57
+ c2 = InlineCommentSchema(file="a.py", line=1, message="msg one")
57
58
  c3 = InlineCommentSchema(file="a.py", line=2, message="msg two")
58
59
 
59
60
  comment_list = InlineCommentListSchema(root=[c1, c2, c3])
@@ -18,5 +18,4 @@ def test_body_with_tag_appends_tag(monkeypatch):
18
18
  body = comment.body_with_tag
19
19
  assert body.startswith("Review passed")
20
20
  assert body.endswith("\n\n#ai-summary")
21
- # убедимся, что перенос строки присутствует
22
21
  assert "\n\n#ai-summary" in body
@@ -4,13 +4,16 @@ from ai_review.services.review.summary.schema import SummaryCommentSchema
4
4
  from ai_review.services.review.summary.service import SummaryCommentService
5
5
 
6
6
 
7
- @pytest.mark.parametrize("raw, expected", [
8
- ("Some summary", "Some summary"),
9
- (" padded summary ", "padded summary"),
10
- ("", ""),
11
- (None, ""),
12
- ])
13
- def test_parse_model_output_normalizes_and_wraps(raw, expected):
7
+ @pytest.mark.parametrize(
8
+ "raw, expected",
9
+ [
10
+ ("Some summary", "Some summary"),
11
+ (" padded summary ", "padded summary"),
12
+ ("", ""),
13
+ (None, ""),
14
+ ]
15
+ )
16
+ def test_parse_model_output_normalizes_and_wraps(raw: str | None, expected: str):
14
17
  result = SummaryCommentService.parse_model_output(raw)
15
18
  assert isinstance(result, SummaryCommentSchema)
16
19
  assert result.text == expected
@@ -0,0 +1,126 @@
1
+ import pytest
2
+
3
+ from ai_review.services.review.service import ReviewService
4
+ from ai_review.tests.fixtures.artifacts import FakeArtifactsService
5
+ from ai_review.tests.fixtures.cost import FakeCostService
6
+ from ai_review.tests.fixtures.diff import FakeDiffService
7
+ from ai_review.tests.fixtures.git import FakeGitService
8
+ from ai_review.tests.fixtures.llm import FakeLLMClient
9
+ from ai_review.tests.fixtures.prompt import FakePromptService
10
+ from ai_review.tests.fixtures.review.inline import FakeInlineCommentService
11
+ from ai_review.tests.fixtures.review.summary import FakeSummaryCommentService
12
+ from ai_review.tests.fixtures.vcs import FakeVCSClient
13
+
14
+
15
+ @pytest.fixture
16
+ def review_service(
17
+ monkeypatch: pytest.MonkeyPatch,
18
+ fake_vcs_client: FakeVCSClient,
19
+ fake_llm_client: FakeLLMClient,
20
+ fake_git_service: FakeGitService,
21
+ fake_diff_service: FakeDiffService,
22
+ fake_cost_service: FakeCostService,
23
+ fake_prompt_service: FakePromptService,
24
+ fake_artifacts_service: FakeArtifactsService,
25
+ fake_inline_comment_service: FakeInlineCommentService,
26
+ fake_summary_comment_service: FakeSummaryCommentService,
27
+ ):
28
+ monkeypatch.setattr("ai_review.services.review.service.get_llm_client", lambda: fake_llm_client)
29
+ monkeypatch.setattr("ai_review.services.review.service.get_vcs_client", lambda: fake_vcs_client)
30
+ monkeypatch.setattr("ai_review.services.review.service.GitService", lambda: fake_git_service)
31
+ monkeypatch.setattr("ai_review.services.review.service.DiffService", lambda: fake_diff_service)
32
+ monkeypatch.setattr("ai_review.services.review.service.PromptService", lambda: fake_prompt_service)
33
+ monkeypatch.setattr("ai_review.services.review.service.InlineCommentService", lambda: fake_inline_comment_service)
34
+ monkeypatch.setattr("ai_review.services.review.service.SummaryCommentService", lambda: fake_summary_comment_service)
35
+ monkeypatch.setattr("ai_review.services.review.service.ArtifactsService", lambda: fake_artifacts_service)
36
+ monkeypatch.setattr("ai_review.services.review.service.CostService", lambda: fake_cost_service)
37
+ return ReviewService()
38
+
39
+
40
+ @pytest.mark.asyncio
41
+ async def test_run_inline_review_happy_path(
42
+ review_service: ReviewService,
43
+ fake_vcs_client: FakeVCSClient,
44
+ fake_llm_client: FakeLLMClient,
45
+ fake_prompt_service: FakePromptService,
46
+ fake_git_service: FakeGitService,
47
+ fake_diff_service: FakeDiffService,
48
+ fake_cost_service: FakeCostService,
49
+ fake_artifacts_service: FakeArtifactsService,
50
+ ):
51
+ """Should perform inline review for changed files and create inline comments via VCS."""
52
+ fake_git_service.responses["get_diff_for_file"] = "FAKE_DIFF"
53
+
54
+ await review_service.run_inline_review()
55
+
56
+ vcs_calls = [c[0] for c in fake_vcs_client.calls]
57
+ assert "get_review_info" in vcs_calls
58
+ assert "create_inline_comment" in vcs_calls
59
+
60
+ assert (
61
+ "get_diff_for_file",
62
+ {"base_sha": "A", "head_sha": "B", "file": "file.py", "unified": 3}
63
+ ) in fake_git_service.calls
64
+ assert any(call[0] == "render_file" for call in fake_diff_service.calls)
65
+
66
+ assert any(call[0] == "build_inline_request" for call in fake_prompt_service.calls)
67
+ assert any(call[0] == "chat" for call in fake_llm_client.calls)
68
+
69
+ assert len(fake_cost_service.reports) == 1
70
+ assert any(call[0] == "save_llm_interaction" for call in fake_artifacts_service.calls)
71
+
72
+
73
+ @pytest.mark.asyncio
74
+ async def test_run_inline_review_skips_when_no_diff(
75
+ review_service: ReviewService,
76
+ fake_vcs_client: FakeVCSClient,
77
+ fake_git_service: FakeGitService,
78
+ fake_llm_client: FakeLLMClient,
79
+ ):
80
+ """Should skip inline review when no diffs exist."""
81
+ fake_git_service.responses["get_diff_for_file"] = ""
82
+
83
+ await review_service.run_inline_review()
84
+
85
+ assert not any(call[0] == "chat" for call in fake_llm_client.calls)
86
+ assert not any(call[0] == "create_inline_comment" for call in fake_vcs_client.calls)
87
+
88
+
89
+ @pytest.mark.asyncio
90
+ async def test_run_context_review_happy_path(
91
+ review_service: ReviewService,
92
+ fake_vcs_client: FakeVCSClient,
93
+ fake_llm_client: FakeLLMClient,
94
+ fake_prompt_service: FakePromptService,
95
+ fake_diff_service: FakeDiffService,
96
+ ):
97
+ """Should perform context review and post inline comments based on rendered files."""
98
+ await review_service.run_context_review()
99
+
100
+ vcs_calls = [c[0] for c in fake_vcs_client.calls]
101
+ assert "get_review_info" in vcs_calls
102
+ assert "create_inline_comment" in vcs_calls
103
+
104
+ assert any(call[0] == "render_files" for call in fake_diff_service.calls)
105
+ assert any(call[0] == "build_context_request" for call in fake_prompt_service.calls)
106
+ assert any(call[0] == "chat" for call in fake_llm_client.calls)
107
+
108
+
109
+ @pytest.mark.asyncio
110
+ async def test_run_summary_review_happy_path(
111
+ review_service: ReviewService,
112
+ fake_vcs_client: FakeVCSClient,
113
+ fake_llm_client: FakeLLMClient,
114
+ fake_prompt_service: FakePromptService,
115
+ fake_diff_service: FakeDiffService,
116
+ ):
117
+ """Should perform summary review and post a single summary comment."""
118
+ await review_service.run_summary_review()
119
+
120
+ vcs_calls = [c[0] for c in fake_vcs_client.calls]
121
+ assert "get_review_info" in vcs_calls
122
+ assert "create_general_comment" in vcs_calls
123
+
124
+ assert any(call[0] == "render_files" for call in fake_diff_service.calls)
125
+ assert any(call[0] == "build_summary_request" for call in fake_prompt_service.calls)
126
+ assert any(call[0] == "chat" for call in fake_llm_client.calls)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xai-review
3
- Version: 0.20.0
3
+ Version: 0.21.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>
@@ -29,6 +29,7 @@ Requires-Dist: pydantic
29
29
  Requires-Dist: pydantic-settings
30
30
  Provides-Extra: test
31
31
  Requires-Dist: pytest; extra == "test"
32
+ Requires-Dist: pytest-asyncio; extra == "test"
32
33
  Dynamic: license-file
33
34
 
34
35
  # AI Review
@@ -192,6 +193,7 @@ Add a workflow like this (manual trigger from **Actions** tab):
192
193
 
193
194
  ```yaml
194
195
  name: AI Review
196
+
195
197
  on:
196
198
  workflow_dispatch:
197
199
  inputs:
@@ -207,7 +209,7 @@ jobs:
207
209
  runs-on: ubuntu-latest
208
210
  steps:
209
211
  - uses: actions/checkout@v4
210
- - uses: Nikita-Filonov/ai-review@v0.20.0
212
+ - uses: Nikita-Filonov/ai-review@v0.21.0
211
213
  with:
212
214
  review-command: ${{ inputs.review-command }}
213
215
  env:
@@ -272,6 +274,7 @@ ai-review:
272
274
  See these folders for reference templates and full configuration options:
273
275
 
274
276
  - [./docs/ci](./docs/ci) — CI/CD integration templates (GitHub Actions, GitLab CI)
277
+ - [./docs/hooks](./docs/hooks) — hook reference and lifecycle events
275
278
  - [./docs/configs](./docs/configs) — full configuration examples (`.yaml`, `.json`, `.env`)
276
279
  - [./docs/prompts](./docs/prompts) — prompt templates for Python/Go (light & strict modes)
277
280
 
@@ -83,53 +83,75 @@ ai_review/resources/pricing.yaml,sha256=jZHCGF78GTlZsXC_IGZT8JutKqpUyKikYXwtxIFE
83
83
  ai_review/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
84
  ai_review/services/artifacts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
85
  ai_review/services/artifacts/schema.py,sha256=AD3cqw_NfPhcourFX-bayjbwNAaVyhq4T_p59lPEhMg,262
86
- ai_review/services/artifacts/service.py,sha256=8RiQOJiJuZo_HSNpGv0jyMs9kgqjHX-9INax3aISGqU,1691
86
+ ai_review/services/artifacts/service.py,sha256=SDHwYm9I4pSPISyNWqHEOR-wTTEa5ThsIi458C9hBt8,1789
87
87
  ai_review/services/artifacts/tools.py,sha256=_ZVNwyhZDiGlPAtIgLzb7TF1zBGJy8Hq32H3xoGvOkQ,223
88
+ ai_review/services/artifacts/types.py,sha256=VPEDuQQciyQL8qcmgFuZxZUuuh2-xLwqwxmNZr62F3E,448
88
89
  ai_review/services/cost/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
90
  ai_review/services/cost/schema.py,sha256=K3uCIMMxGL8AaIPh4a-d0mT5uIJuk3f805DkP8o8DtY,1323
90
- ai_review/services/cost/service.py,sha256=rK-jw0lDszv_O13CRZAGK7R-fB-Y7xakX8aVb86zcEk,2103
91
+ ai_review/services/cost/service.py,sha256=-qbGePoL0oYnEC60Q5gQtd1IH8ucsOiF4349ueZl7Ts,2186
92
+ ai_review/services/cost/types.py,sha256=VyQiF5uH5T7wYlOqkvxlCOjHnjWRu4CMo8j26hQ2Alo,341
91
93
  ai_review/services/diff/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
94
  ai_review/services/diff/renderers.py,sha256=tEUml-uqsi5FNoU2NYjxehsZHU61dTPR2VnFi7QVzV4,5861
93
95
  ai_review/services/diff/schema.py,sha256=17GAQY1-ySwREJ1-NKNKgBcstMJ5Hb42FcFG2p7i6Rs,94
94
- ai_review/services/diff/service.py,sha256=FDuMw_y2QVQcfSbkVv3H2uGf1sIMeQ0_KHYCPhCU24g,3498
96
+ ai_review/services/diff/service.py,sha256=yRb4e0fZcgFTGkAZKm5q8Gw4rWxc3nyFtpBw7ahlnw8,3581
95
97
  ai_review/services/diff/tools.py,sha256=YHmH6Ult_rucCd563UhG0geMzqrPhqKFZKyug79xNuA,1963
98
+ ai_review/services/diff/types.py,sha256=uaX0hK_wfRG7Lxs0DnHgjDdkKGSQ1PS2-kZVCVjUeR8,700
96
99
  ai_review/services/git/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
100
  ai_review/services/git/service.py,sha256=_RMwgcllDoSLUzl84JML38fWkr7swnkUr6MJ46hSkWw,1282
98
101
  ai_review/services/git/types.py,sha256=QTOCTmR-Rt3sUjzZQHu2PGo_6un5gvNupifAa84wON4,413
102
+ ai_review/services/hook/__init__.py,sha256=HDukG_ZosgGg4dT5GCGIzqZX7llbyYUofKVFeG7YR2A,98
103
+ ai_review/services/hook/constants.py,sha256=uQJld5tJVUFk506h5RswTqLy-sIYxQfuQcUwflt7uas,875
104
+ ai_review/services/hook/service.py,sha256=InPoWBas6SPoy0KUyKJFg5xVk90jBlWdWtUTaX71G88,6364
105
+ ai_review/services/hook/types.py,sha256=zwOmnZVGlg53vUoC2rHNhpEiNsTpf0Tnb-s3SRPKFys,1405
99
106
  ai_review/services/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
- ai_review/services/llm/factory.py,sha256=fOyxSznQFdVuJQVilqtc0puWNoHCxZ5cdwtxRVfqTHw,725
101
- ai_review/services/llm/types.py,sha256=zMcMFQ7F9Zcgc9JqwdHCdlTpGOA0HuWyGgoySsUh4_o,345
107
+ ai_review/services/llm/factory.py,sha256=EHCRA5asqyE86Utn-EBYe_HbWRzck0S0UJG1gm5f2uo,741
108
+ ai_review/services/llm/types.py,sha256=OvbJWYRDThBgLhn9TWU0mliuanOW01CS3e8ermtuS-s,353
102
109
  ai_review/services/llm/claude/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
103
- ai_review/services/llm/claude/client.py,sha256=w6CvfDYEAKyi_r88M-z_jCvMIJF3SjhQjV1EHMvNh24,1066
110
+ ai_review/services/llm/claude/client.py,sha256=JJD0FWiXjCCpO7NW3vVoBMXhTQ9VBA4Q93QqkeQqON0,1082
104
111
  ai_review/services/llm/gemini/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
105
- ai_review/services/llm/gemini/client.py,sha256=7FAOlTi8rV6b8g8aWcg-0LIP77AtbAoPtPWiGs0kQN0,1266
112
+ ai_review/services/llm/gemini/client.py,sha256=TR4HshVxtDV8_luQKCM3aFNH9tjAjpzNeFBg-oxdsfA,1282
106
113
  ai_review/services/llm/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
107
- ai_review/services/llm/openai/client.py,sha256=WhMXNfH_G1NTlFkdRK5sgYvrCIE5ZQNfPhdYx49IXFk,1143
114
+ ai_review/services/llm/openai/client.py,sha256=c3DWwLnwTheERdSGnMiQIbg5SaICouUAGClcQZSh1fE,1159
108
115
  ai_review/services/prompt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
109
116
  ai_review/services/prompt/adapter.py,sha256=YgD8Cf73pwPOnKxq9QSlbYr8wySfDxJE_3fIrRWfkUo,984
110
117
  ai_review/services/prompt/schema.py,sha256=x-1KRFOK3HdZozXJNPh-Bp_JrZ2AIdAPD44lhWG5g9k,1863
111
- ai_review/services/prompt/service.py,sha256=D1PR2HC4cgrEND6mAhU5EPRAtp4mgEkOLEyD51WBc0g,2129
118
+ ai_review/services/prompt/service.py,sha256=58OJ6nIPSgXQyAqUXkWAXYAbNz7vMRemtllvD7bvQv0,2218
112
119
  ai_review/services/prompt/tools.py,sha256=-gS74mVM3OZPKWQkY9_QfStkfxaUfssDbJ3Bdi4AQ74,636
120
+ ai_review/services/prompt/types.py,sha256=uVcvW8ZuwmM02MjCmw6Rg-IW5pIT3MeEYl0Vl-jzV4M,913
113
121
  ai_review/services/review/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
114
- ai_review/services/review/service.py,sha256=sYJQ02bKKQ926FpSmsHeeKfmRM1oMIqwjOFdKY88mws,8532
122
+ ai_review/services/review/service.py,sha256=tDeYHet2hNOn057VSYJVENbRrGLVfHtlf1q3Cv5VbSo,6947
123
+ ai_review/services/review/gateway/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
+ ai_review/services/review/gateway/comment.py,sha256=QbswfdDDOWEhC1K6IQ4plPzgeqEX4X9JQe1NcZVazk0,2794
125
+ ai_review/services/review/gateway/llm.py,sha256=kThcCJdWxsFARWA5bxcZx6EOl54dh2UGknNh-f6Sl_M,1431
115
126
  ai_review/services/review/inline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
- ai_review/services/review/inline/schema.py,sha256=ry8sJdTgusQvFW51neRiapzgzVGwswwJzdYhaV3hbT0,1545
117
- ai_review/services/review/inline/service.py,sha256=34I3eK2Ra1l3aUvSYfO-Wx9aPfcgCWqTgjdoGHlNO08,2135
127
+ ai_review/services/review/inline/schema.py,sha256=JYhuDiA3xxr3ItSAiJdaEy8loFVNiJEsZGZlFaNNhoU,1504
128
+ ai_review/services/review/inline/service.py,sha256=EdNz-VwoRwrj-HDYqVIZIHq0nEK7SqWtt7z_VHHVVV0,2245
129
+ ai_review/services/review/inline/types.py,sha256=NF5GNcpYkvYnXITv2FWGhrirkwpKjcp1TooDpJ1gXec,334
118
130
  ai_review/services/review/policy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
131
  ai_review/services/review/policy/service.py,sha256=yGDePLxAEF3N1Pkh47jGVd-4dGEESyxDXIXxV7KQfuY,2027
120
132
  ai_review/services/review/summary/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
121
133
  ai_review/services/review/summary/schema.py,sha256=GipVNWrEKtgZPkytNSrXwzvX9Zq8Pv2wxjXhfJq4D3g,364
122
- ai_review/services/review/summary/service.py,sha256=GB7-l4UyjZfUe6yP_8Q-SD1_uDKHM0W-CZJVMiEL8S0,449
134
+ ai_review/services/review/summary/service.py,sha256=F4diIESc0y7YSiUKbInHWiSOW5tW_eQ0rpf78wKxLAo,562
135
+ ai_review/services/review/summary/types.py,sha256=iDsucvx9xJZ5Xb5FN70da3bub3YDtt4vpQeVEK532E8,235
123
136
  ai_review/services/vcs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
- ai_review/services/vcs/factory.py,sha256=PSjBUClHRw0_38XmBYZBGIs281NMHX-vVzBwhnEn9uo,590
125
- ai_review/services/vcs/types.py,sha256=ErKrALHyn7qdIg1_wZ5Af-A60HUotKHunZhIvC2GRKM,1990
137
+ ai_review/services/vcs/factory.py,sha256=yoZ5qCI_vq2bG-9lbunf70ojcxdoj8ms-4vY4c5BcJE,606
138
+ ai_review/services/vcs/types.py,sha256=S49LhAGHVAd_0QwZUr4JMhfc6DR-HikHR6-T_ETlTus,1998
126
139
  ai_review/services/vcs/github/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
127
- ai_review/services/vcs/github/client.py,sha256=Pv18vGSjcXoapTsiLqKoEUZlv2ua7pp01IBRgxpAmM8,6301
140
+ ai_review/services/vcs/github/client.py,sha256=v6NV97xi_rtRQQi8atRdSrXKhSOQ7CeRHK7YjoyjU6Q,6353
128
141
  ai_review/services/vcs/gitlab/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
129
- ai_review/services/vcs/gitlab/client.py,sha256=y1X99RVgA1KRcr_dW8gNaHuoSqxK1-30hzeIZA4HkhA,6678
142
+ ai_review/services/vcs/gitlab/client.py,sha256=LK95m-uFSxhDEVU-cBGct61NTKjul-ieLZPxfBLIPts,6730
130
143
  ai_review/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
131
144
  ai_review/tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
132
- ai_review/tests/fixtures/git.py,sha256=CwMYAQNVEeHp8OCnFWh2fBKkBkyorWJtN2eDFqpYRbQ,1038
145
+ ai_review/tests/fixtures/artifacts.py,sha256=V5FvUnC9OAo0n-paxxJP5OxAgLz1Zz3OZ8zZvqu_01w,1462
146
+ ai_review/tests/fixtures/cost.py,sha256=A6Ja0CtQ-k6pR2-B5LRE8EzkqPL34xHGXYtaILjhYvw,1612
147
+ ai_review/tests/fixtures/diff.py,sha256=rOLFR-giYJlE2qUYTOT9BxyJhQ-fbXDdYCw3zed4-9M,1471
148
+ ai_review/tests/fixtures/git.py,sha256=zDNNLZDoVC7r4LuF1N1MUgzhcAl2nhDdFC9olpR_PjQ,1441
149
+ ai_review/tests/fixtures/llm.py,sha256=Wztlk0C2va_2z378dPwKoaQKFHSh5k7HWJkn-LY-k-8,886
150
+ ai_review/tests/fixtures/prompt.py,sha256=dk0zretwVGTc0Pzt73zKf8Q36Vo7F_3s4O5NfTjfVqE,1920
151
+ ai_review/tests/fixtures/vcs.py,sha256=Nz76T82DT7V0nVBWrchTKKHAy27KJKQGToNHfpG_bTM,1751
152
+ ai_review/tests/fixtures/review/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
153
+ ai_review/tests/fixtures/review/inline.py,sha256=k4IW6oy5JHMo9Kv0H97DLlFyrGsEInaozObjLARzdPg,1041
154
+ ai_review/tests/fixtures/review/summary.py,sha256=Hkt8mq1ZmqMH5_mELrS1x0wtCoNPbBjOEQ9yIsMbRts,691
133
155
  ai_review/tests/suites/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
134
156
  ai_review/tests/suites/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
135
157
  ai_review/tests/suites/clients/claude/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -161,25 +183,28 @@ ai_review/tests/suites/services/cost/test_schema.py,sha256=AI3Wg1sR6nzLpkEqJGDu6
161
183
  ai_review/tests/suites/services/cost/test_service.py,sha256=fMW4Tg6BRMXKcqOO7MmSqJc1mpuguvFSl0GjS93m7u8,3253
162
184
  ai_review/tests/suites/services/diff/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
163
185
  ai_review/tests/suites/services/diff/test_renderers.py,sha256=IKOpsGedONNW8ZfYTAk0Vq0hfFi7L6TpWs8vVVQroj0,6273
164
- ai_review/tests/suites/services/diff/test_service.py,sha256=iFkGX9Vj2X44JU3eFsoHsg9o9353eKX-QCv_J9KxfzU,3162
165
- ai_review/tests/suites/services/diff/test_tools.py,sha256=HBQ3eCn-kLeb_k5iTgyr09x0VwLYSegSbxm0Qk9ZrCc,3543
186
+ ai_review/tests/suites/services/diff/test_service.py,sha256=xQbgbVHO2cdhv738rvxuZb1knnbCmjuLAvaAeMXnFkA,3186
187
+ ai_review/tests/suites/services/diff/test_tools.py,sha256=cn1c5swLx-0rUZUFPtazn8kbIdNg63IgnTKVjHtbMSM,3615
188
+ ai_review/tests/suites/services/hook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
189
+ ai_review/tests/suites/services/hook/test_service.py,sha256=GM_AiNVGP2Pgp-3BwGOAIfA8lLXl6ah28ey77KZz_C4,2750
166
190
  ai_review/tests/suites/services/prompt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
167
191
  ai_review/tests/suites/services/prompt/test_adapter.py,sha256=9KZOFQmZUs3l_cW7Q5LIMPs4i4J-gOCQ6VrlDPR0ImU,2156
168
192
  ai_review/tests/suites/services/prompt/test_schema.py,sha256=DQyv5gUJ2VkxaD9wiKLS18ECopvvdKvF4sg3MTGcKs8,2547
169
193
  ai_review/tests/suites/services/prompt/test_service.py,sha256=WXYKwDHMmWD6ew1awiEzmoxEJtQBqxvOgiyK8Ii9Mhw,6755
170
194
  ai_review/tests/suites/services/prompt/test_tools.py,sha256=_yNZoBATvPU5enWNIopbjY8lVVjfaB_46kNIKODhCW4,1981
171
195
  ai_review/tests/suites/services/review/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
196
+ ai_review/tests/suites/services/review/test_service.py,sha256=Tcl-p9wtnIp4TjbE3bvoNesoDlArtWorcwpPEHKou6c,5609
172
197
  ai_review/tests/suites/services/review/inline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
173
- ai_review/tests/suites/services/review/inline/test_schema.py,sha256=tIz-1UA_GgwcdsyUqgrodiiVVmd_jhoOVmtEwzRVWiY,2474
198
+ ai_review/tests/suites/services/review/inline/test_schema.py,sha256=C5H6zqEHaf9OO4VHX0atYAt21CmOcCARaijSo-FUqj8,2368
174
199
  ai_review/tests/suites/services/review/inline/test_service.py,sha256=x8d-dhw_uEXtGVLDVv4xVodmscrXIfDkoAnG4UapdlQ,3815
175
200
  ai_review/tests/suites/services/review/policy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
176
201
  ai_review/tests/suites/services/review/policy/test_service.py,sha256=kRWT550OjWYQ7ZfsihBRc-tx-NMkhlynEsqur55RK0M,3687
177
202
  ai_review/tests/suites/services/review/summary/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
178
- ai_review/tests/suites/services/review/summary/test_schema.py,sha256=xSoydvABZldHaVDa0OBFvYrj8wMuZqUDN3MO-XdvxOI,819
179
- ai_review/tests/suites/services/review/summary/test_service.py,sha256=8UMvi_NL9frm280vD6Q1NCDrdI7K8YbXzoViIus-I2g,541
180
- xai_review-0.20.0.dist-info/licenses/LICENSE,sha256=p-v8m7Kmz4KKc7PcvsGiGEmCw9AiSXY4_ylOPy_u--Y,11343
181
- xai_review-0.20.0.dist-info/METADATA,sha256=E03b5bb0vnJyUwY-9Jy5hRTXYIeUeWwDl2NMg-ir7_w,10753
182
- xai_review-0.20.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
183
- xai_review-0.20.0.dist-info/entry_points.txt,sha256=JyC5URanMi5io5P_PXQf7H_I1OGIpk5cZQhaPQ0g4Zs,53
184
- xai_review-0.20.0.dist-info/top_level.txt,sha256=sTsZbfzLoqvRZKdKa-BcxWvjlHdrpbeJ6DrGY0EuR0E,10
185
- xai_review-0.20.0.dist-info/RECORD,,
203
+ ai_review/tests/suites/services/review/summary/test_schema.py,sha256=HUbSDbQzBp-iTsGLs7hJfu-sz6sq9xLO0woGmZPWyx0,735
204
+ ai_review/tests/suites/services/review/summary/test_service.py,sha256=ibiYOWQMZuQKRutIT_EKGq7DEPQvp62YhscNHeSWFVQ,588
205
+ xai_review-0.21.0.dist-info/licenses/LICENSE,sha256=p-v8m7Kmz4KKc7PcvsGiGEmCw9AiSXY4_ylOPy_u--Y,11343
206
+ xai_review-0.21.0.dist-info/METADATA,sha256=N7BrGJd6tOk0p2rE7GZdTZXwrpBalyGY_iGdvUaatbM,10872
207
+ xai_review-0.21.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
208
+ xai_review-0.21.0.dist-info/entry_points.txt,sha256=JyC5URanMi5io5P_PXQf7H_I1OGIpk5cZQhaPQ0g4Zs,53
209
+ xai_review-0.21.0.dist-info/top_level.txt,sha256=sTsZbfzLoqvRZKdKa-BcxWvjlHdrpbeJ6DrGY0EuR0E,10
210
+ xai_review-0.21.0.dist-info/RECORD,,