1414)
1515from lgtm_ai .ai .schemas import AgentSettings , CommentCategory , SupportedAIModels , SupportedAIModelsList
1616from lgtm_ai .base .constants import DEFAULT_HTTPX_TIMEOUT
17- from lgtm_ai .base .schemas import IntOrNoLimit , IssuesPlatform , OutputFormat , PRUrl
17+ from lgtm_ai .base .schemas import IntOrNoLimit , IssuesPlatform , LocalRepository , OutputFormat , PRUrl
1818from lgtm_ai .base .utils import git_source_supports_multiline_suggestions
1919from lgtm_ai .config .constants import DEFAULT_INPUT_TOKEN_LIMIT
2020from lgtm_ai .config .handler import ConfigHandler , PartialConfig , ResolvedConfig
3131from lgtm_ai .validators import (
3232 IntOrNoLimitType ,
3333 ModelChoice ,
34- parse_pr_url ,
34+ parse_target ,
3535 validate_model_url ,
3636)
3737from rich .console import Console
@@ -56,7 +56,7 @@ def entry_point() -> None:
5656def _common_options [** P , T ](func : Callable [P , T ]) -> Callable [P , T ]:
5757 """Wrap a click command and adds common options for lgtm commands."""
5858
59- @click .argument ("pr-url " , required = True , callback = parse_pr_url )
59+ @click .argument ("target " , required = True , callback = parse_target )
6060 @click .option (
6161 "--model" ,
6262 type = ModelChoice (SupportedAIModelsList ),
@@ -69,7 +69,10 @@ def _common_options[**P, T](func: Callable[P, T]) -> Callable[P, T]:
6969 default = None ,
7070 callback = validate_model_url ,
7171 )
72- @click .option ("--git-api-key" , help = "The API key to the git service (GitLab, GitHub, etc.)" )
72+ @click .option (
73+ "--git-api-key" ,
74+ help = "The API key to the git service (GitLab, GitHub, etc.). Required if the target is a PR URL." ,
75+ )
7376 @click .option ("--ai-api-key" , help = "The API key to the AI model service (OpenAI, etc.)" )
7477 @click .option ("--config" , type = click .STRING , help = "Path to the configuration file." )
7578 @click .option (
@@ -134,8 +137,13 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
134137 type = click .Choice (get_args (CommentCategory )),
135138 help = "List of categories the reviewer should focus on. If not provided, the reviewer will focus on all categories." ,
136139)
140+ @click .option (
141+ "--compare" ,
142+ default = None ,
143+ help = "If reviewing a local repository, what to compare against (branch, commit, or HEAD for working dir). Default: HEAD" ,
144+ )
137145def review (
138- pr_url : PRUrl ,
146+ target : PRUrl | LocalRepository ,
139147 model : SupportedAIModels | None ,
140148 model_url : str | None ,
141149 git_api_key : str | None ,
@@ -155,16 +163,26 @@ def review(
155163 issues_platform : IssuesPlatform | None ,
156164 issues_api_key : str | None ,
157165 issues_user : str | None ,
166+ compare : str | None ,
158167) -> None :
159- """Review a Pull Request using AI.
168+ """Review a Pull Request or local repository using AI.
169+
170+ TARGET can be either:
160171
161- PR_URL is the URL of the pull request to review.
172+ - A pull request URL (GitHub, GitLab, etc.).
173+
174+ - A local directory path (use --compare to specify what to compare against).
162175 """
163176 _set_logging_level (logger , verbose )
164177
178+ if compare and not isinstance (target , LocalRepository ):
179+ logger .warning (
180+ "`--compare` option is only used when reviewing a local repository. Ignoring the provided value."
181+ )
182+
165183 logger .info ("lgtm-ai version: %s" , __version__ )
166- logger .debug ("Parsed PR URL: %s" , pr_url )
167- logger .info ("Starting review of %s" , pr_url .full_url )
184+ logger .debug ("Parsed PR URL: %s" , target )
185+ logger .info ("Starting review of %s" , target .full_url )
168186 resolved_config = ConfigHandler (
169187 cli_args = PartialConfig (
170188 technologies = technologies ,
@@ -184,14 +202,16 @@ def review(
184202 issues_platform = issues_platform ,
185203 issues_api_key = issues_api_key ,
186204 issues_user = issues_user ,
205+ compare = compare ,
187206 ),
188207 config_file = config ,
189- ).resolve_config ()
208+ ).resolve_config (target )
209+
190210 agent_extra_settings = AgentSettings (retries = resolved_config .ai_retries )
191211 formatter : Formatter [Any ] = MarkDownFormatter (
192- add_ranges_to_suggestions = git_source_supports_multiline_suggestions (pr_url .source )
212+ add_ranges_to_suggestions = git_source_supports_multiline_suggestions (target .source )
193213 )
194- git_client = get_git_client (source = pr_url .source , token = resolved_config .git_api_key , formatter = formatter )
214+ git_client = get_git_client (source = target .source , token = resolved_config .git_api_key , formatter = formatter )
195215 issues_client = _get_issues_client (resolved_config , git_client , formatter )
196216
197217 code_reviewer = CodeReviewer (
@@ -206,7 +226,7 @@ def review(
206226 git_client = git_client ,
207227 config = resolved_config ,
208228 )
209- review = code_reviewer .review_pull_request (pr_url = pr_url )
229+ review = code_reviewer .review_pull_request (target = target )
210230 logger .info ("Review completed, total comments: %d" , len (review .review_response .comments ))
211231
212232 if not resolved_config .silent :
@@ -216,16 +236,16 @@ def review(
216236 if review .review_response .comments :
217237 printer (formatter .format_review_comments_section (review .review_response .comments ))
218238
219- if resolved_config .publish :
239+ if resolved_config .publish and isinstance ( target , PRUrl ) and git_client :
220240 logger .info ("Publishing review to git service" )
221- git_client .publish_review (pr_url = pr_url , review = review )
241+ git_client .publish_review (pr_url = target , review = review )
222242 logger .info ("Review published successfully" )
223243
224244
225245@entry_point .command ()
226246@_common_options
227247def guide (
228- pr_url : PRUrl ,
248+ target : PRUrl ,
229249 model : SupportedAIModels | None ,
230250 model_url : str | None ,
231251 git_api_key : str | None ,
@@ -241,13 +261,13 @@ def guide(
241261) -> None :
242262 """Generate a review guide for a Pull Request using AI.
243263
244- PR_URL is the URL of the pull request to generate a guide for.
264+ TARGET is the URL of the pull request to generate a guide for.
245265 """
246266 _set_logging_level (logger , verbose )
247267
248268 logger .info ("lgtm-ai version: %s" , __version__ )
249- logger .debug ("Parsed PR URL: %s" , pr_url )
250- logger .info ("Starting generating guide of %s" , pr_url .full_url )
269+ logger .debug ("Parsed PR URL: %s" , target )
270+ logger .info ("Starting generating guide of %s" , target .full_url )
251271 resolved_config = ConfigHandler (
252272 cli_args = PartialConfig (
253273 exclude = exclude ,
@@ -262,9 +282,9 @@ def guide(
262282 ai_input_tokens_limit = ai_input_tokens_limit ,
263283 ),
264284 config_file = config ,
265- ).resolve_config ()
285+ ).resolve_config (target )
266286 agent_extra_settings = AgentSettings (retries = resolved_config .ai_retries )
267- git_client = get_git_client (source = pr_url .source , token = resolved_config .git_api_key , formatter = MarkDownFormatter ())
287+ git_client = get_git_client (source = target .source , token = resolved_config .git_api_key , formatter = MarkDownFormatter ())
268288 review_guide = ReviewGuideGenerator (
269289 guide_agent = get_guide_agent_with_settings (agent_extra_settings ),
270290 model = get_ai_model (
@@ -273,16 +293,16 @@ def guide(
273293 git_client = git_client ,
274294 config = resolved_config ,
275295 )
276- guide = review_guide .generate_review_guide (pr_url = pr_url )
296+ guide = review_guide .generate_review_guide (pr_url = target )
277297
278298 if not resolved_config .silent :
279299 logger .info ("Printing review to console" )
280300 formatter , printer = _get_formatter_and_printer (resolved_config .output_format )
281301 printer (formatter .format_guide (guide ))
282302
283- if resolved_config .publish :
303+ if resolved_config .publish and git_client :
284304 logger .info ("Publishing review guide to git service" )
285- git_client .publish_guide (pr_url = pr_url , guide = guide )
305+ git_client .publish_guide (pr_url = target , guide = guide )
286306 logger .info ("Review Guide published successfully" )
287307
288308
@@ -310,16 +330,16 @@ def _get_formatter_and_printer(output_format: OutputFormat) -> tuple[Formatter[A
310330
311331
312332def _get_issues_client (
313- resolved_config : ResolvedConfig , git_client : GitClient , formatter : Formatter [Any ]
314- ) -> IssuesClient :
333+ resolved_config : ResolvedConfig , git_client : GitClient | None , formatter : Formatter [Any ]
334+ ) -> IssuesClient | None :
315335 """Select a different issues client for retrieving issues.
316336
317337 Will only return a different client if all of the following are true:
318338 1) Be used at all
319339 2) Be retrieved from a git platform and not elsewhere (e.g., Jira, Asana, etc.)
320340 3) Have a specific API key configured
321341 """
322- issues_client : IssuesClient = git_client
342+ issues_client : IssuesClient | None = git_client
323343 if not resolved_config .issues_url or not resolved_config .issues_platform or not resolved_config .issues_regex :
324344 return issues_client
325345 if resolved_config .issues_platform .is_git_platform :
0 commit comments