ai-cr 0.4.8__py3-none-any.whl → 0.6.0__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.
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -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
- ref = f" ({ref})"
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}."
@@ -56,4 +59,4 @@ def bootstrap():
56
59
  except Exception as e:
57
60
  logging.error(f"Unexpected configuration error: {e}")
58
61
  raise SystemExit(3)
59
- mc.logging.LoggingConfig.STRIP_REQUEST_LINES = [100, 15]
62
+ mc.logging.LoggingConfig.STRIP_REQUEST_LINES = [300, 15]
ai_code_review/cli.py CHANGED
@@ -2,62 +2,154 @@ import asyncio
2
2
  import logging
3
3
  import sys
4
4
  import os
5
- import shutil
5
+ import textwrap
6
+ import tempfile
7
+ import requests
6
8
 
7
9
  import microcore as mc
8
- import async_typer
9
10
  import typer
10
- from .core import review
11
- from .report_struct import Report
12
11
  from git import Repo
13
- import requests
14
12
 
13
+ from .core import review, get_diff, filter_diff
14
+ from .report_struct import Report
15
15
  from .constants import ENV_CONFIG_FILE
16
16
  from .bootstrap import bootstrap
17
17
  from .project_config import ProjectConfig
18
+ from .utils import no_subcommand, parse_refs_pair
18
19
 
19
- app = async_typer.AsyncTyper(
20
- pretty_exceptions_show_locals=False,
21
- )
20
+ app = typer.Typer(pretty_exceptions_show_locals=False)
21
+ app_no_subcommand = typer.Typer(pretty_exceptions_show_locals=False)
22
22
 
23
23
 
24
- if sys.platform == "win32":
25
- asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
24
+ def main():
25
+ if sys.platform == "win32":
26
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
27
+ if no_subcommand(app):
28
+ bootstrap()
29
+ app_no_subcommand()
30
+ else:
31
+ app()
26
32
 
27
33
 
28
34
  @app.callback(invoke_without_command=True)
29
- def cli(ctx: typer.Context, filters=typer.Option("", "--filter", "-f", "--filters")):
35
+ def cli(ctx: typer.Context):
30
36
  if ctx.invoked_subcommand != "setup":
31
37
  bootstrap()
32
- if not ctx.invoked_subcommand:
33
- asyncio.run(review(filters=filters))
34
38
 
35
39
 
36
- @app.async_command(help="Configure LLM for local usage interactively")
37
- async def setup():
38
- mc.interactive_setup(ENV_CONFIG_FILE)
40
+ def args_to_target(refs, what, against) -> tuple[str | None, str | None]:
41
+ _what, _against = parse_refs_pair(refs)
42
+ if _what:
43
+ if what:
44
+ raise typer.BadParameter(
45
+ "You cannot specify both 'refs' <WHAT>..<AGAINST> and '--what'. Use one of them."
46
+ )
47
+ else:
48
+ _what = what
49
+ if _against:
50
+ if against:
51
+ raise typer.BadParameter(
52
+ "You cannot specify both 'refs' <WHAT>..<AGAINST> and '--against'. Use one of them."
53
+ )
54
+ else:
55
+ _against = against
56
+ return _what, _against
39
57
 
40
58
 
41
- @app.async_command()
42
- async def render(format: str = Report.Format.MARKDOWN):
43
- print(Report.load().render(format=format))
59
+ def arg_refs() -> typer.Argument:
60
+ return typer.Argument(
61
+ default=None,
62
+ help="Git refs to review, [what]..[against] e.g. 'HEAD..HEAD~1'"
63
+ )
64
+
65
+
66
+ def arg_what() -> typer.Option:
67
+ return typer.Option(None, "--what", "-w", help="Git ref to review")
68
+
69
+
70
+ def arg_filters() -> typer.Option:
71
+ return typer.Option(
72
+ "", "--filter", "-f", "--filters",
73
+ help="""
74
+ filter reviewed files by glob / fnmatch pattern(s),
75
+ e.g. 'src/**/*.py', may be comma-separated
76
+ """,
77
+ )
44
78
 
