xai-review 0.19.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.
- ai_review/services/artifacts/service.py +2 -1
- ai_review/services/artifacts/types.py +20 -0
- ai_review/services/cost/service.py +2 -1
- ai_review/services/cost/types.py +12 -0
- ai_review/services/diff/service.py +2 -1
- ai_review/services/diff/types.py +28 -0
- ai_review/services/hook/__init__.py +5 -0
- ai_review/services/hook/constants.py +24 -0
- ai_review/services/hook/service.py +162 -0
- ai_review/services/hook/types.py +28 -0
- ai_review/services/llm/claude/client.py +2 -2
- ai_review/services/llm/factory.py +2 -2
- ai_review/services/llm/gemini/client.py +2 -2
- ai_review/services/llm/openai/client.py +2 -2
- ai_review/services/llm/types.py +1 -1
- ai_review/services/prompt/service.py +2 -1
- ai_review/services/prompt/types.py +27 -0
- ai_review/services/review/gateway/__init__.py +0 -0
- ai_review/services/review/gateway/comment.py +65 -0
- ai_review/services/review/gateway/llm.py +40 -0
- ai_review/services/review/inline/schema.py +2 -2
- ai_review/services/review/inline/service.py +2 -1
- ai_review/services/review/inline/types.py +11 -0
- ai_review/services/review/service.py +23 -74
- ai_review/services/review/summary/service.py +2 -1
- ai_review/services/review/summary/types.py +8 -0
- ai_review/services/vcs/factory.py +2 -2
- ai_review/services/vcs/github/client.py +4 -2
- ai_review/services/vcs/gitlab/client.py +4 -2
- ai_review/services/vcs/types.py +1 -1
- ai_review/tests/fixtures/artifacts.py +51 -0
- ai_review/tests/fixtures/cost.py +48 -0
- ai_review/tests/fixtures/diff.py +46 -0
- ai_review/tests/fixtures/git.py +11 -5
- ai_review/tests/fixtures/llm.py +26 -0
- ai_review/tests/fixtures/prompt.py +43 -0
- ai_review/tests/fixtures/review/__init__.py +0 -0
- ai_review/tests/fixtures/review/inline.py +25 -0
- ai_review/tests/fixtures/review/summary.py +19 -0
- ai_review/tests/fixtures/vcs.py +49 -0
- ai_review/tests/suites/services/diff/test_service.py +3 -3
- ai_review/tests/suites/services/diff/test_tools.py +9 -9
- ai_review/tests/suites/services/hook/__init__.py +0 -0
- ai_review/tests/suites/services/hook/test_service.py +93 -0
- ai_review/tests/suites/services/review/inline/test_schema.py +10 -9
- ai_review/tests/suites/services/review/summary/test_schema.py +0 -1
- ai_review/tests/suites/services/review/summary/test_service.py +10 -7
- ai_review/tests/suites/services/review/test_service.py +126 -0
- {xai_review-0.19.0.dist-info → xai_review-0.21.0.dist-info}/METADATA +10 -7
- {xai_review-0.19.0.dist-info → xai_review-0.21.0.dist-info}/RECORD +54 -29
- {xai_review-0.19.0.dist-info → xai_review-0.21.0.dist-info}/WHEEL +0 -0
- {xai_review-0.19.0.dist-info → xai_review-0.21.0.dist-info}/entry_points.txt +0 -0
- {xai_review-0.19.0.dist-info → xai_review-0.21.0.dist-info}/licenses/LICENSE +0 -0
- {xai_review-0.19.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,
|
|
59
|
-
|
|
60
|
-
monkeypatch.setattr(tools, "GitService", lambda:
|
|
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
|
-
|
|
74
|
-
monkeypatch.setattr(tools, "GitService", lambda:
|
|
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
|
-
|
|
86
|
-
monkeypatch.setattr(tools, "GitService", lambda:
|
|
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
|
|
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
|
-
|
|
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])
|
|
@@ -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(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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.
|
|
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
|
|
@@ -82,11 +83,11 @@ final decision to human reviewers.
|
|
|
82
83
|
Curious how **AI Review** works in practice? Here are three real Pull Requests reviewed entirely by the tool — one per
|
|
83
84
|
mode:
|
|
84
85
|
|
|
85
|
-
| Mode | Description |
|
|
86
|
-
|
|
87
|
-
| 🧩 Inline | Adds line-by-line comments directly in the diff | [
|
|
88
|
-
| 🧠 Context | Performs broader analysis across multiple files | [
|
|
89
|
-
| 📄 Summary | Posts a concise summary review with key highlights | [
|
|
86
|
+
| Mode | Description | 🐙 GitHub | 🦊 GitLab |
|
|
87
|
+
|------------|----------------------------------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------------|
|
|
88
|
+
| 🧩 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) |
|
|
89
|
+
| 🧠 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) |
|
|
90
|
+
| 📄 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) |
|
|
90
91
|
|
|
91
92
|
👉 Each review was generated automatically via GitHub Actions using the corresponding mode:
|
|
92
93
|
|
|
@@ -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.
|
|
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=
|
|
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
|
|
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=
|
|
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=
|
|
101
|
-
ai_review/services/llm/types.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
117
|
-
ai_review/services/review/inline/service.py,sha256=
|
|
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=
|
|
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=
|
|
125
|
-
ai_review/services/vcs/types.py,sha256=
|
|
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=
|
|
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=
|
|
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/
|
|
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=
|
|
165
|
-
ai_review/tests/suites/services/diff/test_tools.py,sha256=
|
|
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=
|
|
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=
|
|
179
|
-
ai_review/tests/suites/services/review/summary/test_service.py,sha256=
|
|
180
|
-
xai_review-0.
|
|
181
|
-
xai_review-0.
|
|
182
|
-
xai_review-0.
|
|
183
|
-
xai_review-0.
|
|
184
|
-
xai_review-0.
|
|
185
|
-
xai_review-0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|