ai-cr 3.2.2__py3-none-any.whl → 3.3.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.
- {ai_cr-3.2.2.dist-info → ai_cr-3.3.0.dist-info}/LICENSE +21 -21
- {ai_cr-3.2.2.dist-info → ai_cr-3.3.0.dist-info}/METADATA +1 -1
- ai_cr-3.3.0.dist-info/RECORD +41 -0
- {ai_cr-3.2.2.dist-info → ai_cr-3.3.0.dist-info}/WHEEL +1 -1
- gito/__main__.py +4 -4
- gito/bootstrap.py +90 -90
- gito/cli.py +255 -244
- gito/cli_base.py +104 -94
- gito/commands/__init__.py +1 -1
- gito/commands/deploy.py +138 -138
- gito/commands/fix.py +160 -160
- gito/commands/gh_post_review_comment.py +111 -111
- gito/commands/gh_react_to_comment.py +217 -217
- gito/commands/linear_comment.py +53 -53
- gito/commands/repl.py +30 -30
- gito/commands/version.py +8 -8
- gito/config.toml +450 -448
- gito/constants.py +15 -14
- gito/context.py +19 -19
- gito/core.py +520 -508
- gito/env.py +8 -7
- gito/gh_api.py +116 -116
- gito/issue_trackers.py +50 -50
- gito/pipeline.py +83 -83
- gito/pipeline_steps/jira.py +62 -62
- gito/pipeline_steps/linear.py +85 -85
- gito/project_config.py +85 -85
- gito/report_struct.py +136 -136
- gito/tpl/answer.j2 +25 -25
- gito/tpl/github_workflows/components/env-vars.j2 +11 -11
- gito/tpl/github_workflows/components/installs.j2 +23 -23
- gito/tpl/github_workflows/gito-code-review.yml.j2 +32 -32
- gito/tpl/github_workflows/gito-react-to-comments.yml.j2 +70 -70
- gito/tpl/partial/aux_files.j2 +8 -8
- gito/tpl/questions/changes_summary.j2 +55 -55
- gito/tpl/questions/release_notes.j2 +26 -26
- gito/tpl/questions/test_cases.j2 +37 -37
- gito/utils.py +267 -267
- ai_cr-3.2.2.dist-info/RECORD +0 -41
- {ai_cr-3.2.2.dist-info → ai_cr-3.3.0.dist-info}/entry_points.txt +0 -0
gito/cli.py
CHANGED
@@ -1,244 +1,255 @@
|
|
1
|
-
import os
|
2
|
-
import asyncio
|
3
|
-
import logging
|
4
|
-
import sys
|
5
|
-
import textwrap
|
6
|
-
|
7
|
-
import microcore as mc
|
8
|
-
import typer
|
9
|
-
from git import Repo
|
10
|
-
|
11
|
-
|
12
|
-
from .
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
from .
|
24
|
-
from .
|
25
|
-
from .
|
26
|
-
from .
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
from .commands
|
31
|
-
from .commands.
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
if
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
"
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
"
|
65
|
-
"\n [
|
66
|
-
"\n [
|
67
|
-
"\n [
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
"
|
74
|
-
"\n
|
75
|
-
|
76
|
-
)
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
@
|
92
|
-
@app.command(name="
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
(
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
)
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
),
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
),
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
if
|
242
|
-
|
243
|
-
|
244
|
-
|
1
|
+
import os
|
2
|
+
import asyncio
|
3
|
+
import logging
|
4
|
+
import sys
|
5
|
+
import textwrap
|
6
|
+
|
7
|
+
import microcore as mc
|
8
|
+
import typer
|
9
|
+
from git import Repo
|
10
|
+
from gito.constants import REFS_VALUE_ALL
|
11
|
+
|
12
|
+
from .core import review, get_diff, filter_diff, answer
|
13
|
+
from .cli_base import (
|
14
|
+
app,
|
15
|
+
args_to_target,
|
16
|
+
arg_refs,
|
17
|
+
arg_what,
|
18
|
+
arg_filters,
|
19
|
+
arg_out,
|
20
|
+
arg_against,
|
21
|
+
get_repo_context,
|
22
|
+
)
|
23
|
+
from .report_struct import Report
|
24
|
+
from .constants import HOME_ENV_PATH, GITHUB_MD_REPORT_FILE_NAME
|
25
|
+
from .bootstrap import bootstrap
|
26
|
+
from .utils import no_subcommand, extract_gh_owner_repo, remove_html_comments
|
27
|
+
from .gh_api import resolve_gh_token
|
28
|
+
|
29
|
+
# Import fix command to register it
|
30
|
+
from .commands import fix, gh_react_to_comment, repl, deploy, version # noqa
|
31
|
+
from .commands.gh_post_review_comment import post_github_cr_comment
|
32
|
+
from .commands.linear_comment import linear_comment
|
33
|
+
|
34
|
+
app_no_subcommand = typer.Typer(pretty_exceptions_show_locals=False)
|
35
|
+
|
36
|
+
|
37
|
+
def main():
|
38
|
+
if sys.platform == "win32":
|
39
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
40
|
+
# Help subcommand alias: if 'help' appears as first non-option arg, replace it with '--help'
|
41
|
+
if len(sys.argv) > 1 and sys.argv[1] == "help":
|
42
|
+
sys.argv = [sys.argv[0]] + sys.argv[2:] + ["--help"]
|
43
|
+
|
44
|
+
if no_subcommand(app):
|
45
|
+
bootstrap()
|
46
|
+
app_no_subcommand()
|
47
|
+
else:
|
48
|
+
app()
|
49
|
+
|
50
|
+
|
51
|
+
@app.callback(
|
52
|
+
invoke_without_command=True,
|
53
|
+
help="\bGito is an open-source AI code reviewer that works with any language model provider."
|
54
|
+
"\nIt detects issues in GitHub pull requests or local codebase changes"
|
55
|
+
"—instantly, reliably, and without vendor lock-in."
|
56
|
+
)
|
57
|
+
def cli(
|
58
|
+
ctx: typer.Context,
|
59
|
+
verbosity: int = typer.Option(
|
60
|
+
None,
|
61
|
+
'--verbosity', '-v',
|
62
|
+
show_default=False,
|
63
|
+
help="\b"
|
64
|
+
"Set verbosity level. Supported values: 0-3. Default: 1."
|
65
|
+
"\n [ 0 ]: no additional output, "
|
66
|
+
"\n [ 1 ]: normal mode, shows warnings, shortened LLM requests and logging.INFO"
|
67
|
+
"\n [ 2 ]: verbose mode, show full LLM requests"
|
68
|
+
"\n [ 3 ]: very verbose mode, also debug information"
|
69
|
+
),
|
70
|
+
verbose: bool = typer.Option(
|
71
|
+
default=None,
|
72
|
+
help="\b"
|
73
|
+
"--verbose is equivalent to -v2, "
|
74
|
+
"\n--no-verbose is equivalent to -v0. "
|
75
|
+
"\n(!) Can't be used together with -v or --verbosity."
|
76
|
+
),
|
77
|
+
):
|
78
|
+
if verbose is not None and verbosity is not None:
|
79
|
+
raise typer.BadParameter(
|
80
|
+
"Please specify either --verbose or --verbosity, not both."
|
81
|
+
)
|
82
|
+
if verbose is not None:
|
83
|
+
verbosity = 2 if verbose else 0
|
84
|
+
if verbosity is None:
|
85
|
+
verbosity = 1
|
86
|
+
|
87
|
+
if ctx.invoked_subcommand != "setup":
|
88
|
+
bootstrap(verbosity)
|
89
|
+
|
90
|
+
|
91
|
+
@app_no_subcommand.command(name="review", help="Perform code review")
|
92
|
+
@app.command(name="review", help="Perform a code review of the target codebase changes.")
|
93
|
+
@app.command(name="run", hidden=True)
|
94
|
+
def cmd_review(
|
95
|
+
refs: str = arg_refs(),
|
96
|
+
what: str = arg_what(),
|
97
|
+
against: str = arg_against(),
|
98
|
+
filters: str = arg_filters(),
|
99
|
+
merge_base: bool = typer.Option(default=True, help="Use merge base for comparison"),
|
100
|
+
url: str = typer.Option("", "--url", help="Git repository URL"),
|
101
|
+
path: str = typer.Option("", "--path", help="Git repository path"),
|
102
|
+
post_comment: bool = typer.Option(default=False, help="Post review comment to GitHub"),
|
103
|
+
pr: int = typer.Option(
|
104
|
+
default=None,
|
105
|
+
help=textwrap.dedent("""\n
|
106
|
+
GitHub Pull Request number to post the comment to
|
107
|
+
(for local usage together with --post-comment,
|
108
|
+
in the github actions PR is resolved from the environment)
|
109
|
+
""")
|
110
|
+
),
|
111
|
+
out: str = arg_out(),
|
112
|
+
all: bool = typer.Option(default=False, help="Review all codebase"),
|
113
|
+
):
|
114
|
+
if all:
|
115
|
+
if refs and refs != REFS_VALUE_ALL:
|
116
|
+
raise typer.BadParameter(
|
117
|
+
"The --all option overrides the refs argument. "
|
118
|
+
"Please remove the refs argument if you want to review all codebase."
|
119
|
+
)
|
120
|
+
refs = REFS_VALUE_ALL
|
121
|
+
merge_base = False
|
122
|
+
_what, _against = args_to_target(refs, what, against)
|
123
|
+
pr = pr or os.getenv("PR_NUMBER_FROM_WORKFLOW_DISPATCH")
|
124
|
+
with get_repo_context(url, _what) as (repo, out_folder):
|
125
|
+
asyncio.run(review(
|
126
|
+
repo=repo,
|
127
|
+
what=_what,
|
128
|
+
against=_against,
|
129
|
+
filters=filters,
|
130
|
+
use_merge_base=merge_base,
|
131
|
+
out_folder=out or out_folder,
|
132
|
+
pr=pr,
|
133
|
+
))
|
134
|
+
if post_comment:
|
135
|
+
try:
|
136
|
+
owner, repo_name = extract_gh_owner_repo(repo)
|
137
|
+
except ValueError as e:
|
138
|
+
logging.error(
|
139
|
+
"Error posting comment:\n"
|
140
|
+
"Could not extract GitHub owner and repository name from the local repository."
|
141
|
+
)
|
142
|
+
raise typer.Exit(code=1) from e
|
143
|
+
post_github_cr_comment(
|
144
|
+
md_report_file=os.path.join(out or out_folder, GITHUB_MD_REPORT_FILE_NAME),
|
145
|
+
pr=pr,
|
146
|
+
gh_repo=f"{owner}/{repo_name}",
|
147
|
+
token=resolve_gh_token()
|
148
|
+
)
|
149
|
+
|
150
|
+
|
151
|
+
@app.command(name="ask", help="Answer questions about the target codebase changes.")
|
152
|
+
@app.command(name="answer", hidden=True)
|
153
|
+
@app.command(name="talk", hidden=True)
|
154
|
+
def cmd_answer(
|
155
|
+
question: str = typer.Argument(help="Question to ask about the codebase changes"),
|
156
|
+
refs: str = arg_refs(),
|
157
|
+
what: str = arg_what(),
|
158
|
+
against: str = arg_against(),
|
159
|
+
filters: str = arg_filters(),
|
160
|
+
merge_base: bool = typer.Option(default=True, help="Use merge base for comparison"),
|
161
|
+
use_pipeline: bool = typer.Option(default=True),
|
162
|
+
post_to: str = typer.Option(
|
163
|
+
help="Post answer to ... Supported values: linear",
|
164
|
+
default=None,
|
165
|
+
show_default=False
|
166
|
+
),
|
167
|
+
pr: int = typer.Option(
|
168
|
+
default=None,
|
169
|
+
help="GitHub Pull Request number"
|
170
|
+
),
|
171
|
+
aux_files: list[str] = typer.Option(
|
172
|
+
default=None,
|
173
|
+
help="Auxiliary files that might be helpful"
|
174
|
+
)
|
175
|
+
):
|
176
|
+
_what, _against = args_to_target(refs, what, against)
|
177
|
+
pr = pr or os.getenv("PR_NUMBER_FROM_WORKFLOW_DISPATCH")
|
178
|
+
if str(question).startswith("tpl:"):
|
179
|
+
prompt_file = str(question)[4:]
|
180
|
+
question = ""
|
181
|
+
else:
|
182
|
+
prompt_file = None
|
183
|
+
out = answer(
|
184
|
+
question=question,
|
185
|
+
what=_what,
|
186
|
+
against=_against,
|
187
|
+
filters=filters,
|
188
|
+
use_merge_base=merge_base,
|
189
|
+
prompt_file=prompt_file,
|
190
|
+
use_pipeline=use_pipeline,
|
191
|
+
pr=pr,
|
192
|
+
aux_files=aux_files,
|
193
|
+
)
|
194
|
+
if post_to == 'linear':
|
195
|
+
logging.info("Posting answer to Linear...")
|
196
|
+
linear_comment(remove_html_comments(out))
|
197
|
+
return out
|
198
|
+
|
199
|
+
|
200
|
+
@app.command(help="Configure LLM for local usage interactively.")
|
201
|
+
def setup():
|
202
|
+
mc.interactive_setup(HOME_ENV_PATH)
|
203
|
+
|
204
|
+
|
205
|
+
@app.command(name="render", help="Render and display code review report.")
|
206
|
+
@app.command(name="report", hidden=True)
|
207
|
+
def render(
|
208
|
+
format: str = typer.Argument(default=Report.Format.CLI),
|
209
|
+
source: str = typer.Option(
|
210
|
+
"",
|
211
|
+
"--src",
|
212
|
+
"--source",
|
213
|
+
help="Source file (json) to load the report from"
|
214
|
+
)
|
215
|
+
):
|
216
|
+
Report.load(file_name=source).to_cli(report_format=format)
|
217
|
+
|
218
|
+
|
219
|
+
@app.command(
|
220
|
+
help="\bList files in the changeset. "
|
221
|
+
"\nMight be useful to check what will be reviewed if run `gito review` "
|
222
|
+
"with current CLI arguments and options."
|
223
|
+
)
|
224
|
+
def files(
|
225
|
+
refs: str = arg_refs(),
|
226
|
+
what: str = arg_what(),
|
227
|
+
against: str = arg_against(),
|
228
|
+
filters: str = arg_filters(),
|
229
|
+
merge_base: bool = typer.Option(default=True, help="Use merge base for comparison"),
|
230
|
+
diff: bool = typer.Option(default=False, help="Show diff content")
|
231
|
+
):
|
232
|
+
_what, _against = args_to_target(refs, what, against)
|
233
|
+
repo = Repo(".")
|
234
|
+
try:
|
235
|
+
patch_set = get_diff(repo=repo, what=_what, against=_against, use_merge_base=merge_base)
|
236
|
+
patch_set = filter_diff(patch_set, filters)
|
237
|
+
print(
|
238
|
+
f"Changed files: "
|
239
|
+
f"{mc.ui.green(_what or 'INDEX')} vs "
|
240
|
+
f"{mc.ui.yellow(_against or repo.remotes.origin.refs.HEAD.reference.name)}"
|
241
|
+
f"{' filtered by ' + mc.ui.cyan(filters) if filters else ''}"
|
242
|
+
)
|
243
|
+
|
244
|
+
for patch in patch_set:
|
245
|
+
if patch.is_added_file:
|
246
|
+
color = mc.ui.green
|
247
|
+
elif patch.is_removed_file:
|
248
|
+
color = mc.ui.red
|
249
|
+
else:
|
250
|
+
color = mc.ui.blue
|
251
|
+
print(f"- {color(patch.path)}")
|
252
|
+
if diff:
|
253
|
+
print(mc.ui.gray(textwrap.indent(str(patch), " ")))
|
254
|
+
finally:
|
255
|
+
repo.close()
|