ai-cr 0.4.8__tar.gz → 0.5.0__tar.gz
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-0.4.8 → ai_cr-0.5.0}/PKG-INFO +5 -5
- {ai_cr-0.4.8 → ai_cr-0.5.0}/README.md +2 -2
- ai_cr-0.5.0/ai_code_review/__main__.py +4 -0
- {ai_cr-0.4.8 → ai_cr-0.5.0}/ai_code_review/bootstrap.py +4 -1
- {ai_cr-0.4.8 → ai_cr-0.5.0}/ai_code_review/cli.py +55 -10
- {ai_cr-0.4.8 → ai_cr-0.5.0}/ai_code_review/constants.py +1 -1
- {ai_cr-0.4.8 → ai_cr-0.5.0}/ai_code_review/core.py +18 -7
- {ai_cr-0.4.8 → ai_cr-0.5.0}/ai_code_review/utils.py +27 -0
- {ai_cr-0.4.8 → ai_cr-0.5.0}/pyproject.toml +4 -4
- {ai_cr-0.4.8 → ai_cr-0.5.0}/LICENSE +0 -0
- {ai_cr-0.4.8 → ai_cr-0.5.0}/ai_code_review/.ai-code-review.toml +0 -0
- {ai_cr-0.4.8 → ai_cr-0.5.0}/ai_code_review/__init__.py +0 -0
- {ai_cr-0.4.8 → ai_cr-0.5.0}/ai_code_review/project_config.py +0 -0
- {ai_cr-0.4.8 → ai_cr-0.5.0}/ai_code_review/report_struct.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: ai-cr
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.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
|
@@ -16,8 +16,8 @@ Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
17
17
|
Classifier: Topic :: Software Development
|
18
18
|
Requires-Dist: GitPython (==3.1.44)
|
19
|
-
Requires-Dist: ai-microcore (==4.0.0.
|
20
|
-
Requires-Dist: anthropic (==0.
|
19
|
+
Requires-Dist: ai-microcore (==4.0.0.dev18)
|
20
|
+
Requires-Dist: anthropic (==0.52.2)
|
21
21
|
Requires-Dist: async-typer (==0.1.8)
|
22
22
|
Requires-Dist: google-generativeai (==0.8.5)
|
23
23
|
Requires-Dist: typer (==0.9.4)
|
@@ -70,7 +70,7 @@ jobs:
|
|
70
70
|
uses: actions/setup-python@v5
|
71
71
|
with: { python-version: "3.13" }
|
72
72
|
- name: Install AI Code Review tool
|
73
|
-
run: pip install ai-code-review==0.
|
73
|
+
run: pip install ai-code-review==0.5.0
|
74
74
|
- name: Run AI code analysis
|
75
75
|
env:
|
76
76
|
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
@@ -151,7 +151,7 @@ pytest
|
|
151
151
|
|
152
152
|
## 🤝 Contributing
|
153
153
|
|
154
|
-
**Looking for a specific feature or having trouble?**
|
154
|
+
**Looking for a specific feature or having trouble?**
|
155
155
|
Contributions are welcome! ❤️
|
156
156
|
See [CONTRIBUTING.md](https://github.com/Nayjest/ai-code-review/blob/main/CONTRIBUTING.md) for details.
|
157
157
|
|
@@ -42,7 +42,7 @@ jobs:
|
|
42
42
|
uses: actions/setup-python@v5
|
43
43
|
with: { python-version: "3.13" }
|
44
44
|
- name: Install AI Code Review tool
|
45
|
-
run: pip install ai-code-review==0.
|
45
|
+
run: pip install ai-code-review==0.5.0
|
46
46
|
- name: Run AI code analysis
|
47
47
|
env:
|
48
48
|
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
@@ -123,7 +123,7 @@ pytest
|
|
123
123
|
|
124
124
|
## 🤝 Contributing
|
125
125
|
|
126
|
-
**Looking for a specific feature or having trouble?**
|
126
|
+
**Looking for a specific feature or having trouble?**
|
127
127
|
Contributions are welcome! ❤️
|
128
128
|
See [CONTRIBUTING.md](https://github.com/Nayjest/ai-code-review/blob/main/CONTRIBUTING.md) for details.
|
129
129
|
|
@@ -41,7 +41,10 @@ def bootstrap():
|
|
41
41
|
if is_running_in_github_action():
|
42
42
|
ref = os.getenv("GITHUB_WORKFLOW_REF", "")
|
43
43
|
if ref:
|
44
|
-
|
44
|
+
# example value: 'owner/repo/.github/workflows/ai-code-review.yml@refs/pull/1/merge'
|
45
|
+
ref = ref.split("@")[0]
|
46
|
+
ref = ref.split(".github/workflows/")[-1]
|
47
|
+
ref = f" (.github/workflows/{ref})"
|
45
48
|
msg += (
|
46
49
|
f"\nPlease check your GitHub Action Secrets "
|
47
50
|
f"and `env` configuration section of the corresponding workflow step{ref}."
|
@@ -3,34 +3,79 @@ import logging
|
|
3
3
|
import sys
|
4
4
|
import os
|
5
5
|
import shutil
|
6
|
+
import requests
|
6
7
|
|
7
8
|
import microcore as mc
|
8
9
|
import async_typer
|
9
10
|
import typer
|
10
|
-
from .
|
11
|
-
from .report_struct import Report
|
11
|
+
from ai_code_review.utils import parse_refs_pair
|
12
12
|
from git import Repo
|
13
|
-
import requests
|
14
13
|
|
14
|
+
from .core import review
|
15
|
+
from .report_struct import Report
|
15
16
|
from .constants import ENV_CONFIG_FILE
|
16
17
|
from .bootstrap import bootstrap
|
17
18
|
from .project_config import ProjectConfig
|
18
|
-
|
19
|
-
app = async_typer.AsyncTyper(
|
20
|
-
pretty_exceptions_show_locals=False,
|
21
|
-
)
|
19
|
+
from .utils import is_app_command_invocation
|
22
20
|
|
23
21
|
|
22
|
+
app = async_typer.AsyncTyper(pretty_exceptions_show_locals=False)
|
23
|
+
default_command_app = async_typer.AsyncTyper(pretty_exceptions_show_locals=False)
|
24
24
|
if sys.platform == "win32":
|
25
25
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
26
26
|
|
27
27
|
|
28
|
+
def main():
|
29
|
+
if is_app_command_invocation(app):
|
30
|
+
app()
|
31
|
+
else:
|
32
|
+
bootstrap()
|
33
|
+
default_command_app()
|
34
|
+
|
35
|
+
|
28
36
|
@app.callback(invoke_without_command=True)
|
29
|
-
def cli(ctx: typer.Context
|
37
|
+
def cli(ctx: typer.Context):
|
30
38
|
if ctx.invoked_subcommand != "setup":
|
31
39
|
bootstrap()
|
32
|
-
|
33
|
-
|
40
|
+
|
41
|
+
|
42
|
+
@default_command_app.async_command(name="review", help="Perform code review")
|
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
|
+
):
|
63
|
+
_what, _against = parse_refs_pair(refs)
|
64
|
+
if _what:
|
65
|
+
if what:
|
66
|
+
raise typer.BadParameter(
|
67
|
+
"You cannot specify both 'refs' <WHAT>..<AGAINST> and '--what'. Use one of them."
|
68
|
+
)
|
69
|
+
else:
|
70
|
+
_what = what
|
71
|
+
if _against:
|
72
|
+
if against:
|
73
|
+
raise typer.BadParameter(
|
74
|
+
"You cannot specify both 'refs' <WHAT>..<AGAINST> and '--against'. Use one of them."
|
75
|
+
)
|
76
|
+
else:
|
77
|
+
_against = against
|
78
|
+
await review(what=_what, against=_against, filters=filters)
|
34
79
|
|
35
80
|
|
36
81
|
@app.async_command(help="Configure LLM for local usage interactively")
|
@@ -3,5 +3,5 @@ from pathlib import Path
|
|
3
3
|
|
4
4
|
PROJECT_CONFIG_FILE = Path(".ai-code-review.toml")
|
5
5
|
PROJECT_CONFIG_DEFAULTS_FILE = Path(__file__).resolve().parent / PROJECT_CONFIG_FILE
|
6
|
-
ENV_CONFIG_FILE =
|
6
|
+
ENV_CONFIG_FILE = Path("~/.env.ai-code-review").expanduser()
|
7
7
|
JSON_REPORT_FILE_NAME = "code-review-report.json"
|
@@ -11,11 +11,18 @@ from .project_config import ProjectConfig
|
|
11
11
|
from .report_struct import Report
|
12
12
|
|
13
13
|
|
14
|
-
def get_diff(
|
14
|
+
def get_diff(
|
15
|
+
repo: Repo = None,
|
16
|
+
what: str = None,
|
17
|
+
against: str = None
|
18
|
+
) -> PatchSet | list[PatchedFile]:
|
15
19
|
repo = repo or Repo(".")
|
16
|
-
|
17
|
-
|
18
|
-
|
20
|
+
if not against:
|
21
|
+
against = repo.remotes.origin.refs.HEAD.reference.name # origin/main
|
22
|
+
if not what:
|
23
|
+
what = None # working copy
|
24
|
+
logging.info(f"Reviewing {mc.ui.green(what or 'working copy')} vs {mc.ui.yellow(against)}")
|
25
|
+
diff_content = repo.git.diff(against, what)
|
19
26
|
diff = PatchSet.from_string(diff_content)
|
20
27
|
return diff
|
21
28
|
|
@@ -62,10 +69,14 @@ def make_cr_summary(cfg: ProjectConfig, report: Report, diff):
|
|
62
69
|
).to_llm() if cfg.summary_prompt else ""
|
63
70
|
|
64
71
|
|
65
|
-
async def review(
|
72
|
+
async def review(
|
73
|
+
what: str = None,
|
74
|
+
against: str = None,
|
75
|
+
filters: str | list[str] = ""
|
76
|
+
):
|
66
77
|
cfg = ProjectConfig.load()
|
67
78
|
repo = Repo(".")
|
68
|
-
diff = get_diff(repo=repo, against=
|
79
|
+
diff = get_diff(repo=repo, what=what, against=against)
|
69
80
|
diff = filter_diff(diff, filters)
|
70
81
|
if not diff:
|
71
82
|
logging.error("Nothing to review")
|
@@ -78,7 +89,7 @@ async def review(filters: str | list[str] = ""):
|
|
78
89
|
cfg.max_code_tokens
|
79
90
|
- mc.tokenizing.num_tokens_from_string(str(file_diff)),
|
80
91
|
)
|
81
|
-
if file_diff.target_file != DEV_NULL
|
92
|
+
if file_diff.target_file != DEV_NULL and not file_diff.is_added_file
|
82
93
|
else ""
|
83
94
|
)
|
84
95
|
for file_diff in diff
|
@@ -1,5 +1,8 @@
|
|
1
|
+
import sys
|
1
2
|
import os
|
2
3
|
from pathlib import Path
|
4
|
+
import typer
|
5
|
+
|
3
6
|
|
4
7
|
_EXT_TO_HINT: dict[str, str] = {
|
5
8
|
# scripting & languages
|
@@ -87,3 +90,27 @@ def syntax_hint(file_path: str | Path) -> str:
|
|
87
90
|
|
88
91
|
def is_running_in_github_action():
|
89
92
|
return os.getenv("GITHUB_ACTIONS") == "true"
|
93
|
+
|
94
|
+
|
95
|
+
def is_app_command_invocation(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 (
|
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):
|
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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "ai-cr"
|
3
|
-
version = "0.
|
3
|
+
version = "0.5.0"
|
4
4
|
description = "LLM-agnostic GitHub AI Code Review Tool with integration to GitHub actions"
|
5
5
|
authors = ["Nayjest <mail@vitaliy.in>"]
|
6
6
|
readme = "README.md"
|
@@ -21,11 +21,11 @@ packages = [
|
|
21
21
|
|
22
22
|
[tool.poetry.dependencies]
|
23
23
|
python = "^3.11"
|
24
|
-
ai-microcore = "4.0.0.
|
24
|
+
ai-microcore = "4.0.0.dev18"
|
25
25
|
GitPython = "3.1.44"
|
26
26
|
unidiff = "0.7.5"
|
27
27
|
google-generativeai = "0.8.5"
|
28
|
-
anthropic = "0.
|
28
|
+
anthropic = "0.52.2"
|
29
29
|
typer = "0.9.4"
|
30
30
|
async-typer = "0.1.8"
|
31
31
|
|
@@ -49,7 +49,7 @@ requires = ["poetry-core"]
|
|
49
49
|
build-backend = "poetry.core.masonry.api"
|
50
50
|
|
51
51
|
[tool.poetry.scripts]
|
52
|
-
ai-cr = "ai_code_review.cli:
|
52
|
+
ai-cr = "ai_code_review.cli:main"
|
53
53
|
|
54
54
|
[tool.pytest.ini_options]
|
55
55
|
minversion = "6.0"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|