ai-cr 1.0.1__py3-none-any.whl → 2.0.0.dev2__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_cr-2.0.0.dev2.dist-info/METADATA +207 -0
- ai_cr-2.0.0.dev2.dist-info/RECORD +23 -0
- ai_cr-2.0.0.dev2.dist-info/entry_points.txt +3 -0
- {ai_code_review → gito}/bootstrap.py +8 -4
- {ai_code_review → gito}/cli.py +29 -12
- gito/commands/__init__.py +1 -0
- gito/commands/fix.py +157 -0
- gito/commands/gh_comment.py +157 -0
- {ai_code_review → gito}/commands/repl.py +3 -1
- ai_code_review/.ai-code-review.toml → gito/config.toml +113 -9
- gito/constants.py +9 -0
- {ai_code_review → gito}/core.py +64 -16
- gito/issue_trackers.py +15 -0
- gito/pipeline.py +70 -0
- gito/pipeline_steps/__init__.py +0 -0
- gito/pipeline_steps/jira.py +83 -0
- gito/project_config.py +71 -0
- {ai_code_review → gito}/report_struct.py +32 -7
- gito/utils.py +214 -0
- ai_code_review/constants.py +0 -7
- ai_code_review/project_config.py +0 -99
- ai_code_review/utils.py +0 -116
- ai_cr-1.0.1.dist-info/METADATA +0 -197
- ai_cr-1.0.1.dist-info/RECORD +0 -16
- ai_cr-1.0.1.dist-info/entry_points.txt +0 -3
- {ai_cr-1.0.1.dist-info → ai_cr-2.0.0.dev2.dist-info}/LICENSE +0 -0
- {ai_cr-1.0.1.dist-info → ai_cr-2.0.0.dev2.dist-info}/WHEEL +0 -0
- {ai_code_review → gito}/__init__.py +0 -0
- {ai_code_review → gito}/__main__.py +0 -0
gito/utils.py
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
import re
|
2
|
+
import sys
|
3
|
+
import os
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
import typer
|
7
|
+
import git
|
8
|
+
from git import Repo
|
9
|
+
|
10
|
+
_EXT_TO_HINT: dict[str, str] = {
|
11
|
+
# scripting & languages
|
12
|
+
".py": "python",
|
13
|
+
".js": "javascript",
|
14
|
+
".ts": "typescript",
|
15
|
+
".java": "java",
|
16
|
+
".c": "c",
|
17
|
+
".cpp": "cpp",
|
18
|
+
".cc": "cpp",
|
19
|
+
".cxx": "cpp",
|
20
|
+
".h": "cpp",
|
21
|
+
".hpp": "cpp",
|
22
|
+
".cs": "csharp",
|
23
|
+
".rb": "ruby",
|
24
|
+
".go": "go",
|
25
|
+
".rs": "rust",
|
26
|
+
".swift": "swift",
|
27
|
+
".kt": "kotlin",
|
28
|
+
".scala": "scala",
|
29
|
+
".dart": "dart",
|
30
|
+
".php": "php",
|
31
|
+
".pl": "perl",
|
32
|
+
".pm": "perl",
|
33
|
+
".lua": "lua",
|
34
|
+
# web & markup
|
35
|
+
".html": "html",
|
36
|
+
".htm": "html",
|
37
|
+
".css": "css",
|
38
|
+
".scss": "scss",
|
39
|
+
".less": "less",
|
40
|
+
".json": "json",
|
41
|
+
".xml": "xml",
|
42
|
+
".yaml": "yaml",
|
43
|
+
".yml": "yaml",
|
44
|
+
".toml": "toml",
|
45
|
+
".ini": "ini",
|
46
|
+
".csv": "csv",
|
47
|
+
".md": "markdown",
|
48
|
+
".rst": "rest",
|
49
|
+
# shell & config
|
50
|
+
".sh": "bash",
|
51
|
+
".bash": "bash",
|
52
|
+
".zsh": "bash",
|
53
|
+
".fish": "bash",
|
54
|
+
".ps1": "powershell",
|
55
|
+
".dockerfile": "dockerfile",
|
56
|
+
# build & CI
|
57
|
+
".makefile": "makefile",
|
58
|
+
".mk": "makefile",
|
59
|
+
"CMakeLists.txt": "cmake",
|
60
|
+
"Dockerfile": "dockerfile",
|
61
|
+
".gradle": "groovy",
|
62
|
+
".travis.yml": "yaml",
|
63
|
+
# data & queries
|
64
|
+
".sql": "sql",
|
65
|
+
".graphql": "graphql",
|
66
|
+
".proto": "protobuf",
|
67
|
+
".yara": "yara",
|
68
|
+
}
|
69
|
+
|
70
|
+
|
71
|
+
def syntax_hint(file_path: str | Path) -> str:
|
72
|
+
"""
|
73
|
+
Returns a syntax highlighting hint based on the file's extension or name.
|
74
|
+
|
75
|
+
This can be used to annotate code blocks for rendering with syntax highlighting,
|
76
|
+
e.g., using Markdown-style code blocks: ```<syntax_hint>\n<code>\n```.
|
77
|
+
|
78
|
+
Args:
|
79
|
+
file_path (str | Path): Path to the file.
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
str: A syntax identifier suitable for code highlighting (e.g., 'python', 'json').
|
83
|
+
"""
|
84
|
+
p = Path(file_path)
|
85
|
+
ext = p.suffix.lower()
|
86
|
+
if not ext:
|
87
|
+
name = p.name.lower()
|
88
|
+
if name == "dockerfile":
|
89
|
+
return "dockerfile"
|
90
|
+
return ""
|
91
|
+
return _EXT_TO_HINT.get(ext, ext.lstrip("."))
|
92
|
+
|
93
|
+
|
94
|
+
def is_running_in_github_action():
|
95
|
+
return os.getenv("GITHUB_ACTIONS") == "true"
|
96
|
+
|
97
|
+
|
98
|
+
def no_subcommand(app: typer.Typer) -> bool:
|
99
|
+
"""
|
100
|
+
Checks if the current script is being invoked as a command in a target Typer application.
|
101
|
+
"""
|
102
|
+
return not (
|
103
|
+
(first_arg := next((a for a in sys.argv[1:] if not a.startswith('-')), None))
|
104
|
+
and first_arg in (
|
105
|
+
cmd.name or cmd.callback.__name__.replace('_', '-')
|
106
|
+
for cmd in app.registered_commands
|
107
|
+
)
|
108
|
+
or '--help' in sys.argv
|
109
|
+
)
|
110
|
+
|
111
|
+
|
112
|
+
def parse_refs_pair(refs: str) -> tuple[str | None, str | None]:
|
113
|
+
SEPARATOR = '..'
|
114
|
+
if not refs:
|
115
|
+
return None, None
|
116
|
+
if SEPARATOR not in refs:
|
117
|
+
return refs, None
|
118
|
+
what, against = refs.split(SEPARATOR, 1)
|
119
|
+
return what or None, against or None
|
120
|
+
|
121
|
+
|
122
|
+
def max_line_len(text: str) -> int:
|
123
|
+
return max((len(line) for line in text.splitlines()), default=0)
|
124
|
+
|
125
|
+
|
126
|
+
def block_wrap_lr(text: str, left: str = "", right: str = "", max_rwrap: int = 60) -> str:
|
127
|
+
ml = max_line_len(text)
|
128
|
+
lines = text.splitlines()
|
129
|
+
wrapped_lines = []
|
130
|
+
for line in lines:
|
131
|
+
ln = left+line
|
132
|
+
if ml <= max_rwrap:
|
133
|
+
ln += ' ' * (ml - len(line)) + right
|
134
|
+
wrapped_lines.append(ln)
|
135
|
+
return "\n".join(wrapped_lines)
|
136
|
+
|
137
|
+
|
138
|
+
def extract_gh_owner_repo(repo: git.Repo) -> tuple[str, str]:
|
139
|
+
"""
|
140
|
+
Extracts the GitHub owner and repository name.
|
141
|
+
|
142
|
+
Returns:
|
143
|
+
tuple[str, str]: A tuple containing the owner and repository name.
|
144
|
+
"""
|
145
|
+
remote_url = repo.remotes.origin.url
|
146
|
+
if remote_url.startswith('git@github.com:'):
|
147
|
+
# SSH format: git@github.com:owner/repo.git
|
148
|
+
repo_path = remote_url.split(':')[1].replace('.git', '')
|
149
|
+
elif remote_url.startswith('https://github.com/'):
|
150
|
+
# HTTPS format: https://github.com/owner/repo.git
|
151
|
+
repo_path = remote_url.replace('https://github.com/', '').replace('.git', '')
|
152
|
+
else:
|
153
|
+
raise ValueError("Unsupported remote URL format")
|
154
|
+
owner, repo_name = repo_path.split('/')
|
155
|
+
return owner, repo_name
|
156
|
+
|
157
|
+
|
158
|
+
def detect_github_env() -> dict:
|
159
|
+
"""
|
160
|
+
Try to detect GitHub repository/PR info from environment variables (for GitHub Actions).
|
161
|
+
Returns a dict with github_repo, github_pr_sha, github_pr_number, github_ref, etc.
|
162
|
+
"""
|
163
|
+
repo = os.environ.get("GITHUB_REPOSITORY", "")
|
164
|
+
pr_sha = os.environ.get("GITHUB_SHA", "")
|
165
|
+
pr_number = os.environ.get("GITHUB_REF", "")
|
166
|
+
branch = ""
|
167
|
+
ref = os.environ.get("GITHUB_REF", "")
|
168
|
+
# Try to resolve PR head SHA if available.
|
169
|
+
# On PRs, GITHUB_HEAD_REF/BASE_REF contain branch names.
|
170
|
+
if "GITHUB_HEAD_REF" in os.environ:
|
171
|
+
branch = os.environ["GITHUB_HEAD_REF"]
|
172
|
+
elif ref.startswith("refs/heads/"):
|
173
|
+
branch = ref[len("refs/heads/"):]
|
174
|
+
elif ref.startswith("refs/pull/"):
|
175
|
+
# for pull_request events
|
176
|
+
branch = ref
|
177
|
+
|
178
|
+
d = {
|
179
|
+
"github_repo": repo,
|
180
|
+
"github_pr_sha": pr_sha,
|
181
|
+
"github_pr_number": pr_number,
|
182
|
+
"github_branch": branch,
|
183
|
+
"github_ref": ref,
|
184
|
+
}
|
185
|
+
# Fallback for local usage: try to get from git
|
186
|
+
if not repo:
|
187
|
+
git_repo = None
|
188
|
+
try:
|
189
|
+
git_repo = Repo(".", search_parent_directories=True)
|
190
|
+
origin = git_repo.remotes.origin.url
|
191
|
+
# e.g. git@github.com:Nayjest/ai-code-review.git -> Nayjest/ai-code-review
|
192
|
+
match = re.search(r"[:/]([\w\-]+)/([\w\-\.]+?)(\.git)?$", origin)
|
193
|
+
if match:
|
194
|
+
d["github_repo"] = f"{match.group(1)}/{match.group(2)}"
|
195
|
+
d["github_pr_sha"] = git_repo.head.commit.hexsha
|
196
|
+
d["github_branch"] = (
|
197
|
+
git_repo.active_branch.name if hasattr(git_repo, "active_branch") else ""
|
198
|
+
)
|
199
|
+
except Exception:
|
200
|
+
pass
|
201
|
+
finally:
|
202
|
+
if git_repo:
|
203
|
+
try:
|
204
|
+
git_repo.close()
|
205
|
+
except Exception:
|
206
|
+
pass
|
207
|
+
# If branch is not a commit SHA, prefer branch for links
|
208
|
+
if d["github_branch"]:
|
209
|
+
d["github_pr_sha_or_branch"] = d["github_branch"]
|
210
|
+
elif d["github_pr_sha"]:
|
211
|
+
d["github_pr_sha_or_branch"] = d["github_pr_sha"]
|
212
|
+
else:
|
213
|
+
d["github_pr_sha_or_branch"] = "main"
|
214
|
+
return d
|
ai_code_review/constants.py
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
from pathlib import Path
|
2
|
-
|
3
|
-
|
4
|
-
PROJECT_CONFIG_FILE = Path(".ai-code-review.toml")
|
5
|
-
PROJECT_CONFIG_DEFAULTS_FILE = Path(__file__).resolve().parent / PROJECT_CONFIG_FILE
|
6
|
-
ENV_CONFIG_FILE = Path("~/.env.ai-code-review").expanduser()
|
7
|
-
JSON_REPORT_FILE_NAME = "code-review-report.json"
|
ai_code_review/project_config.py
DELETED
@@ -1,99 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
import tomllib
|
3
|
-
from dataclasses import dataclass, field
|
4
|
-
from pathlib import Path
|
5
|
-
|
6
|
-
import microcore as mc
|
7
|
-
|
8
|
-
from .constants import PROJECT_CONFIG_FILE, PROJECT_CONFIG_DEFAULTS_FILE
|
9
|
-
|
10
|
-
|
11
|
-
def _detect_github_env() -> dict:
|
12
|
-
"""
|
13
|
-
Try to detect GitHub repository/PR info from environment variables (for GitHub Actions).
|
14
|
-
Returns a dict with github_repo, github_pr_sha, github_pr_number, github_ref, etc.
|
15
|
-
"""
|
16
|
-
import os
|
17
|
-
|
18
|
-
repo = os.environ.get("GITHUB_REPOSITORY", "")
|
19
|
-
pr_sha = os.environ.get("GITHUB_SHA", "")
|
20
|
-
pr_number = os.environ.get("GITHUB_REF", "")
|
21
|
-
branch = ""
|
22
|
-
ref = os.environ.get("GITHUB_REF", "")
|
23
|
-
# Try to resolve PR head SHA if available.
|
24
|
-
# On PRs, GITHUB_HEAD_REF/BASE_REF contain branch names.
|
25
|
-
if "GITHUB_HEAD_REF" in os.environ:
|
26
|
-
branch = os.environ["GITHUB_HEAD_REF"]
|
27
|
-
elif ref.startswith("refs/heads/"):
|
28
|
-
branch = ref[len("refs/heads/"):]
|
29
|
-
elif ref.startswith("refs/pull/"):
|
30
|
-
# for pull_request events
|
31
|
-
branch = ref
|
32
|
-
|
33
|
-
d = {
|
34
|
-
"github_repo": repo,
|
35
|
-
"github_pr_sha": pr_sha,
|
36
|
-
"github_pr_number": pr_number,
|
37
|
-
"github_branch": branch,
|
38
|
-
"github_ref": ref,
|
39
|
-
}
|
40
|
-
# Fallback for local usage: try to get from git
|
41
|
-
if not repo:
|
42
|
-
try:
|
43
|
-
from git import Repo as GitRepo
|
44
|
-
|
45
|
-
git = GitRepo(".", search_parent_directories=True)
|
46
|
-
origin = git.remotes.origin.url
|
47
|
-
# e.g. git@github.com:Nayjest/ai-code-review.git -> Nayjest/ai-code-review
|
48
|
-
import re
|
49
|
-
|
50
|
-
match = re.search(r"[:/]([\w\-]+)/([\w\-\.]+?)(\.git)?$", origin)
|
51
|
-
if match:
|
52
|
-
d["github_repo"] = f"{match.group(1)}/{match.group(2)}"
|
53
|
-
d["github_pr_sha"] = git.head.commit.hexsha
|
54
|
-
d["github_branch"] = (
|
55
|
-
git.active_branch.name if hasattr(git, "active_branch") else ""
|
56
|
-
)
|
57
|
-
except Exception:
|
58
|
-
pass
|
59
|
-
# If branch is not a commit SHA, prefer branch for links
|
60
|
-
if d["github_branch"]:
|
61
|
-
d["github_pr_sha_or_branch"] = d["github_branch"]
|
62
|
-
elif d["github_pr_sha"]:
|
63
|
-
d["github_pr_sha_or_branch"] = d["github_pr_sha"]
|
64
|
-
else:
|
65
|
-
d["github_pr_sha_or_branch"] = "main"
|
66
|
-
return d
|
67
|
-
|
68
|
-
|
69
|
-
@dataclass
|
70
|
-
class ProjectConfig:
|
71
|
-
prompt: str = ""
|
72
|
-
summary_prompt: str = ""
|
73
|
-
report_template_md: str = ""
|
74
|
-
"""Markdown report template"""
|
75
|
-
post_process: str = ""
|
76
|
-
retries: int = 3
|
77
|
-
"""LLM retries for one request"""
|
78
|
-
max_code_tokens: int = 32000
|
79
|
-
prompt_vars: dict = field(default_factory=dict)
|
80
|
-
|
81
|
-
@staticmethod
|
82
|
-
def load(custom_config_file: str | Path | None = None) -> "ProjectConfig":
|
83
|
-
config_file = Path(custom_config_file or PROJECT_CONFIG_FILE)
|
84
|
-
with open(PROJECT_CONFIG_DEFAULTS_FILE, "rb") as f:
|
85
|
-
config = tomllib.load(f)
|
86
|
-
github_env = _detect_github_env()
|
87
|
-
config["prompt_vars"] |= github_env | dict(github_env=github_env)
|
88
|
-
if config_file.exists():
|
89
|
-
logging.info(
|
90
|
-
f"Loading project-specific configuration from {mc.utils.file_link(config_file)}...")
|
91
|
-
default_prompt_vars = config["prompt_vars"]
|
92
|
-
with open(config_file, "rb") as f:
|
93
|
-
config.update(tomllib.load(f))
|
94
|
-
# overriding prompt_vars config section will not empty default values
|
95
|
-
config["prompt_vars"] = default_prompt_vars | config["prompt_vars"]
|
96
|
-
else:
|
97
|
-
logging.info(f"Config file {config_file} not found, using defaults")
|
98
|
-
|
99
|
-
return ProjectConfig(**config)
|
ai_code_review/utils.py
DELETED
@@ -1,116 +0,0 @@
|
|
1
|
-
import sys
|
2
|
-
import os
|
3
|
-
from pathlib import Path
|
4
|
-
import typer
|
5
|
-
|
6
|
-
|
7
|
-
_EXT_TO_HINT: dict[str, str] = {
|
8
|
-
# scripting & languages
|
9
|
-
".py": "python",
|
10
|
-
".js": "javascript",
|
11
|
-
".ts": "typescript",
|
12
|
-
".java": "java",
|
13
|
-
".c": "c",
|
14
|
-
".cpp": "cpp",
|
15
|
-
".cc": "cpp",
|
16
|
-
".cxx": "cpp",
|
17
|
-
".h": "cpp",
|
18
|
-
".hpp": "cpp",
|
19
|
-
".cs": "csharp",
|
20
|
-
".rb": "ruby",
|
21
|
-
".go": "go",
|
22
|
-
".rs": "rust",
|
23
|
-
".swift": "swift",
|
24
|
-
".kt": "kotlin",
|
25
|
-
".scala": "scala",
|
26
|
-
".dart": "dart",
|
27
|
-
".php": "php",
|
28
|
-
".pl": "perl",
|
29
|
-
".pm": "perl",
|
30
|
-
".lua": "lua",
|
31
|
-
# web & markup
|
32
|
-
".html": "html",
|
33
|
-
".htm": "html",
|
34
|
-
".css": "css",
|
35
|
-
".scss": "scss",
|
36
|
-
".less": "less",
|
37
|
-
".json": "json",
|
38
|
-
".xml": "xml",
|
39
|
-
".yaml": "yaml",
|
40
|
-
".yml": "yaml",
|
41
|
-
".toml": "toml",
|
42
|
-
".ini": "ini",
|
43
|
-
".csv": "csv",
|
44
|
-
".md": "markdown",
|
45
|
-
".rst": "rest",
|
46
|
-
# shell & config
|
47
|
-
".sh": "bash",
|
48
|
-
".bash": "bash",
|
49
|
-
".zsh": "bash",
|
50
|
-
".fish": "bash",
|
51
|
-
".ps1": "powershell",
|
52
|
-
".dockerfile": "dockerfile",
|
53
|
-
# build & CI
|
54
|
-
".makefile": "makefile",
|
55
|
-
".mk": "makefile",
|
56
|
-
"CMakeLists.txt": "cmake",
|
57
|
-
"Dockerfile": "dockerfile",
|
58
|
-
".gradle": "groovy",
|
59
|
-
".travis.yml": "yaml",
|
60
|
-
# data & queries
|
61
|
-
".sql": "sql",
|
62
|
-
".graphql": "graphql",
|
63
|
-
".proto": "protobuf",
|
64
|
-
".yara": "yara",
|
65
|
-
}
|
66
|
-
|
67
|
-
|
68
|
-
def syntax_hint(file_path: str | Path) -> str:
|
69
|
-
"""
|
70
|
-
Returns a syntax highlighting hint based on the file's extension or name.
|
71
|
-
|
72
|
-
This can be used to annotate code blocks for rendering with syntax highlighting,
|
73
|
-
e.g., using Markdown-style code blocks: ```<syntax_hint>\n<code>\n```.
|
74
|
-
|
75
|
-
Args:
|
76
|
-
file_path (str | Path): Path to the file.
|
77
|
-
|
78
|
-
Returns:
|
79
|
-
str: A syntax identifier suitable for code highlighting (e.g., 'python', 'json').
|
80
|
-
"""
|
81
|
-
p = Path(file_path)
|
82
|
-
ext = p.suffix.lower()
|
83
|
-
if not ext:
|
84
|
-
name = p.name.lower()
|
85
|
-
if name == "dockerfile":
|
86
|
-
return "dockerfile"
|
87
|
-
return ""
|
88
|
-
return _EXT_TO_HINT.get(ext, ext.lstrip("."))
|
89
|
-
|
90
|
-
|
91
|
-
def is_running_in_github_action():
|
92
|
-
return os.getenv("GITHUB_ACTIONS") == "true"
|
93
|
-
|
94
|
-
|
95
|
-
def no_subcommand(app: typer.Typer) -> bool:
|
96
|
-
"""
|
97
|
-
Checks if the current script is being invoked as a command in a target Typer application.
|
98
|
-
"""
|
99
|
-
return not (
|
100
|
-
(first_arg := next((a for a in sys.argv[1:] if not a.startswith('-')), None))
|
101
|
-
and first_arg in (
|
102
|
-
cmd.name or cmd.callback.__name__.replace('_', '-')
|
103
|
-
for cmd in app.registered_commands
|
104
|
-
)
|
105
|
-
or '--help' in sys.argv
|
106
|
-
)
|
107
|
-
|
108
|
-
|
109
|
-
def parse_refs_pair(refs: str) -> tuple[str | None, str | None]:
|
110
|
-
SEPARATOR = '..'
|
111
|
-
if not refs:
|
112
|
-
return None, None
|
113
|
-
if SEPARATOR not in refs:
|
114
|
-
return refs, None
|
115
|
-
what, against = refs.split(SEPARATOR)
|
116
|
-
return what or None, against or None
|
ai_cr-1.0.1.dist-info/METADATA
DELETED
@@ -1,197 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.3
|
2
|
-
Name: ai-cr
|
3
|
-
Version: 1.0.1
|
4
|
-
Summary: LLM-agnostic GitHub AI Code Review Tool with integration to GitHub actions
|
5
|
-
License: MIT
|
6
|
-
Keywords: static code analysis,code review,code quality,ai,coding,assistant,llm,github,automation,devops,developer tools,github actions,workflows,git
|
7
|
-
Author: Nayjest
|
8
|
-
Author-email: mail@vitaliy.in
|
9
|
-
Requires-Python: >=3.11,<4.0
|
10
|
-
Classifier: Environment :: Console
|
11
|
-
Classifier: Intended Audience :: Developers
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
13
|
-
Classifier: Programming Language :: Python :: 3
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
15
|
-
Classifier: Programming Language :: Python :: 3.12
|
16
|
-
Classifier: Programming Language :: Python :: 3.13
|
17
|
-
Classifier: Topic :: Software Development
|
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)
|
24
|
-
Project-URL: Homepage, https://github.com/Nayjest/github-ai-code-review
|
25
|
-
Project-URL: Repository, https://github.com/Nayjest/github-ai-code-review
|
26
|
-
Description-Content-Type: text/markdown
|
27
|
-
|
28
|
-
<p align="right">
|
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>
|
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>
|
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
|
-
<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
|
-
</p>
|
35
|
-
|
36
|
-
# 🤖 AI Code Review Tool
|
37
|
-
|
38
|
-
An AI-powered GitHub code review tool that uses LLMs to detect high-confidence, high-impact issues—such as security vulnerabilities, bugs, and maintainability concerns.
|
39
|
-
|
40
|
-
## ✨ Features
|
41
|
-
|
42
|
-
- Automatically reviews pull requests via GitHub Actions
|
43
|
-
- Focuses on critical issues (e.g., bugs, security risks, design flaws)
|
44
|
-
- Posts review results as a comment on your PR
|
45
|
-
- Can be used locally; works with both local and remote Git repositories
|
46
|
-
- Optional, fun AI-generated code awards 🏆
|
47
|
-
- Easily configurable via [`.ai-code-review.toml`](https://github.com/Nayjest/ai-code-review/blob/main/ai_code_review/.ai-code-review.toml) in your repository root
|
48
|
-
- Extremely fast, parallel LLM usage
|
49
|
-
- Model-agnostic (OpenAI, Anthropic, Google, local PyTorch inference, etc.)
|
50
|
-
|
51
|
-
See code review in action: [example](https://github.com/Nayjest/ai-code-review/pull/39#issuecomment-2906968729)
|
52
|
-
|
53
|
-
## 🚀 Quickstart
|
54
|
-
|
55
|
-
### 1. Review Pull Requests via GitHub Actions
|
56
|
-
|
57
|
-
Create a `.github/workflows/ai-code-review.yml` file:
|
58
|
-
|
59
|
-
```yaml
|
60
|
-
name: AI Code Review
|
61
|
-
on: { pull_request: { types: [opened, synchronize, reopened] } }
|
62
|
-
jobs:
|
63
|
-
review:
|
64
|
-
runs-on: ubuntu-latest
|
65
|
-
permissions: { contents: read, pull-requests: write } # 'write' for leaving the summary comment
|
66
|
-
steps:
|
67
|
-
- uses: actions/checkout@v4
|
68
|
-
with: { fetch-depth: 0 }
|
69
|
-
- name: Set up Python
|
70
|
-
uses: actions/setup-python@v5
|
71
|
-
with: { python-version: "3.13" }
|
72
|
-
- name: Install AI Code Review tool
|
73
|
-
run: pip install ai-code-review~=1.0
|
74
|
-
- name: Run AI code analysis
|
75
|
-
env:
|
76
|
-
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
77
|
-
LLM_API_TYPE: openai
|
78
|
-
MODEL: "gpt-4.1"
|
79
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
80
|
-
run: |
|
81
|
-
ai-code-review
|
82
|
-
ai-code-review github-comment --token ${{ secrets.GITHUB_TOKEN }}
|
83
|
-
- uses: actions/upload-artifact@v4
|
84
|
-
with:
|
85
|
-
name: ai-code-review-results
|
86
|
-
path: |
|
87
|
-
code-review-report.md
|
88
|
-
code-review-report.json
|
89
|
-
```
|
90
|
-
|
91
|
-
> ⚠️ Make sure to add `LLM_API_KEY` to your repository’s GitHub secrets.
|
92
|
-
|
93
|
-
💪 Done!
|
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.
|
96
|
-
|
97
|
-
### 2. Running Code Analysis Locally
|
98
|
-
|
99
|
-
#### Initial Local Setup
|
100
|
-
|
101
|
-
**Prerequisites:** [Python](https://www.python.org/downloads/) 3.11 / 3.12 / 3.13
|
102
|
-
|
103
|
-
**Step1:** Install [ai-code-review](https://github.com/Nayjest/ai-code-review) using [pip](https://en.wikipedia.org/wiki/Pip_(package_manager)).
|
104
|
-
```bash
|
105
|
-
pip install ai-code-review
|
106
|
-
```
|
107
|
-
|
108
|
-
> **Troubleshooting:**
|
109
|
-
> pip may be also available via cli as `pip3` depending on your Python installation.
|
110
|
-
|
111
|
-
**Step2:** Perform initial setup
|
112
|
-
|
113
|
-
The following command will perform one-time setup using an interactive wizard.
|
114
|
-
You will be prompted to enter LLM configuration details (API type, API key, etc).
|
115
|
-
Configuration will be saved to ~/.env.ai-code-review.
|
116
|
-
|
117
|
-
```bash
|
118
|
-
ai-code-review setup
|
119
|
-
```
|
120
|
-
|
121
|
-
> **Troubleshooting:**
|
122
|
-
> On some systems, `ai-code-review` command may not became available immediately after installation.
|
123
|
-
> Try restarting your terminal or running `python -m ai_code_review` instead.
|
124
|
-
|
125
|
-
|
126
|
-
#### Perform your first AI code review locally
|
127
|
-
|
128
|
-
**Step1:** Navigate to your repository root directory.
|
129
|
-
**Step2:** Switch to the branch you want to review.
|
130
|
-
**Step3:** Run following command
|
131
|
-
```bash
|
132
|
-
ai-code-review
|
133
|
-
```
|
134
|
-
|
135
|
-
> **Note:** This will analyze the current branch against the repository main branch by default.
|
136
|
-
> Files that are not staged for commit will be ignored.
|
137
|
-
> See `ai-code-review --help` for more options.
|
138
|
-
|
139
|
-
**Reviewing remote repository**
|
140
|
-
|
141
|
-
```bash
|
142
|
-
ai-code-review remote git@github.com:owner/repo.git <FEATURE_BRANCH>..<MAIN_BRANCH>
|
143
|
-
```
|
144
|
-
Use interactive help for details:
|
145
|
-
```bash
|
146
|
-
ai-code-review remote --help
|
147
|
-
```
|
148
|
-
|
149
|
-
## 🔧 Configuration
|
150
|
-
|
151
|
-
Change behavior via `.ai-code-review.toml`:
|
152
|
-
|
153
|
-
- Prompt templates, filtering and post-processing using Python code snippets
|
154
|
-
- Tagging, severity, and confidence settings
|
155
|
-
- Custom AI awards for developer brilliance
|
156
|
-
- Output customization
|
157
|
-
|
158
|
-
You can override the default config by placing `.ai-code-review.toml` in your repo root.
|
159
|
-
|
160
|
-
|
161
|
-
See default configuration [here](https://github.com/Nayjest/ai-code-review/blob/main/ai_code_review/.ai-code-review.toml).
|
162
|
-
|
163
|
-
More details can be found in [📖 Configuration Cookbook](https://github.com/Nayjest/ai-code-review/blob/main/documentation/config_cookbook.md)
|
164
|
-
|
165
|
-
## 💻 Development Setup
|
166
|
-
|
167
|
-
Install dependencies:
|
168
|
-
|
169
|
-
```bash
|
170
|
-
make install
|
171
|
-
```
|
172
|
-
|
173
|
-
Format code and check style:
|
174
|
-
|
175
|
-
```bash
|
176
|
-
make black
|
177
|
-
make cs
|
178
|
-
```
|
179
|
-
|
180
|
-
Run tests:
|
181
|
-
|
182
|
-
```bash
|
183
|
-
pytest
|
184
|
-
```
|
185
|
-
|
186
|
-
## 🤝 Contributing
|
187
|
-
|
188
|
-
**Looking for a specific feature or having trouble?**
|
189
|
-
Contributions are welcome! ❤️
|
190
|
-
See [CONTRIBUTING.md](https://github.com/Nayjest/ai-code-review/blob/main/CONTRIBUTING.md) for details.
|
191
|
-
|
192
|
-
## 📝 License
|
193
|
-
|
194
|
-
Licensed under the [MIT License](https://github.com/Nayjest/ai-code-review/blob/main/LICENSE).
|
195
|
-
|
196
|
-
© 2025 [Vitalii Stepanenko](mailto:mail@vitaliy.in)
|
197
|
-
|
ai_cr-1.0.1.dist-info/RECORD
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
ai_code_review/.ai-code-review.toml,sha256=t5Q7oRzc-qsmOOYdXIx5sR6ss9OEertoF-ttG2pJ6Z4,10722
|
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=9OWQP2voQOfrhVfJsCzP-nQ9RtLU1cHhMi81QY56vzc,7441
|
6
|
-
ai_code_review/commands/repl.py,sha256=Ms5p6vgcf0EBAUUWKQfJu3X9XFvzJXB018qcvSiJ-oI,396
|
7
|
-
ai_code_review/constants.py,sha256=K9mNxTq9seTG3aVm__3r1lXb5oCOQjH24Cl3hfX9FsE,281
|
8
|
-
ai_code_review/core.py,sha256=BJNs4ZER2-bMulXY2apY6B6hI0nvRCOrLqsJ7L8Bizc,6693
|
9
|
-
ai_code_review/project_config.py,sha256=RDbplmncALKw0zgqSG8POofi300z0DPvtF33wt7_1Sk,3651
|
10
|
-
ai_code_review/report_struct.py,sha256=N-EnNMwBY9LyJ9sdFHpUzn2fwBvxo5TZYYiJagBl8Po,3488
|
11
|
-
ai_code_review/utils.py,sha256=vlzU3M89qK6_mVkBMnppZaOFsXddVsIBVdfmbN3cxDY,2939
|
12
|
-
ai_cr-1.0.1.dist-info/entry_points.txt,sha256=u0N5NroPYEGqmDGaGqFmiijJ5swzpe7EyKBupnkp1FY,49
|
13
|
-
ai_cr-1.0.1.dist-info/LICENSE,sha256=XATf3zv-CppUSJqI18KLhwnPEomUXEl5WbBzFyb9OSU,1096
|
14
|
-
ai_cr-1.0.1.dist-info/METADATA,sha256=Agf_IHJQ-bRJ545lfta0htTSoBivCGH7pw7f6qfv1xM,7109
|
15
|
-
ai_cr-1.0.1.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
16
|
-
ai_cr-1.0.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|