ai-cr 1.0.1__tar.gz → 2.0.0.dev2__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.
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.3
2
+ Name: ai-cr
3
+ Version: 2.0.0.dev2
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
+ License: MIT
6
+ Keywords: static code analysis,code review,code quality,ai,coding,assistant,llm,github,automation,devops,developer tools,github actions,workflows,git
7
+ Author: Nayjest
8
+ Author-email: mail@vitaliy.in
9
+ Requires-Python: >=3.11,<4.0
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development
18
+ Requires-Dist: GitPython (>=3.1.44,<4.0.0)
19
+ Requires-Dist: ai-microcore (==4.0.0)
20
+ Requires-Dist: anthropic (>=0.52.2,<0.53.0)
21
+ Requires-Dist: ghapi (>=1.0.6,<1.1.0)
22
+ Requires-Dist: google-generativeai (>=0.8.5,<0.9.0)
23
+ Requires-Dist: jira (>=3.8.0,<4.0.0)
24
+ Requires-Dist: typer (>=0.16.0,<0.17.0)
25
+ Requires-Dist: unidiff (>=0.7.5,<0.8.0)
26
+ Project-URL: Homepage, https://github.com/Nayjest/Gito
27
+ Project-URL: Repository, https://github.com/Nayjest/Gito
28
+ Description-Content-Type: text/markdown
29
+
30
+ <h1 align="center"><a href="#"><img alt="Gito: AI Code Reviewer" src="press-kit/logo/gito-ai-code-reviewer_logo-180.png" align="center" width="180"></a></h1>
31
+ <p align="center">
32
+ <a href="https://pypi.org/project/gito.bot/" target="_blank"><img src="https://img.shields.io/pypi/v/gito.bot" alt="PYPI Release"></a>
33
+ <a href="https://github.com/Nayjest/Gito/actions/workflows/code-style.yml" target="_blank"><img src="https://github.com/Nayjest/Gito/actions/workflows/code-style.yml/badge.svg" alt="PyLint"></a>
34
+ <a href="https://github.com/Nayjest/Gito/actions/workflows/tests.yml" target="_blank"><img src="https://github.com/Nayjest/Gito/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
35
+ <img src="https://github.com/Nayjest/Gito/blob/main/coverage.svg" alt="Code Coverage">
36
+ <a href="https://github.com/Nayjest/Gito/blob/main/LICENSE" target="_blank"><img src="https://img.shields.io/static/v1?label=license&message=MIT&color=d08aff" alt="License"></a>
37
+ </p>
38
+
39
+ **Gito** is an open-source **AI code reviewer** that works with any language model provider.
40
+ It detects issues in GitHub pull requests or local changes—instantly, reliably, and without vendor lock-in.
41
+
42
+ Get consistent, thorough code reviews in seconds—no waiting for human availability.
43
+
44
+ ## ✨ Why Gito?
45
+
46
+ - [⚡] **Lightning Fast:** Get detailed code reviews in seconds, not days — powered by parallelized LLM processing
47
+ - [🔧] **Vendor Agnostic:** Works with any language model provider (OpenAI, Anthropic, Google, local models, etc.)
48
+ - [🌐] **Universal:** Supports all major programming languages and frameworks
49
+ - [🔍] **Comprehensive Analysis:** Detect issues across security, performance, maintainability, best practices, and much more
50
+ - [📈] **Consistent Quality:** Never tired, never biased—consistent review quality every time
51
+ - [🚀] **Easy Integration:** Automatically reviews pull requests via GitHub Actions and posts results as PR comments
52
+ - [🎛️] **Infinitely Flexible:** Adapt to any project's standards—configure review rules, severity levels, and focus areas, build custom workflows
53
+
54
+ ## 🎯 Perfect For
55
+
56
+ - Solo developers who want expert-level code review without the wait
57
+ - Teams looking to catch issues before human review
58
+ - Open source projects maintaining high code quality at scale
59
+ - CI/CD pipelines requiring automated quality gates
60
+
61
+ ✨ See [code review in action](https://github.com/Nayjest/Gito/pull/39#issuecomment-2906968729) ✨
62
+
63
+ ## 🚀 Quickstart
64
+
65
+ ### 1. Review Pull Requests via GitHub Actions
66
+
67
+ Create a `.github/workflows/gito-code-review.yml` file:
68
+
69
+ ```yaml
70
+ name: "Gito: AI Code Review"
71
+ on: { pull_request: { types: [opened, synchronize, reopened] } }
72
+ jobs:
73
+ review:
74
+ runs-on: ubuntu-latest
75
+ permissions: { contents: read, pull-requests: write } # 'write' for leaving the summary comment
76
+ steps:
77
+ - uses: actions/checkout@v4
78
+ with: { fetch-depth: 0 }
79
+ - name: Set up Python
80
+ uses: actions/setup-python@v5
81
+ with: { python-version: "3.13" }
82
+ - name: Install AI Code Review tool
83
+ run: pip install gito.bot~=2.0
84
+ - name: Run AI code analysis
85
+ env:
86
+ LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
87
+ LLM_API_TYPE: openai
88
+ MODEL: "gpt-4.1"
89
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
90
+ run: |
91
+ gito --verbose review
92
+ gito github-comment --token ${{ secrets.GITHUB_TOKEN }}
93
+ - uses: actions/upload-artifact@v4
94
+ with:
95
+ name: ai-code-review-results
96
+ path: |
97
+ code-review-report.md
98
+ code-review-report.json
99
+ ```
100
+
101
+ > ⚠️ Make sure to add `LLM_API_KEY` to your repository’s GitHub secrets.
102
+
103
+ 💪 Done!
104
+ PRs to your repository will now receive AI code reviews automatically. ✨
105
+ See [GitHub Setup Guide](https://github.com/Nayjest/Gito/blob/main/documentation/github_setup.md) for more details.
106
+
107
+ ### 2. Running Code Analysis Locally
108
+
109
+ #### Initial Local Setup
110
+
111
+ **Prerequisites:** [Python](https://www.python.org/downloads/) 3.11 / 3.12 / 3.13
112
+
113
+ **Step1:** Install [gito.bot](https://github.com/Nayjest/Gito) using [pip](https://en.wikipedia.org/wiki/Pip_(package_manager)).
114
+ ```bash
115
+ pip install gito.bot
116
+ ```
117
+
118
+ > **Troubleshooting:**
119
+ > pip may be also available via cli as `pip3` depending on your Python installation.
120
+
121
+ **Step2:** Perform initial setup
122
+
123
+ The following command will perform one-time setup using an interactive wizard.
124
+ You will be prompted to enter LLM configuration details (API type, API key, etc).
125
+ Configuration will be saved to `~/.gito/.env`.
126
+
127
+ ```bash
128
+ gito setup
129
+ ```
130
+
131
+ > **Troubleshooting:**
132
+ > On some systems, `gito` command may not became available immediately after installation.
133
+ > Try restarting your terminal or running `python -m gito` instead.
134
+
135
+
136
+ #### Perform your first AI code review locally
137
+
138
+ **Step1:** Navigate to your repository root directory.
139
+ **Step2:** Switch to the branch you want to review.
140
+ **Step3:** Run following command
141
+ ```bash
142
+ gito review
143
+ ```
144
+
145
+ > **Note:** This will analyze the current branch against the repository main branch by default.
146
+ > Files that are not staged for commit will be ignored.
147
+ > See `gito --help` for more options.
148
+
149
+ **Reviewing remote repository**
150
+
151
+ ```bash
152
+ gito remote git@github.com:owner/repo.git <FEATURE_BRANCH>..<MAIN_BRANCH>
153
+ ```
154
+ Use interactive help for details:
155
+ ```bash
156
+ gito remote --help
157
+ ```
158
+
159
+ ## 🔧 Configuration
160
+
161
+ Change behavior via `.gito/config.toml`:
162
+
163
+ - Prompt templates, filtering and post-processing using Python code snippets
164
+ - Tagging, severity, and confidence settings
165
+ - Custom AI awards for developer brilliance
166
+ - Output customization
167
+
168
+ You can override the default config by placing `.gito/config.toml` in your repo root.
169
+
170
+
171
+ See default configuration [here](https://github.com/Nayjest/Gito/blob/main/gito/config.toml).
172
+
173
+ More details can be found in [📖 Configuration Cookbook](https://github.com/Nayjest/Gito/blob/main/documentation/config_cookbook.md)
174
+
175
+ ## 💻 Development Setup
176
+
177
+ Install dependencies:
178
+
179
+ ```bash
180
+ make install
181
+ ```
182
+
183
+ Format code and check style:
184
+
185
+ ```bash
186
+ make black
187
+ make cs
188
+ ```
189
+
190
+ Run tests:
191
+
192
+ ```bash
193
+ pytest
194
+ ```
195
+
196
+ ## 🤝 Contributing
197
+
198
+ **Looking for a specific feature or having trouble?**
199
+ Contributions are welcome! ❤️
200
+ See [CONTRIBUTING.md](https://github.com/Nayjest/Gito/blob/main/CONTRIBUTING.md) for details.
201
+
202
+ ## 📝 License
203
+
204
+ Licensed under the [MIT License](https://github.com/Nayjest/Gito/blob/main/LICENSE).
205
+
206
+ © 2025 [Vitalii Stepanenko](mailto:mail@vitaliy.in)
207
+
@@ -0,0 +1,177 @@
1
+ <h1 align="center"><a href="#"><img alt="Gito: AI Code Reviewer" src="press-kit/logo/gito-ai-code-reviewer_logo-180.png" align="center" width="180"></a></h1>
2
+ <p align="center">
3
+ <a href="https://pypi.org/project/gito.bot/" target="_blank"><img src="https://img.shields.io/pypi/v/gito.bot" alt="PYPI Release"></a>
4
+ <a href="https://github.com/Nayjest/Gito/actions/workflows/code-style.yml" target="_blank"><img src="https://github.com/Nayjest/Gito/actions/workflows/code-style.yml/badge.svg" alt="PyLint"></a>
5
+ <a href="https://github.com/Nayjest/Gito/actions/workflows/tests.yml" target="_blank"><img src="https://github.com/Nayjest/Gito/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
6
+ <img src="https://github.com/Nayjest/Gito/blob/main/coverage.svg" alt="Code Coverage">
7
+ <a href="https://github.com/Nayjest/Gito/blob/main/LICENSE" target="_blank"><img src="https://img.shields.io/static/v1?label=license&message=MIT&color=d08aff" alt="License"></a>
8
+ </p>
9
+
10
+ **Gito** is an open-source **AI code reviewer** that works with any language model provider.
11
+ It detects issues in GitHub pull requests or local changes—instantly, reliably, and without vendor lock-in.
12
+
13
+ Get consistent, thorough code reviews in seconds—no waiting for human availability.
14
+
15
+ ## ✨ Why Gito?
16
+
17
+ - [⚡] **Lightning Fast:** Get detailed code reviews in seconds, not days — powered by parallelized LLM processing
18
+ - [🔧] **Vendor Agnostic:** Works with any language model provider (OpenAI, Anthropic, Google, local models, etc.)
19
+ - [🌐] **Universal:** Supports all major programming languages and frameworks
20
+ - [🔍] **Comprehensive Analysis:** Detect issues across security, performance, maintainability, best practices, and much more
21
+ - [📈] **Consistent Quality:** Never tired, never biased—consistent review quality every time
22
+ - [🚀] **Easy Integration:** Automatically reviews pull requests via GitHub Actions and posts results as PR comments
23
+ - [🎛️] **Infinitely Flexible:** Adapt to any project's standards—configure review rules, severity levels, and focus areas, build custom workflows
24
+
25
+ ## 🎯 Perfect For
26
+
27
+ - Solo developers who want expert-level code review without the wait
28
+ - Teams looking to catch issues before human review
29
+ - Open source projects maintaining high code quality at scale
30
+ - CI/CD pipelines requiring automated quality gates
31
+
32
+ ✨ See [code review in action](https://github.com/Nayjest/Gito/pull/39#issuecomment-2906968729) ✨
33
+
34
+ ## 🚀 Quickstart
35
+
36
+ ### 1. Review Pull Requests via GitHub Actions
37
+
38
+ Create a `.github/workflows/gito-code-review.yml` file:
39
+
40
+ ```yaml
41
+ name: "Gito: AI Code Review"
42
+ on: { pull_request: { types: [opened, synchronize, reopened] } }
43
+ jobs:
44
+ review:
45
+ runs-on: ubuntu-latest
46
+ permissions: { contents: read, pull-requests: write } # 'write' for leaving the summary comment
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+ with: { fetch-depth: 0 }
50
+ - name: Set up Python
51
+ uses: actions/setup-python@v5
52
+ with: { python-version: "3.13" }
53
+ - name: Install AI Code Review tool
54
+ run: pip install gito.bot~=2.0
55
+ - name: Run AI code analysis
56
+ env:
57
+ LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
58
+ LLM_API_TYPE: openai
59
+ MODEL: "gpt-4.1"
60
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61
+ run: |
62
+ gito --verbose review
63
+ gito github-comment --token ${{ secrets.GITHUB_TOKEN }}
64
+ - uses: actions/upload-artifact@v4
65
+ with:
66
+ name: ai-code-review-results
67
+ path: |
68
+ code-review-report.md
69
+ code-review-report.json
70
+ ```
71
+
72
+ > ⚠️ Make sure to add `LLM_API_KEY` to your repository’s GitHub secrets.
73
+
74
+ 💪 Done!
75
+ PRs to your repository will now receive AI code reviews automatically. ✨
76
+ See [GitHub Setup Guide](https://github.com/Nayjest/Gito/blob/main/documentation/github_setup.md) for more details.
77
+
78
+ ### 2. Running Code Analysis Locally
79
+
80
+ #### Initial Local Setup
81
+
82
+ **Prerequisites:** [Python](https://www.python.org/downloads/) 3.11 / 3.12 / 3.13
83
+
84
+ **Step1:** Install [gito.bot](https://github.com/Nayjest/Gito) using [pip](https://en.wikipedia.org/wiki/Pip_(package_manager)).
85
+ ```bash
86
+ pip install gito.bot
87
+ ```
88
+
89
+ > **Troubleshooting:**
90
+ > pip may be also available via cli as `pip3` depending on your Python installation.
91
+
92
+ **Step2:** Perform initial setup
93
+
94
+ The following command will perform one-time setup using an interactive wizard.
95
+ You will be prompted to enter LLM configuration details (API type, API key, etc).
96
+ Configuration will be saved to `~/.gito/.env`.
97
+
98
+ ```bash
99
+ gito setup
100
+ ```
101
+
102
+ > **Troubleshooting:**
103
+ > On some systems, `gito` command may not became available immediately after installation.
104
+ > Try restarting your terminal or running `python -m gito` instead.
105
+
106
+
107
+ #### Perform your first AI code review locally
108
+
109
+ **Step1:** Navigate to your repository root directory.
110
+ **Step2:** Switch to the branch you want to review.
111
+ **Step3:** Run following command
112
+ ```bash
113
+ gito review
114
+ ```
115
+
116
+ > **Note:** This will analyze the current branch against the repository main branch by default.
117
+ > Files that are not staged for commit will be ignored.
118
+ > See `gito --help` for more options.
119
+
120
+ **Reviewing remote repository**
121
+
122
+ ```bash
123
+ gito remote git@github.com:owner/repo.git <FEATURE_BRANCH>..<MAIN_BRANCH>
124
+ ```
125
+ Use interactive help for details:
126
+ ```bash
127
+ gito remote --help
128
+ ```
129
+
130
+ ## 🔧 Configuration
131
+
132
+ Change behavior via `.gito/config.toml`:
133
+
134
+ - Prompt templates, filtering and post-processing using Python code snippets
135
+ - Tagging, severity, and confidence settings
136
+ - Custom AI awards for developer brilliance
137
+ - Output customization
138
+
139
+ You can override the default config by placing `.gito/config.toml` in your repo root.
140
+
141
+
142
+ See default configuration [here](https://github.com/Nayjest/Gito/blob/main/gito/config.toml).
143
+
144
+ More details can be found in [📖 Configuration Cookbook](https://github.com/Nayjest/Gito/blob/main/documentation/config_cookbook.md)
145
+
146
+ ## 💻 Development Setup
147
+
148
+ Install dependencies:
149
+
150
+ ```bash
151
+ make install
152
+ ```
153
+
154
+ Format code and check style:
155
+
156
+ ```bash
157
+ make black
158
+ make cs
159
+ ```
160
+
161
+ Run tests:
162
+
163
+ ```bash
164
+ pytest
165
+ ```
166
+
167
+ ## 🤝 Contributing
168
+
169
+ **Looking for a specific feature or having trouble?**
170
+ Contributions are welcome! ❤️
171
+ See [CONTRIBUTING.md](https://github.com/Nayjest/Gito/blob/main/CONTRIBUTING.md) for details.
172
+
173
+ ## 📝 License
174
+
175
+ Licensed under the [MIT License](https://github.com/Nayjest/Gito/blob/main/LICENSE).
176
+
177
+ © 2025 [Vitalii Stepanenko](mailto:mail@vitaliy.in)
@@ -3,9 +3,10 @@ import os
3
3
  from datetime import datetime
4
4
 
5
5
  import microcore as mc
6
- from ai_code_review.utils import is_running_in_github_action
6
+ import typer
7
7
 
8
- from .constants import ENV_CONFIG_FILE
8
+ from .utils import is_running_in_github_action
9
+ from .constants import HOME_ENV_PATH, EXECUTABLE
9
10
 
10
11
 
11
12
  def setup_logging():
@@ -32,7 +33,7 @@ def bootstrap():
32
33
  logging.info("Bootstrapping...")
33
34
  try:
34
35
  mc.configure(
35
- DOT_ENV_FILE=ENV_CONFIG_FILE,
36
+ DOT_ENV_FILE=HOME_ENV_PATH,
36
37
  USE_LOGGING=True,
37
38
  EMBEDDING_DB_TYPE=mc.EmbeddingDbType.NONE,
38
39
  )
@@ -51,7 +52,7 @@ def bootstrap():
51
52
  )
52
53
  else:
53
54
  msg += (
54
- "\nPlease run 'ai-code-review setup' "
55
+ f"\nPlease run '{EXECUTABLE} setup' "
55
56
  "to configure LLM API access (API keys, model, etc)."
56
57
  )
57
58
  print(mc.ui.red(msg))
@@ -60,3 +61,6 @@ def bootstrap():
60
61
  logging.error(f"Unexpected configuration error: {e}")
61
62
  raise SystemExit(3)
62
63
  mc.logging.LoggingConfig.STRIP_REQUEST_LINES = [300, 15]
64
+
65
+
66
+ app = typer.Typer(pretty_exceptions_show_locals=False)
@@ -12,18 +12,25 @@ from git import Repo
12
12
 
13
13
  from .core import review, get_diff, filter_diff
14
14
  from .report_struct import Report
15
- from .constants import ENV_CONFIG_FILE
16
- from .bootstrap import bootstrap
15
+ from .constants import HOME_ENV_PATH
16
+ from .bootstrap import bootstrap, app
17
17
  from .project_config import ProjectConfig
18
18
  from .utils import no_subcommand, parse_refs_pair
19
19
 
20
- app = typer.Typer(pretty_exceptions_show_locals=False)
20
+ # Import fix command to register it
21
+ from .commands import fix, gh_comment # noqa
22
+
23
+
21
24
  app_no_subcommand = typer.Typer(pretty_exceptions_show_locals=False)
22
25
 
23
26
 
24
27
  def main():
25
28
  if sys.platform == "win32":
26
29
  asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
30
+ # Help subcommand alias: if 'help' appears as first non-option arg, replace it with '--help'
31
+ if len(sys.argv) > 1 and sys.argv[1] == "help":
32
+ sys.argv = [sys.argv[0]] + sys.argv[2:] + ["--help"]
33
+
27
34
  if no_subcommand(app):
28
35
  bootstrap()
29
36
  app_no_subcommand()
@@ -33,10 +40,10 @@ def main():
33
40
 
34
41
  @app.callback(invoke_without_command=True)
35
42
  def cli(ctx: typer.Context, verbose: bool = typer.Option(default=False)):
36
- if verbose:
37
- mc.logging.LoggingConfig.STRIP_REQUEST_LINES = None
38
43
  if ctx.invoked_subcommand != "setup":
39
44
  bootstrap()
45
+ if verbose:
46
+ mc.logging.LoggingConfig.STRIP_REQUEST_LINES = None
40
47
 
41
48
 
42
49
  def args_to_target(refs, what, against) -> tuple[str | None, str | None]:
@@ -97,6 +104,7 @@ def arg_against() -> typer.Option:
97
104
 
98
105
  @app_no_subcommand.command(name="review", help="Perform code review")
99
106
  @app.command(name="review", help="Perform code review")
107
+ @app.command(name="run", hidden=True)
100
108
  def cmd_review(
101
109
  refs: str = arg_refs(),
102
110
  what: str = arg_what(),
@@ -117,12 +125,21 @@ def cmd_review(
117
125
 
118
126
  @app.command(help="Configure LLM for local usage interactively")
119
127
  def setup():
120
- mc.interactive_setup(ENV_CONFIG_FILE)
121
-
122
-
123
- @app.command()
124
- def render(format: str = Report.Format.MARKDOWN):
125
- print(Report.load().render(format=format))
128
+ mc.interactive_setup(HOME_ENV_PATH)
129
+
130
+
131
+ @app.command(name="render")
132
+ @app.command(name="report", hidden=True)
133
+ def render(
134
+ format: str = typer.Argument(default=Report.Format.CLI),
135
+ source: str = typer.Option(
136
+ "",
137
+ "--src",
138
+ "--source",
139
+ help="Source file (json) to load the report from"
140
+ )
141
+ ):
142
+ Report.load(file_name=source).to_cli(report_format=format)
126
143
 
127
144
 
128
145
  @app.command(help="Review remote code")
@@ -225,7 +242,7 @@ def files(
225
242
  f"{mc.ui.yellow(_against or repo.remotes.origin.refs.HEAD.reference.name)}"
226
243
  f"{' filtered by '+mc.ui.cyan(filters) if filters else ''}"
227
244
  )
228
-
245
+ repo.close()
229
246
  for patch in patch_set:
230
247
  if patch.is_added_file:
231
248
  color = mc.ui.green
@@ -0,0 +1 @@
1
+ # Command modules register themselves with the CLI app
@@ -0,0 +1,157 @@
1
+ """
2
+ Fix issues from code review report
3
+ """
4
+ import json
5
+ import logging
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ import git
10
+ import typer
11
+ from microcore import ui
12
+
13
+ from ..bootstrap import app
14
+ from ..constants import JSON_REPORT_FILE_NAME
15
+ from ..report_struct import Report, Issue
16
+
17
+
18
+ @app.command(help="Fix an issue from the code review report")
19
+ def fix(
20
+ issue_number: int = typer.Argument(..., help="Issue number to fix"),
21
+ report_path: Optional[str] = typer.Option(
22
+ None,
23
+ "--report",
24
+ "-r",
25
+ help="Path to the code review report (default: code-review-report.json)"
26
+ ),
27
+ dry_run: bool = typer.Option(
28
+ False, "--dry-run", "-d", help="Only print changes without applying them"
29
+ ),
30
+ commit: bool = typer.Option(default=False, help="Commit changes after applying them"),
31
+ push: bool = typer.Option(default=False, help="Push changes to the remote repository"),
32
+ ) -> list[str]:
33
+ """
34
+ Applies the proposed change for the specified issue number from the code review report.
35
+ """
36
+ # Load the report
37
+ report_path = report_path or JSON_REPORT_FILE_NAME
38
+ try:
39
+ report = Report.load(report_path)
40
+ except (FileNotFoundError, json.JSONDecodeError) as e:
41
+ logging.error(f"Failed to load report from {report_path}: {e}")
42
+ raise typer.Exit(code=1)
43
+
44
+ # Find the issue by number
45
+ issue: Optional[Issue] = None
46
+ for file_issues in report.issues.values():
47
+ for i in file_issues:
48
+ if i.id == issue_number:
49
+ issue = i
50
+ break
51
+ if issue:
52
+ break
53
+
54
+ if not issue:
55
+ logging.error(f"Issue #{issue_number} not found in the report")
56
+ raise typer.Exit(code=1)
57
+
58
+ if not issue.affected_lines:
59
+ logging.error(f"Issue #{issue_number} has no affected lines specified")
60
+ raise typer.Exit(code=1)
61
+
62
+ if not any(affected_line.proposal for affected_line in issue.affected_lines):
63
+ logging.error(f"Issue #{issue_number} has no proposal for fixing")
64
+ raise typer.Exit(code=1)
65
+
66
+ # Apply the fix
67
+ logging.info(f"Fixing issue #{issue_number}: {ui.cyan(issue.title)}")
68
+
69
+ for affected_line in issue.affected_lines:
70
+ if not affected_line.proposal:
71
+ continue
72
+
73
+ file_path = Path(issue.file)
74
+ if not file_path.exists():
75
+ logging.error(f"File {file_path} not found")
76
+ continue
77
+
78
+ try:
79
+ with open(file_path, "r", encoding="utf-8") as f:
80
+ lines = f.readlines()
81
+ except Exception as e:
82
+ logging.error(f"Failed to read file {file_path}: {e}")
83
+ continue
84
+
85
+ # Check if line numbers are valid
86
+ if affected_line.start_line < 1 or affected_line.end_line > len(lines):
87
+ logging.error(
88
+ f"Invalid line range: {affected_line.start_line}-{affected_line.end_line} "
89
+ f"(file has {len(lines)} lines)"
90
+ )
91
+ continue
92
+
93
+ # Get the affected line content for display
94
+ affected_content = "".join(lines[affected_line.start_line - 1:affected_line.end_line])
95
+ print(f"\nFile: {ui.blue(issue.file)}")
96
+ print(f"Lines: {affected_line.start_line}-{affected_line.end_line}")
97
+ print(f"Current content:\n{ui.red(affected_content)}")
98
+ print(f"Proposed change:\n{ui.green(affected_line.proposal)}")
99
+
100
+ if dry_run:
101
+ print(f"{ui.yellow('Dry run')}: Changes not applied")
102
+ continue
103
+
104
+ # Apply the change
105
+ proposal_lines = affected_line.proposal.splitlines(keepends=True)
106
+ if not proposal_lines:
107
+ proposal_lines = [""]
108
+ elif not proposal_lines[-1].endswith(("\n", "\r")):
109
+ # Ensure the last line has a newline if the original does
110
+ if (
111
+ affected_line.end_line < len(lines)
112
+ and lines[affected_line.end_line - 1].endswith(("\n", "\r"))
113
+ ):
114
+ proposal_lines[-1] += "\n"
115
+
116
+ lines[affected_line.start_line - 1:affected_line.end_line] = proposal_lines
117
+
118
+ # Write changes back to the file
119
+ try:
120
+ with open(file_path, "w", encoding="utf-8") as f:
121
+ f.writelines(lines)
122
+ print(f"{ui.green('Success')}: Changes applied to {file_path}")
123
+ except Exception as e:
124
+ logging.error(f"Failed to write changes to {file_path}: {e}")
125
+ raise typer.Exit(code=1)
126
+
127
+ print(f"\n{ui.green('✓')} Issue #{issue_number} fixed successfully")
128
+
129
+ changed_files = [file_path.as_posix()]
130
+ if commit:
131
+ commit_changes(
132
+ changed_files,
133
+ commit_message=f"[AI] Fix issue {issue_number}:{issue.title}",
134
+ push=push
135
+ )
136
+ return changed_files
137
+
138
+
139
+ def commit_changes(
140
+ files: list[str],
141
+ repo: git.Repo = None,
142
+ commit_message: str = "fix by AI",
143
+ push: bool = True
144
+ ) -> None:
145
+ if opened_repo := not repo:
146
+ repo = git.Repo(".")
147
+ for i in files:
148
+ repo.index.add(i)
149
+ repo.index.commit(commit_message)
150
+ if push:
151
+ origin = repo.remotes.origin
152
+ origin.push()
153
+ logging.info(f"Changes pushed to {origin.name}")
154
+ else:
155
+ logging.info("Changes committed but not pushed to remote")
156
+ if opened_repo:
157
+ repo.close()