xai-review 0.18.0__py3-none-any.whl → 0.20.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 (27) hide show
  1. ai_review/clients/github/pr/client.py +13 -7
  2. ai_review/clients/github/pr/schema/pull_request.py +6 -6
  3. ai_review/clients/gitlab/mr/client.py +29 -20
  4. ai_review/clients/gitlab/mr/schema/changes.py +5 -5
  5. ai_review/clients/gitlab/mr/schema/discussions.py +1 -4
  6. ai_review/clients/gitlab/mr/schema/notes.py +19 -0
  7. ai_review/services/llm/factory.py +1 -1
  8. ai_review/services/prompt/adapter.py +15 -15
  9. ai_review/services/prompt/schema.py +18 -18
  10. ai_review/services/review/service.py +45 -42
  11. ai_review/services/vcs/factory.py +1 -1
  12. ai_review/services/vcs/github/client.py +52 -34
  13. ai_review/services/vcs/gitlab/client.py +62 -44
  14. ai_review/services/vcs/types.py +38 -29
  15. ai_review/tests/suites/services/cost/__init__.py +0 -0
  16. ai_review/tests/suites/services/cost/test_schema.py +124 -0
  17. ai_review/tests/suites/services/cost/test_service.py +99 -0
  18. ai_review/tests/suites/services/prompt/test_adapter.py +59 -0
  19. ai_review/tests/suites/services/prompt/test_schema.py +18 -18
  20. ai_review/tests/suites/services/prompt/test_service.py +13 -11
  21. {xai_review-0.18.0.dist-info → xai_review-0.20.0.dist-info}/METADATA +24 -9
  22. {xai_review-0.18.0.dist-info → xai_review-0.20.0.dist-info}/RECORD +26 -22
  23. ai_review/clients/gitlab/mr/schema/comments.py +0 -19
  24. {xai_review-0.18.0.dist-info → xai_review-0.20.0.dist-info}/WHEEL +0 -0
  25. {xai_review-0.18.0.dist-info → xai_review-0.20.0.dist-info}/entry_points.txt +0 -0
  26. {xai_review-0.18.0.dist-info → xai_review-0.20.0.dist-info}/licenses/LICENSE +0 -0
  27. {xai_review-0.18.0.dist-info → xai_review-0.20.0.dist-info}/top_level.txt +0 -0
@@ -11,21 +11,31 @@ from ai_review.clients.github.pr.schema.files import GitHubGetPRFilesResponseSch
11
11
  from ai_review.clients.github.pr.schema.pull_request import GitHubGetPRResponseSchema
12
12
  from ai_review.clients.github.pr.schema.reviews import GitHubGetPRReviewsResponseSchema
13
13
  from ai_review.libs.http.client import HTTPClient
14
+ from ai_review.libs.http.handlers import HTTPClientError, handle_http_error
15
+
16
+
17
+ class GitHubPullRequestsHTTPClientError(HTTPClientError):
18
+ pass
14
19
 
15
20
 
16
21
  class GitHubPullRequestsHTTPClient(HTTPClient):
22
+ @handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
17
23
  async def get_pull_request_api(self, owner: str, repo: str, pull_number: str) -> Response:
18
24
  return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}")
19
25
 
26
+ @handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
20
27
  async def get_files_api(self, owner: str, repo: str, pull_number: str) -> Response:
21
28
  return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}/files")
22
29
 
30
+ @handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
23
31
  async def get_issue_comments_api(self, owner: str, repo: str, issue_number: str) -> Response:
24
32
  return await self.get(f"/repos/{owner}/{repo}/issues/{issue_number}/comments")
25
33
 
34
+ @handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
26
35
  async def get_review_comments_api(self, owner: str, repo: str, pull_number: str) -> Response:
27
36
  return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}/comments")
28
37
 
38
+ @handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
29
39
  async def create_review_comment_api(
30
40
  self,
31
41
  owner: str,
@@ -38,6 +48,7 @@ class GitHubPullRequestsHTTPClient(HTTPClient):
38
48
  json=request.model_dump(),
39
49
  )
40
50
 
51
+ @handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
41
52
  async def create_issue_comment_api(
42
53
  self,
43
54
  owner: str,
@@ -50,6 +61,7 @@ class GitHubPullRequestsHTTPClient(HTTPClient):
50
61
  json=request.model_dump(),
51
62
  )
