xai-review 0.11.0__py3-none-any.whl → 0.13.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/clients/github/__init__.py +0 -0
- ai_review/clients/github/client.py +31 -0
- ai_review/clients/github/pr/__init__.py +0 -0
- ai_review/clients/github/pr/client.py +101 -0
- ai_review/clients/github/pr/schema/__init__.py +0 -0
- ai_review/clients/github/pr/schema/comments.py +33 -0
- ai_review/clients/github/pr/schema/files.py +12 -0
- ai_review/clients/github/pr/schema/pull_request.py +30 -0
- ai_review/clients/github/pr/schema/reviews.py +13 -0
- ai_review/libs/config/github.py +13 -0
- ai_review/libs/config/prompt.py +1 -0
- ai_review/libs/config/vcs.py +8 -1
- ai_review/services/prompt/service.py +19 -18
- ai_review/services/prompt/tools.py +24 -0
- ai_review/services/vcs/factory.py +3 -0
- ai_review/services/vcs/github/__init__.py +0 -0
- ai_review/services/vcs/github/client.py +147 -0
- ai_review/tests/suites/clients/github/__init__.py +0 -0
- ai_review/tests/suites/clients/github/test_client.py +36 -0
- ai_review/tests/suites/services/prompt/test_service.py +33 -0
- ai_review/tests/suites/services/prompt/test_tools.py +72 -0
- {xai_review-0.11.0.dist-info → xai_review-0.13.0.dist-info}/METADATA +1 -1
- {xai_review-0.11.0.dist-info → xai_review-0.13.0.dist-info}/RECORD +27 -11
- {xai_review-0.11.0.dist-info → xai_review-0.13.0.dist-info}/WHEEL +0 -0
- {xai_review-0.11.0.dist-info → xai_review-0.13.0.dist-info}/entry_points.txt +0 -0
- {xai_review-0.11.0.dist-info → xai_review-0.13.0.dist-info}/licenses/LICENSE +0 -0
- {xai_review-0.11.0.dist-info → xai_review-0.13.0.dist-info}/top_level.txt +0 -0
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from httpx import AsyncClient, AsyncHTTPTransport
|
|
2
|
+
|
|
3
|
+
from ai_review.clients.github.pr.client import GitHubPullRequestsHTTPClient
|
|
4
|
+
from ai_review.config import settings
|
|
5
|
+
from ai_review.libs.http.event_hooks.logger import LoggerEventHook
|
|
6
|
+
from ai_review.libs.http.transports.retry import RetryTransport
|
|
7
|
+
from ai_review.libs.logger import get_logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GitHubHTTPClient:
|
|
11
|
+
def __init__(self, client: AsyncClient):
|
|
12
|
+
self.pr = GitHubPullRequestsHTTPClient(client)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_github_http_client() -> GitHubHTTPClient:
|
|
16
|
+
logger = get_logger("GITHUB_HTTP_CLIENT")
|
|
17
|
+
logger_event_hook = LoggerEventHook(logger=logger)
|
|
18
|
+
retry_transport = RetryTransport(transport=AsyncHTTPTransport())
|
|
19
|
+
|
|
20
|
+
client = AsyncClient(
|
|
21
|
+
timeout=settings.llm.http_client.timeout,
|
|
22
|
+
headers={"Authorization": f"Bearer {settings.vcs.http_client.api_token_value}"},
|
|
23
|
+
base_url=settings.vcs.http_client.api_url_value,
|
|
24
|
+
transport=retry_transport,
|
|
25
|
+
event_hooks={
|
|
26
|
+
'request': [logger_event_hook.request],
|
|
27
|
+
'response': [logger_event_hook.response]
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return GitHubHTTPClient(client=client)
|
|
File without changes
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from httpx import Response
|
|
2
|
+
|
|
3
|
+
from ai_review.clients.github.pr.schema.comments import (
|
|
4
|
+
GitHubGetPRCommentsResponseSchema,
|
|
5
|
+
GitHubCreateIssueCommentRequestSchema,
|
|
6
|
+
GitHubCreateIssueCommentResponseSchema,
|
|
7
|
+
GitHubCreateReviewCommentRequestSchema,
|
|
8
|
+
GitHubCreateReviewCommentResponseSchema
|
|
9
|
+
)
|
|
10
|
+
from ai_review.clients.github.pr.schema.files import GitHubGetPRFilesResponseSchema
|
|
11
|
+
from ai_review.clients.github.pr.schema.pull_request import GitHubGetPRResponseSchema
|
|
12
|
+
from ai_review.clients.github.pr.schema.reviews import GitHubGetPRReviewsResponseSchema
|
|
13
|
+
from ai_review.libs.http.client import HTTPClient
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GitHubPullRequestsHTTPClient(HTTPClient):
|
|
17
|
+
async def get_pull_request_api(self, owner: str, repo: str, pull_number: str) -> Response:
|
|
18
|
+
return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}")
|
|
19
|
+
|
|
20
|
+
async def get_files_api(self, owner: str, repo: str, pull_number: str) -> Response:
|
|
21
|
+
return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}/files")
|
|
22
|
+
|
|
23
|
+
async def get_issue_comments_api(self, owner: str, repo: str, issue_number: str) -> Response:
|
|
24
|
+
return await self.get(f"/repos/{owner}/{repo}/issues/{issue_number}/comments")
|
|
25
|
+
|
|
26
|
+
async def get_review_comments_api(self, owner: str, repo: str, pull_number: str) -> Response:
|
|
27
|
+
return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}/comments")
|
|
28
|
+
|
|
29
|
+
async def create_review_comment_api(
|
|
30
|
+
self,
|
|
31
|
+
owner: str,
|
|
32
|
+
repo: str,
|
|
33
|
+
pull_number: str,
|
|
34
|
+
request: GitHubCreateReviewCommentRequestSchema,
|
|
35
|
+
) -> Response:
|
|
36
|
+
return await self.post(
|
|
37
|
+
f"/repos/{owner}/{repo}/pulls/{pull_number}/comments",
|
|
38
|
+
json=request.model_dump(),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
async def create_issue_comment_api(
|
|
42
|
+
self,
|
|
43
|
+
owner: str,
|
|
44
|
+
repo: str,
|
|
45
|
+
issue_number: str,
|
|
46
|
+
request: GitHubCreateIssueCommentRequestSchema,
|
|
47
|
+
) -> Response:
|
|
48
|
+
return await self.post(
|
|
49
|
+
f"/repos/{owner}/{repo}/issues/{issue_number}/comments",
|
|
50
|
+
json=request.model_dump(),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
async def get_reviews_api(self, owner: str, repo: str, pull_number: str) -> Response:
|
|
54
|
+
return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}/reviews")
|
|
55
|
+
|
|
56
|
+
async def get_pull_request(self, owner: str, repo: str, pull_number: str) -> GitHubGetPRResponseSchema:
|
|
57
|
+
response = await self.get_pull_request_api(owner, repo, pull_number)
|
|
58
|
+
return GitHubGetPRResponseSchema.model_validate_json(response.text)
|
|
59
|
+
|
|
60
|
+
async def get_files(self, owner: str, repo: str, pull_number: str) -> GitHubGetPRFilesResponseSchema:
|
|
61
|
+
response = await self.get_files_api(owner, repo, pull_number)
|
|
62
|
+
return GitHubGetPRFilesResponseSchema.model_validate_json(response.text)
|
|
63
|
+
|
|
64
|
+
async def get_issue_comments(self, owner: str, repo: str, issue_number: str) -> GitHubGetPRCommentsResponseSchema:
|
|
65
|
+
response = await self.get_issue_comments_api(owner, repo, issue_number)
|
|
66
|
+
return GitHubGetPRCommentsResponseSchema.model_validate_json(response.text)
|
|
67
|
+
|
|
68
|
+
async def get_review_comments(self, owner: str, repo: str, pull_number: str) -> GitHubGetPRCommentsResponseSchema:
|
|
69
|
+
response = await self.get_review_comments_api(owner, repo, pull_number)
|
|
70
|
+
return GitHubGetPRCommentsResponseSchema.model_validate_json(response.text)
|
|
71
|
+
|
|
72
|
+
async def get_reviews(self, owner: str, repo: str, pull_number: str) -> GitHubGetPRReviewsResponseSchema:
|
|
73
|
+
response = await self.get_reviews_api(owner, repo, pull_number)
|
|
74
|
+
return GitHubGetPRReviewsResponseSchema.model_validate_json(response.text)
|
|
75
|
+
|
|
76
|
+
async def create_review_comment(
|
|
77
|
+
self,
|
|
78
|
+
owner: str,
|
|
79
|
+
repo: str,
|
|
80
|
+
pull_number: str,
|
|
81
|
+
body: str,
|
|
82
|
+
commit_id: str,
|
|
83
|
+
path: str,
|
|
84
|
+
line: int,
|
|
85
|
+
) -> GitHubCreateReviewCommentResponseSchema:
|
|
86
|
+
request = GitHubCreateReviewCommentRequestSchema(
|
|
87
|
+
body=body, commit_id=commit_id, path=path, line=line
|
|
88
|
+
)
|
|
89
|
+
response = await self.create_review_comment_api(owner, repo, pull_number, request)
|
|
90
|
+
return GitHubCreateReviewCommentResponseSchema.model_validate_json(response.text)
|
|
91
|
+
|
|
92
|
+
async def create_issue_comment(
|
|
93
|
+
self,
|
|
94
|
+
owner: str,
|
|
95
|
+
repo: str,
|
|
96
|
+
issue_number: str,
|
|
97
|
+
body: str,
|
|
98
|
+
) -> GitHubCreateIssueCommentResponseSchema:
|
|
99
|
+
request = GitHubCreateIssueCommentRequestSchema(body=body)
|
|
100
|
+
response = await self.create_issue_comment_api(owner, repo, issue_number, request)
|
|
101
|
+
return GitHubCreateIssueCommentResponseSchema.model_validate_json(response.text)
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from pydantic import BaseModel, RootModel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class GitHubPRCommentSchema(BaseModel):
|
|
5
|
+
id: int
|
|
6
|
+
body: str
|
|
7
|
+
path: str | None = None
|
|
8
|
+
line: int | None = None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GitHubGetPRCommentsResponseSchema(RootModel[list[GitHubPRCommentSchema]]):
|
|
12
|
+
root: list[GitHubPRCommentSchema]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GitHubCreateIssueCommentRequestSchema(BaseModel):
|
|
16
|
+
body: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class GitHubCreateIssueCommentResponseSchema(BaseModel):
|
|
20
|
+
id: int
|
|
21
|
+
body: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GitHubCreateReviewCommentRequestSchema(BaseModel):
|
|
25
|
+
body: str
|
|
26
|
+
path: str
|
|
27
|
+
line: int
|
|
28
|
+
commit_id: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class GitHubCreateReviewCommentResponseSchema(BaseModel):
|
|
32
|
+
id: int
|
|
33
|
+
body: str
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from pydantic import BaseModel, RootModel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class GitHubPRFileSchema(BaseModel):
|
|
5
|
+
sha: str
|
|
6
|
+
patch: str | None = None
|
|
7
|
+
status: str
|
|
8
|
+
filename: str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GitHubGetPRFilesResponseSchema(RootModel[list[GitHubPRFileSchema]]):
|
|
12
|
+
root: list[GitHubPRFileSchema]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class GitHubUserSchema(BaseModel):
|
|
5
|
+
id: int
|
|
6
|
+
login: str
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GitHubLabelSchema(BaseModel):
|
|
10
|
+
id: int
|
|
11
|
+
name: str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GitHubBranchSchema(BaseModel):
|
|
15
|
+
ref: str
|
|
16
|
+
sha: str
|
|
17
|
+
label: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GitHubGetPRResponseSchema(BaseModel):
|
|
21
|
+
id: int
|
|
22
|
+
number: int
|
|
23
|
+
title: str
|
|
24
|
+
body: str | None = None
|
|
25
|
+
user: GitHubUserSchema
|
|
26
|
+
labels: list[GitHubLabelSchema]
|
|
27
|
+
assignees: list[GitHubUserSchema] = []
|
|
28
|
+
requested_reviewers: list[GitHubUserSchema] = []
|
|
29
|
+
base: GitHubBranchSchema
|
|
30
|
+
head: GitHubBranchSchema
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, RootModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GitHubPRReviewSchema(BaseModel):
|
|
7
|
+
id: int
|
|
8
|
+
body: Optional[str] = None
|
|
9
|
+
state: str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GitHubGetPRReviewsResponseSchema(RootModel[list[GitHubPRReviewSchema]]):
|
|
13
|
+
root: list[GitHubPRReviewSchema]
|
ai_review/libs/config/prompt.py
CHANGED
|
@@ -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
|
ai_review/libs/config/vcs.py
CHANGED
|
@@ -2,6 +2,7 @@ from typing import Annotated, Literal
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
|
+
from ai_review.libs.config.github import GitHubPipelineConfig, GitHubHTTPClientConfig
|
|
5
6
|
from ai_review.libs.config.gitlab import GitLabPipelineConfig, GitLabHTTPClientConfig
|
|
6
7
|
from ai_review.libs.constants.vcs_provider import VCSProvider
|
|
7
8
|
|
|
@@ -16,4 +17,10 @@ class GitLabVCSConfig(VCSConfigBase):
|
|
|
16
17
|
http_client: GitLabHTTPClientConfig
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
class GitHubVCSConfig(VCSConfigBase):
|
|
21
|
+
provider: Literal[VCSProvider.GITHUB]
|
|
22
|
+
pipeline: GitHubPipelineConfig
|
|
23
|
+
http_client: GitHubHTTPClientConfig
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
VCSConfig = Annotated[GitLabVCSConfig | GitHubVCSConfig, Field(discriminator="provider")]
|
|
@@ -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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
14
|
-
inline_prompts = context.apply_format(inline_prompts)
|
|
20
|
+
prompt = cls.prepare_prompt(settings.prompt.load_inline(), context)
|
|
15
21
|
return (
|
|
16
|
-
f"{
|
|
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"{
|
|
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"{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
from ai_review.config import settings
|
|
2
2
|
from ai_review.libs.constants.vcs_provider import VCSProvider
|
|
3
|
+
from ai_review.services.vcs.github.client import GitHubVCSClient
|
|
3
4
|
from ai_review.services.vcs.gitlab.client import GitLabVCSClient
|
|
4
5
|
from ai_review.services.vcs.types import VCSClient
|
|
5
6
|
|
|
@@ -8,5 +9,7 @@ def get_vcs_client() -> VCSClient:
|
|
|
8
9
|
match settings.vcs.provider:
|
|
9
10
|
case VCSProvider.GITLAB:
|
|
10
11
|
return GitLabVCSClient()
|
|
12
|
+
case VCSProvider.GITHUB:
|
|
13
|
+
return GitHubVCSClient()
|
|
11
14
|
case _:
|
|
12
15
|
raise ValueError(f"Unsupported provider: {settings.llm.provider}")
|
|
File without changes
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from ai_review.clients.github.client import get_github_http_client
|
|
2
|
+
from ai_review.config import settings
|
|
3
|
+
from ai_review.libs.logger import get_logger
|
|
4
|
+
from ai_review.services.vcs.types import (
|
|
5
|
+
VCSClient,
|
|
6
|
+
MRNoteSchema,
|
|
7
|
+
MRUserSchema,
|
|
8
|
+
MRInfoSchema,
|
|
9
|
+
MRCommentSchema,
|
|
10
|
+
MRDiscussionSchema,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
logger = get_logger("GITHUB_VCS_CLIENT")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GitHubVCSClient(VCSClient):
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.http_client = get_github_http_client()
|
|
19
|
+
self.owner = settings.vcs.pipeline.owner
|
|
20
|
+
self.repo = settings.vcs.pipeline.repo
|
|
21
|
+
self.pull_number = settings.vcs.pipeline.pull_number
|
|
22
|
+
|
|
23
|
+
async def get_mr_info(self) -> MRInfoSchema:
|
|
24
|
+
try:
|
|
25
|
+
pr = await self.http_client.pr.get_pull_request(
|
|
26
|
+
owner=self.owner, repo=self.repo, pull_number=self.pull_number
|
|
27
|
+
)
|
|
28
|
+
files = await self.http_client.pr.get_files(
|
|
29
|
+
owner=self.owner, repo=self.repo, pull_number=self.pull_number
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
logger.info(
|
|
33
|
+
f"Fetched PR info for {self.owner}/{self.repo}#{self.pull_number}"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return MRInfoSchema(
|
|
37
|
+
title=pr.title,
|
|
38
|
+
author=MRUserSchema(
|
|
39
|
+
name=pr.user.login,
|
|
40
|
+
username=pr.user.login,
|
|
41
|
+
),
|
|
42
|
+
labels=[label.name for label in pr.labels],
|
|
43
|
+
base_sha=pr.base.sha,
|
|
44
|
+
head_sha=pr.head.sha,
|
|
45
|
+
assignees=[
|
|
46
|
+
MRUserSchema(name=user.login, username=user.login)
|
|
47
|
+
for user in pr.assignees
|
|
48
|
+
],
|
|
49
|
+
reviewers=[
|
|
50
|
+
MRUserSchema(name=user.login, username=user.login)
|
|
51
|
+
for user in pr.requested_reviewers
|
|
52
|
+
],
|
|
53
|
+
description=pr.body or "",
|
|
54
|
+
source_branch=pr.head.ref,
|
|
55
|
+
target_branch=pr.base.ref,
|
|
56
|
+
changed_files=[file.filename for file in files.root],
|
|
57
|
+
)
|
|
58
|
+
except Exception as error:
|
|
59
|
+
logger.exception(
|
|
60
|
+
f"Failed to fetch PR info {self.owner}/{self.repo}#{self.pull_number}: {error}"
|
|
61
|
+
)
|
|
62
|
+
return MRInfoSchema()
|
|
63
|
+
|
|
64
|
+
async def get_comments(self) -> list[MRCommentSchema]:
|
|
65
|
+
try:
|
|
66
|
+
response = await self.http_client.pr.get_issue_comments(
|
|
67
|
+
owner=self.owner,
|
|
68
|
+
repo=self.repo,
|
|
69
|
+
issue_number=self.pull_number,
|
|
70
|
+
)
|
|
71
|
+
logger.info(
|
|
72
|
+
f"Fetched issue comments for {self.owner}/{self.repo}#{self.pull_number}"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return [MRCommentSchema(id=comment.id, body=comment.body) for comment in response.root]
|
|
76
|
+
except Exception as error:
|
|
77
|
+
logger.exception(
|
|
78
|
+
f"Failed to fetch issue comments {self.owner}/{self.repo}#{self.pull_number}: {error}"
|
|
79
|
+
)
|
|
80
|
+
return []
|
|
81
|
+
|
|
82
|
+
async def get_discussions(self) -> list[MRDiscussionSchema]:
|
|
83
|
+
try:
|
|
84
|
+
response = await self.http_client.pr.get_review_comments(
|
|
85
|
+
owner=self.owner,
|
|
86
|
+
repo=self.repo,
|
|
87
|
+
pull_number=self.pull_number,
|
|
88
|
+
)
|
|
89
|
+
logger.info(
|
|
90
|
+
f"Fetched review comments for {self.owner}/{self.repo}#{self.pull_number}"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return [
|
|
94
|
+
MRDiscussionSchema(
|
|
95
|
+
id=str(comment.id),
|
|
96
|
+
notes=[MRNoteSchema(id=comment.id, body=comment.body or "")]
|
|
97
|
+
)
|
|
98
|
+
for comment in response.root
|
|
99
|
+
]
|
|
100
|
+
except Exception as error:
|
|
101
|
+
logger.exception(
|
|
102
|
+
f"Failed to fetch review comments {self.owner}/{self.repo}#{self.pull_number}: {error}"
|
|
103
|
+
)
|
|
104
|
+
return []
|
|
105
|
+
|
|
106
|
+
async def create_comment(self, message: str) -> None:
|
|
107
|
+
try:
|
|
108
|
+
logger.info(
|
|
109
|
+
f"Posting general comment to PR {self.owner}/{self.repo}#{self.pull_number}: {message}"
|
|
110
|
+
)
|
|
111
|
+
await self.http_client.pr.create_issue_comment(
|
|
112
|
+
owner=self.owner,
|
|
113
|
+
repo=self.repo,
|
|
114
|
+
issue_number=self.pull_number,
|
|
115
|
+
body=message,
|
|
116
|
+
)
|
|
117
|
+
logger.info(
|
|
118
|
+
f"Created general comment in PR {self.owner}/{self.repo}#{self.pull_number}"
|
|
119
|
+
)
|
|
120
|
+
except Exception as error:
|
|
121
|
+
logger.exception(
|
|
122
|
+
f"Failed to create general comment in PR {self.owner}/{self.repo}#{self.pull_number}: {error}"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
async def create_discussion(self, file: str, line: int, message: str) -> None:
|
|
126
|
+
try:
|
|
127
|
+
pr = await self.http_client.pr.get_pull_request(
|
|
128
|
+
owner=self.owner, repo=self.repo, pull_number=self.pull_number
|
|
129
|
+
)
|
|
130
|
+
commit_id = pr.head.sha
|
|
131
|
+
|
|
132
|
+
await self.http_client.pr.create_review_comment(
|
|
133
|
+
owner=self.owner,
|
|
134
|
+
repo=self.repo,
|
|
135
|
+
pull_number=self.pull_number,
|
|
136
|
+
body=message,
|
|
137
|
+
path=file,
|
|
138
|
+
line=line,
|
|
139
|
+
commit_id=commit_id,
|
|
140
|
+
)
|
|
141
|
+
logger.info(
|
|
142
|
+
f"Created inline comment in {self.owner}/{self.repo}#{self.pull_number} at {file}:{line}"
|
|
143
|
+
)
|
|
144
|
+
except Exception as error:
|
|
145
|
+
logger.exception(
|
|
146
|
+
f"Failed to create inline comment in {self.owner}/{self.repo}#{self.pull_number} at {file}:{line}: {error}"
|
|
147
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from httpx import AsyncClient
|
|
3
|
+
from pydantic import HttpUrl, SecretStr
|
|
4
|
+
|
|
5
|
+
from ai_review.clients.github.client import get_github_http_client, GitHubHTTPClient
|
|
6
|
+
from ai_review.clients.github.pr.client import GitHubPullRequestsHTTPClient
|
|
7
|
+
from ai_review.config import settings
|
|
8
|
+
from ai_review.libs.config.github import GitHubPipelineConfig, GitHubHTTPClientConfig
|
|
9
|
+
from ai_review.libs.config.vcs import GitHubVCSConfig
|
|
10
|
+
from ai_review.libs.constants.vcs_provider import VCSProvider
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture(autouse=True)
|
|
14
|
+
def github_http_client_config(monkeypatch: pytest.MonkeyPatch):
|
|
15
|
+
fake_config = GitHubVCSConfig(
|
|
16
|
+
provider=VCSProvider.GITHUB,
|
|
17
|
+
pipeline=GitHubPipelineConfig(
|
|
18
|
+
repo="repo",
|
|
19
|
+
owner="owner",
|
|
20
|
+
pull_number="pull_number"
|
|
21
|
+
),
|
|
22
|
+
http_client=GitHubHTTPClientConfig(
|
|
23
|
+
timeout=10,
|
|
24
|
+
api_url=HttpUrl("https://github.com"),
|
|
25
|
+
api_token=SecretStr("fake-token"),
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
monkeypatch.setattr(settings, "vcs", fake_config)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_get_github_http_client_builds_ok():
|
|
32
|
+
github_http_client = get_github_http_client()
|
|
33
|
+
|
|
34
|
+
assert isinstance(github_http_client, GitHubHTTPClient)
|
|
35
|
+
assert isinstance(github_http_client.pr, GitHubPullRequestsHTTPClient)
|
|
36
|
+
assert isinstance(github_http_client.pr.client, AsyncClient)
|
|
@@ -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
|
|
@@ -14,6 +14,15 @@ ai_review/clients/claude/schema.py,sha256=LE6KCjJKDXqBGU2Cno5XL5R8vUfScgskE9MqvE
|
|
|
14
14
|
ai_review/clients/gemini/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
ai_review/clients/gemini/client.py,sha256=7ZPgqx77ER7gonxX0VoN4YrMpex3iBEQtd9Hi-bnDms,1780
|
|
16
16
|
ai_review/clients/gemini/schema.py,sha256=5oVvbI-h_sw8bFreS4JUmMj-aXa_frvxK3H8sg4iJIA,2264
|
|
17
|
+
ai_review/clients/github/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
ai_review/clients/github/client.py,sha256=4uZsnMY-OZ9BNzMLqEHG80jgpyQdd61ePNdVuwGMcrI,1134
|
|
19
|
+
ai_review/clients/github/pr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
ai_review/clients/github/pr/client.py,sha256=Trv5MwmOOi5gAM8KHknbpf8NWNZ-Tnag-bi_74KIdu0,4678
|
|
21
|
+
ai_review/clients/github/pr/schema/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
ai_review/clients/github/pr/schema/comments.py,sha256=K9KQ9TmWv9Hjw8uTrCPkzyAVbFohjCCf_rww6Ucj3wM,650
|
|
23
|
+
ai_review/clients/github/pr/schema/files.py,sha256=mLHg1CfXUKCdQf5YUtuJ8n6xOROKoAjiJY5PL70kP-w,269
|
|
24
|
+
ai_review/clients/github/pr/schema/pull_request.py,sha256=sdSvPgBkspW2DVO9GIyiqdhTngaVFFpYMCgcc5kFf8I,573
|
|
25
|
+
ai_review/clients/github/pr/schema/reviews.py,sha256=v99DLYT5LOAcc18PATIse1mld8J0wKEAaTzUKI70s0c,288
|
|
17
26
|
ai_review/clients/gitlab/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
27
|
ai_review/clients/gitlab/client.py,sha256=acMflkHGp8mv0TVLdZ1gmdXkWQPcq609QjmkYWjEmys,1136
|
|
19
28
|
ai_review/clients/gitlab/mr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -36,14 +45,15 @@ ai_review/libs/config/artifacts.py,sha256=8BzbQu5GxwV6i6qzrUKM1De1Ogb00Ph5WTqwZ3
|
|
|
36
45
|
ai_review/libs/config/base.py,sha256=sPf3OKeF1ID0ouOwiVaUtvpWuZXJXQvIw5kbnPUyN9o,686
|
|
37
46
|
ai_review/libs/config/claude.py,sha256=E9AJmszfY4TH8PkJjnDDDJYNAU9bLGsUThM3kriVA58,302
|
|
38
47
|
ai_review/libs/config/gemini.py,sha256=sXHud43LWb4xTvhdkGQeHSLC7qvWl5LfU41fgcIVE5E,274
|
|
48
|
+
ai_review/libs/config/github.py,sha256=1yFfvkTOt5ernIrxjqmiUKDpbEEHpa6lTpDiFQ5gVn4,238
|
|
39
49
|
ai_review/libs/config/gitlab.py,sha256=VFvoVtni86tWky6Y34XCYdNrBuAtbgFFYGK3idPSOS4,234
|
|
40
50
|
ai_review/libs/config/http.py,sha256=QsIj0yH1IYELOFBQ5AoqPZT0kGIIrQ19cxk1ozPRhLE,345
|
|
41
51
|
ai_review/libs/config/llm.py,sha256=cK-e4NCQxnnixLATCsO8-r5k3zUWz1N0BdPCoqerORM,1824
|
|
42
52
|
ai_review/libs/config/logger.py,sha256=oPmjpjf6EZwW7CgOjT8mOQdGnT98CLwXepiGB_ajZvU,384
|
|
43
53
|
ai_review/libs/config/openai.py,sha256=vOYqhUq0ceEuNdQrQaHq44lVS5M648mB61Zc4YlfJVw,271
|
|
44
|
-
ai_review/libs/config/prompt.py,sha256=
|
|
54
|
+
ai_review/libs/config/prompt.py,sha256=8aO5WNnhVhQcpWzWxqzb9lq6PzormaJazVwPHuf_ia8,4469
|
|
45
55
|
ai_review/libs/config/review.py,sha256=LEZni68iH_0m4URPfN0d3F6yrrK7KSn-BwXf-7w2al8,1058
|
|
46
|
-
ai_review/libs/config/vcs.py,sha256=
|
|
56
|
+
ai_review/libs/config/vcs.py,sha256=ULuLicuulFgG-_sTuDWsldyVWIT2SkiS8brPUU1svgk,778
|
|
47
57
|
ai_review/libs/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
58
|
ai_review/libs/constants/llm_provider.py,sha256=sKnDLylCIIosYjq0-0r91LMiYJ4DlHVH2jeRDv_DlsQ,121
|
|
49
59
|
ai_review/libs/constants/vcs_provider.py,sha256=mZMC8DWIDWQ1YeUZh1a1jduX5enOAe1rWeza0RBmpTY,99
|
|
@@ -98,7 +108,8 @@ ai_review/services/llm/openai/client.py,sha256=WhMXNfH_G1NTlFkdRK5sgYvrCIE5ZQNfP
|
|
|
98
108
|
ai_review/services/prompt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
99
109
|
ai_review/services/prompt/adapter.py,sha256=humGHLRVBu0JspeULgYHCs782BAy4YYKSf5yaG8aF24,1003
|
|
100
110
|
ai_review/services/prompt/schema.py,sha256=erAecUYzOWyZfixt-pjmPSnvcMDh5DajMd1b7_SPm_o,2052
|
|
101
|
-
ai_review/services/prompt/service.py,sha256=
|
|
111
|
+
ai_review/services/prompt/service.py,sha256=D1PR2HC4cgrEND6mAhU5EPRAtp4mgEkOLEyD51WBc0g,2129
|
|
112
|
+
ai_review/services/prompt/tools.py,sha256=-gS74mVM3OZPKWQkY9_QfStkfxaUfssDbJ3Bdi4AQ74,636
|
|
102
113
|
ai_review/services/review/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
103
114
|
ai_review/services/review/service.py,sha256=8YhRFqhZAk2pAnkDaytKSCENlOeOti1brAJq3R9tVMY,8394
|
|
104
115
|
ai_review/services/review/inline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -110,8 +121,10 @@ ai_review/services/review/summary/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeR
|
|
|
110
121
|
ai_review/services/review/summary/schema.py,sha256=GipVNWrEKtgZPkytNSrXwzvX9Zq8Pv2wxjXhfJq4D3g,364
|
|
111
122
|
ai_review/services/review/summary/service.py,sha256=GB7-l4UyjZfUe6yP_8Q-SD1_uDKHM0W-CZJVMiEL8S0,449
|
|
112
123
|
ai_review/services/vcs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
113
|
-
ai_review/services/vcs/factory.py,sha256=
|
|
124
|
+
ai_review/services/vcs/factory.py,sha256=na1AOXgL9oUHqGIdRwT73BofxnkXEFnDr7fL3Sk_hkw,586
|
|
114
125
|
ai_review/services/vcs/types.py,sha256=o3CJ8bZJ8unB9AKSpS66NwPVkFkweV4R02nCYsNqCko,1270
|
|
126
|
+
ai_review/services/vcs/github/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
127
|
+
ai_review/services/vcs/github/client.py,sha256=EfFa6DwQ527sfUmq0RWTc-y3t1I2GRRNRVG4A0U-xgY,5477
|
|
115
128
|
ai_review/services/vcs/gitlab/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
116
129
|
ai_review/services/vcs/gitlab/client.py,sha256=-ZZFFlB7vv2DgEYAU016FP4CcYO8hp5LY1E2xokuCmU,6140
|
|
117
130
|
ai_review/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -125,6 +138,8 @@ ai_review/tests/suites/clients/claude/test_schema.py,sha256=MUZXvEROgLNpUVHfCsH5
|
|
|
125
138
|
ai_review/tests/suites/clients/gemini/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
126
139
|
ai_review/tests/suites/clients/gemini/test_client.py,sha256=6hxpK7r7iZVbOzAffRNDJnA63-3Zxvqw5ynANPhBhBg,1066
|
|
127
140
|
ai_review/tests/suites/clients/gemini/test_schema.py,sha256=88dU28m7sEWvGx6tqYl7if7weWYuVc8erlkFkKKI3bk,3109
|
|
141
|
+
ai_review/tests/suites/clients/github/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
142
|
+
ai_review/tests/suites/clients/github/test_client.py,sha256=iCFQRaDPNota21No5SaCwMvWRW4VTJu_MgmeCC4Dk2o,1328
|
|
128
143
|
ai_review/tests/suites/clients/gitlab/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
129
144
|
ai_review/tests/suites/clients/gitlab/test_client.py,sha256=vXN7UZLC2yc7P7GZftpVvvUDycqR231ZFnfHZk97VLY,1325
|
|
130
145
|
ai_review/tests/suites/clients/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -147,7 +162,8 @@ ai_review/tests/suites/services/diff/test_service.py,sha256=iFkGX9Vj2X44JU3eFsoH
|
|
|
147
162
|
ai_review/tests/suites/services/diff/test_tools.py,sha256=HBQ3eCn-kLeb_k5iTgyr09x0VwLYSegSbxm0Qk9ZrCc,3543
|
|
148
163
|
ai_review/tests/suites/services/prompt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
149
164
|
ai_review/tests/suites/services/prompt/test_schema.py,sha256=XkJk4N9ovgod7G3i6oZwRBjpd71sv0vtVDJhSOfIwGA,2660
|
|
150
|
-
ai_review/tests/suites/services/prompt/test_service.py,sha256=
|
|
165
|
+
ai_review/tests/suites/services/prompt/test_service.py,sha256=plJ8xDnBifCrLtHJO00cdl11U1EsqSw7lBrEGxu0AIw,6752
|
|
166
|
+
ai_review/tests/suites/services/prompt/test_tools.py,sha256=_yNZoBATvPU5enWNIopbjY8lVVjfaB_46kNIKODhCW4,1981
|
|
151
167
|
ai_review/tests/suites/services/review/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
152
168
|
ai_review/tests/suites/services/review/inline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
153
169
|
ai_review/tests/suites/services/review/inline/test_schema.py,sha256=tIz-1UA_GgwcdsyUqgrodiiVVmd_jhoOVmtEwzRVWiY,2474
|
|
@@ -157,9 +173,9 @@ ai_review/tests/suites/services/review/policy/test_service.py,sha256=kRWT550OjWY
|
|
|
157
173
|
ai_review/tests/suites/services/review/summary/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
158
174
|
ai_review/tests/suites/services/review/summary/test_schema.py,sha256=xSoydvABZldHaVDa0OBFvYrj8wMuZqUDN3MO-XdvxOI,819
|
|
159
175
|
ai_review/tests/suites/services/review/summary/test_service.py,sha256=8UMvi_NL9frm280vD6Q1NCDrdI7K8YbXzoViIus-I2g,541
|
|
160
|
-
xai_review-0.
|
|
161
|
-
xai_review-0.
|
|
162
|
-
xai_review-0.
|
|
163
|
-
xai_review-0.
|
|
164
|
-
xai_review-0.
|
|
165
|
-
xai_review-0.
|
|
176
|
+
xai_review-0.13.0.dist-info/licenses/LICENSE,sha256=p-v8m7Kmz4KKc7PcvsGiGEmCw9AiSXY4_ylOPy_u--Y,11343
|
|
177
|
+
xai_review-0.13.0.dist-info/METADATA,sha256=mUB-mJITaTeO1i_y4DHOTFl1MyTxsqX6vayu4iBLZZM,9618
|
|
178
|
+
xai_review-0.13.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
179
|
+
xai_review-0.13.0.dist-info/entry_points.txt,sha256=JyC5URanMi5io5P_PXQf7H_I1OGIpk5cZQhaPQ0g4Zs,53
|
|
180
|
+
xai_review-0.13.0.dist-info/top_level.txt,sha256=sTsZbfzLoqvRZKdKa-BcxWvjlHdrpbeJ6DrGY0EuR0E,10
|
|
181
|
+
xai_review-0.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|