xai-review 0.7.0__py3-none-any.whl → 0.10.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/gitlab/client.py +1 -1
- ai_review/libs/json.py +19 -0
- ai_review/services/diff/renderers.py +19 -12
- ai_review/services/prompt/adapter.py +1 -1
- ai_review/services/review/inline/service.py +24 -9
- ai_review/tests/suites/libs/test_json.py +41 -0
- ai_review/tests/suites/services/diff/test_renderers.py +23 -0
- ai_review/tests/suites/services/review/inline/test_service.py +52 -0
- {xai_review-0.7.0.dist-info → xai_review-0.10.0.dist-info}/METADATA +2 -2
- {xai_review-0.7.0.dist-info → xai_review-0.10.0.dist-info}/RECORD +14 -12
- {xai_review-0.7.0.dist-info → xai_review-0.10.0.dist-info}/WHEEL +0 -0
- {xai_review-0.7.0.dist-info → xai_review-0.10.0.dist-info}/entry_points.txt +0 -0
- {xai_review-0.7.0.dist-info → xai_review-0.10.0.dist-info}/licenses/LICENSE +0 -0
- {xai_review-0.7.0.dist-info → xai_review-0.10.0.dist-info}/top_level.txt +0 -0
|
@@ -13,7 +13,7 @@ class GitLabHTTPClient:
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def get_gitlab_http_client() -> GitLabHTTPClient:
|
|
16
|
-
logger = get_logger("
|
|
16
|
+
logger = get_logger("GITLAB_HTTP_CLIENT")
|
|
17
17
|
logger_event_hook = LoggerEventHook(logger=logger)
|
|
18
18
|
retry_transport = RetryTransport(transport=AsyncHTTPTransport())
|
|
19
19
|
|
ai_review/libs/json.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
CONTROL_CHARS_RE = re.compile(r"[\x00-\x1F]")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def sanitize_json_string(raw: str) -> str:
|
|
7
|
+
def replace(match: re.Match) -> str:
|
|
8
|
+
char = match.group()
|
|
9
|
+
match char:
|
|
10
|
+
case "\n":
|
|
11
|
+
return "\\n"
|
|
12
|
+
case "\r":
|
|
13
|
+
return "\\r"
|
|
14
|
+
case "\t":
|
|
15
|
+
return "\\t"
|
|
16
|
+
case _:
|
|
17
|
+
return f"\\u{ord(char):04x}"
|
|
18
|
+
|
|
19
|
+
return CONTROL_CHARS_RE.sub(replace, raw)
|
|
@@ -13,7 +13,7 @@ Supported build modes:
|
|
|
13
13
|
- ADDED_AND_REMOVED_WITH_CONTEXT added + removed + surrounding unchanged lines
|
|
14
14
|
"""
|
|
15
15
|
from enum import Enum
|
|
16
|
-
from typing import Iterable
|
|
16
|
+
from typing import Iterable
|
|
17
17
|
|
|
18
18
|
from ai_review.libs.diff.models import DiffFile, DiffLineType
|
|
19
19
|
from ai_review.services.diff.tools import normalize_file_path, marker_for_line, read_snapshot
|
|
@@ -24,7 +24,7 @@ class MarkerType(Enum):
|
|
|
24
24
|
REMOVED = "removed"
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def build_full_file_current(file:
|
|
27
|
+
def build_full_file_current(file: DiffFile | None, file_path: str, head_sha: str | None) -> str:
|
|
28
28
|
text = read_snapshot(file_path, head_sha=head_sha)
|
|
29
29
|
if text is None:
|
|
30
30
|
return f"# Failed to read current snapshot for {file_path}"
|
|
@@ -33,7 +33,7 @@ def build_full_file_current(file: Optional[DiffFile], file_path: str, head_sha:
|
|
|
33
33
|
return render_plain_numbered(text.splitlines(), added_new, marker_type=MarkerType.ADDED)
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def build_full_file_previous(file:
|
|
36
|
+
def build_full_file_previous(file: DiffFile | None, file_path: str, base_sha: str | None) -> str:
|
|
37
37
|
text = read_snapshot(file_path, base_sha=base_sha)
|
|
38
38
|
if text is None:
|
|
39
39
|
return f"# Failed to read previous snapshot for {file_path} (base_sha missing or file absent)"
|
|
@@ -42,31 +42,31 @@ def build_full_file_previous(file: Optional[DiffFile], file_path: str, base_sha:
|
|
|
42
42
|
return render_plain_numbered(text.splitlines(), removed_old, marker_type=MarkerType.REMOVED)
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def build_full_file_diff(file: DiffFile) -> str:
|
|
45
|
+
def build_full_file_diff(file: DiffFile | None) -> str:
|
|
46
46
|
return render_unified(file, include_added=True, include_removed=True, include_unchanged=True, context=0)
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def build_only_added(file: DiffFile) -> str:
|
|
49
|
+
def build_only_added(file: DiffFile | None) -> str:
|
|
50
50
|
return render_unified(file, include_added=True, include_removed=False, include_unchanged=False, context=0)
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def build_only_removed(file: DiffFile) -> str:
|
|
53
|
+
def build_only_removed(file: DiffFile | None) -> str:
|
|
54
54
|
return render_unified(file, include_added=False, include_removed=True, include_unchanged=False, context=0)
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
def build_added_and_removed(file: DiffFile) -> str:
|
|
57
|
+
def build_added_and_removed(file: DiffFile | None) -> str:
|
|
58
58
|
return render_unified(file, include_added=True, include_removed=True, include_unchanged=False, context=0)
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
def build_only_added_with_context(file: DiffFile, context: int) -> str:
|
|
61
|
+
def build_only_added_with_context(file: DiffFile | None, context: int) -> str:
|
|
62
62
|
return render_unified(file, include_added=True, include_removed=False, include_unchanged=True, context=context)
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
def build_only_removed_with_context(file: DiffFile, context: int) -> str:
|
|
65
|
+
def build_only_removed_with_context(file: DiffFile | None, context: int) -> str:
|
|
66
66
|
return render_unified(file, include_added=False, include_removed=True, include_unchanged=True, context=context)
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
def build_added_and_removed_with_context(file: DiffFile, context: int) -> str:
|
|
69
|
+
def build_added_and_removed_with_context(file: DiffFile | None, context: int) -> str:
|
|
70
70
|
return render_unified(file, include_added=True, include_removed=True, include_unchanged=True, context=context)
|
|
71
71
|
|
|
72
72
|
|
|
@@ -87,7 +87,7 @@ def render_plain_numbered(lines: Iterable[str], changed: set[int], marker_type:
|
|
|
87
87
|
|
|
88
88
|
|
|
89
89
|
def render_unified(
|
|
90
|
-
file: DiffFile,
|
|
90
|
+
file: DiffFile | None,
|
|
91
91
|
*,
|
|
92
92
|
include_added: bool,
|
|
93
93
|
include_removed: bool,
|
|
@@ -104,12 +104,19 @@ def render_unified(
|
|
|
104
104
|
|
|
105
105
|
Context controls how many unchanged lines around modifications are shown.
|
|
106
106
|
"""
|
|
107
|
+
if file is None:
|
|
108
|
+
return "# Diff target not found"
|
|
109
|
+
|
|
110
|
+
if not file.hunks:
|
|
111
|
+
header = normalize_file_path(file.new_name or file.orig_name)
|
|
112
|
+
return f"# No matching lines for mode in {header}"
|
|
113
|
+
|
|
107
114
|
lines_out: list[str] = []
|
|
108
115
|
|
|
109
116
|
added_new_positions = file.added_line_numbers()
|
|
110
117
|
removed_old_positions = file.removed_line_numbers()
|
|
111
118
|
|
|
112
|
-
def in_context(old_no:
|
|
119
|
+
def in_context(old_no: int | None, new_no: int | None) -> bool:
|
|
113
120
|
"""Check if an unchanged line falls within context radius."""
|
|
114
121
|
if context <= 0:
|
|
115
122
|
return False
|
|
@@ -12,7 +12,7 @@ def build_prompt_context_from_mr_info(mr: MRInfoSchema) -> PromptContextSchema:
|
|
|
12
12
|
|
|
13
13
|
merge_request_reviewers=[reviewer.name for reviewer in mr.reviewers],
|
|
14
14
|
merge_request_reviewers_usernames=[reviewer.username for reviewer in mr.reviewers],
|
|
15
|
-
merge_request_reviewer=mr.reviewers[0].name if mr.reviewers else
|
|
15
|
+
merge_request_reviewer=mr.reviewers[0].name if mr.reviewers else "",
|
|
16
16
|
|
|
17
17
|
merge_request_assignees=[assignee.name for assignee in mr.assignees],
|
|
18
18
|
merge_request_assignees_usernames=[assignee.username for assignee in mr.assignees],
|
|
@@ -2,6 +2,7 @@ import re
|
|
|
2
2
|
|
|
3
3
|
from pydantic import ValidationError
|
|
4
4
|
|
|
5
|
+
from ai_review.libs.json import sanitize_json_string
|
|
5
6
|
from ai_review.libs.logger import get_logger
|
|
6
7
|
from ai_review.services.review.inline.schema import InlineCommentListSchema
|
|
7
8
|
|
|
@@ -12,6 +13,19 @@ CLEAN_JSON_BLOCK_RE = re.compile(r"```(?:json)?(.*?)```", re.DOTALL | re.IGNOREC
|
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class InlineCommentService:
|
|
16
|
+
@classmethod
|
|
17
|
+
def try_parse_model_output(cls, raw: str) -> InlineCommentListSchema | None:
|
|
18
|
+
try:
|
|
19
|
+
return InlineCommentListSchema.model_validate_json(raw)
|
|
20
|
+
except ValidationError as error:
|
|
21
|
+
logger.debug(f"Parse failed, trying sanitized JSON: {raw[:200]=}, {error=}")
|
|
22
|
+
try:
|
|
23
|
+
cleaned = sanitize_json_string(raw)
|
|
24
|
+
return InlineCommentListSchema.model_validate_json(cleaned)
|
|
25
|
+
except ValidationError as error:
|
|
26
|
+
logger.debug(f"Sanitized JSON still invalid: {raw[:200]=}, {error=}")
|
|
27
|
+
return None
|
|
28
|
+
|
|
15
29
|
@classmethod
|
|
16
30
|
def parse_model_output(cls, output: str) -> InlineCommentListSchema:
|
|
17
31
|
output = (output or "").strip()
|
|
@@ -22,17 +36,18 @@ class InlineCommentService:
|
|
|
22
36
|
if match := CLEAN_JSON_BLOCK_RE.search(output):
|
|
23
37
|
output = match.group(1).strip()
|
|
24
38
|
|
|
25
|
-
|
|
26
|
-
return
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
if parsed := cls.try_parse_model_output(output):
|
|
40
|
+
return parsed
|
|
41
|
+
|
|
42
|
+
logger.warning("Failed to parse LLM output as JSON, trying to extract first JSON array...")
|
|
29
43
|
|
|
30
44
|
if json_array_match := FIRST_JSON_ARRAY_RE.search(output):
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
if parsed := cls.try_parse_model_output(json_array_match.group(0)):
|
|
46
|
+
logger.info("Successfully parsed JSON after extracting array from output")
|
|
47
|
+
return parsed
|
|
48
|
+
else:
|
|
49
|
+
logger.error("Extracted JSON array is still invalid after sanitization")
|
|
35
50
|
else:
|
|
36
|
-
logger.
|
|
51
|
+
logger.error("No JSON array found in LLM output")
|
|
37
52
|
|
|
38
53
|
return InlineCommentListSchema(root=[])
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ai_review.libs.json import sanitize_json_string
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.mark.parametrize(
|
|
7
|
+
("actual", "expected"),
|
|
8
|
+
[
|
|
9
|
+
("hello world", "hello world"),
|
|
10
|
+
("line1\nline2", "line1\\nline2"),
|
|
11
|
+
("foo\rbar", "foo\\rbar"),
|
|
12
|
+
("a\tb", "a\\tb"),
|
|
13
|
+
("abc\0def", "abc\\u0000def"),
|
|
14
|
+
("x\n\ry\t\0z", "x\\n\\ry\\t\\u0000z"),
|
|
15
|
+
],
|
|
16
|
+
)
|
|
17
|
+
def test_sanitize_basic_cases(actual: str, expected: str) -> None:
|
|
18
|
+
assert sanitize_json_string(actual) == expected
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_sanitize_multiple_control_chars() -> None:
|
|
22
|
+
raw = "A\nB\rC\tD\0E"
|
|
23
|
+
result = sanitize_json_string(raw)
|
|
24
|
+
assert result == "A\\nB\\rC\\tD\\u0000E"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_sanitize_idempotent() -> None:
|
|
28
|
+
raw = "foo\nbar"
|
|
29
|
+
once = sanitize_json_string(raw)
|
|
30
|
+
twice = sanitize_json_string(once)
|
|
31
|
+
assert once == twice
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_sanitize_empty_string() -> None:
|
|
35
|
+
assert sanitize_json_string("") == ""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_sanitize_only_control_chars() -> None:
|
|
39
|
+
raw = "\n\r\t\0"
|
|
40
|
+
result = sanitize_json_string(raw)
|
|
41
|
+
assert result == "\\n\\r\\t\\u0000"
|
|
@@ -166,3 +166,26 @@ def test_build_added_and_removed_with_context(sample_diff_file: DiffFile) -> Non
|
|
|
166
166
|
" 2: keep B\n"
|
|
167
167
|
"+3: added me # added"
|
|
168
168
|
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_build_full_file_diff_empty_file() -> None:
|
|
172
|
+
"""
|
|
173
|
+
Should handle new empty file (mode=NEW, no hunks).
|
|
174
|
+
"""
|
|
175
|
+
file = DiffFile(
|
|
176
|
+
header="diff --git a/LICENSE b/LICENSE",
|
|
177
|
+
mode=FileMode.NEW,
|
|
178
|
+
orig_name="",
|
|
179
|
+
new_name="LICENSE",
|
|
180
|
+
hunks=[],
|
|
181
|
+
)
|
|
182
|
+
out = renderers.build_full_file_diff(file)
|
|
183
|
+
assert "New empty file: LICENSE" in out or "No matching lines" in out
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_build_full_file_diff_none() -> None:
|
|
187
|
+
"""
|
|
188
|
+
Should handle case when diff target is None.
|
|
189
|
+
"""
|
|
190
|
+
out = renderers.build_full_file_diff(None)
|
|
191
|
+
assert "Diff target not found" in out or out == ""
|
|
@@ -47,3 +47,55 @@ def test_no_json_array_found_logs_and_returns_empty():
|
|
|
47
47
|
output = "this is not json at all"
|
|
48
48
|
result = InlineCommentService.parse_model_output(output)
|
|
49
49
|
assert result.root == []
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_json_with_raw_newline_sanitized():
|
|
53
|
+
output = '[{"file": "e.py", "line": 3, "message": "line1\nline2"}]'
|
|
54
|
+
result = InlineCommentService.parse_model_output(output)
|
|
55
|
+
assert len(result.root) == 1
|
|
56
|
+
assert result.root[0].message == "line1\nline2"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_json_with_tab_character_sanitized():
|
|
60
|
+
output = '[{"file": "f.py", "line": 4, "message": "a\tb"}]'
|
|
61
|
+
result = InlineCommentService.parse_model_output(output)
|
|
62
|
+
assert len(result.root) == 1
|
|
63
|
+
assert result.root[0].message == "a\tb"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_json_with_null_byte_sanitized():
|
|
67
|
+
raw = "abc\0def"
|
|
68
|
+
output = f'[{{"file": "g.py", "line": 5, "message": "{raw}"}}]'
|
|
69
|
+
result = InlineCommentService.parse_model_output(output)
|
|
70
|
+
assert len(result.root) == 1
|
|
71
|
+
assert result.root[0].message == "abc\0def"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_json_with_multiple_control_chars():
|
|
75
|
+
raw = "x\n\ry\t\0z"
|
|
76
|
+
output = f'[{{"file": "h.py", "line": 6, "message": "{raw}"}}]'
|
|
77
|
+
result = InlineCommentService.parse_model_output(output)
|
|
78
|
+
assert len(result.root) == 1
|
|
79
|
+
assert result.root[0].message == "x\n\ry\t\0z"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_try_parse_valid_json():
|
|
83
|
+
raw = '[{"file": "ok.py", "line": 1, "message": "all good"}]'
|
|
84
|
+
result = InlineCommentService.try_parse_model_output(raw)
|
|
85
|
+
assert isinstance(result, InlineCommentListSchema)
|
|
86
|
+
assert len(result.root) == 1
|
|
87
|
+
assert result.root[0].file == "ok.py"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_try_parse_needs_sanitization():
|
|
91
|
+
raw = '[{"file": "bad.py", "line": 2, "message": "line1\nline2"}]'
|
|
92
|
+
result = InlineCommentService.try_parse_model_output(raw)
|
|
93
|
+
assert result is not None
|
|
94
|
+
assert result.root[0].file == "bad.py"
|
|
95
|
+
assert "line1" in result.root[0].message
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_try_parse_totally_invalid_returns_none():
|
|
99
|
+
raw = "this is not json at all"
|
|
100
|
+
result = InlineCommentService.try_parse_model_output(raw)
|
|
101
|
+
assert result is None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xai-review
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.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>
|
|
@@ -298,6 +298,6 @@ ai-review:
|
|
|
298
298
|
|
|
299
299
|
## 📂 Examples
|
|
300
300
|
|
|
301
|
-
- [./docs/ci
|
|
301
|
+
- [./docs/ci](./docs/ci) — ready-to-use CI snippets
|
|
302
302
|
- [./docs/configs](./docs/configs) — sample `.yaml`, `.json`, `.env` configs
|
|
303
303
|
- [./docs/prompts](./docs/prompts) — prompt templates for Python/Go (light & strict modes)
|
|
@@ -15,7 +15,7 @@ ai_review/clients/gemini/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
|
|
|
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
17
|
ai_review/clients/gitlab/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
ai_review/clients/gitlab/client.py,sha256=
|
|
18
|
+
ai_review/clients/gitlab/client.py,sha256=acMflkHGp8mv0TVLdZ1gmdXkWQPcq609QjmkYWjEmys,1136
|
|
19
19
|
ai_review/clients/gitlab/mr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
20
|
ai_review/clients/gitlab/mr/client.py,sha256=-4KHBie8NlHzX5LXdV9c9aL7UbHxuQ5XsDq701U-6q8,3844
|
|
21
21
|
ai_review/clients/gitlab/mr/schema/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -26,6 +26,7 @@ ai_review/clients/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
|
|
|
26
26
|
ai_review/clients/openai/client.py,sha256=N07ZRnbptOtab8imMUZbGL-kRoOwIZmNYbHySumD3IU,1707
|
|
27
27
|
ai_review/clients/openai/schema.py,sha256=glxwMtBrDA6W0BQgH-ruKe0bKH3Ps1P-Y1-2jGdqaUM,764
|
|
28
28
|
ai_review/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
ai_review/libs/json.py,sha256=koGnjcPgHBq3DHvzr090pM1ZCPtM9TjAoqWhnZJIo1I,460
|
|
29
30
|
ai_review/libs/logger.py,sha256=LbXR2Zk1btJ-83I-vHee7cUETgT1mHToSsqEI_8uM0U,370
|
|
30
31
|
ai_review/libs/resources.py,sha256=s9taAbL1Shl_GiGkPpkkivUcM1Yh6d_IQAG97gffsJU,748
|
|
31
32
|
ai_review/libs/asynchronous/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -78,7 +79,7 @@ ai_review/services/cost/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
|
78
79
|
ai_review/services/cost/schema.py,sha256=K3uCIMMxGL8AaIPh4a-d0mT5uIJuk3f805DkP8o8DtY,1323
|
|
79
80
|
ai_review/services/cost/service.py,sha256=rK-jw0lDszv_O13CRZAGK7R-fB-Y7xakX8aVb86zcEk,2103
|
|
80
81
|
ai_review/services/diff/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
81
|
-
ai_review/services/diff/renderers.py,sha256=
|
|
82
|
+
ai_review/services/diff/renderers.py,sha256=tEUml-uqsi5FNoU2NYjxehsZHU61dTPR2VnFi7QVzV4,5861
|
|
82
83
|
ai_review/services/diff/schema.py,sha256=17GAQY1-ySwREJ1-NKNKgBcstMJ5Hb42FcFG2p7i6Rs,94
|
|
83
84
|
ai_review/services/diff/service.py,sha256=FDuMw_y2QVQcfSbkVv3H2uGf1sIMeQ0_KHYCPhCU24g,3498
|
|
84
85
|
ai_review/services/diff/tools.py,sha256=YHmH6Ult_rucCd563UhG0geMzqrPhqKFZKyug79xNuA,1963
|
|
@@ -95,14 +96,14 @@ ai_review/services/llm/gemini/client.py,sha256=7FAOlTi8rV6b8g8aWcg-0LIP77AtbAoPt
|
|
|
95
96
|
ai_review/services/llm/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
96
97
|
ai_review/services/llm/openai/client.py,sha256=WhMXNfH_G1NTlFkdRK5sgYvrCIE5ZQNfPhdYx49IXFk,1143
|
|
97
98
|
ai_review/services/prompt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
98
|
-
ai_review/services/prompt/adapter.py,sha256=
|
|
99
|
+
ai_review/services/prompt/adapter.py,sha256=humGHLRVBu0JspeULgYHCs782BAy4YYKSf5yaG8aF24,1003
|
|
99
100
|
ai_review/services/prompt/schema.py,sha256=erAecUYzOWyZfixt-pjmPSnvcMDh5DajMd1b7_SPm_o,2052
|
|
100
101
|
ai_review/services/prompt/service.py,sha256=VsY8mj6UvY1a4Zsb8JlDJIg_8l7LBW6PXrObiHCwCzo,2128
|
|
101
102
|
ai_review/services/review/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
102
103
|
ai_review/services/review/service.py,sha256=8YhRFqhZAk2pAnkDaytKSCENlOeOti1brAJq3R9tVMY,8394
|
|
103
104
|
ai_review/services/review/inline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
104
105
|
ai_review/services/review/inline/schema.py,sha256=ry8sJdTgusQvFW51neRiapzgzVGwswwJzdYhaV3hbT0,1545
|
|
105
|
-
ai_review/services/review/inline/service.py,sha256=
|
|
106
|
+
ai_review/services/review/inline/service.py,sha256=qJqtjLY1En07_ZiI_wnhJHZDFfUSUHDqkMXutd1ZuMc,2131
|
|
106
107
|
ai_review/services/review/policy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
107
108
|
ai_review/services/review/policy/service.py,sha256=yGDePLxAEF3N1Pkh47jGVd-4dGEESyxDXIXxV7KQfuY,2027
|
|
108
109
|
ai_review/services/review/summary/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -130,6 +131,7 @@ ai_review/tests/suites/clients/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCe
|
|
|
130
131
|
ai_review/tests/suites/clients/openai/test_client.py,sha256=Ox5ifP1C_gSeDRacBa9DnXhe__Z8-WcbzR2JH_V3xKo,1050
|
|
131
132
|
ai_review/tests/suites/clients/openai/test_schema.py,sha256=x1tamS4GC9pOTpjieKDbK2D73CVV4BkATppytwMevLo,1599
|
|
132
133
|
ai_review/tests/suites/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
134
|
+
ai_review/tests/suites/libs/test_json.py,sha256=vmbkzRRyPuP-0uyLLlj-Tf_0MAWI_V2wDev9sM8tAHw,1054
|
|
133
135
|
ai_review/tests/suites/libs/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
134
136
|
ai_review/tests/suites/libs/config/test_prompt.py,sha256=kDMTnykC54tTPfE6cqYRBbV8d5jzAKucXdJfwtqUybM,2242
|
|
135
137
|
ai_review/tests/suites/libs/diff/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -140,7 +142,7 @@ ai_review/tests/suites/libs/template/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeu
|
|
|
140
142
|
ai_review/tests/suites/libs/template/test_render.py,sha256=n-ss5bd_hwc-RzYmqWmFM6KSlP1zLSnlsW1Yki12Bpw,1890
|
|
141
143
|
ai_review/tests/suites/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
142
144
|
ai_review/tests/suites/services/diff/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
143
|
-
ai_review/tests/suites/services/diff/test_renderers.py,sha256=
|
|
145
|
+
ai_review/tests/suites/services/diff/test_renderers.py,sha256=IKOpsGedONNW8ZfYTAk0Vq0hfFi7L6TpWs8vVVQroj0,6273
|
|
144
146
|
ai_review/tests/suites/services/diff/test_service.py,sha256=iFkGX9Vj2X44JU3eFsoHsg9o9353eKX-QCv_J9KxfzU,3162
|
|
145
147
|
ai_review/tests/suites/services/diff/test_tools.py,sha256=HBQ3eCn-kLeb_k5iTgyr09x0VwLYSegSbxm0Qk9ZrCc,3543
|
|
146
148
|
ai_review/tests/suites/services/prompt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -149,15 +151,15 @@ ai_review/tests/suites/services/prompt/test_service.py,sha256=M8vvBhEbyHnXCSiIRu
|
|
|
149
151
|
ai_review/tests/suites/services/review/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
150
152
|
ai_review/tests/suites/services/review/inline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
151
153
|
ai_review/tests/suites/services/review/inline/test_schema.py,sha256=tIz-1UA_GgwcdsyUqgrodiiVVmd_jhoOVmtEwzRVWiY,2474
|
|
152
|
-
ai_review/tests/suites/services/review/inline/test_service.py,sha256=
|
|
154
|
+
ai_review/tests/suites/services/review/inline/test_service.py,sha256=ZcrEWKyD-Fsu3tqAOzT1Et1bxLbX6Hn0_Jc9xE_1_78,3566
|
|
153
155
|
ai_review/tests/suites/services/review/policy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
154
156
|
ai_review/tests/suites/services/review/policy/test_service.py,sha256=kRWT550OjWYQ7ZfsihBRc-tx-NMkhlynEsqur55RK0M,3687
|
|
155
157
|
ai_review/tests/suites/services/review/summary/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
156
158
|
ai_review/tests/suites/services/review/summary/test_schema.py,sha256=xSoydvABZldHaVDa0OBFvYrj8wMuZqUDN3MO-XdvxOI,819
|
|
157
159
|
ai_review/tests/suites/services/review/summary/test_service.py,sha256=8UMvi_NL9frm280vD6Q1NCDrdI7K8YbXzoViIus-I2g,541
|
|
158
|
-
xai_review-0.
|
|
159
|
-
xai_review-0.
|
|
160
|
-
xai_review-0.
|
|
161
|
-
xai_review-0.
|
|
162
|
-
xai_review-0.
|
|
163
|
-
xai_review-0.
|
|
160
|
+
xai_review-0.10.0.dist-info/licenses/LICENSE,sha256=p-v8m7Kmz4KKc7PcvsGiGEmCw9AiSXY4_ylOPy_u--Y,11343
|
|
161
|
+
xai_review-0.10.0.dist-info/METADATA,sha256=eiLnNDKQiqcKlXpaAL2-rkF8mHENCWJtUAUliMzeVic,9618
|
|
162
|
+
xai_review-0.10.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
163
|
+
xai_review-0.10.0.dist-info/entry_points.txt,sha256=JyC5URanMi5io5P_PXQf7H_I1OGIpk5cZQhaPQ0g4Zs,53
|
|
164
|
+
xai_review-0.10.0.dist-info/top_level.txt,sha256=sTsZbfzLoqvRZKdKa-BcxWvjlHdrpbeJ6DrGY0EuR0E,10
|
|
165
|
+
xai_review-0.10.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|