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.
Files changed (40) hide show
  1. {ai_cr-3.2.0 → ai_cr-3.2.2}/PKG-INFO +1 -1
  2. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/core.py +77 -74
  3. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/report_struct.py +5 -5
  4. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/utils.py +25 -0
  5. {ai_cr-3.2.0 → ai_cr-3.2.2}/pyproject.toml +1 -1
  6. {ai_cr-3.2.0 → ai_cr-3.2.2}/LICENSE +0 -0
  7. {ai_cr-3.2.0 → ai_cr-3.2.2}/README.md +0 -0
  8. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/__init__.py +0 -0
  9. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/__main__.py +0 -0
  10. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/bootstrap.py +0 -0
  11. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/cli.py +0 -0
  12. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/cli_base.py +0 -0
  13. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/__init__.py +0 -0
  14. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/deploy.py +0 -0
  15. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/fix.py +0 -0
  16. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/gh_post_review_comment.py +0 -0
  17. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/gh_react_to_comment.py +0 -0
  18. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/linear_comment.py +0 -0
  19. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/repl.py +0 -0
  20. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/commands/version.py +0 -0
  21. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/config.toml +0 -0
  22. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/constants.py +0 -0
  23. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/context.py +0 -0
  24. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/env.py +0 -0
  25. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/gh_api.py +0 -0
  26. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/issue_trackers.py +0 -0
  27. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/pipeline.py +0 -0
  28. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/pipeline_steps/__init__.py +0 -0
  29. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/pipeline_steps/jira.py +0 -0
  30. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/pipeline_steps/linear.py +0 -0
  31. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/project_config.py +0 -0
  32. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/answer.j2 +0 -0
  33. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/github_workflows/components/env-vars.j2 +0 -0
  34. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/github_workflows/components/installs.j2 +0 -0
  35. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/github_workflows/gito-code-review.yml.j2 +0 -0
  36. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/github_workflows/gito-react-to-comments.yml.j2 +0 -0
  37. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/partial/aux_files.j2 +0 -0
  38. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/questions/changes_summary.j2 +0 -0
  39. {ai_cr-3.2.0 → ai_cr-3.2.2}/gito/tpl/questions/release_notes.j2 +0 -0
  40. {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.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
@@ -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
  )
@@ -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.0"
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