ai-cr 3.2.0__tar.gz → 3.2.2__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-3.2.0 → ai_cr-3.2.2}/PKG-INFO +1 -1
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/core.py +77 -74
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/report_struct.py +5 -5
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/utils.py +25 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/pyproject.toml +1 -1
- {ai_cr-3.2.0 → ai_cr-3.2.2}/LICENSE +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/README.md +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/__init__.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/__main__.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/bootstrap.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/cli.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/cli_base.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/__init__.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/deploy.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/fix.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/gh_post_review_comment.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/gh_react_to_comment.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/linear_comment.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/repl.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/version.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/config.toml +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/constants.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/context.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/env.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/gh_api.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/issue_trackers.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/pipeline.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/pipeline_steps/__init__.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/pipeline_steps/jira.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/pipeline_steps/linear.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/project_config.py +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/answer.j2 +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/github_workflows/components/env-vars.j2 +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/github_workflows/components/installs.j2 +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/github_workflows/gito-code-review.yml.j2 +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/github_workflows/gito-react-to-comments.yml.j2 +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/partial/aux_files.j2 +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/questions/changes_summary.j2 +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/questions/release_notes.j2 +0 -0
- {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/questions/test_cases.j2 +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: ai-cr
|
3
|
-
Version: 3.2.
|
3
|
+
Version: 3.2.2
|
4
4
|
Summary: AI code review tool that works with any language model provider. It detects issues in GitHub pull requests or local changes—instantly, reliably, and without vendor lock-in.
|
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
|
@@ -121,85 +121,88 @@ def get_diff(
|
|
121
121
|
if review_subject_is_index(what):
|
122
122
|
what = None # working copy
|
123
123
|
if use_merge_base:
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
"Detected detached HEAD state, using HEAD as current reference"
|
132
|
-
)
|
133
|
-
else:
|
134
|
-
current_ref = what
|
135
|
-
merge_base = repo.merge_base(current_ref or repo.active_branch.name, against)[0]
|
136
|
-
logging.info(
|
137
|
-
f"Merge base({ui.green(current_ref)},{ui.yellow(against)})"
|
138
|
-
f" --> {ui.cyan(merge_base.hexsha)}"
|
139
|
-
)
|
140
|
-
# if branch is already an ancestor of "against", merge_base == branch ⇒ it’s been merged
|
141
|
-
if merge_base.hexsha == repo.commit(current_ref or repo.active_branch.name).hexsha:
|
142
|
-
# @todo: check case: reviewing working copy index in main branch #103
|
143
|
-
logging.info(
|
144
|
-
f"Branch is already merged. ({ui.green(current_ref)} vs {ui.yellow(against)})"
|
145
|
-
)
|
146
|
-
merge_sha = repo.git.log(
|
147
|
-
'--merges',
|
148
|
-
'--ancestry-path',
|
149
|
-
f'{current_ref}..{against}',
|
150
|
-
'-n',
|
151
|
-
'1',
|
152
|
-
'--pretty=format:%H'
|
153
|
-
).strip()
|
154
|
-
if merge_sha:
|
155
|
-
logging.info(f"Merge commit is {ui.cyan(merge_sha)}")
|
156
|
-
merge_commit = repo.commit(merge_sha)
|
157
|
-
|
158
|
-
other_merge_parent = None
|
159
|
-
for parent in merge_commit.parents:
|
160
|
-
logging.info(f"Checking merge parent: {parent.hexsha[:8]}")
|
161
|
-
if parent.hexsha == merge_base.hexsha:
|
162
|
-
logging.info(f"merge parent is {ui.cyan(parent.hexsha[:8])}, skipping")
|
163
|
-
continue
|
164
|
-
if not commit_in_branch(repo, parent, against):
|
165
|
-
logging.warning(f"merge parent is not in {against}, skipping")
|
166
|
-
continue
|
167
|
-
logging.info(f"Found other merge parent: {ui.cyan(parent.hexsha[:8])}")
|
168
|
-
other_merge_parent = parent
|
169
|
-
break
|
170
|
-
if other_merge_parent:
|
171
|
-
first_common_ancestor = repo.merge_base(other_merge_parent, merge_base)[0]
|
172
|
-
# for gito remote (feature_branch vs origin/main)
|
173
|
-
# the same merge base appears in first_common_ancestor again
|
174
|
-
if first_common_ancestor.hexsha == merge_base.hexsha:
|
175
|
-
if merge_base.parents:
|
176
|
-
first_common_ancestor = repo.merge_base(
|
177
|
-
other_merge_parent, merge_base.parents[0]
|
178
|
-
)[0]
|
179
|
-
else:
|
180
|
-
logging.error(
|
181
|
-
"merge_base has no parents, "
|
182
|
-
"using merge_base as first_common_ancestor"
|
183
|
-
)
|
124
|
+
try:
|
125
|
+
if review_subject_is_index(what):
|
126
|
+
try:
|
127
|
+
current_ref = repo.active_branch.name
|
128
|
+
except TypeError:
|
129
|
+
# In detached HEAD state, use HEAD directly
|
130
|
+
current_ref = "HEAD"
|
184
131
|
logging.info(
|
185
|
-
|
186
|
-
f"first common ancestor of {what} and {against}: "
|
187
|
-
f"{ui.cyan(first_common_ancestor.hexsha[:8])}"
|
132
|
+
"Detected detached HEAD state, using HEAD as current reference"
|
188
133
|
)
|
189
|
-
against = first_common_ancestor.hexsha
|
190
|
-
else:
|
191
|
-
logging.error(f"Can't find other merge parent for {merge_sha}")
|
192
134
|
else:
|
193
|
-
|
194
|
-
|
195
|
-
"falling back to merge‐base diff"
|
196
|
-
)
|
197
|
-
else:
|
198
|
-
# normal case: branch not yet merged
|
199
|
-
against = merge_base.hexsha
|
135
|
+
current_ref = what
|
136
|
+
merge_base = repo.merge_base(current_ref or repo.active_branch.name, against)[0]
|
200
137
|
logging.info(
|
201
|
-
f"
|
138
|
+
f"Merge base({ui.green(current_ref)},{ui.yellow(against)})"
|
139
|
+
f" --> {ui.cyan(merge_base.hexsha)}"
|
202
140
|
)
|
141
|
+
# if branch is already an ancestor of "against", merge_base == branch ⇒ it’s been merged
|
142
|
+
if merge_base.hexsha == repo.commit(current_ref or repo.active_branch.name).hexsha:
|
143
|
+
# @todo: check case: reviewing working copy index in main branch #103
|
144
|
+
logging.info(
|
145
|
+
f"Branch is already merged. ({ui.green(current_ref)} vs {ui.yellow(against)})"
|
146
|
+
)
|
147
|
+
merge_sha = repo.git.log(
|
148
|
+
'--merges',
|
149
|
+
'--ancestry-path',
|
150
|
+
f'{current_ref}..{against}',
|
151
|
+
'-n',
|
152
|
+
'1',
|
153
|
+
'--pretty=format:%H'
|
154
|
+
).strip()
|
155
|
+
if merge_sha:
|
156
|
+
logging.info(f"Merge commit is {ui.cyan(merge_sha)}")
|
157
|
+
merge_commit = repo.commit(merge_sha)
|
158
|
+
|
159
|
+
other_merge_parent = None
|
160
|
+
for parent in merge_commit.parents:
|
161
|
+
logging.info(f"Checking merge parent: {parent.hexsha[:8]}")
|
162
|
+
if parent.hexsha == merge_base.hexsha:
|
163
|
+
logging.info(f"merge parent is {ui.cyan(parent.hexsha[:8])}, skipping")
|
164
|
+
continue
|
165
|
+
if not commit_in_branch(repo, parent, against):
|
166
|
+
logging.warning(f"merge parent is not in {against}, skipping")
|
167
|
+
continue
|
168
|
+
logging.info(f"Found other merge parent: {ui.cyan(parent.hexsha[:8])}")
|
169
|
+
other_merge_parent = parent
|
170
|
+
break
|
171
|
+
if other_merge_parent:
|
172
|
+
first_common_ancestor = repo.merge_base(other_merge_parent, merge_base)[0]
|
173
|
+
# for gito remote (feature_branch vs origin/main)
|
174
|
+
# the same merge base appears in first_common_ancestor again
|
175
|
+
if first_common_ancestor.hexsha == merge_base.hexsha:
|
176
|
+
if merge_base.parents:
|
177
|
+
first_common_ancestor = repo.merge_base(
|
178
|
+
other_merge_parent, merge_base.parents[0]
|
179
|
+
)[0]
|
180
|
+
else:
|
181
|
+
logging.error(
|
182
|
+
"merge_base has no parents, "
|
183
|
+
"using merge_base as first_common_ancestor"
|
184
|
+
)
|
185
|
+
logging.info(
|
186
|
+
f"{what} will be compared to "
|
187
|
+
f"first common ancestor of {what} and {against}: "
|
188
|
+
f"{ui.cyan(first_common_ancestor.hexsha[:8])}"
|
189
|
+
)
|
190
|
+
against = first_common_ancestor.hexsha
|
191
|
+
else:
|
192
|
+
logging.error(f"Can't find other merge parent for {merge_sha}")
|
193
|
+
else:
|
194
|
+
logging.warning(
|
195
|
+
f"No merge‐commit found for {current_ref!r}→{against!r}; "
|
196
|
+
"falling back to merge‐base diff"
|
197
|
+
)
|
198
|
+
else:
|
199
|
+
# normal case: branch not yet merged
|
200
|
+
against = merge_base.hexsha
|
201
|
+
logging.info(
|
202
|
+
f"Using merge base: {ui.cyan(merge_base.hexsha[:8])} ({merge_base.summary})"
|
203
|
+
)
|
204
|
+
except Exception as e:
|
205
|
+
logging.error(f"Error finding merge base: {e}")
|
203
206
|
logging.info(
|
204
207
|
f"Making diff: {ui.green(what or 'INDEX')} vs {ui.yellow(against)}"
|
205
208
|
)
|
@@ -12,7 +12,7 @@ import textwrap
|
|
12
12
|
|
13
13
|
from .constants import JSON_REPORT_FILE_NAME, HTML_TEXT_ICON, HTML_CR_COMMENT_MARKER
|
14
14
|
from .project_config import ProjectConfig
|
15
|
-
from .utils import syntax_hint, block_wrap_lr, max_line_len, remove_html_comments
|
15
|
+
from .utils import syntax_hint, block_wrap_lr, max_line_len, remove_html_comments, filter_kwargs
|
16
16
|
|
17
17
|
|
18
18
|
@dataclass
|
@@ -40,7 +40,7 @@ class Issue:
|
|
40
40
|
|
41
41
|
def __post_init__(self):
|
42
42
|
self.affected_lines = [
|
43
|
-
Issue.AffectedCode(**dict(file=self.file) | i)
|
43
|
+
Issue.AffectedCode(**filter_kwargs(Issue.AffectedCode, dict(file=self.file) | i))
|
44
44
|
for i in self.affected_lines
|
45
45
|
]
|
46
46
|
|
@@ -63,7 +63,7 @@ class Report:
|
|
63
63
|
MARKDOWN = "md"
|
64
64
|
CLI = "cli"
|
65
65
|
|
66
|
-
issues: dict[str, list[Issue]] = field(default_factory=dict)
|
66
|
+
issues: dict[str, list[Issue | dict]] = field(default_factory=dict)
|
67
67
|
summary: str = field(default="")
|
68
68
|
number_of_processed_files: int = field(default=0)
|
69
69
|
total_issues: int = field(init=False)
|
@@ -84,10 +84,10 @@ class Report:
|
|
84
84
|
for file in self.issues.keys():
|
85
85
|
self.issues[file] = [
|
86
86
|
Issue(
|
87
|
-
**{
|
87
|
+
**filter_kwargs(Issue, {
|
88
88
|
"id": (issue_id := issue_id + 1),
|
89
89
|
"file": file,
|
90
|
-
} | issue
|
90
|
+
} | issue)
|
91
91
|
)
|
92
92
|
for issue in self.issues[file]
|
93
93
|
]
|
@@ -1,6 +1,8 @@
|
|
1
|
+
import logging
|
1
2
|
import re
|
2
3
|
import sys
|
3
4
|
import os
|
5
|
+
from dataclasses import fields
|
4
6
|
from pathlib import Path
|
5
7
|
import importlib.metadata
|
6
8
|
from typing import Optional
|
@@ -240,3 +242,26 @@ def remove_html_comments(text):
|
|
240
242
|
Removes all HTML comments (<!-- ... -->) from the input text.
|
241
243
|
"""
|
242
244
|
return re.sub(r'<!--.*?-->\s*', '', text, flags=re.DOTALL)
|
245
|
+
|
246
|
+
|
247
|
+
def filter_kwargs(cls, kwargs, log_warnings=True):
|
248
|
+
"""
|
249
|
+
Filters the keyword arguments to only include those that are fields of the given dataclass.
|
250
|
+
Args:
|
251
|
+
cls: The dataclass type to filter against.
|
252
|
+
kwargs: A dictionary of keyword arguments.
|
253
|
+
log_warnings: If True, logs warnings for fields not in the dataclass.
|
254
|
+
Returns:
|
255
|
+
A dictionary containing only the fields that are defined in the dataclass.
|
256
|
+
"""
|
257
|
+
cls_fields = {f.name for f in fields(cls)}
|
258
|
+
filtered = {}
|
259
|
+
for k, v in kwargs.items():
|
260
|
+
if k in cls_fields:
|
261
|
+
filtered[k] = v
|
262
|
+
else:
|
263
|
+
if log_warnings:
|
264
|
+
logging.warning(
|
265
|
+
f"Warning: field '{k}' not in {cls.__name__}, dropping."
|
266
|
+
)
|
267
|
+
return filtered
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "ai-cr"
|
3
|
-
version = "3.2.
|
3
|
+
version = "3.2.2"
|
4
4
|
description = "AI code review tool that works with any language model provider. It detects issues in GitHub pull requests or local changes—instantly, reliably, and without vendor lock-in."
|
5
5
|
authors = ["Nayjest <mail@vitaliy.in>"]
|
6
6
|
readme = "README.md"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|