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