ai-cr 2.0.0.dev1__py3-none-any.whl → 2.0.1__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-2.0.0.dev1.dist-info → ai_cr-2.0.1.dist-info}/LICENSE +21 -21
- {ai_cr-2.0.0.dev1.dist-info → ai_cr-2.0.1.dist-info}/METADATA +14 -4
- ai_cr-2.0.1.dist-info/RECORD +26 -0
- {ai_cr-2.0.0.dev1.dist-info → ai_cr-2.0.1.dist-info}/WHEEL +1 -1
- gito/__main__.py +4 -4
- gito/bootstrap.py +66 -66
- gito/cli.py +217 -254
- gito/commands/__init__.py +1 -1
- gito/commands/fix.py +157 -124
- gito/commands/gh_post_review_comment.py +63 -0
- gito/commands/gh_react_to_comment.py +194 -0
- gito/commands/repl.py +5 -2
- gito/config.toml +453 -385
- gito/constants.py +12 -9
- gito/core.py +288 -221
- gito/gh_api.py +35 -0
- gito/issue_trackers.py +49 -0
- gito/pipeline.py +82 -0
- gito/pipeline_steps/__init__.py +0 -0
- gito/pipeline_steps/jira.py +57 -0
- gito/pipeline_steps/linear.py +84 -0
- gito/project_config.py +73 -119
- gito/report_struct.py +134 -132
- gito/utils.py +226 -132
- ai_cr-2.0.0.dev1.dist-info/RECORD +0 -18
- {ai_cr-2.0.0.dev1.dist-info → ai_cr-2.0.1.dist-info}/entry_points.txt +0 -0
gito/cli.py
CHANGED
@@ -1,254 +1,217 @@
|
|
1
|
-
import asyncio
|
2
|
-
import logging
|
3
|
-
import sys
|
4
|
-
import
|
5
|
-
import
|
6
|
-
|
7
|
-
import
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
from
|
12
|
-
|
13
|
-
from .
|
14
|
-
from .
|
15
|
-
from .
|
16
|
-
|
17
|
-
|
18
|
-
from .
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
if sys.
|
29
|
-
|
30
|
-
|
31
|
-
if
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
@app.command(
|
126
|
-
def
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
@app.command(help="
|
145
|
-
def
|
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
|
-
if 200 <= resp.status_code < 300:
|
219
|
-
logging.info(f"Posted review comment to PR #{pr} in {repo}")
|
220
|
-
else:
|
221
|
-
logging.error(f"Failed to post comment: {resp.status_code} {resp.reason}\n{resp.text}")
|
222
|
-
raise typer.Exit(5)
|
223
|
-
|
224
|
-
|
225
|
-
@app.command(help="List files in the diff. Might be useful to check what will be reviewed.")
|
226
|
-
def files(
|
227
|
-
refs: str = arg_refs(),
|
228
|
-
what: str = arg_what(),
|
229
|
-
against: str = arg_against(),
|
230
|
-
filters: str = arg_filters(),
|
231
|
-
merge_base: bool = typer.Option(default=True, help="Use merge base for comparison"),
|
232
|
-
diff: bool = typer.Option(default=False, help="Show diff content")
|
233
|
-
):
|
234
|
-
_what, _against = args_to_target(refs, what, against)
|
235
|
-
repo = Repo(".")
|
236
|
-
patch_set = get_diff(repo=repo, what=_what, against=_against, use_merge_base=merge_base)
|
237
|
-
patch_set = filter_diff(patch_set, filters)
|
238
|
-
print(
|
239
|
-
f"Changed files: "
|
240
|
-
f"{mc.ui.green(_what or 'INDEX')} vs "
|
241
|
-
f"{mc.ui.yellow(_against or repo.remotes.origin.refs.HEAD.reference.name)}"
|
242
|
-
f"{' filtered by '+mc.ui.cyan(filters) if filters else ''}"
|
243
|
-
)
|
244
|
-
repo.close()
|
245
|
-
for patch in patch_set:
|
246
|
-
if patch.is_added_file:
|
247
|
-
color = mc.ui.green
|
248
|
-
elif patch.is_removed_file:
|
249
|
-
color = mc.ui.red
|
250
|
-
else:
|
251
|
-
color = mc.ui.blue
|
252
|
-
print(f"- {color(patch.path)}")
|
253
|
-
if diff:
|
254
|
-
print(mc.ui.gray(textwrap.indent(str(patch), " ")))
|
1
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
import sys
|
4
|
+
import textwrap
|
5
|
+
import tempfile
|
6
|
+
|
7
|
+
import microcore as mc
|
8
|
+
import typer
|
9
|
+
from git import Repo
|
10
|
+
|
11
|
+
from .core import review, get_diff, filter_diff, answer
|
12
|
+
from .report_struct import Report
|
13
|
+
from .constants import HOME_ENV_PATH
|
14
|
+
from .bootstrap import bootstrap, app
|
15
|
+
from .utils import no_subcommand, parse_refs_pair
|
16
|
+
|
17
|
+
# Import fix command to register it
|
18
|
+
from .commands import fix, gh_post_review_comment, gh_react_to_comment, repl # noqa
|
19
|
+
|
20
|
+
|
21
|
+
app_no_subcommand = typer.Typer(pretty_exceptions_show_locals=False)
|
22
|
+
|
23
|
+
|
24
|
+
def main():
|
25
|
+
if sys.platform == "win32":
|
26
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
27
|
+
# Help subcommand alias: if 'help' appears as first non-option arg, replace it with '--help'
|
28
|
+
if len(sys.argv) > 1 and sys.argv[1] == "help":
|
29
|
+
sys.argv = [sys.argv[0]] + sys.argv[2:] + ["--help"]
|
30
|
+
|
31
|
+
if no_subcommand(app):
|
32
|
+
bootstrap()
|
33
|
+
app_no_subcommand()
|
34
|
+
else:
|
35
|
+
app()
|
36
|
+
|
37
|
+
|
38
|
+
@app.callback(invoke_without_command=True)
|
39
|
+
def cli(ctx: typer.Context, verbose: bool = typer.Option(default=False)):
|
40
|
+
if ctx.invoked_subcommand != "setup":
|
41
|
+
bootstrap()
|
42
|
+
if verbose:
|
43
|
+
mc.logging.LoggingConfig.STRIP_REQUEST_LINES = None
|
44
|
+
|
45
|
+
|
46
|
+
def args_to_target(refs, what, against) -> tuple[str | None, str | None]:
|
47
|
+
_what, _against = parse_refs_pair(refs)
|
48
|
+
if _what:
|
49
|
+
if what:
|
50
|
+
raise typer.BadParameter(
|
51
|
+
"You cannot specify both 'refs' <WHAT>..<AGAINST> and '--what'. Use one of them."
|
52
|
+
)
|
53
|
+
else:
|
54
|
+
_what = what
|
55
|
+
if _against:
|
56
|
+
if against:
|
57
|
+
raise typer.BadParameter(
|
58
|
+
"You cannot specify both 'refs' <WHAT>..<AGAINST> and '--against'. Use one of them."
|
59
|
+
)
|
60
|
+
else:
|
61
|
+
_against = against
|
62
|
+
return _what, _against
|
63
|
+
|
64
|
+
|
65
|
+
def arg_refs() -> typer.Argument:
|
66
|
+
return typer.Argument(
|
67
|
+
default=None,
|
68
|
+
help="Git refs to review, [what]..[against] e.g. 'HEAD..HEAD~1'"
|
69
|
+
)
|
70
|
+
|
71
|
+
|
72
|
+
def arg_what() -> typer.Option:
|
73
|
+
return typer.Option(None, "--what", "-w", help="Git ref to review")
|
74
|
+
|
75
|
+
|
76
|
+
def arg_filters() -> typer.Option:
|
77
|
+
return typer.Option(
|
78
|
+
"", "--filter", "-f", "--filters",
|
79
|
+
help="""
|
80
|
+
filter reviewed files by glob / fnmatch pattern(s),
|
81
|
+
e.g. 'src/**/*.py', may be comma-separated
|
82
|
+
""",
|
83
|
+
)
|
84
|
+
|
85
|
+
|
86
|
+
def arg_out() -> typer.Option:
|
87
|
+
return typer.Option(
|
88
|
+
None,
|
89
|
+
"--out", "-o", "--output",
|
90
|
+
help="Output folder for the code review report"
|
91
|
+
)
|
92
|
+
|
93
|
+
|
94
|
+
def arg_against() -> typer.Option:
|
95
|
+
return typer.Option(
|
96
|
+
None,
|
97
|
+
"--against", "-vs", "--vs",
|
98
|
+
help="Git ref to compare against"
|
99
|
+
)
|
100
|
+
|
101
|
+
|
102
|
+
@app_no_subcommand.command(name="review", help="Perform code review")
|
103
|
+
@app.command(name="review", help="Perform code review")
|
104
|
+
@app.command(name="run", hidden=True)
|
105
|
+
def cmd_review(
|
106
|
+
refs: str = arg_refs(),
|
107
|
+
what: str = arg_what(),
|
108
|
+
against: str = arg_against(),
|
109
|
+
filters: str = arg_filters(),
|
110
|
+
merge_base: bool = typer.Option(default=True, help="Use merge base for comparison"),
|
111
|
+
out: str = arg_out()
|
112
|
+
):
|
113
|
+
_what, _against = args_to_target(refs, what, against)
|
114
|
+
asyncio.run(review(
|
115
|
+
what=_what,
|
116
|
+
against=_against,
|
117
|
+
filters=filters,
|
118
|
+
use_merge_base=merge_base,
|
119
|
+
out_folder=out,
|
120
|
+
))
|
121
|
+
|
122
|
+
|
123
|
+
@app.command(name="ask", help="Answer questions about codebase changes")
|
124
|
+
@app.command(name="answer", hidden=True)
|
125
|
+
@app.command(name="talk", hidden=True)
|
126
|
+
def cmd_answer(
|
127
|
+
question: str = typer.Argument(help="Question to ask about the codebase changes"),
|
128
|
+
refs: str = arg_refs(),
|
129
|
+
what: str = arg_what(),
|
130
|
+
against: str = arg_against(),
|
131
|
+
filters: str = arg_filters(),
|
132
|
+
merge_base: bool = typer.Option(default=True, help="Use merge base for comparison"),
|
133
|
+
):
|
134
|
+
_what, _against = args_to_target(refs, what, against)
|
135
|
+
return answer(
|
136
|
+
question=question,
|
137
|
+
what=_what,
|
138
|
+
against=_against,
|
139
|
+
filters=filters,
|
140
|
+
use_merge_base=merge_base,
|
141
|
+
)
|
142
|
+
|
143
|
+
|
144
|
+
@app.command(help="Configure LLM for local usage interactively")
|
145
|
+
def setup():
|
146
|
+
mc.interactive_setup(HOME_ENV_PATH)
|
147
|
+
|
148
|
+
|
149
|
+
@app.command(name="render")
|
150
|
+
@app.command(name="report", hidden=True)
|
151
|
+
def render(
|
152
|
+
format: str = typer.Argument(default=Report.Format.CLI),
|
153
|
+
source: str = typer.Option(
|
154
|
+
"",
|
155
|
+
"--src",
|
156
|
+
"--source",
|
157
|
+
help="Source file (json) to load the report from"
|
158
|
+
)
|
159
|
+
):
|
160
|
+
Report.load(file_name=source).to_cli(report_format=format)
|
161
|
+
|
162
|
+
|
163
|
+
@app.command(help="Review remote code")
|
164
|
+
def remote(
|
165
|
+
url: str = typer.Argument(..., help="Git repository URL"),
|
166
|
+
refs: str = arg_refs(),
|
167
|
+
what: str = arg_what(),
|
168
|
+
against: str = arg_against(),
|
169
|
+
filters: str = arg_filters(),
|
170
|
+
merge_base: bool = typer.Option(default=True, help="Use merge base for comparison"),
|
171
|
+
out: str = arg_out()
|
172
|
+
):
|
173
|
+
_what, _against = args_to_target(refs, what, against)
|
174
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
175
|
+
logging.info(f"Cloning [{mc.ui.green(url)}] to {mc.utils.file_link(temp_dir)} ...")
|
176
|
+
repo = Repo.clone_from(url, branch=_what, to_path=temp_dir)
|
177
|
+
asyncio.run(review(
|
178
|
+
repo=repo,
|
179
|
+
what=_what,
|
180
|
+
against=_against,
|
181
|
+
filters=filters,
|
182
|
+
use_merge_base=merge_base,
|
183
|
+
out_folder=out or '.',
|
184
|
+
))
|
185
|
+
repo.close()
|
186
|
+
|
187
|
+
|
188
|
+
@app.command(help="List files in the diff. Might be useful to check what will be reviewed.")
|
189
|
+
def files(
|
190
|
+
refs: str = arg_refs(),
|
191
|
+
what: str = arg_what(),
|
192
|
+
against: str = arg_against(),
|
193
|
+
filters: str = arg_filters(),
|
194
|
+
merge_base: bool = typer.Option(default=True, help="Use merge base for comparison"),
|
195
|
+
diff: bool = typer.Option(default=False, help="Show diff content")
|
196
|
+
):
|
197
|
+
_what, _against = args_to_target(refs, what, against)
|
198
|
+
repo = Repo(".")
|
199
|
+
patch_set = get_diff(repo=repo, what=_what, against=_against, use_merge_base=merge_base)
|
200
|
+
patch_set = filter_diff(patch_set, filters)
|
201
|
+
print(
|
202
|
+
f"Changed files: "
|
203
|
+
f"{mc.ui.green(_what or 'INDEX')} vs "
|
204
|
+
f"{mc.ui.yellow(_against or repo.remotes.origin.refs.HEAD.reference.name)}"
|
205
|
+
f"{' filtered by '+mc.ui.cyan(filters) if filters else ''}"
|
206
|
+
)
|
207
|
+
repo.close()
|
208
|
+
for patch in patch_set:
|
209
|
+
if patch.is_added_file:
|
210
|
+
color = mc.ui.green
|
211
|
+
elif patch.is_removed_file:
|
212
|
+
color = mc.ui.red
|
213
|
+
else:
|
214
|
+
color = mc.ui.blue
|
215
|
+
print(f"- {color(patch.path)}")
|
216
|
+
if diff:
|
217
|
+
print(mc.ui.gray(textwrap.indent(str(patch), " ")))
|
gito/commands/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
# Command modules register themselves with the CLI app
|
1
|
+
# Command modules register themselves with the CLI app
|