ai-cr 0.4.7__py3-none-any.whl → 0.5.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()
@@ -1,7 +1,9 @@
1
1
  import logging
2
+ import os
2
3
  from datetime import datetime
3
4
 
4
5
  import microcore as mc
6
+ from ai_code_review.utils import is_running_in_github_action
5
7
 
6
8
  from .constants import ENV_CONFIG_FILE
7
9
 
@@ -28,10 +30,33 @@ def bootstrap():
28
30
  """Bootstrap the application with the environment configuration."""
29
31
  setup_logging()
30
32
  logging.info("Bootstrapping...")
31
- mc.configure(
32
- DOT_ENV_FILE=ENV_CONFIG_FILE,
33
- VALIDATE_CONFIG=False,
34
- USE_LOGGING=True,
35
- EMBEDDING_DB_TYPE=mc.EmbeddingDbType.NONE,
36
- )
33
+ try:
34
+ mc.configure(
35
+ DOT_ENV_FILE=ENV_CONFIG_FILE,
36
+ USE_LOGGING=True,
37
+ EMBEDDING_DB_TYPE=mc.EmbeddingDbType.NONE,
38
+ )
39
+ except mc.LLMConfigError as e:
40
+ msg = str(e)
41
+ if is_running_in_github_action():
42
+ ref = os.getenv("GITHUB_WORKFLOW_REF", "")
43
+ if 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})"
48
+ msg += (
49
+ f"\nPlease check your GitHub Action Secrets "
50
+ f"and `env` configuration section of the corresponding workflow step{ref}."
51
+ )
52
+ else:
53
+ msg += (
54
+ "\nPlease run 'ai-code-review setup' "
55
+ "to configure LLM API access (API keys, model, etc)."
56
+ )
57
+ print(mc.ui.red(msg))
58
+ raise SystemExit(2)
59
+ except Exception as e:
60
+ logging.error(f"Unexpected configuration error: {e}")
61
+ raise SystemExit(3)
37
62
  mc.logging.LoggingConfig.STRIP_REQUEST_LINES = [100, 15]
ai_code_review/cli.py CHANGED
@@ -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 .core import review
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, filters=typer.Option("", "--filter", "-f", "--filters")):
37
+ def cli(ctx: typer.Context):
30
38
  if ctx.invoked_subcommand != "setup":
31
39
  bootstrap()
32
- if not ctx.invoked_subcommand:
33
- asyncio.run(review(filters=filters))
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")
ai_code_review/core.py CHANGED
@@ -11,11 +11,18 @@ from .project_config import ProjectConfig
11
11
  from .report_struct import Report
12
12
 
13
13
 
14
- def get_diff(repo: Repo = None, against: str = "HEAD") -> PatchSet | list[PatchedFile]:
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
- base = repo.remotes.origin.refs.HEAD.reference.name
17
- logging.info(f"{base}...{against}")
18
- diff_content = repo.git.diff(base, against)
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(filters: str | list[str] = ""):
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="HEAD")
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
ai_code_review/utils.py CHANGED
@@ -1,4 +1,8 @@
1
+ import sys
2
+ import os
1
3
  from pathlib import Path
4
+ import typer
5
+
2
6
 
