ai-cr 0.5.0__py3-none-any.whl → 0.6.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.
- ai_code_review/__main__.py +4 -4
- ai_code_review/bootstrap.py +1 -1
- ai_code_review/cli.py +135 -56
- ai_code_review/core.py +83 -17
- ai_code_review/utils.py +3 -3
- {ai_cr-0.5.0.dist-info → ai_cr-0.6.0.dist-info}/METADATA +10 -9
- ai_cr-0.6.0.dist-info/RECORD +15 -0
- ai_cr-0.5.0.dist-info/RECORD +0 -15
- {ai_cr-0.5.0.dist-info → ai_cr-0.6.0.dist-info}/LICENSE +0 -0
- {ai_cr-0.5.0.dist-info → ai_cr-0.6.0.dist-info}/WHEEL +0 -0
- {ai_cr-0.5.0.dist-info → ai_cr-0.6.0.dist-info}/entry_points.txt +0 -0
ai_code_review/__main__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from .cli import main
|
2
|
-
|
3
|
-
if __name__ == "__main__":
|
4
|
-
main()
|
1
|
+
from .cli import main
|
2
|
+
|
3
|
+
if __name__ == "__main__":
|
4
|
+
main()
|
ai_code_review/bootstrap.py
CHANGED
ai_code_review/cli.py
CHANGED
@@ -2,35 +2,33 @@ import asyncio
|
|
2
2
|
import logging
|
3
3
|
import sys
|
4
4
|
import os
|
5
|
-
import
|
5
|
+
import textwrap
|
6
|
+
import tempfile
|
6
7
|
import requests
|
7
8
|
|
8
9
|
import microcore as mc
|
9
|
-
import async_typer
|
10
10
|
import typer
|
11
|
-
from ai_code_review.utils import parse_refs_pair
|
12
11
|
from git import Repo
|
13
12
|
|
14
|
-
from .core import review
|
13
|
+
from .core import review, get_diff, filter_diff
|
15
14
|
from .report_struct import Report
|
16
15
|
from .constants import ENV_CONFIG_FILE
|
17
16
|
from .bootstrap import bootstrap
|
18
17
|
from .project_config import ProjectConfig
|
19
|
-
from .utils import
|
18
|
+
from .utils import no_subcommand, parse_refs_pair
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
default_command_app = async_typer.AsyncTyper(pretty_exceptions_show_locals=False)
|
24
|
-
if sys.platform == "win32":
|
25
|
-
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
20
|
+
app = typer.Typer(pretty_exceptions_show_locals=False)
|
21
|
+
app_no_subcommand = typer.Typer(pretty_exceptions_show_locals=False)
|
26
22
|
|
27
23
|
|
28
24
|
def main():
|
29
|
-
if
|
30
|
-
|
31
|
-
|
25
|
+
if sys.platform == "win32":
|
26
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
27
|
+
if no_subcommand(app):
|
32
28
|
bootstrap()
|
33
|
-
|
29
|
+
app_no_subcommand()
|
30
|
+
else:
|
31
|
+
app()
|
34
32
|
|
35
33
|
|
36
34
|
@app.callback(invoke_without_command=True)
|
@@ -39,27 +37,7 @@ def cli(ctx: typer.Context):
|
|
39
37
|
bootstrap()
|
40
38
|
|
41
39
|
|
42
|
-
|
43
|
-
@app.async_command(name="review", help="Perform code review")
|
44
|
-
async def cmd_review(
|
45
|
-
refs: str = typer.Argument(
|
46
|
-
default=None,
|
47
|
-
help="Git refs to review, [what]..[against] e.g. 'HEAD..HEAD~1'"
|
48
|
-
),
|
49
|
-
what: str = typer.Option(None, "--what", "-w", help="Git ref to review"),
|
50
|
-
against: str = typer.Option(
|
51
|
-
None,
|
52
|
-
"--against", "-vs", "--vs",
|
53
|
-
help="Git ref to compare against"
|
54
|
-
),
|
55
|
-
filters: str = typer.Option(
|
56
|
-
"", "--filter", "-f", "--filters",
|
57
|
-
help="""
|
58
|
-
filter reviewed files by glob / fnmatch pattern(s),
|
59
|
-
e.g. 'src/**/*.py', may be comma-separated
|
60
|
-
""",
|
61
|
-
)
|
62
|
-
):
|
40
|
+
def args_to_target(refs, what, against) -> tuple[str | None, str | None]:
|
63
41
|
_what, _against = parse_refs_pair(refs)
|
64
42
|
if _what:
|
65
43
|
if what:
|
@@ -75,34 +53,103 @@ async def cmd_review(
|
|
75
53
|
)
|
76
54
|
else:
|
77
55
|
_against = against
|
78
|
-
|
56
|
+
return _what, _against
|
79
57
|
|
80
58
|
|
81
|
-
|
82
|
-
|
83
|
-
|
59
|
+
def arg_refs() -> typer.Argument:
|
60
|
+
return typer.Argument(
|
61
|
+
default=None,
|
62
|
+
help="Git refs to review, [what]..[against] e.g. 'HEAD..HEAD~1'"
|
63
|
+
)
|
84
64
|
|
85
65
|
|
86
|
-
|
87
|
-
|
88
|
-
print(Report.load().render(format=format))
|
66
|
+
def arg_what() -> typer.Option:
|
67
|
+
return typer.Option(None, "--what", "-w", help="Git ref to review")
|
89
68
|
|
90
69
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
await review()
|
100
|
-
finally:
|
101
|
-
os.chdir(prev_dir)
|
70
|
+
def arg_filters() -> typer.Option:
|
71
|
+
return typer.Option(
|
72
|
+
"", "--filter", "-f", "--filters",
|
73
|
+
help="""
|
74
|
+
filter reviewed files by glob / fnmatch pattern(s),
|
75
|
+
e.g. 'src/**/*.py', may be comma-separated
|
76
|
+
""",
|
77
|
+
)
|
102
78
|
|
103
79
|
|
104
|
-
|
105
|
-
|
80
|
+
def arg_out() -> typer.Option:
|
81
|
+
return typer.Option(
|
82
|
+
None,
|
83
|
+
"--out", "-o", "--output",
|
84
|
+
help="Output folder for the code review report"
|
85
|
+
)
|
86
|
+
|
87
|
+
|
88
|
+
def arg_against() -> typer.Option:
|
89
|
+
return typer.Option(
|
90
|
+
None,
|
91
|
+
"--against", "-vs", "--vs",
|
92
|
+
help="Git ref to compare against"
|
93
|
+
)
|
94
|
+
|
95
|
+
|
96
|
+
@app_no_subcommand.command(name="review", help="Perform code review")
|
97
|
+
@app.command(name="review", help="Perform code review")
|
98
|
+
def cmd_review(
|
99
|
+
refs: str = arg_refs(),
|
100
|
+
what: str = arg_what(),
|
101
|
+
against: str = arg_against(),
|
102
|
+
filters: str = arg_filters(),
|
103
|
+
merge_base: bool = typer.Option(default=True, help="Use merge base for comparison"),
|
104
|
+
out: str = arg_out()
|
105
|
+
):
|
106
|
+
_what, _against = args_to_target(refs, what, against)
|
107
|
+
asyncio.run(review(
|
108
|
+
what=_what,
|
109
|
+
against=_against,
|
110
|
+
filters=filters,
|
111
|
+
use_merge_base=merge_base,
|
112
|
+
out_folder=out,
|
113
|
+
))
|
114
|
+
|
115
|
+
|
116
|
+
@app.command(help="Configure LLM for local usage interactively")
|
117
|
+
def setup():
|
118
|
+
mc.interactive_setup(ENV_CONFIG_FILE)
|
119
|
+
|
120
|
+
|
121
|
+
@app.command()
|
122
|
+
def render(format: str = Report.Format.MARKDOWN):
|
123
|
+
print(Report.load().render(format=format))
|
124
|
+
|
125
|
+
|
126
|
+
@app.command(help="Review remote code")
|
127
|
+
def remote(
|
128
|
+
url: str = typer.Argument(..., help="Git repository URL"),
|
129
|
+
refs: str = arg_refs(),
|
130
|
+
what: str = arg_what(),
|
131
|
+
against: str = arg_against(),
|
132
|
+
filters: str = arg_filters(),
|
133
|
+
merge_base: bool = typer.Option(default=True, help="Use merge base for comparison"),
|
134
|
+
out: str = arg_out()
|
135
|
+
):
|
136
|
+
_what, _against = args_to_target(refs, what, against)
|
137
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
138
|
+
logging.info(f"Cloning [{mc.ui.green(url)}] to {mc.utils.file_link(temp_dir)} ...")
|
139
|
+
repo = Repo.clone_from(url, branch=_what, to_path=temp_dir)
|
140
|
+
asyncio.run(review(
|
141
|
+
repo=repo,
|
142
|
+
what=_what,
|
143
|
+
against=_against,
|
144
|
+
filters=filters,
|
145
|
+
use_merge_base=merge_base,
|
146
|
+
out_folder=out or '.',
|
147
|
+
))
|
148
|
+
repo.close()
|
149
|
+
|
150
|
+
|
151
|
+
@app.command(help="Leave a GitHub PR comment with the review.")
|
152
|
+
def github_comment(
|
106
153
|
token: str = typer.Option(
|
107
154
|
os.environ.get("GITHUB_TOKEN", ""), help="GitHub token (or set GITHUB_TOKEN env var)"
|
108
155
|
),
|
@@ -110,7 +157,7 @@ async def github_comment(
|
|
110
157
|
"""
|
111
158
|
Leaves a comment with the review on the current GitHub pull request.
|
112
159
|
"""
|
113
|
-
file = "code-review-report.
|
160
|
+
file = "code-review-report.md"
|
114
161
|
if not os.path.exists(file):
|
115
162
|
print(f"Review file not found: {file}")
|
116
163
|
raise typer.Exit(4)
|
@@ -155,3 +202,35 @@ async def github_comment(
|
|
155
202
|
else:
|
156
203
|
logging.error(f"Failed to post comment: {resp.status_code} {resp.reason}\n{resp.text}")
|
157
204
|
raise typer.Exit(5)
|
205
|
+
|
206
|
+
|
207
|
+
@app.command(help="List files in the diff. Might be useful to check what will be reviewed.")
|
208
|
+
def files(
|
209
|
+
refs: str = arg_refs(),
|
210
|
+
what: str = arg_what(),
|
211
|
+
against: str = arg_against(),
|
212
|
+
filters: str = arg_filters(),
|
213
|
+
merge_base: bool = typer.Option(default=True, help="Use merge base for comparison"),
|
214
|
+
diff: bool = typer.Option(default=False, help="Show diff content")
|
215
|
+
):
|
216
|
+
_what, _against = args_to_target(refs, what, against)
|
217
|
+
repo = Repo(".")
|
218
|
+
patch_set = get_diff(repo=repo, what=_what, against=_against, use_merge_base=merge_base)
|
219
|
+
patch_set = filter_diff(patch_set, filters)
|
220
|
+
print(
|
221
|
+
f"Changed files: "
|
222
|
+
f"{mc.ui.green(_what or 'INDEX')} vs "
|
223
|
+
f"{mc.ui.yellow(_against or repo.remotes.origin.refs.HEAD.reference.name)}"
|
224
|
+
f"{' filtered by '+mc.ui.cyan(filters) if filters else ''}"
|
225
|
+
)
|
226
|
+
|
227
|
+
for patch in patch_set:
|
228
|
+
if patch.is_added_file:
|
229
|
+
color = mc.ui.green
|
230
|
+
elif patch.is_removed_file:
|
231
|
+
color = mc.ui.red
|
232
|
+
else:
|
233
|
+
color = mc.ui.blue
|
234
|
+
print(f"- {color(patch.path)}")
|
235
|
+
if diff:
|
236
|
+
print(mc.ui.gray(textwrap.indent(str(patch), " ")))
|
ai_code_review/core.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
import fnmatch
|
2
2
|
import logging
|
3
|
+
from os import PathLike
|
3
4
|
from typing import Iterable
|
5
|
+
from pathlib import Path
|
4
6
|
|
5
7
|
import microcore as mc
|
6
8
|
from git import Repo
|
@@ -9,22 +11,76 @@ from unidiff.constants import DEV_NULL
|
|
9
11
|
|
10
12
|
from .project_config import ProjectConfig
|
11
13
|
from .report_struct import Report
|
14
|
+
from .constants import JSON_REPORT_FILE_NAME
|
15
|
+
|
16
|
+
|
17
|
+
def is_binary_file(repo: Repo, file_path: str) -> bool:
|
18
|
+
"""
|
19
|
+
Check if a file is binary by attempting to read it as text.
|
20
|
+
Returns True if the file is binary, False otherwise.
|
21
|
+
"""
|
22
|
+
try:
|
23
|
+
# Attempt to read the file content from the repository tree
|
24
|
+
content = repo.tree()[file_path].data_stream.read()
|
25
|
+
# Try decoding as UTF-8; if it fails, it's likely binary
|
26
|
+
content.decode("utf-8")
|
27
|
+
return False
|
28
|
+
except (UnicodeDecodeError, KeyError):
|
29
|
+
return True
|
30
|
+
except Exception as e:
|
31
|
+
logging.warning(f"Error checking if file {file_path} is binary: {e}")
|
32
|
+
return True # Conservatively treat errors as binary to avoid issues
|
12
33
|
|
13
34
|
|
14
35
|
def get_diff(
|
15
36
|
repo: Repo = None,
|
16
37
|
what: str = None,
|
17
|
-
against: str = None
|
38
|
+
against: str = None,
|
39
|
+
use_merge_base: bool = True,
|
18
40
|
) -> PatchSet | list[PatchedFile]:
|
19
41
|
repo = repo or Repo(".")
|
20
42
|
if not against:
|
21
43
|
against = repo.remotes.origin.refs.HEAD.reference.name # origin/main
|
22
44
|
if not what:
|
23
45
|
what = None # working copy
|
24
|
-
|
46
|
+
if use_merge_base:
|
47
|
+
if what is None:
|
48
|
+
try:
|
49
|
+
current_ref = repo.active_branch.name
|
50
|
+
except TypeError:
|
51
|
+
# In detached HEAD state, use HEAD directly
|
52
|
+
current_ref = "HEAD"
|
53
|
+
logging.info(
|
54
|
+
"Detected detached HEAD state, using HEAD as current reference"
|
55
|
+
)
|
56
|
+
else:
|
57
|
+
current_ref = what
|
58
|
+
merge_base = repo.merge_base(current_ref or repo.active_branch.name, against)[0]
|
59
|
+
against = merge_base.hexsha
|
60
|
+
logging.info(
|
61
|
+
f"Using merge base: {mc.ui.cyan(merge_base.hexsha[:8])} ({merge_base.summary})"
|
62
|
+
)
|
63
|
+
logging.info(
|
64
|
+
f"Making diff: {mc.ui.green(what or 'INDEX')} vs {mc.ui.yellow(against)}"
|
65
|
+
)
|
25
66
|
diff_content = repo.git.diff(against, what)
|
26
67
|
diff = PatchSet.from_string(diff_content)
|
27
|
-
|
68
|
+
diff = PatchSet.from_string(diff_content)
|
69
|
+
|
70
|
+
# Filter out binary files
|
71
|
+
non_binary_diff = PatchSet([])
|
72
|
+
for patched_file in diff:
|
73
|
+
# Check if the file is binary using the source or target file path
|
74
|
+
file_path = (
|
75
|
+
patched_file.target_file
|
76
|
+
if patched_file.target_file != DEV_NULL
|
77
|
+
else patched_file.source_file
|
78
|
+
)
|
79
|
+
if file_path == DEV_NULL or is_binary_file(repo, file_path.lstrip("b/")):
|
80
|
+
logging.info(f"Skipping binary file: {patched_file.path}")
|
81
|
+
continue
|
82
|
+
non_binary_diff.append(patched_file)
|
83
|
+
return non_binary_diff
|
28
84
|
|
29
85
|
|
30
86
|
def filter_diff(
|
@@ -33,7 +89,6 @@ def filter_diff(
|
|
33
89
|
"""
|
34
90
|
Filter the diff files by the given fnmatch filters.
|
35
91
|
"""
|
36
|
-
print([f.path for f in patch_set])
|
37
92
|
assert isinstance(filters, (list, str))
|
38
93
|
if not isinstance(filters, list):
|
39
94
|
filters = [f.strip() for f in filters.split(",") if f.strip()]
|
@@ -44,7 +99,6 @@ def filter_diff(
|
|
44
99
|
for file in patch_set
|
45
100
|
if any(fnmatch.fnmatch(file.path, pattern) for pattern in filters)
|
46
101
|
]
|
47
|
-
print([f.path for f in files])
|
48
102
|
return files
|
49
103
|
|
50
104
|
|
@@ -61,22 +115,32 @@ def file_lines(repo: Repo, file: str, max_tokens: int = None) -> str:
|
|
61
115
|
|
62
116
|
|
63
117
|
def make_cr_summary(cfg: ProjectConfig, report: Report, diff):
|
64
|
-
return
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
118
|
+
return (
|
119
|
+
mc.prompt(
|
120
|
+
cfg.summary_prompt,
|
121
|
+
diff=mc.tokenizing.fit_to_token_size(diff, cfg.max_code_tokens)[0],
|
122
|
+
issues=report.issues,
|
123
|
+
**cfg.prompt_vars,
|
124
|
+
).to_llm()
|
125
|
+
if cfg.summary_prompt
|
126
|
+
else ""
|
127
|
+
)
|
70
128
|
|
71
129
|
|
72
130
|
async def review(
|
131
|
+
repo: Repo = None,
|
73
132
|
what: str = None,
|
74
133
|
against: str = None,
|
75
|
-
filters: str | list[str] = ""
|
134
|
+
filters: str | list[str] = "",
|
135
|
+
use_merge_base: bool = True,
|
136
|
+
out_folder: str | PathLike | None = None,
|
76
137
|
):
|
77
138
|
cfg = ProjectConfig.load()
|
78
|
-
repo = Repo(".")
|
79
|
-
|
139
|
+
repo = repo or Repo(".")
|
140
|
+
out_folder = Path(out_folder or repo.working_tree_dir)
|
141
|
+
diff = get_diff(
|
142
|
+
repo=repo, what=what, against=against, use_merge_base=use_merge_base
|
143
|
+
)
|
80
144
|
diff = filter_diff(diff, filters)
|
81
145
|
if not diff:
|
82
146
|
logging.error("Nothing to review")
|
@@ -114,12 +178,14 @@ async def review(
|
|
114
178
|
if lines[file]:
|
115
179
|
f_lines = [""] + lines[file].splitlines()
|
116
180
|
i["affected_code"] = "\n".join(
|
117
|
-
f_lines[i["start_line"]: i["end_line"]+1]
|
181
|
+
f_lines[i["start_line"]: i["end_line"] + 1]
|
118
182
|
)
|
119
183
|
exec(cfg.post_process, {"mc": mc, **locals()})
|
184
|
+
out_folder.mkdir(parents=True, exist_ok=True)
|
120
185
|
report = Report(issues=issues, number_of_processed_files=len(diff))
|
121
186
|
report.summary = make_cr_summary(cfg, report, diff)
|
122
|
-
report.save()
|
187
|
+
report.save(file_name=out_folder / JSON_REPORT_FILE_NAME)
|
123
188
|
report_text = report.render(cfg, Report.Format.MARKDOWN)
|
124
189
|
print(mc.ui.yellow(report_text))
|
125
|
-
|
190
|
+
text_report_path = out_folder / "code-review-report.md"
|
191
|
+
text_report_path.write_text(report_text, encoding="utf-8")
|
ai_code_review/utils.py
CHANGED
@@ -92,11 +92,11 @@ def is_running_in_github_action():
|
|
92
92
|
return os.getenv("GITHUB_ACTIONS") == "true"
|
93
93
|
|
94
94
|
|
95
|
-
def
|
95
|
+
def no_subcommand(app: typer.Typer) -> bool:
|
96
96
|
"""
|
97
97
|
Checks if the current script is being invoked as a command in a target Typer application.
|
98
98
|
"""
|
99
|
-
return (
|
99
|
+
return not (
|
100
100
|
(first_arg := next((a for a in sys.argv[1:] if not a.startswith('-')), None))
|
101
101
|
and first_arg in (
|
102
102
|
cmd.name or cmd.callback.__name__.replace('_', '-')
|
@@ -106,7 +106,7 @@ def is_app_command_invocation(app: typer.Typer) -> bool:
|
|
106
106
|
)
|
107
107
|
|
108
108
|
|
109
|
-
def parse_refs_pair(refs: str):
|
109
|
+
def parse_refs_pair(refs: str) -> tuple[str | None, str | None]:
|
110
110
|
SEPARATOR = '..'
|
111
111
|
if not refs:
|
112
112
|
return None, None
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: ai-cr
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.0
|
4
4
|
Summary: LLM-agnostic GitHub AI Code Review Tool with integration to GitHub actions
|
5
5
|
License: MIT
|
6
6
|
Keywords: static code analysis,code review,code quality,ai,coding,assistant,llm,github,automation,devops,developer tools,github actions,workflows,git
|
@@ -15,13 +15,12 @@ Classifier: Programming Language :: Python :: 3.11
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
17
17
|
Classifier: Topic :: Software Development
|
18
|
-
Requires-Dist: GitPython (
|
19
|
-
Requires-Dist: ai-microcore (==4.0.0.
|
20
|
-
Requires-Dist: anthropic (
|
21
|
-
Requires-Dist:
|
22
|
-
Requires-Dist:
|
23
|
-
Requires-Dist:
|
24
|
-
Requires-Dist: unidiff (==0.7.5)
|
18
|
+
Requires-Dist: GitPython (>=3.1.44,<4.0.0)
|
19
|
+
Requires-Dist: ai-microcore (==4.0.0.dev19)
|
20
|
+
Requires-Dist: anthropic (>=0.52.2,<0.53.0)
|
21
|
+
Requires-Dist: google-generativeai (>=0.8.5,<0.9.0)
|
22
|
+
Requires-Dist: typer (>=0.16.0,<0.17.0)
|
23
|
+
Requires-Dist: unidiff (>=0.7.5,<0.8.0)
|
25
24
|
Project-URL: Homepage, https://github.com/Nayjest/github-ai-code-review
|
26
25
|
Project-URL: Repository, https://github.com/Nayjest/github-ai-code-review
|
27
26
|
Description-Content-Type: text/markdown
|
@@ -30,6 +29,7 @@ Description-Content-Type: text/markdown
|
|
30
29
|
<a href="https://pypi.org/project/ai-code-review/" target="_blank"><img src="https://badge.fury.io/py/ai-code-review.svg" alt="PYPI Release"></a>
|
31
30
|
<a href="https://github.com/Nayjest/ai-code-review/actions/workflows/code-style.yml" target="_blank"><img src="https://github.com/Nayjest/ai-code-review/actions/workflows/code-style.yml/badge.svg" alt="Pylint"></a>
|
32
31
|
<a href="https://github.com/Nayjest/ai-code-review/actions/workflows/tests.yml" target="_blank"><img src="https://github.com/Nayjest/ai-code-review/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
|
32
|
+
<img src="https://github.com/Nayjest/ai-code-review/blob/main/coverage.svg" alt="Code Coverage">
|
33
33
|
<a href="https://github.com/Nayjest/ai-code-review/blob/main/LICENSE" target="_blank"><img src="https://img.shields.io/static/v1?label=license&message=MIT&color=d08aff" alt="License"></a>
|
34
34
|
</p>
|
35
35
|
|
@@ -91,7 +91,8 @@ jobs:
|
|
91
91
|
> ⚠️ Make sure to add `LLM_API_KEY` to your repository’s GitHub secrets.
|
92
92
|
|
93
93
|
💪 Done!
|
94
|
-
PRs to your repository will now receive AI code reviews automatically. ✨
|
94
|
+
PRs to your repository will now receive AI code reviews automatically. ✨
|
95
|
+
See [GitHub Setup Guide](https://github.com/Nayjest/ai-code-review/blob/main/documentation/github_setup.md) for more details.
|
95
96
|
|
96
97
|
### 2. Run Code Analysis Locally
|
97
98
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
ai_code_review/.ai-code-review.toml,sha256=z-3iliCvkkpdAr9l_yUyM-IbgRm9hbAA3CIV6ocuurY,10378
|
2
|
+
ai_code_review/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
ai_code_review/__main__.py,sha256=EClCwCzb6h6YBpt0hrnG4h0mlNhNePyg_xBNNSVm1os,65
|
4
|
+
ai_code_review/bootstrap.py,sha256=jqioR_UtTsn5nXezmjMLU3aB8tzlVz73ZRBk33ud5F4,2336
|
5
|
+
ai_code_review/cli.py,sha256=1wrF-s6U_f8BjwCl70G7IXIBEhmMUbQDvij5AMDxQ5M,7318
|
6
|
+
ai_code_review/constants.py,sha256=K9mNxTq9seTG3aVm__3r1lXb5oCOQjH24Cl3hfX9FsE,281
|
7
|
+
ai_code_review/core.py,sha256=BJNs4ZER2-bMulXY2apY6B6hI0nvRCOrLqsJ7L8Bizc,6693
|
8
|
+
ai_code_review/project_config.py,sha256=RDbplmncALKw0zgqSG8POofi300z0DPvtF33wt7_1Sk,3651
|
9
|
+
ai_code_review/report_struct.py,sha256=N-EnNMwBY9LyJ9sdFHpUzn2fwBvxo5TZYYiJagBl8Po,3488
|
10
|
+
ai_code_review/utils.py,sha256=vlzU3M89qK6_mVkBMnppZaOFsXddVsIBVdfmbN3cxDY,2939
|
11
|
+
ai_cr-0.6.0.dist-info/entry_points.txt,sha256=u0N5NroPYEGqmDGaGqFmiijJ5swzpe7EyKBupnkp1FY,49
|
12
|
+
ai_cr-0.6.0.dist-info/LICENSE,sha256=XATf3zv-CppUSJqI18KLhwnPEomUXEl5WbBzFyb9OSU,1096
|
13
|
+
ai_cr-0.6.0.dist-info/METADATA,sha256=poIlyrRUJQEKwRN7JIgKGOjgL1DR291tw6jibTi4EMs,5835
|
14
|
+
ai_cr-0.6.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
15
|
+
ai_cr-0.6.0.dist-info/RECORD,,
|
ai_cr-0.5.0.dist-info/RECORD
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
ai_code_review/.ai-code-review.toml,sha256=z-3iliCvkkpdAr9l_yUyM-IbgRm9hbAA3CIV6ocuurY,10378
|
2
|
-
ai_code_review/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
ai_code_review/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
|
4
|
-
ai_code_review/bootstrap.py,sha256=b9BlhRgliwsjmWRCCZcsTivsOw9AbXOf8_AZYjQTx-c,2336
|
5
|
-
ai_code_review/cli.py,sha256=L1utFMHs4D9HVcuJ1VC96PEvP6q26UHsfCz_Lq49VMg,4893
|
6
|
-
ai_code_review/constants.py,sha256=K9mNxTq9seTG3aVm__3r1lXb5oCOQjH24Cl3hfX9FsE,281
|
7
|
-
ai_code_review/core.py,sha256=WMGU2zwZTJ1P6PkUId-RJP7KHQbzB31GKSnI5wsedcA,4177
|
8
|
-
ai_code_review/project_config.py,sha256=RDbplmncALKw0zgqSG8POofi300z0DPvtF33wt7_1Sk,3651
|
9
|
-
ai_code_review/report_struct.py,sha256=N-EnNMwBY9LyJ9sdFHpUzn2fwBvxo5TZYYiJagBl8Po,3488
|
10
|
-
ai_code_review/utils.py,sha256=UzFp2ShNzQvsydxJ98bdr_j2rBUHcm62pPtl6hBuZD4,2914
|
11
|
-
ai_cr-0.5.0.dist-info/entry_points.txt,sha256=u0N5NroPYEGqmDGaGqFmiijJ5swzpe7EyKBupnkp1FY,49
|
12
|
-
ai_cr-0.5.0.dist-info/LICENSE,sha256=XATf3zv-CppUSJqI18KLhwnPEomUXEl5WbBzFyb9OSU,1096
|
13
|
-
ai_cr-0.5.0.dist-info/METADATA,sha256=k3FaB6nbVV0xbgZYPg_VQ6zMauB65Xy6CLWI4qFOMF0,5609
|
14
|
-
ai_cr-0.5.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
15
|
-
ai_cr-0.5.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|