52
63
 
64
+ @handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
53
65
  async def get_reviews_api(self, owner: str, repo: str, pull_number: str) -> Response:
54
66
  return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}/reviews")
55
67
 
@@ -78,14 +90,8 @@ class GitHubPullRequestsHTTPClient(HTTPClient):
78
90
  owner: str,
79
91
  repo: str,
80
92
  pull_number: str,
81
- body: str,
82
- commit_id: str,
83
- path: str,
84
- line: int,
93
+ request: GitHubCreateReviewCommentRequestSchema
85
94
  ) -> GitHubCreateReviewCommentResponseSchema:
86
- request = GitHubCreateReviewCommentRequestSchema(
87
- body=body, commit_id=commit_id, path=path, line=line
88
- )
89
95
  response = await self.create_review_comment_api(owner, repo, pull_number, request)
90
96
  return GitHubCreateReviewCommentResponseSchema.model_validate_json(response.text)
91
97
 
@@ -1,4 +1,4 @@
1
- from pydantic import BaseModel
1
+ from pydantic import BaseModel, Field
2
2
 
3
3
 
4
4
  class GitHubUserSchema(BaseModel):
@@ -8,13 +8,13 @@ class GitHubUserSchema(BaseModel):
8
8
 
9
9
  class GitHubLabelSchema(BaseModel):
10
10
  id: int
11
- name: str
11
+ name: str | None = None
12
12
 
13
13
 
14
14
  class GitHubBranchSchema(BaseModel):
15
15
  ref: str
16
16
  sha: str
17
- label: str
17
+ label: str | None = None
18
18
 
19
19
 
20
20
  class GitHubGetPRResponseSchema(BaseModel):
@@ -23,8 +23,8 @@ class GitHubGetPRResponseSchema(BaseModel):
23
23
  title: str
24
24
  body: str | None = None
25
25
  user: GitHubUserSchema
26
- labels: list[GitHubLabelSchema]
27
- assignees: list[GitHubUserSchema] = []
28
- requested_reviewers: list[GitHubUserSchema] = []
26
+ labels: list[GitHubLabelSchema] = Field(default_factory=list)
27
+ assignees: list[GitHubUserSchema] = Field(default_factory=list)
28
+ requested_reviewers: list[GitHubUserSchema] = Field(default_factory=list)
29
29
  base: GitHubBranchSchema
30
30
  head: GitHubBranchSchema
@@ -1,47 +1,56 @@
1
1
  from httpx import Response
2
2
 
3
3
  from ai_review.clients.gitlab.mr.schema.changes import GitLabGetMRChangesResponseSchema
4
- from ai_review.clients.gitlab.mr.schema.comments import (
5
- GitLabGetMRCommentsResponseSchema,
6
- GitLabCreateMRCommentRequestSchema,
7
- GitLabCreateMRCommentResponseSchema,
8
- )
9
4
  from ai_review.clients.gitlab.mr.schema.discussions import (
10
5
  GitLabGetMRDiscussionsResponseSchema,
11
6
  GitLabCreateMRDiscussionRequestSchema,
12
7
  GitLabCreateMRDiscussionResponseSchema
13
8
  )
9
+ from ai_review.clients.gitlab.mr.schema.notes import (
10
+ GitLabGetMRNotesResponseSchema,
11
+ GitLabCreateMRNoteRequestSchema,
12
+ GitLabCreateMRNoteResponseSchema,
13
+ )
14
14
  from ai_review.libs.http.client import HTTPClient
15
+ from ai_review.libs.http.handlers import handle_http_error, HTTPClientError
15
16
 
16
17
 
17
- class GitLabMergeRequestsHTTPClient(HTTPClient):
18
+ class GitLabMergeRequestsHTTPClientError(HTTPClientError):
19
+ pass
18
20
 
21
+
22
+ class GitLabMergeRequestsHTTPClient(HTTPClient):
23
+ @handle_http_error(client="GitLabMergeRequestsHTTPClient", exception=GitLabMergeRequestsHTTPClientError)
19
24
  async def get_changes_api(self, project_id: str, merge_request_id: str) -> Response:
