2424
2525from . import __version__
2626from .github_fetcher import parse_github_url , post_comment
27+ from .local_fetcher import validate_local_path
2728from .output_formatter import format_output
2829from .virtual_runner import VirtualReviewRunner
2930from .expert_prompts import get_expert_prompt
@@ -153,6 +154,76 @@ async def run_review(
153154 sys .exit (1 )
154155
155156
157+ async def run_local_review (
158+ path : str ,
159+ question : str | None = None ,
160+ output_format : str = "text" ,
161+ quiet : bool = False ,
162+ model : str | None = None ,
163+ expert : bool = False ,
164+ ):
165+ """Run a review on a local directory."""
166+ # Validate path first
167+ try :
168+ abs_path = validate_local_path (path )
169+ except ValueError as e :
170+ print_error (str (e ))
171+ sys .exit (1 )
172+
173+ # Determine the question to use
174+ if expert :
175+ actual_question = get_expert_prompt (question )
176+ review_mode = "Expert Review"
177+ elif question :
178+ actual_question = question
179+ review_mode = "Review"
180+ else :
181+ print_error ("Either --question or --expert is required" )
182+ sys .exit (1 )
183+
184+ if not quiet :
185+ print_info (f"Reviewing local directory: { abs_path } " )
186+ if expert :
187+ print_info (f"Mode: Expert Code Review (SOLID, Security, Code Quality)" )
188+ else :
189+ print_info (f"Question: { actual_question } " )
190+ console .print ()
191+
192+ # Create runner
193+ runner = VirtualReviewRunner (
194+ model = model ,
195+ quiet = quiet ,
196+ on_step = None if quiet else print_step ,
197+ )
198+
199+ try :
200+ answer , sources , metadata = await runner .review_local (abs_path , actual_question )
201+ except Exception as e :
202+ print_error (f"Review failed: { e } " )
203+ sys .exit (1 )
204+
205+ # Format and print output
206+ model_name = metadata .get ("model" , model or "unknown" )
207+ output = format_output (
208+ answer = answer ,
209+ sources = sources ,
210+ model = model_name ,
211+ output_format = output_format ,
212+ metadata = metadata if output_format == "json" else None ,
213+ )
214+
215+ if quiet or output_format == "json" :
216+ # Raw output for scripting
217+ print (output )
218+ else :
219+ # Rich formatted output
220+ console .print ()
221+ if output_format == "markdown" :
222+ console .print (Panel (Markdown (output ), title = "Review" , border_style = "green" ))
223+ else :
224+ console .print (Panel (output , title = "Review" , border_style = "green" ))
225+
226+
156227def main ():
157228 """Main CLI entry point."""
158229 parser = argparse .ArgumentParser (
@@ -177,20 +248,28 @@ def main():
177248 # review command
178249 review_parser = subparsers .add_parser (
179250 "review" ,
180- help = "Review a GitHub PR or Issue " ,
251+ help = "Review a GitHub PR/Issue or local directory " ,
181252 )
182- review_parser .add_argument (
253+
254+ # URL and path are mutually exclusive
255+ source_group = review_parser .add_mutually_exclusive_group (required = True )
256+ source_group .add_argument (
183257 "--url" , "-u" ,
184258 type = str ,
185- required = True ,
186259 help = "GitHub PR or Issue URL" ,
187260 )
261+ source_group .add_argument (
262+ "--path" , "-p" ,
263+ type = str ,
264+ help = "Local directory path to review" ,
265+ )
266+
188267 review_parser .add_argument (
189268 "--question" , "-q" ,
190269 type = str ,
191270 required = False ,
192271 default = None ,
193- help = "Question to ask about the PR/Issue (optional with --expert)" ,
272+ help = "Question to ask about the PR/Issue/directory (optional with --expert)" ,
194273 )
195274 review_parser .add_argument (
196275 "--expert" ,
@@ -218,21 +297,37 @@ def main():
218297 review_parser .add_argument (
219298 "--submit" ,
220299 action = "store_true" ,
221- help = "Post review as a comment on the PR/Issue (requires GITHUB_TOKEN)" ,
300+ help = "Post review as a comment on the PR/Issue (GitHub only, requires GITHUB_TOKEN)" ,
222301 )
223-
302+
224303 args = parser .parse_args ()
225-
304+
226305 if args .command == "review" :
227- asyncio .run (run_review (
228- url = args .url ,
229- question = args .question ,
230- output_format = args .output ,
231- quiet = args .quiet ,
232- model = args .model ,
233- expert = args .expert ,
234- submit = args .submit ,
235- ))
306+ # Validate --submit is only used with --url
307+ if args .submit and args .path :
308+ print_error ("--submit can only be used with --url (GitHub reviews)" )
309+ sys .exit (1 )
310+
311+ # Dispatch to appropriate review function
312+ if args .url :
313+ asyncio .run (run_review (
314+ url = args .url ,
315+ question = args .question ,
316+ output_format = args .output ,
317+ quiet = args .quiet ,
318+ model = args .model ,
319+ expert = args .expert ,
320+ submit = args .submit ,
321+ ))
322+ elif args .path :
323+ asyncio .run (run_local_review (
324+ path = args .path ,
325+ question = args .question ,
326+ output_format = args .output ,
327+ quiet = args .quiet ,
328+ model = args .model ,
329+ expert = args .expert ,
330+ ))
236331 else :
237332 parser .print_help ()
238333 sys .exit (1 )
0 commit comments