45
79
 
46
- @app.async_command(help="Review remote code")
47
- async def remote(url=typer.Option(), branch=typer.Option()):
48
- if os.path.exists("reviewed-repo"):
49
- shutil.rmtree("reviewed-repo")
50
- Repo.clone_from(url, branch=branch, to_path="reviewed-repo")
51
- prev_dir = os.getcwd()
52
- try:
53
- os.chdir("reviewed-repo")
54
- await review()
55
- finally:
56
- os.chdir(prev_dir)
80
+ def arg_out() -> typer.Option:
81
+ return typer.Option(
82
+ None,
83
+ "--out", "-o", "--output",
84
+ help="Output folder for the code review report"
85
+ )
57
86
 
58
87
 
59
- @app.async_command(help="Leave a GitHub PR comment with the review.")
60
- async def github_comment(
88
+ def arg_against() -> typer.Option:
89
+ return typer.Option(
90
+ None,
91
+ "--against", "-vs", "--vs",
92
+ help="Git ref to compare against"
93
+ )
94
+
95
+
96
+ @app_no_subcommand.command(name="review", help="Perform code review")
97
+ @app.command(name="review", help="Perform code review")
98
+ def cmd_review(
99
+ refs: str = arg_refs(),
100
+ what: str = arg_what(),
101
+ against: str = arg_against(),
102
+ filters: str = arg_filters(),
103
+ merge_base: bool = typer.Option(default=True, help="Use merge base for comparison"),
104
+ out: str = arg_out()
105
+ ):
106
+ _what, _against = args_to_target(refs, what, against)
107
+ asyncio.run(review(
108
+ what=_what,
109
+ against=_against,
110
+ filters=filters,
111
+ use_merge_base=merge_base,
112
+ out_folder=out,
113
+ ))
114
+
115
+
116
+ @app.command(help="Configure LLM for local usage interactively")
117
+ def setup():
118
+ mc.interactive_setup(ENV_CONFIG_FILE)
119
+
120
+
121
+ @app.command()
122
+ def render(format: str = Report.Format.MARKDOWN):
123
+ print(Report.load().render(format=format))
124
+
125
+
126
+ @app.command(help="Review remote code")
127
+ def remote(
128
+ url: str = typer.Argument(..., help="Git repository URL"),
129
+ refs: str = arg_refs(),
130
+ what: str = arg_what(),
131
+ against: str = arg_against(),
132
+ filters: str = arg_filters(),
133
+ merge_base: bool = typer.Option(default=True, help="Use merge base for comparison"),
134
+ out: str = arg_out()
135
+ ):
136
+ _what, _against = args_to_target(refs, what, against)
137
+ with tempfile.TemporaryDirectory() as temp_dir:
138
+ logging.info(f"Cloning [{mc.ui.green(url)}] to {mc.utils.file_link(temp_dir)} ...")
139
+ repo = Repo.clone_from(url, branch=_what, to_path=temp_dir)
140
+ asyncio.run(review(
141
+ repo=repo,
142
+ what=_what,
143
+ against=_against,
144
+ filters=filters,
145
+ use_merge_base=merge_base,
146
+ out_folder=out or '.',
147
+ ))
148
+ repo.close()
149
+
150
+
151
+ @app.command(help="Leave a GitHub PR comment with the review.")
152
+ def github_comment(
61
153
  token: str = typer.Option(
62
154
  os.environ.get("GITHUB_TOKEN", ""), help="GitHub token (or set GITHUB_TOKEN env var)"
63
155
  ),
@@ -65,7 +157,7 @@ async def github_comment(
65
157
  """
66
158
  Leaves a comment with the review on the current GitHub pull request.
67
159
  """
68
- file = "code-review-report.txt"
160
+ file = "code-review-report.md"
69
161
  if not os.path.exists(file):
70
162
  print(f"Review file not found: {file}")
71
163
  raise typer.Exit(4)
@@ -110,3 +202,35 @@ async def github_comment(
110
202
  else:
111
203
  logging.error(f"Failed to post comment: {resp.status_code} {resp.reason}\n{resp.text}")
112
204
  raise typer.Exit(5)
205
+
206
+
207
+ @app.command(help="List files in the diff. Might be useful to check what will be reviewed.")
208
+ def files(
209
+ refs: str = arg_refs(),
210
+ what: str = arg_what(),
211
+ against: str = arg_against(),
212
+ filters: str = arg_filters(),
213
+ merge_base: bool = typer.Option(default=True, help="Use merge base for comparison"),
214
+ diff: bool = typer.Option(default=False, help="Show diff content")
215
+ ):
216
+ _what, _against = args_to_target(refs, what, against)
217
+ repo = Repo(".")
218
+ patch_set = get_diff(repo=repo, what=_what, against=_against, use_merge_base=merge_base)
219
+ patch_set = filter_diff(patch_set, filters)
220
+ print(
221
+ f"Changed files: "
222
+ f"{mc.ui.green(_what or 'INDEX')} vs "
223
+ f"{mc.ui.yellow(_against or repo.remotes.origin.refs.HEAD.reference.name)}"
224
+ f"{' filtered by '+mc.ui.cyan(filters) if filters else ''}"
225
+ )
226
+
227
+ for patch in patch_set:
228
+ if patch.is_added_file:
229
+ color = mc.ui.green
230
+ elif patch.is_removed_file:
231
+ color = mc.ui.red
232
+ else:
233
+ color = mc.ui.blue
234
+ print(f"- {color(patch.path)}")
235
+ if diff:
236
+ print(mc.ui.gray(textwrap.indent(str(patch), " ")))
@@ -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 = ""#Path("~/.env.ai-code-review").expanduser()
6
+ ENV_CONFIG_FILE = Path("~/.env.ai-code-review").expanduser()
7
7
  JSON_REPORT_FILE_NAME = "code-review-report.json"
ai_code_review/core.py CHANGED
@@ -1,6 +1,8 @@
1
1
  import fnmatch
2
2
  import logging
3
+ from os import PathLike
3
4
  from typing import Iterable
5
+ from pathlib import Path
4
6
 
5
7
  import microcore as mc
6
8
  from git import Repo
@@ -9,15 +11,76 @@ from unidiff.constants import DEV_NULL
9
11
 
10
12
  from .project_config import ProjectConfig
11
13
  from .report_struct import Report
14
+ from .constants import JSON_REPORT_FILE_NAME
12
15
 
13
16
 
14
- def get_diff(repo: Repo = None, against: str = "HEAD") -> PatchSet | list[PatchedFile]:
17
+ def is_binary_file(repo: Repo, file_path: str) -> bool:
18
+ """
19
+ Check if a file is binary by attempting to read it as text.
20
+ Returns True if the file is binary, False otherwise.
21
+ """
22
+ try:
23
+ # Attempt to read the file content from the repository tree
24
+ content = repo.tree()[file_path].data_stream.read()
25
+ # Try decoding as UTF-8; if it fails, it's likely binary
26
+ content.decode("utf-8")
27
+ return False
28
+ except (UnicodeDecodeError, KeyError):
29
+ return True
30
+ except Exception as e:
31
+ logging.warning(f"Error checking if file {file_path} is binary: {e}")
32
+ return True # Conservatively treat errors as binary to avoid issues
33
+
34
+
35
+ def get_diff(
36
+ repo: Repo = None,
37
+ what: str = None,
38
+ against: str = None,
39
+ use_merge_base: bool = True,
40
+ ) -> PatchSet | list[PatchedFile]:
15
41
  repo = repo or Repo(".")
16
- base = repo.remotes.origin.refs.HEAD.reference.name
17
- logging.info(f"{base}...{against}")
18
- diff_content = repo.git.diff(base, against)
42
+ if not against:
43
+ against = repo.remotes.origin.refs.HEAD.reference.name # origin/main
44
+ if not what:
45
+ what = None # working copy
46
+ if use_merge_base:
47
+ if what is None:
48
+ try:
49
+ current_ref = repo.active_branch.name
50
+ except TypeError:
51
+ # In detached HEAD state, use HEAD directly
52
+ current_ref = "HEAD"
53
+ logging.info(
54
+ "Detected detached HEAD state, using HEAD as current reference"
55
+ )
56
+ else:
57
+ current_ref = what
58
+ merge_base = repo.merge_base(current_ref or repo.active_branch.name, against)[0]
59
+ against = merge_base.hexsha
60
+ logging.info(
61
+ f"Using merge base: {mc.ui.cyan(merge_base.hexsha[:8])} ({merge_base.summary})"
62
+ )
63
+ logging.info(
64
+ f"Making diff: {mc.ui.green(what or 'INDEX')} vs {mc.ui.yellow(against)}"
65
+ )
66
+ diff_content = repo.git.diff(against, what)
19
67
  diff = PatchSet.from_string(diff_content)
20
- return diff
68
+ diff = PatchSet.from_string(diff_content)
69
+
70
+ # Filter out binary files
71
+ non_binary_diff = PatchSet([])
72
+ for patched_file in diff:
73
+ # Check if the file is binary using the source or target file path
74
+ file_path = (
75
+ patched_file.target_file
76
+ if patched_file.target_file != DEV_NULL
77
+ else patched_file.source_file
78
+ )
79
+ if file_path == DEV_NULL or is_binary_file(repo, file_path.lstrip("b/")):
80
+ logging.info(f"Skipping binary file: {patched_file.path}")
81
+ continue
82
+ non_binary_diff.append(patched_file)
83
+ return non_binary_diff
21
84
 
22
85
 
23
86
  def filter_diff(
@@ -26,7 +89,6 @@ def filter_diff(
26
89
  """
27
90
  Filter the diff files by the given fnmatch filters.
28
91
  """
29
- print([f.path for f in patch_set])
30
92
  assert isinstance(filters, (list, str))
31
93
  if not isinstance(filters, list):
32
94
  filters = [f.strip() for f in filters.split(",") if f.strip()]
@@ -37,7 +99,6 @@ def filter_diff(
37
99
  for file in patch_set
38
100
  if any(fnmatch.fnmatch(file.path, pattern) for pattern in filters)
39
101
  ]
40
- print([f.path for f in files])
41
102
  return files
42
103
 
43
104
 
@@ -54,18 +115,32 @@ def file_lines(repo: Repo, file: str, max_tokens: int = None) -> str:
54
115
 
55
116
 
56
117
  def make_cr_summary(cfg: ProjectConfig, report: Report, diff):
57
- return mc.prompt(
58
- cfg.summary_prompt,
59
- diff=mc.tokenizing.fit_to_token_size(diff, cfg.max_code_tokens)[0],
60
- issues=report.issues,
61
- **cfg.prompt_vars,
62
- ).to_llm() if cfg.summary_prompt else ""
118
+ return (
119
+ mc.prompt(
120
+ cfg.summary_prompt,
121
+ diff=mc.tokenizing.fit_to_token_size(diff, cfg.max_code_tokens)[0],
122
+ issues=report.issues,
123
+ **cfg.prompt_vars,
124
+ ).to_llm()
125
+ if cfg.summary_prompt
126
+ else ""
127
+ )
63
128
 
64
129
 
65
- async def review(filters: str | list[str] = ""):
130
+ async def review(
131
+ repo: Repo = None,
132
+ what: str = None,
133
+ against: str = None,
134
+ filters: str | list[str] = "",
135
+ use_merge_base: bool = True,
136
+ out_folder: str | PathLike | None = None,
137
+ ):
66
138
  cfg = ProjectConfig.load()
67
- repo = Repo(".")
68
- diff = get_diff(repo=repo, against="HEAD")
139
+ repo = repo or Repo(".")
140
+ out_folder = Path(out_folder or repo.working_tree_dir)
141
+ diff = get_diff(
142
+ repo=repo, what=what, against=against, use_merge_base=use_merge_base
143
+ )
69
144
  diff = filter_diff(diff, filters)
70
145
  if not diff:
71
146
  logging.error("Nothing to review")
@@ -78,7 +153,7 @@ async def review(filters: str | list[str] = ""):
78
153
  cfg.max_code_tokens
79
154
  - mc.tokenizing.num_tokens_from_string(str(file_diff)),
80
155
  )
81
- if file_diff.target_file != DEV_NULL
156
+ if file_diff.target_file != DEV_NULL and not file_diff.is_added_file
82
157
  else ""
83
158
  )
84
159
  for file_diff in diff
@@ -103,12 +178,14 @@ async def review(filters: str | list[str] = ""):
103
178
  if lines[file]:
104
179
  f_lines = [""] + lines[file].splitlines()
105
180
  i["affected_code"] = "\n".join(
106
- f_lines[i["start_line"]: i["end_line"]+1]
181
+ f_lines[i["start_line"]: i["end_line"] + 1]
107
182
  )
108
183
  exec(cfg.post_process, {"mc": mc, **locals()})
184
+ out_folder.mkdir(parents=True, exist_ok=True)
109
185
  report = Report(issues=issues, number_of_processed_files=len(diff))
110
186
  report.summary = make_cr_summary(cfg, report, diff)
111
- report.save()
187
+ report.save(file_name=out_folder / JSON_REPORT_FILE_NAME)
112
188
  report_text = report.render(cfg, Report.Format.MARKDOWN)
113
189
  print(mc.ui.yellow(report_text))
114
- open("code-review-report.txt", "w", encoding="utf-8").write(report_text)
190
+ text_report_path = out_folder / "code-review-report.md"
191
+ text_report_path.write_text(report_text, encoding="utf-8")
ai_code_review/utils.py CHANGED
@@ -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 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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ai-cr
3
- Version: 0.4.8
3
+ Version: 0.6.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
@@ -15,13 +15,12 @@ Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Programming Language :: Python :: 3.13
17
17
  Classifier: Topic :: Software Development
18
- Requires-Dist: GitPython (==3.1.44)
19
- Requires-Dist: ai-microcore (==4.0.0.dev16)
20
- Requires-Dist: anthropic (==0.49.0)
21
- Requires-Dist: async-typer (==0.1.8)
22
- Requires-Dist: google-generativeai (==0.8.5)
23
- Requires-Dist: typer (==0.9.4)
24
- Requires-Dist: unidiff (==0.7.5)
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)
25
24
  Project-URL: Homepage, https://github.com/Nayjest/github-ai-code-review
26
25
  Project-URL: Repository, https://github.com/Nayjest/github-ai-code-review
27
26
  Description-Content-Type: text/markdown
@@ -30,6 +29,7 @@ Description-Content-Type: text/markdown
30
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>
31
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>
32
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
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
34
  </p>
35
35
 
@@ -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.4.7
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 }}
@@ -91,7 +91,8 @@ jobs:
91
91
  > ⚠️ Make sure to add `LLM_API_KEY` to your repository’s GitHub secrets.
92
92
 
93
93
  💪 Done!
94
- PRs to your repository will now receive AI code reviews automatically. ✨
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.
95
96
 
96
97
  ### 2. Run Code Analysis Locally
97
98
 
@@ -151,7 +152,7 @@ pytest
151
152
 
152
153
  ## 🤝 Contributing
153
154
 
154
- **Looking for a specific feature or having trouble?**
155
+ **Looking for a specific feature or having trouble?**
155
156
  Contributions are welcome! ❤️
156
157
  See [CONTRIBUTING.md](https://github.com/Nayjest/ai-code-review/blob/main/CONTRIBUTING.md) for details.
157
158
 
@@ -0,0 +1,15 @@
1
+ ai_code_review/.ai-code-review.toml,sha256=z-3iliCvkkpdAr9l_yUyM-IbgRm9hbAA3CIV6ocuurY,10378
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=1wrF-s6U_f8BjwCl70G7IXIBEhmMUbQDvij5AMDxQ5M,7318
6
+ ai_code_review/constants.py,sha256=K9mNxTq9seTG3aVm__3r1lXb5oCOQjH24Cl3hfX9FsE,281
7
+ ai_code_review/core.py,sha256=BJNs4ZER2-bMulXY2apY6B6hI0nvRCOrLqsJ7L8Bizc,6693
8
+ ai_code_review/project_config.py,sha256=RDbplmncALKw0zgqSG8POofi300z0DPvtF33wt7_1Sk,3651
9
+ ai_code_review/report_struct.py,sha256=N-EnNMwBY9LyJ9sdFHpUzn2fwBvxo5TZYYiJagBl8Po,3488
10
+ ai_code_review/utils.py,sha256=vlzU3M89qK6_mVkBMnppZaOFsXddVsIBVdfmbN3cxDY,2939
11
+ ai_cr-0.6.0.dist-info/entry_points.txt,sha256=u0N5NroPYEGqmDGaGqFmiijJ5swzpe7EyKBupnkp1FY,49
12
+ ai_cr-0.6.0.dist-info/LICENSE,sha256=XATf3zv-CppUSJqI18KLhwnPEomUXEl5WbBzFyb9OSU,1096
13
+ ai_cr-0.6.0.dist-info/METADATA,sha256=poIlyrRUJQEKwRN7JIgKGOjgL1DR291tw6jibTi4EMs,5835
14
+ ai_cr-0.6.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
15
+ ai_cr-0.6.0.dist-info/RECORD,,
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ ai-cr=ai_code_review.cli:main
3
+
@@ -1,14 +0,0 @@
1
- ai_code_review/.ai-code-review.toml,sha256=z-3iliCvkkpdAr9l_yUyM-IbgRm9hbAA3CIV6ocuurY,10378
2
- ai_code_review/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- ai_code_review/bootstrap.py,sha256=qiI_jcNYQ30BC0exNzUgjmI2TE_-BrsBSVzMW9JjoFI,2116
4
- ai_code_review/cli.py,sha256=lulkEEUmQ1Fn3j3aQ0ZXc6aPHPcQDsr4YqA_C6gFFH4,3409
5
- ai_code_review/constants.py,sha256=jac2dNiGjShe5jd64WXKCYxpM-4lSh1WcXsbQTIYnAU,284
6
- ai_code_review/core.py,sha256=LhstEanCpMD0rVEYja0EQ4GuwB47RXGJJNOTkD6WqYk,3888
7
- ai_code_review/project_config.py,sha256=RDbplmncALKw0zgqSG8POofi300z0DPvtF33wt7_1Sk,3651
8
- ai_code_review/report_struct.py,sha256=N-EnNMwBY9LyJ9sdFHpUzn2fwBvxo5TZYYiJagBl8Po,3488
9
- ai_code_review/utils.py,sha256=xHocdWXr-_Ghna31Hz80Et37gIXokz59UbcM_zOqK_k,2171
10
- ai_cr-0.4.8.dist-info/entry_points.txt,sha256=fQYGUz6CVCClhei9kNCTtJUX6ia-dTca6p1sZ9pnst8,48
11
- ai_cr-0.4.8.dist-info/LICENSE,sha256=XATf3zv-CppUSJqI18KLhwnPEomUXEl5WbBzFyb9OSU,1096
12
- ai_cr-0.4.8.dist-info/METADATA,sha256=mQCK_8YgDCeq-bsVCS56pcKxl2v0ufTS8K_vmWsHnfM,5607
13
- ai_cr-0.4.8.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
14
- ai_cr-0.4.8.dist-info/RECORD,,
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- ai-cr=ai_code_review.cli:app
3
-
File without changes
File without changes