20
25
  return await self.get(
21
26
  f"/api/v4/projects/{project_id}/merge_requests/{merge_request_id}/changes"
22
27
  )
23
28
 
24
- async def get_comments_api(self, project_id: str, merge_request_id: str) -> Response:
29
+ @handle_http_error(client="GitLabMergeRequestsHTTPClient", exception=GitLabMergeRequestsHTTPClientError)
30
+ async def get_notes_api(self, project_id: str, merge_request_id: str) -> Response:
25
31
  return await self.get(
26
32
  f"/api/v4/projects/{project_id}/merge_requests/{merge_request_id}/notes"
27
33
  )
28
34
 
35
+ @handle_http_error(client="GitLabMergeRequestsHTTPClient", exception=GitLabMergeRequestsHTTPClientError)
29
36
  async def get_discussions_api(self, project_id: str, merge_request_id: str) -> Response:
30
37
  return await self.get(
31
38
  f"/api/v4/projects/{project_id}/merge_requests/{merge_request_id}/discussions"
32
39
  )
33
40
 
34
- async def create_comment_api(
41
+ @handle_http_error(client="GitLabMergeRequestsHTTPClient", exception=GitLabMergeRequestsHTTPClientError)
42
+ async def create_note_api(
35
43
  self,
36
44
  project_id: str,
37
45
  merge_request_id: str,
38
- request: GitLabCreateMRCommentRequestSchema,
46
+ request: GitLabCreateMRNoteRequestSchema,
39
47
  ) -> Response:
40
48
  return await self.post(
41
49
  f"/api/v4/projects/{project_id}/merge_requests/{merge_request_id}/notes",
42
50
  json=request.model_dump(),
43
51
  )
44
52
 
53
+ @handle_http_error(client="GitLabMergeRequestsHTTPClient", exception=GitLabMergeRequestsHTTPClientError)
45
54
  async def create_discussion_api(
46
55
  self,
47
56
  project_id: str,
@@ -57,13 +66,13 @@ class GitLabMergeRequestsHTTPClient(HTTPClient):
57
66
  response = await self.get_changes_api(project_id, merge_request_id)
58
67
  return GitLabGetMRChangesResponseSchema.model_validate_json(response.text)
59
68
 
60
- async def get_comments(
69
+ async def get_notes(
61
70
  self,
62
71
  project_id: str,
63
72
  merge_request_id: str
64
- ) -> GitLabGetMRCommentsResponseSchema:
65
- response = await self.get_comments_api(project_id, merge_request_id)
66
- return GitLabGetMRCommentsResponseSchema.model_validate_json(response.text)
73
+ ) -> GitLabGetMRNotesResponseSchema:
74
+ response = await self.get_notes_api(project_id, merge_request_id)
75
+ return GitLabGetMRNotesResponseSchema.model_validate_json(response.text)
67
76
 
68
77
  async def get_discussions(
69
78
  self,
@@ -73,26 +82,26 @@ class GitLabMergeRequestsHTTPClient(HTTPClient):
73
82
  response = await self.get_discussions_api(project_id, merge_request_id)
74
83
  return GitLabGetMRDiscussionsResponseSchema.model_validate_json(response.text)
75
84
 
76
- async def create_comment(
85
+ async def create_note(
77
86
  self,
78
- comment: str,
87
+ body: str,
79
88
  project_id: str,
80
89
  merge_request_id: str,
81
- ) -> GitLabCreateMRCommentResponseSchema:
82
- request = GitLabCreateMRCommentRequestSchema(body=comment)
83
- response = await self.create_comment_api(
90
+ ) -> GitLabCreateMRNoteResponseSchema:
91
+ request = GitLabCreateMRNoteRequestSchema(body=body)
92
+ response = await self.create_note_api(
84
93
  request=request,
85
94
  project_id=project_id,
86
95
  merge_request_id=merge_request_id
87
96
  )
88
- return GitLabCreateMRCommentResponseSchema.model_validate_json(response.text)
97
+ return GitLabCreateMRNoteResponseSchema.model_validate_json(response.text)
89
98
 
90
99
  async def create_discussion(
91
100
  self,
92
101
  project_id: str,
93
102
  merge_request_id: str,
94
103
  request: GitLabCreateMRDiscussionRequestSchema
95
- ):
104
+ ) -> GitLabCreateMRDiscussionResponseSchema:
96
105
  response = await self.create_discussion_api(
97
106
  request=request,
98
107
  project_id=project_id,
@@ -14,9 +14,9 @@ class GitLabDiffRefsSchema(BaseModel):
14
14
 
15
15
 
16
16
  class GitLabMRChangeSchema(BaseModel):
17
- diff: str
18
- old_path: str
19
- new_path: str
17
+ diff: str | None = None
18
+ old_path: str | None = None
19
+ new_path: str | None = None
20
20
 
21
21
 
22
22
  class GitLabGetMRChangesResponseSchema(BaseModel):
@@ -24,12 +24,12 @@ class GitLabGetMRChangesResponseSchema(BaseModel):
24
24
  iid: int
25
25
  title: str
26
26
  author: GitLabUserSchema
27
- labels: list[str] = []
27
+ labels: list[str] = Field(default_factory=list)
28
28
  changes: list[GitLabMRChangeSchema]
29
29
  assignees: list[GitLabUserSchema] = Field(default_factory=list)
30
30
  reviewers: list[GitLabUserSchema] = Field(default_factory=list)
31
31
  diff_refs: GitLabDiffRefsSchema
32
32
  project_id: int
33
- description: str
33
+ description: str | None = None
34
34
  source_branch: str
35
35
  target_branch: str
@@ -1,9 +1,6 @@
1
1
  from pydantic import BaseModel, RootModel
2
2
 
3
-
4
- class GitLabNoteSchema(BaseModel):
5
- id: int
6
- body: str
3
+ from ai_review.clients.gitlab.mr.schema.notes import GitLabNoteSchema
7
4
 
8
5
 
9
6
  class GitLabDiscussionSchema(BaseModel):
@@ -0,0 +1,19 @@
1
+ from pydantic import BaseModel, RootModel
2
+
3
+
4
+ class GitLabNoteSchema(BaseModel):
5
+ id: int
6
+ body: str
7
+
8
+
9
+ class GitLabGetMRNotesResponseSchema(RootModel[list[GitLabNoteSchema]]):
10
+ root: list[GitLabNoteSchema]
11
+
12
+
13
+ class GitLabCreateMRNoteRequestSchema(BaseModel):
14
+ body: str
15
+
16
+
17
+ class GitLabCreateMRNoteResponseSchema(BaseModel):
18
+ id: int
19
+ body: str
@@ -15,4 +15,4 @@ def get_llm_client() -> LLMClient:
15
15
  case LLMProvider.CLAUDE:
16
16
  return ClaudeLLMClient()
17
17
  case _:
18
- raise ValueError(f"Unsupported provider: {settings.llm.provider}")
18
+ raise ValueError(f"Unsupported LLM provider: {settings.llm.provider}")
@@ -1,25 +1,25 @@
1
1
  from ai_review.services.prompt.schema import PromptContextSchema
2
- from ai_review.services.vcs.types import MRInfoSchema
2
+ from ai_review.services.vcs.types import ReviewInfoSchema
3
3
 
4
4
 
5
- def build_prompt_context_from_mr_info(mr: MRInfoSchema) -> PromptContextSchema:
5
+ def build_prompt_context_from_mr_info(review: ReviewInfoSchema) -> PromptContextSchema:
6
6
  return PromptContextSchema(
7
- merge_request_title=mr.title,
8
- merge_request_description=mr.description,
7
+ review_title=review.title,
8
+ review_description=review.description,
9
9
 
10
- merge_request_author_name=mr.author.name,
11
- merge_request_author_username=mr.author.username,
10
+ review_author_name=review.author.name,
11
+ review_author_username=review.author.username,
12
12
 
13
- merge_request_reviewers=[reviewer.name for reviewer in mr.reviewers],
14
- merge_request_reviewers_usernames=[reviewer.username for reviewer in mr.reviewers],
15
- merge_request_reviewer=mr.reviewers[0].name if mr.reviewers else "",
13
+ review_reviewers=[user.name for user in review.reviewers],
14
+ review_reviewers_usernames=[user.username for user in review.reviewers],
15
+ review_reviewer=review.reviewers[0].name if review.reviewers else "",
16
16
 
17
- merge_request_assignees=[assignee.name for assignee in mr.assignees],
18
- merge_request_assignees_usernames=[assignee.username for assignee in mr.assignees],
17
+ review_assignees=[user.name for user in review.assignees],
18
+ review_assignees_usernames=[user.username for user in review.assignees],
19
19
 
20
- source_branch=mr.source_branch,
21
- target_branch=mr.target_branch,
20
+ source_branch=review.source_branch.ref,
21
+ target_branch=review.target_branch.ref,
22
22
 
23
- labels=mr.labels,
24
- changed_files=mr.changed_files,
23
+ labels=review.labels,
24
+ changed_files=review.changed_files,
25
25
  )
@@ -5,18 +5,18 @@ from ai_review.libs.template.render import render_template
5
5
 
6
6
 
7
7
  class PromptContextSchema(BaseModel):
8
- merge_request_title: str = ""
9
- merge_request_description: str = ""
8
+ review_title: str = ""
9
+ review_description: str = ""
10
10
 
11
- merge_request_author_name: str = ""
12
- merge_request_author_username: str = ""
11
+ review_author_name: str = ""
12
+ review_author_username: str = ""
13
13
 
14
- merge_request_reviewer: str = ""
15
- merge_request_reviewers: list[str] = Field(default_factory=list)
16
- merge_request_reviewers_usernames: list[str] = Field(default_factory=list)
14
+ review_reviewer: str = ""
15
+ review_reviewers: list[str] = Field(default_factory=list)
16
+ review_reviewers_usernames: list[str] = Field(default_factory=list)
17
17
 
18
- merge_request_assignees: list[str] = Field(default_factory=list)
19
- merge_request_assignees_usernames: list[str] = Field(default_factory=list)
18
+ review_assignees: list[str] = Field(default_factory=list)
19
+ review_assignees_usernames: list[str] = Field(default_factory=list)
20
20
 
21
21
  source_branch: str = ""
22
22
  target_branch: str = ""
@@ -27,18 +27,18 @@ class PromptContextSchema(BaseModel):
27
27
  @property
28
28
  def render_values(self) -> dict[str, str]:
29
29
  return {
30
- "merge_request_title": self.merge_request_title,
31
- "merge_request_description": self.merge_request_description,
30
+ "review_title": self.review_title,
31
+ "review_description": self.review_description,
32
32
 
33
- "merge_request_author_name": self.merge_request_author_name,
34
- "merge_request_author_username": self.merge_request_author_username,
33
+ "review_author_name": self.review_author_name,
34
+ "review_author_username": self.review_author_username,
35
35
 
36
- "merge_request_reviewer": self.merge_request_reviewer,
37
- "merge_request_reviewers": ", ".join(self.merge_request_reviewers),
38
- "merge_request_reviewers_usernames": ", ".join(self.merge_request_reviewers_usernames),
36
+ "review_reviewer": self.review_reviewer,
37
+ "review_reviewers": ", ".join(self.review_reviewers),
38
+ "review_reviewers_usernames": ", ".join(self.review_reviewers_usernames),
39
39
 
40
- "merge_request_assignees": ", ".join(self.merge_request_assignees),
41
- "merge_request_assignees_usernames": ", ".join(self.merge_request_assignees_usernames),
40
+ "review_assignees": ", ".join(self.review_assignees),
41
+ "review_assignees_usernames": ", ".join(self.review_assignees_usernames),
42
42
 
43
43
  "source_branch": self.source_branch,
44
44
  "target_branch": self.target_branch,
@@ -15,7 +15,7 @@ from ai_review.services.review.inline.service import InlineCommentService
15
15
  from ai_review.services.review.policy.service import ReviewPolicyService
16
16
  from ai_review.services.review.summary.service import SummaryCommentService
17
17
  from ai_review.services.vcs.factory import get_vcs_client
18
- from ai_review.services.vcs.types import MRInfoSchema
18
+ from ai_review.services.vcs.types import ReviewInfoSchema
19
19
 
20
20
  logger = get_logger("REVIEW_SERVICE")
21
21
 
@@ -52,37 +52,43 @@ class ReviewService:
52
52
  logger.exception(f"LLM request failed: {error}")
53
53
  raise
54
54
 
55
- async def has_existing_inline_discussions(self) -> bool:
56
- discussions = await self.vcs.get_discussions()
57
- has_discussions = any(
58
- settings.review.inline_tag in note.body
59
- for discussion in discussions
60
- for note in discussion.notes
55
+ async def has_existing_inline_comments(self) -> bool:
56
+ comments = await self.vcs.get_inline_comments()
57
+ has_comments = any(
58
+ settings.review.inline_tag in comment.body
59
+ for comment in comments
61
60
  )
62
- if has_discussions:
63
- logger.info("Skipping inline review: AI inline discussions already exist")
61
+ if has_comments:
62
+ logger.info("Skipping inline review: AI inline comments already exist")
64
63
 
65
- return has_discussions
64
+ return has_comments
66
65
 
67
66
  async def has_existing_summary_comments(self) -> bool:
68
- comments = await self.vcs.get_comments()
69
- has_comments = any(settings.review.summary_tag in comment.body for comment in comments)
67
+ comments = await self.vcs.get_general_comments()
68
+ has_comments = any(
69
+ settings.review.summary_tag in comment.body for comment in comments
70
+ )
70
71
  if has_comments:
71
72
  logger.info("Skipping summary review: AI summary comment already exists")
72
73
 
73
74
  return has_comments
74
75
 
75
- async def process_discussions(self, flow: Literal["inline", "context"], comments: InlineCommentListSchema) -> None:
76
+ async def process_inline_comments(
77
+ self,
78
+ flow: Literal["inline", "context"],
79
+ comments: InlineCommentListSchema
80
+ ) -> None:
76
81
  results = await bounded_gather([
77
- self.vcs.create_discussion(
82
+ self.vcs.create_inline_comment(
78
83
  file=comment.file,
79
84
  line=comment.line,
80
85
  message=comment.body_with_tag
81
86
  )
82
87
  for comment in comments.root
83
88
  ])
89
+
84
90
  fallbacks = [
85
- self.vcs.create_comment(comment.fallback_body_with_tag)
91
+ self.vcs.create_general_comment(comment.fallback_body_with_tag)
86
92
  for comment, result in zip(comments.root, results)
87
93
  if isinstance(result, Exception)
88
94
  ]
@@ -90,19 +96,19 @@ class ReviewService:
90
96
  logger.warning(f"Falling back to {len(fallbacks)} general comments ({flow} review)")
91
97
  await bounded_gather(fallbacks)
92
98
 
93
- async def process_file_inline(self, file: str, mr_info: MRInfoSchema) -> None:
94
- raw_diff = self.git.get_diff_for_file(mr_info.base_sha, mr_info.head_sha, file)
99
+ async def process_file_inline(self, file: str, review_info: ReviewInfoSchema) -> None:
100
+ raw_diff = self.git.get_diff_for_file(review_info.base_sha, review_info.head_sha, file)
95
101
  if not raw_diff.strip():
96
102
  logger.debug(f"No diff for {file}, skipping")
97
103
  return
98
104
 
99
105
  rendered_file = self.diff.render_file(
100
106
  file=file,
101
- base_sha=mr_info.base_sha,
102
- head_sha=mr_info.head_sha,
107
+ base_sha=review_info.base_sha,
108
+ head_sha=review_info.head_sha,
103
109
  raw_diff=raw_diff,
104
110
  )
105
- prompt_context = build_prompt_context_from_mr_info(mr_info)
111
+ prompt_context = build_prompt_context_from_mr_info(review_info)
106
112
  prompt = self.prompt.build_inline_request(rendered_file, prompt_context)
107
113
  prompt_system = self.prompt.build_system_inline_request(prompt_context)
108
114
  prompt_result = await self.ask_llm(prompt, prompt_system)
@@ -114,29 +120,27 @@ class ReviewService:
114
120
  return
115
121
 
116
122
  logger.info(f"Posting {len(comments.root)} inline comments to {file}")
117
- await self.process_discussions(flow="inline", comments=comments)
123
+ await self.process_inline_comments(flow="inline", comments=comments)
118
124
 
119
125
  async def run_inline_review(self) -> None:
120
- if await self.has_existing_inline_discussions():
126
+ if await self.has_existing_inline_comments():
121
127
  return
122
128
 
123
- mr_info = await self.vcs.get_mr_info()
129
+ review_info = await self.vcs.get_review_info()
130
+ logger.info(f"Starting inline review: {len(review_info.changed_files)} files changed")
124
131
 
125
- logger.info(f"Starting inline review: {len(mr_info.changed_files)} files changed")
126
-
127
- changed_files = self.policy.apply_for_files(mr_info.changed_files)
132
+ changed_files = self.policy.apply_for_files(review_info.changed_files)
128
133
  await bounded_gather([
129
- self.process_file_inline(changed_file, mr_info)
134
+ self.process_file_inline(changed_file, review_info)
130
135
  for changed_file in changed_files
131
136
  ])
132
137
 
133
138
  async def run_context_review(self) -> None:
134
- if await self.has_existing_inline_discussions():
139
+ if await self.has_existing_inline_comments():
135
140
  return
136
141
 
137
- mr_info = await self.vcs.get_mr_info()
138
- changed_files = self.policy.apply_for_files(mr_info.changed_files)
139
-
142
+ review_info = await self.vcs.get_review_info()
143
+ changed_files = self.policy.apply_for_files(review_info.changed_files)
140
144
  if not changed_files:
141
145
  logger.info("No files to review for context review")
142
146
  return
@@ -146,10 +150,10 @@ class ReviewService:
146
150
  rendered_files = self.diff.render_files(
147
151
  git=self.git,
148
152
  files=changed_files,
149
- base_sha=mr_info.base_sha,
150
- head_sha=mr_info.head_sha,
153
+ base_sha=review_info.base_sha,
154
+ head_sha=review_info.head_sha,
151
155
  )
152
- prompt_context = build_prompt_context_from_mr_info(mr_info)
156
+ prompt_context = build_prompt_context_from_mr_info(review_info)
153
157
  prompt = self.prompt.build_context_request(rendered_files, prompt_context)
154
158
  prompt_system = self.prompt.build_system_context_request(prompt_context)
155
159
  prompt_result = await self.ask_llm(prompt, prompt_system)
@@ -161,15 +165,14 @@ class ReviewService:
161
165
  return
162
166
 
163
167
  logger.info(f"Posting {len(comments.root)} inline comments (context review)")
164
- await self.process_discussions(flow="context", comments=comments)
168
+ await self.process_inline_comments(flow="context", comments=comments)
165
169
 
166
170
  async def run_summary_review(self) -> None:
167
171
  if await self.has_existing_summary_comments():
168
172
  return
169
173
 
170
- mr_info = await self.vcs.get_mr_info()
171
- changed_files = self.policy.apply_for_files(mr_info.changed_files)
172
-
174
+ review_info = await self.vcs.get_review_info()
175
+ changed_files = self.policy.apply_for_files(review_info.changed_files)
173
176
  if not changed_files:
174
177
  logger.info("No files to review for summary")
175
178
  return
@@ -179,10 +182,10 @@ class ReviewService:
179
182
  rendered_files = self.diff.render_files(
180
183
  git=self.git,
181
184
  files=changed_files,
182
- base_sha=mr_info.base_sha,
183
- head_sha=mr_info.head_sha,
185
+ base_sha=review_info.base_sha,
186
+ head_sha=review_info.head_sha,
184
187
  )
185
- prompt_context = build_prompt_context_from_mr_info(mr_info)
188
+ prompt_context = build_prompt_context_from_mr_info(review_info)
186
189
  prompt = self.prompt.build_summary_request(rendered_files, prompt_context)
187
190
  prompt_system = self.prompt.build_system_summary_request(prompt_context)
188
191
  prompt_result = await self.ask_llm(prompt, prompt_system)
@@ -193,7 +196,7 @@ class ReviewService:
193
196
  return
194
197
 
195
198
  logger.info(f"Posting summary review comment ({len(summary.text)} chars)")
196
- await self.vcs.create_comment(summary.body_with_tag)
199
+ await self.vcs.create_general_comment(summary.body_with_tag)
197
200
 
198
201
  def report_total_cost(self):
199
202
  total_report = self.cost.aggregate()
@@ -12,4 +12,4 @@ def get_vcs_client() -> VCSClient:
12
12
  case VCSProvider.GITHUB:
13
13
  return GitHubVCSClient()
14
14
  case _:
15
- raise ValueError(f"Unsupported provider: {settings.llm.provider}")
15
+ raise ValueError(f"Unsupported VCS provider: {settings.vcs.provider}")