ai-cr 3.2.0__py3-none-any.whl → 3.2.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ai-cr
3
- Version: 3.2.0
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
@@ -14,7 +14,7 @@ gito/commands/version.py,sha256=OdAuKtjGV9Ok2_igAs-EqdJijXCKU1dcLcA5KEz1ydg,178
14
14
  gito/config.toml,sha256=51TeM_m0_v89u3X1lmafA-D5TqjiXWc-j1W_12VBppg,17777
15
15
  gito/constants.py,sha256=1ElhE4RH0EPEq3xlhwyYRUcgr38X8wXduos0gV9yPy8,819
16
16
  gito/context.py,sha256=OBfcQOREsNx8WHANsplNrnrKYrXz1PyZyne11lSfZjw,446
17
- gito/core.py,sha256=H4DcVFt46BdqXfy1AxYFJMFlKgzLiXzBPqY4oaO6NeM,17379
17
+ gito/core.py,sha256=nw3eUiLFWDOQJB7N_y6UE5hUrr4cS8xD_Q_RgktepUM,17795
18
18
  gito/env.py,sha256=TVNxqrnLefqfZt5sSg495p8UenSgHaMgTImK1rRMIlY,146
19
19
  gito/gh_api.py,sha256=2yDikXr9BM2tndYpCo-weJxjoCAlEtuPh-QBinlnHtg,4052
20
20
  gito/issue_trackers.py,sha256=XYspyaIuf0ANQSvDUea5_oOdo9tQvpZZsapI5S9g78U,1551
@@ -23,7 +23,7 @@ gito/pipeline_steps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
23
23
  gito/pipeline_steps/jira.py,sha256=NjFgpGFAkT5PSXi6jQ9Yr8CY9M8sa3_rMb7RFhdpoNg,1862
24
24
  gito/pipeline_steps/linear.py,sha256=6UDc8nGKGpwHruPq8VItE2QBWshWxaTapoMhu_qjN_g,2445
25
25
  gito/project_config.py,sha256=ZdHy4aNzjlVAgTUjmznrGcdd2NHMMvGX8VeSmAnzZ34,3237
26
- gito/report_struct.py,sha256=96gDYnw0MXhOZrrGaNOTyWstqpjjS7_cuWufd0XJzR4,4320
26
+ gito/report_struct.py,sha256=zSMhPojsBIK34Kp9i74qKJ09DupKluuHHsubS1t9_1E,4399
27
27
  gito/tpl/answer.j2,sha256=JrBOv-a8xBqTccRJeWly2U1Y5jehc_mOBt5nSbRJldI,700
28
28
  gito/tpl/github_workflows/components/env-vars.j2,sha256=ypmf938h5PA38mXTZnP1eI4Un3AIhhmnl6wXg2X4kqI,406
29
29
  gito/tpl/github_workflows/components/installs.j2,sha256=j5wl0yVEIrXZDpAgzqBwmhXQA9End3xFspPxr2ZzHR0,693
@@ -33,9 +33,9 @@ gito/tpl/partial/aux_files.j2,sha256=lJhqnCsHBbEEocpyyOmQX27jzuLvEIuEVXY0RGqxWnY
33
33
  gito/tpl/questions/changes_summary.j2,sha256=N80OQoo9UKii0CWLuck5bOwbijul5RefvCqHJljenmE,2213
34
34
  gito/tpl/questions/release_notes.j2,sha256=OXi6o7T1bum88_2Pt4FiLHmKUe86A1t9Be_3s4mrnmU,889
35
35
  gito/tpl/questions/test_cases.j2,sha256=bB7ESjy02mwWml4zyq87DqkFDj-I0-BYpfJVgzE77cc,1410
36
- gito/utils.py,sha256=OSBn7IeWKjoLJoGOcdgQcJHBA69UiHUChtaYMInUeko,6852
37
- ai_cr-3.2.0.dist-info/LICENSE,sha256=VbdF_GbbDK24JvdTfnsxa2M6jmhsxmRSFeHCx-lICGE,1075
38
- ai_cr-3.2.0.dist-info/METADATA,sha256=jpHSdAUyaroAZRxlDG6rX2QfohgnCgrbCvFHslRguPA,8874
39
- ai_cr-3.2.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
40
- ai_cr-3.2.0.dist-info/entry_points.txt,sha256=Ua1DxkhJJ8TZuLgnH-IlWCkrre_0S0dq_GtYRaYupWk,38
41
- ai_cr-3.2.0.dist-info/RECORD,,
36
+ gito/utils.py,sha256=-agcMHug8nbrPxfz2nAtOKEAox7Kt-0_AIC3d-X23NE,7693
37
+ ai_cr-3.2.2.dist-info/LICENSE,sha256=VbdF_GbbDK24JvdTfnsxa2M6jmhsxmRSFeHCx-lICGE,1075
38
+ ai_cr-3.2.2.dist-info/METADATA,sha256=9M0y1sbIyF1v3zl96TjACPBfdKn8ha5PZWk0t_sS-B0,8874
39
+ ai_cr-3.2.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
40
+ ai_cr-3.2.2.dist-info/entry_points.txt,sha256=Ua1DxkhJJ8TZuLgnH-IlWCkrre_0S0dq_GtYRaYupWk,38
41
+ ai_cr-3.2.2.dist-info/RECORD,,
gito/core.py CHANGED
@@ -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
- if review_subject_is_index(what):
125
- try:
126
- current_ref = repo.active_branch.name
127
- except TypeError:
128
- # In detached HEAD state, use HEAD directly
129
- current_ref = "HEAD"
130
- logging.info(
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
- f"{what} will be compared to "
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
- logging.warning(
194
- f"No merge‐commit found for {current_ref!r}→{against!r}; "
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"Using merge base: {ui.cyan(merge_base.hexsha[:8])} ({merge_base.summary})"
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
  )
gito/report_struct.py CHANGED
@@ -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
  ]
gito/utils.py CHANGED
@@ -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
File without changes
File without changes