3
7
  _EXT_TO_HINT: dict[str, str] = {
4
8
  # scripting & languages
@@ -61,11 +65,20 @@ _EXT_TO_HINT: dict[str, str] = {
61
65
  }
62
66
 
63
67
 
64
- def syntax_hint(file_name: str) -> str:
68
+ def syntax_hint(file_path: str | Path) -> str:
65
69
  """
66
- Get the syntax hint for a file based on its extension.
70
+ Returns a syntax highlighting hint based on the file's extension or name.
71
+
72
+ This can be used to annotate code blocks for rendering with syntax highlighting,
73
+ e.g., using Markdown-style code blocks: ```<syntax_hint>\n<code>\n```.
74
+
75
+ Args:
76
+ file_path (str | Path): Path to the file.
77
+
78
+ Returns:
79
+ str: A syntax identifier suitable for code highlighting (e.g., 'python', 'json').
67
80
  """
68
- p = Path(file_name)
81
+ p = Path(file_path)
69
82
  ext = p.suffix.lower()
70
83
  if not ext:
71
84
  name = p.name.lower()
@@ -73,3 +86,31 @@ def syntax_hint(file_name: str) -> str:
73
86
  return "dockerfile"
74
87
  return ""
75
88
  return _EXT_TO_HINT.get(ext, ext.lstrip("."))
89
+
90
+
91
+ def is_running_in_github_action():
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
  Metadata-Version: 2.3
2
2
  Name: ai-cr
3
- Version: 0.4.7
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.dev16)
20
- Requires-Dist: anthropic (==0.49.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.4.1
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,9 @@ pytest
151
151
 
152
152
  ## 🤝 Contributing
153
153
 
154
- We ❤️ contributions! See [CONTRIBUTING.md](https://github.com/Nayjest/ai-code-review/blob/main/CONTRIBUTING.md).
154
+ **Looking for a specific feature or having trouble?**
155
+ Contributions are welcome! ❤️
156
+ See [CONTRIBUTING.md](https://github.com/Nayjest/ai-code-review/blob/main/CONTRIBUTING.md) for details.
155
157
 
156
158
  ## 📝 License
157
159
 
@@ -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=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
4
+ ai_code_review/bootstrap.py,sha256=b9BlhRgliwsjmWRCCZcsTivsOw9AbXOf8_AZYjQTx-c,2336
5
+ ai_code_review/cli.py,sha256=L1utFMHs4D9HVcuJ1VC96PEvP6q26UHsfCz_Lq49VMg,4893
6
+ ai_code_review/constants.py,sha256=K9mNxTq9seTG3aVm__3r1lXb5oCOQjH24Cl3hfX9FsE,281
7
+ ai_code_review/core.py,sha256=WMGU2zwZTJ1P6PkUId-RJP7KHQbzB31GKSnI5wsedcA,4177
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=UzFp2ShNzQvsydxJ98bdr_j2rBUHcm62pPtl6hBuZD4,2914
11
+ ai_cr-0.5.0.dist-info/entry_points.txt,sha256=u0N5NroPYEGqmDGaGqFmiijJ5swzpe7EyKBupnkp1FY,49
12
+ ai_cr-0.5.0.dist-info/LICENSE,sha256=XATf3zv-CppUSJqI18KLhwnPEomUXEl5WbBzFyb9OSU,1096
13
+ ai_cr-0.5.0.dist-info/METADATA,sha256=k3FaB6nbVV0xbgZYPg_VQ6zMauB65Xy6CLWI4qFOMF0,5609
14
+ ai_cr-0.5.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
15
+ ai_cr-0.5.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=vhAWjrfhkyyA504YusXhBlndsXd_bg2RP9hQbfnvM1M,1280
4
- ai_code_review/cli.py,sha256=lulkEEUmQ1Fn3j3aQ0ZXc6aPHPcQDsr4YqA_C6gFFH4,3409
5
- ai_code_review/constants.py,sha256=K9mNxTq9seTG3aVm__3r1lXb5oCOQjH24Cl3hfX9FsE,281
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=DLzX9kILl5ABAsuwqFJk_3kItjgzzeTUrgZ3RMnDgAY,1713
10
- ai_cr-0.4.7.dist-info/entry_points.txt,sha256=fQYGUz6CVCClhei9kNCTtJUX6ia-dTca6p1sZ9pnst8,48
11
- ai_cr-0.4.7.dist-info/LICENSE,sha256=XATf3zv-CppUSJqI18KLhwnPEomUXEl5WbBzFyb9OSU,1096
12
- ai_cr-0.4.7.dist-info/METADATA,sha256=QCXyksLtb1v8Jl8K5DE3p3nU6Nh-IEZv9XhI4G5vCf0,5530
13
- ai_cr-0.4.7.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
14
- ai_cr-0.4.7.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