code-review-ai-cli 2.2.5__tar.gz → 2.3.0__tar.gz

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 (34) hide show
  1. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/PKG-INFO +4 -4
  2. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/README.md +1 -0
  3. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/code_review_ai_cli.egg-info/PKG-INFO +4 -4
  4. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/code_review_ai_cli.egg-info/requires.txt +2 -4
  5. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/pyproject.toml +3 -3
  6. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/src/ai_review.py +266 -3
  7. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/src/config.py +2 -2
  8. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/src/context_extractor.py +96 -190
  9. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/src/formatter.py +26 -0
  10. code_review_ai_cli-2.3.0/src/git_utils.py +628 -0
  11. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/src/llm_client.py +77 -76
  12. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/src/prompts/config.yaml.template +3 -3
  13. code_review_ai_cli-2.3.0/src/prompts/review_prompt.md.template +21 -0
  14. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/src/tfs_client.py +9 -13
  15. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/tests/test_ai_review.py +97 -0
  16. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/tests/test_context_extractor.py +94 -88
  17. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/tests/test_formatter.py +24 -1
  18. code_review_ai_cli-2.3.0/tests/test_git_utils.py +318 -0
  19. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/tests/test_llm_client.py +87 -17
  20. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/tests/test_tfs_client.py +36 -5
  21. code_review_ai_cli-2.2.5/src/git_utils.py +0 -282
  22. code_review_ai_cli-2.2.5/src/prompts/review_prompt.md.template +0 -85
  23. code_review_ai_cli-2.2.5/tests/test_git_utils.py +0 -159
  24. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/code_review_ai_cli.egg-info/SOURCES.txt +0 -0
  25. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/code_review_ai_cli.egg-info/dependency_links.txt +0 -0
  26. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/code_review_ai_cli.egg-info/entry_points.txt +0 -0
  27. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/code_review_ai_cli.egg-info/top_level.txt +0 -0
  28. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/setup.cfg +0 -0
  29. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/src/__init__.py +0 -0
  30. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/src/prompt_utils.py +0 -0
  31. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/src/token_estimator.py +0 -0
  32. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/tests/test_config.py +0 -0
  33. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/tests/test_prompt_utils.py +0 -0
  34. {code_review_ai_cli-2.2.5 → code_review_ai_cli-2.3.0}/tests/test_token_estimator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-review-ai-cli
3
- Version: 2.2.5
3
+ Version: 2.3.0
4
4
  Summary: Automated AI-powered code review CLI for Azure DevOps / TFS Pull Requests
5
5
  License: MIT
6
6
  Keywords: code-review,ai,azure-devops,tfs,pull-request,llm
@@ -19,6 +19,8 @@ Requires-Dist: requests>=2.28.0
19
19
  Requires-Dist: PyYAML>=6.0
20
20
  Requires-Dist: litellm>=1.40.0
21
21
  Requires-Dist: tiktoken>=0.7.0
22
+ Requires-Dist: tree-sitter<0.22,>=0.20
23
+ Requires-Dist: tree-sitter-languages>=1.10.2
22
24
  Provides-Extra: bedrock
23
25
  Requires-Dist: boto3>=1.34.0; extra == "bedrock"
24
26
  Provides-Extra: openai
@@ -27,8 +29,6 @@ Provides-Extra: gemini
27
29
  Requires-Dist: google-generativeai>=0.3.0; extra == "gemini"
28
30
  Provides-Extra: claude
29
31
  Requires-Dist: anthropic>=0.18.0; extra == "claude"
30
- Provides-Extra: tree-sitter
31
- Requires-Dist: tree-sitter-languages>=1.10.2; extra == "tree-sitter"
32
32
  Provides-Extra: all
33
33
  Requires-Dist: boto3>=1.34.0; extra == "all"
34
34
  Requires-Dist: litellm>=1.40.0; extra == "all"
@@ -36,7 +36,6 @@ Requires-Dist: tiktoken>=0.7.0; extra == "all"
36
36
  Requires-Dist: openai>=1.0.0; extra == "all"
37
37
  Requires-Dist: google-generativeai>=0.3.0; extra == "all"
38
38
  Requires-Dist: anthropic>=0.18.0; extra == "all"
39
- Requires-Dist: tree-sitter-languages>=1.10.2; extra == "all"
40
39
  Provides-Extra: dev
41
40
  Requires-Dist: pytest>=8.3.0; extra == "dev"
42
41
  Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
@@ -49,6 +48,7 @@ AI Code Review is an automated AI-powered code review CLI, compatible with vario
49
48
  ## Features
50
49
 
51
50
  - AI Pull Request review (`pr-review`)
51
+ - Local Git review without a PR (`local` / `local-review` — working tree, `--staged`, `--from <branch>`, `--commit <sha>`)
52
52
  - Structured PR comments (inline + general summary)
53
53
  - `dry-run` mode to validate without posting
54
54
  - PR listing with filters (`list-prs`)
@@ -5,6 +5,7 @@ AI Code Review is an automated AI-powered code review CLI, compatible with vario
5
5
  ## Features
6
6
 
7
7
  - AI Pull Request review (`pr-review`)
8
+ - Local Git review without a PR (`local` / `local-review` — working tree, `--staged`, `--from <branch>`, `--commit <sha>`)
8
9
  - Structured PR comments (inline + general summary)
9
10
  - `dry-run` mode to validate without posting
10
11
  - PR listing with filters (`list-prs`)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-review-ai-cli
3
- Version: 2.2.5
3
+ Version: 2.3.0
4
4
  Summary: Automated AI-powered code review CLI for Azure DevOps / TFS Pull Requests
5
5
  License: MIT
6
6
  Keywords: code-review,ai,azure-devops,tfs,pull-request,llm
@@ -19,6 +19,8 @@ Requires-Dist: requests>=2.28.0
19
19
  Requires-Dist: PyYAML>=6.0
20
20
  Requires-Dist: litellm>=1.40.0
21
21
  Requires-Dist: tiktoken>=0.7.0
22
+ Requires-Dist: tree-sitter<0.22,>=0.20
23
+ Requires-Dist: tree-sitter-languages>=1.10.2
22
24
  Provides-Extra: bedrock
23
25
  Requires-Dist: boto3>=1.34.0; extra == "bedrock"
24
26
  Provides-Extra: openai
@@ -27,8 +29,6 @@ Provides-Extra: gemini
27
29
  Requires-Dist: google-generativeai>=0.3.0; extra == "gemini"
28
30
  Provides-Extra: claude
29
31
  Requires-Dist: anthropic>=0.18.0; extra == "claude"
30
- Provides-Extra: tree-sitter
31
- Requires-Dist: tree-sitter-languages>=1.10.2; extra == "tree-sitter"
32
32
  Provides-Extra: all
33
33
  Requires-Dist: boto3>=1.34.0; extra == "all"
34
34
  Requires-Dist: litellm>=1.40.0; extra == "all"
@@ -36,7 +36,6 @@ Requires-Dist: tiktoken>=0.7.0; extra == "all"
36
36
  Requires-Dist: openai>=1.0.0; extra == "all"
37
37
  Requires-Dist: google-generativeai>=0.3.0; extra == "all"
38
38
  Requires-Dist: anthropic>=0.18.0; extra == "all"
39
- Requires-Dist: tree-sitter-languages>=1.10.2; extra == "all"
40
39
  Provides-Extra: dev
41
40
  Requires-Dist: pytest>=8.3.0; extra == "dev"
42
41
  Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
@@ -49,6 +48,7 @@ AI Code Review is an automated AI-powered code review CLI, compatible with vario
49
48
  ## Features
50
49
 
51
50
  - AI Pull Request review (`pr-review`)
51
+ - Local Git review without a PR (`local` / `local-review` — working tree, `--staged`, `--from <branch>`, `--commit <sha>`)
52
52
  - Structured PR comments (inline + general summary)
53
53
  - `dry-run` mode to validate without posting
54
54
  - PR listing with filters (`list-prs`)
@@ -2,6 +2,8 @@ requests>=2.28.0
2
2
  PyYAML>=6.0
3
3
  litellm>=1.40.0
4
4
  tiktoken>=0.7.0
5
+ tree-sitter<0.22,>=0.20
6
+ tree-sitter-languages>=1.10.2
5
7
 
6
8
  [all]
7
9
  boto3>=1.34.0
@@ -10,7 +12,6 @@ tiktoken>=0.7.0
10
12
  openai>=1.0.0
11
13
  google-generativeai>=0.3.0
12
14
  anthropic>=0.18.0
13
- tree-sitter-languages>=1.10.2
14
15
 
15
16
  [bedrock]
16
17
  boto3>=1.34.0
@@ -28,6 +29,3 @@ google-generativeai>=0.3.0
28
29
 
29
30
  [openai]
30
31
  openai>=1.0.0
31
-
32
- [tree-sitter]
33
- tree-sitter-languages>=1.10.2
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code-review-ai-cli"
7
- version = "2.2.5"
7
+ version = "2.3.0"
8
8
  description = "Automated AI-powered code review CLI for Azure DevOps / TFS Pull Requests"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -26,6 +26,8 @@ dependencies = [
26
26
  "PyYAML>=6.0",
27
27
  "litellm>=1.40.0",
28
28
  "tiktoken>=0.7.0",
29
+ "tree-sitter>=0.20,<0.22",
30
+ "tree-sitter-languages>=1.10.2",
29
31
  ]
30
32
 
31
33
  [project.optional-dependencies]
@@ -33,7 +35,6 @@ bedrock = ["boto3>=1.34.0"]
33
35
  openai = ["openai>=1.0.0"]
34
36
  gemini = ["google-generativeai>=0.3.0"]
35
37
  claude = ["anthropic>=0.18.0"]
36
- tree-sitter = ["tree-sitter-languages>=1.10.2"]
37
38
  all = [
38
39
  "boto3>=1.34.0",
39
40
  "litellm>=1.40.0",
@@ -41,7 +42,6 @@ all = [
41
42
  "openai>=1.0.0",
42
43
  "google-generativeai>=0.3.0",
43
44
  "anthropic>=0.18.0",
44
- "tree-sitter-languages>=1.10.2",
45
45
  ]
46
46
  dev = [
47
47
  "pytest>=8.3.0",
@@ -13,6 +13,7 @@ Usage:
13
13
  python ai_review.py # Interactive mode (main menu)
14
14
  python ai_review.py pr-review # List PRs and do interactive review
15
15
  python ai_review.py pr-review <id> # Direct review of a specific PR
16
+ python ai_review.py local # Review local Git changes (uncommitted/staged/branch)
16
17
  python ai_review.py list-prs # List active Pull Requests
17
18
 
18
19
  Options:
@@ -82,7 +83,8 @@ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
82
83
  PROJECT_ROOT = _ensure_project_root_on_path(__file__)
83
84
 
84
85
  from src.config import ReviewConfig, VALID_PROVIDERS # noqa: E402
85
- from src.git_utils import GitUtils # noqa: E402
86
+ from src.context_extractor import ContextExtractor # noqa: E402
87
+ from src.git_utils import GitError, GitUtils # noqa: E402
86
88
  from src.llm_client import LLMClient, LLMError # noqa: E402
87
89
  from src.formatter import ReviewFormatter, Colors, save_output # noqa: E402
88
90
  from src import __version__ as VERSION # noqa: E402
@@ -162,6 +164,9 @@ def build_parser() -> argparse.ArgumentParser:
162
164
  " %(prog)s pr-review # List PRs and select\n"
163
165
  " %(prog)s pr-review 42 --dry-run # Review PR #42 without posting\n"
164
166
  " %(prog)s pr-review 42 --provider bedrock # Review PR #42 with Bedrock\n"
167
+ " %(prog)s local # Review local uncommitted changes\n"
168
+ " %(prog)s local --staged # Review staged changes\n"
169
+ " %(prog)s local --from main # Review branch diff against main\n"
165
170
  " %(prog)s list-prs --status active # List active PRs\n"
166
171
  ),
167
172
  )
@@ -186,6 +191,33 @@ def build_parser() -> argparse.ArgumentParser:
186
191
  sub_pr_review.add_argument("--target-branch", default=None,
187
192
  help="Filter PRs by target branch")
188
193
 
194
+ # --- local / local-review (LOCAL GIT MODE) ---
195
+ sub_local = subparsers.add_parser(
196
+ "local",
197
+ aliases=["local-review"],
198
+ help="💻 Local Git Review (uncommitted, staged, branch range, or commit)",
199
+ )
200
+ sub_local.add_argument(
201
+ "--staged", "-s", action="store_true",
202
+ help="Review only staged changes (git diff --cached)",
203
+ )
204
+ sub_local.add_argument(
205
+ "--from", "--base", dest="from_ref", default=None, metavar="REF",
206
+ help="Base branch or commit to compare from (e.g., main, origin/main)",
207
+ )
208
+ sub_local.add_argument(
209
+ "--to", dest="to_ref", default=None, metavar="REF",
210
+ help="Target branch or commit to compare to (default: HEAD when --from is used)",
211
+ )
212
+ sub_local.add_argument(
213
+ "--commit", default=None, metavar="SHA",
214
+ help="Review a specific commit (compares <commit>~1 to <commit>)",
215
+ )
216
+ sub_local.add_argument(
217
+ "--repo-path", default=None, metavar="PATH",
218
+ help="Path to the local Git repository (default: current directory)",
219
+ )
220
+
189
221
  # --- init ---
190
222
  subparsers.add_parser(
191
223
  "init", help="Create a config.yaml template in the current directory"
@@ -203,7 +235,7 @@ def build_parser() -> argparse.ArgumentParser:
203
235
  help="Filter by author")
204
236
 
205
237
  # --- Global options ---
206
- all_subs = [sub_pr_review, sub_list_prs]
238
+ all_subs = [sub_pr_review, sub_local, sub_list_prs]
207
239
  for sub in all_subs:
208
240
  _add_global_options(sub)
209
241
 
@@ -294,6 +326,26 @@ def _save_pr_review_output(output_file: str, pr_id: int, repo_name: str,
294
326
  save_output(md_output, output_file)
295
327
 
296
328
 
329
+ def _save_local_review_output(output_file: str, repo_name: str,
330
+ branch_label: str, review_text: str,
331
+ was_truncated: bool) -> None:
332
+ """Saves the local Git review output if a target file was provided."""
333
+ if not output_file:
334
+ return
335
+
336
+ md_formatter = ReviewFormatter(color=False, output_format="markdown")
337
+ md_output = "\n".join([
338
+ md_formatter.format_header(
339
+ review_type="Local Git Review",
340
+ repo_name=repo_name,
341
+ branch=branch_label,
342
+ ),
343
+ review_text,
344
+ md_formatter.format_footer(truncated=was_truncated),
345
+ ])
346
+ save_output(md_output, output_file)
347
+
348
+
297
349
  # ---------------------------------------------------------------------------
298
350
  # Command execution functions
299
351
  # ---------------------------------------------------------------------------
@@ -348,6 +400,8 @@ def run_review(args: argparse.Namespace) -> int:
348
400
 
349
401
  if command == "pr-review":
350
402
  return run_pr_review_workflow(args, config, formatter)
403
+ elif command in ("local", "local-review"):
404
+ return run_local_review_workflow(args, config, formatter)
351
405
  elif command == "list-prs":
352
406
  return run_list_prs(args, config, formatter)
353
407
 
@@ -713,6 +767,174 @@ def _select_comments_to_post(comments: list[dict],
713
767
  return []
714
768
 
715
769
 
770
+ # ---------------------------------------------------------------------------
771
+ # Local Git Review
772
+ # ---------------------------------------------------------------------------
773
+ def run_local_review_workflow(args: argparse.Namespace, config: ReviewConfig,
774
+ formatter: ReviewFormatter) -> int:
775
+ """
776
+ Local Git review workflow (working tree, staged, branch range, or commit).
777
+ Does not require TFS/Azure DevOps configuration.
778
+ """
779
+ repo_path = getattr(args, "repo_path", None)
780
+ try:
781
+ git_utils = GitUtils(repo_path=repo_path)
782
+ except GitError as exc:
783
+ print(formatter.format_error(str(exc)))
784
+ return 1
785
+
786
+ staged = bool(getattr(args, "staged", False))
787
+ from_ref = getattr(args, "from_ref", None)
788
+ to_ref = getattr(args, "to_ref", None)
789
+ commit = getattr(args, "commit", None)
790
+
791
+ repo_name = git_utils.get_repo_name()
792
+ branch = git_utils.get_current_branch()
793
+
794
+ if commit:
795
+ target_label = f"Commit {commit}"
796
+ elif from_ref:
797
+ target_label = f"{from_ref} → {to_ref or 'HEAD'}"
798
+ elif staged:
799
+ target_label = "Staged changes (--cached)"
800
+ else:
801
+ target_label = "Uncommitted changes (working tree)"
802
+
803
+ print(formatter.format_progress(f"Inspecting local Git changes ({target_label})"))
804
+
805
+ try:
806
+ changed_files = git_utils.get_local_changed_files(
807
+ staged=staged,
808
+ from_ref=from_ref,
809
+ to_ref=to_ref,
810
+ commit=commit,
811
+ )
812
+ except GitError as exc:
813
+ print(formatter.format_error(str(exc)))
814
+ return 1
815
+
816
+ if not changed_files:
817
+ print(formatter.format_warning("No local code changes found to review."))
818
+ return 0
819
+
820
+ local_details = {
821
+ "repository": repo_name,
822
+ "branch": branch,
823
+ "target": target_label,
824
+ "changed_files": changed_files,
825
+ }
826
+ print(formatter.format_local_details(local_details))
827
+
828
+ if config.debug_dump and not config.debug_dump_file:
829
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
830
+ config.debug_dump_file = os.path.join("logs", f"local_{timestamp}_debug.log")
831
+ os.makedirs(os.path.dirname(config.debug_dump_file), exist_ok=True)
832
+ print(formatter.format_info(f"🐛 Debug dump enabled: {config.debug_dump_file}"))
833
+
834
+ print(formatter.format_progress("Building local diff and AST context"))
835
+
836
+ extractor = ContextExtractor(
837
+ max_scope_blocks=config.max_scope_blocks,
838
+ max_scope_lines=config.max_scope_lines,
839
+ adaptive_context_lines=config.adaptive_context_lines,
840
+ )
841
+
842
+ try:
843
+ diff = git_utils.get_local_diff(
844
+ review_scope=config.review_scope,
845
+ excluded_paths=config.excluded_paths or None,
846
+ file_extensions_filter=config.file_extensions_filter or None,
847
+ staged=staged,
848
+ from_ref=from_ref,
849
+ to_ref=to_ref,
850
+ commit=commit,
851
+ context_extractor=extractor,
852
+ )
853
+ except GitError as exc:
854
+ print(formatter.format_error(str(exc)))
855
+ return 1
856
+
857
+ if not diff.strip():
858
+ print(formatter.format_warning("No eligible code changes found after filtering."))
859
+ return 0
860
+
861
+ print(formatter.format_info(
862
+ f"Provider: {config.llm_provider} | Model: {config.model} | "
863
+ f"Mode: {config.verbosity} | Scope: {config.review_scope}"
864
+ ))
865
+
866
+ diff = git_utils.filter_diff_additions_only(diff)
867
+ if not diff.strip():
868
+ print(formatter.format_warning(
869
+ "After filtering additions only, the diff is empty. No new code to review."
870
+ ))
871
+ return 0
872
+
873
+ diff_limited, files_limited, omitted_files = git_utils.limit_diff_files(
874
+ diff,
875
+ config.max_diff_files,
876
+ )
877
+ if files_limited:
878
+ print(formatter.format_warning(
879
+ f"Diff truncated to {config.max_diff_files} files. "
880
+ f"{omitted_files} file(s) omitted."
881
+ ))
882
+
883
+ files_summary = git_utils.get_changed_files_summary(diff_limited)
884
+
885
+ diff_truncated, was_truncated = git_utils.truncate_diff(
886
+ diff_limited,
887
+ config.max_diff_lines,
888
+ )
889
+ if was_truncated:
890
+ print(formatter.format_warning(
891
+ f"Diff truncated to {config.max_diff_lines} lines per file."
892
+ ))
893
+
894
+ progress = ProgressIndicator("Analyzing local code with AI (may take 30-60s)")
895
+ progress.start()
896
+ start_time = time.time()
897
+
898
+ try:
899
+ llm = LLMClient(config)
900
+ review_text, structured_comments = llm.review_pr(
901
+ diff=diff_truncated,
902
+ files_summary=files_summary,
903
+ context=getattr(args, "context", ""),
904
+ review_scope=config.review_scope,
905
+ )
906
+ except LLMError as exc:
907
+ progress.stop()
908
+ print(formatter.format_error(str(exc)))
909
+ return 1
910
+
911
+ elapsed = time.time() - start_time
912
+ progress.stop(formatter.format_success(f"Review completed in {elapsed:.1f}s"))
913
+
914
+ token_estimate = getattr(llm, "last_token_estimate", None)
915
+ if token_estimate is not None and token_estimate.total_prompt_tokens is not None:
916
+ print(formatter.format_info(
917
+ f"Total Prompt: {token_estimate.total_prompt_tokens} tokens"
918
+ ))
919
+ else:
920
+ print(formatter.format_warning(
921
+ "Total Prompt: unavailable (LiteLLM and local tiktoken could not count the model)"
922
+ ))
923
+
924
+ print(formatter.format_structured_comments(structured_comments))
925
+
926
+ output_file = config.output_file or getattr(args, "output", "")
927
+ _save_local_review_output(
928
+ output_file=output_file,
929
+ repo_name=repo_name,
930
+ branch_label=f"{branch} ({target_label})",
931
+ review_text=review_text,
932
+ was_truncated=was_truncated,
933
+ )
934
+
935
+ return 0
936
+
937
+
716
938
  # ---------------------------------------------------------------------------
717
939
  # List PRs
718
940
  # ---------------------------------------------------------------------------
@@ -768,12 +990,15 @@ def interactive_mode() -> int:
768
990
  print(f" {c.CYAN}1{c.RESET}) 🌟 Pull Request Review (list PRs and select)")
769
991
  print(f" {c.CYAN}2{c.RESET}) 📋 List active Pull Requests")
770
992
  print("")
993
+ print(f" {c.CYAN}{c.BOLD}── Local Git ──{c.RESET}")
994
+ print(f" {c.CYAN}4{c.RESET}) 💻 Local Git Review (working tree, staged, or branch diff)")
995
+ print("")
771
996
  print(f" {c.CYAN}{c.BOLD}── Other ──{c.RESET}")
772
997
  print(f" {c.CYAN}3{c.RESET}) Current configuration")
773
998
  print(f" {c.CYAN}0{c.RESET}) Exit")
774
999
 
775
1000
  try:
776
- choice = input(f"\n{c.BOLD}Choose [0-3]: {c.RESET}").strip()
1001
+ choice = input(f"\n{c.BOLD}Choose [0-4]: {c.RESET}").strip()
777
1002
  except (KeyboardInterrupt, EOFError):
778
1003
  print("\n")
779
1004
  return 0
@@ -792,6 +1017,9 @@ def interactive_mode() -> int:
792
1017
  _show_config(config)
793
1018
  return 0
794
1019
 
1020
+ if choice == "4":
1021
+ return _interactive_local_review(config)
1022
+
795
1023
  print(f"{c.RED}Invalid option.{c.RESET}")
796
1024
  return 1
797
1025
 
@@ -836,6 +1064,41 @@ def _interactive_list_prs(config: ReviewConfig) -> int:
836
1064
  return run_review(parsed)
837
1065
 
838
1066
 
1067
+ def _interactive_local_review(config: ReviewConfig) -> int:
1068
+ """Interactive local Git review workflow."""
1069
+ c = Colors
1070
+
1071
+ print(f"\n{c.BOLD}Local review target:{c.RESET}")
1072
+ print(f" {c.CYAN}1{c.RESET}) Uncommitted changes (working tree + staged)")
1073
+ print(f" {c.CYAN}2{c.RESET}) Staged changes only (--staged)")
1074
+ print(f" {c.CYAN}3{c.RESET}) Compare against base branch (--from)")
1075
+
1076
+ try:
1077
+ target_choice = input(f"{c.BOLD}Choose [1-3, default=1]: {c.RESET}").strip()
1078
+ except (KeyboardInterrupt, EOFError):
1079
+ print("\n")
1080
+ return 0
1081
+
1082
+ argv = ["local"]
1083
+ if target_choice == "2":
1084
+ argv.append("--staged")
1085
+ elif target_choice == "3":
1086
+ try:
1087
+ base_ref = input(f"{c.BOLD}Base branch/ref (default=main): {c.RESET}").strip() or "main"
1088
+ except (KeyboardInterrupt, EOFError):
1089
+ print("\n")
1090
+ return 0
1091
+ argv.extend(["--from", base_ref])
1092
+
1093
+ verbosity = _ask_verbosity()
1094
+ if verbosity:
1095
+ argv.append(f"--{verbosity}")
1096
+
1097
+ parser = build_parser()
1098
+ parsed = parser.parse_args(argv)
1099
+ return run_review(parsed)
1100
+
1101
+
839
1102
  def _ask_verbosity() -> str:
840
1103
  """Asks for the verbosity mode."""
841
1104
  c = Colors
@@ -92,8 +92,8 @@ class ReviewConfig:
92
92
  review_scope: str = "diff_only" # "diff_only" | "full_code"
93
93
  max_diff_files: int = 50 # Max diff files sent to LLM
94
94
  max_diff_lines: int = 2000 # Max diff lines
95
- max_scope_blocks: int = 3 # Max changed blocks for Enclosing Scopes strategy
96
- max_scope_lines: int = 250 # Max lines in a single scope block before fallback to file_skeleton
95
+ max_scope_blocks: int = 8 # Max changed blocks for Enclosing Scopes strategy
96
+ max_scope_lines: int = 250 # Max lines in a scope before Adaptive Diff fallback
97
97
  adaptive_context_lines: int = 10 # Context lines for Adaptive Diff fallback
98
98
  custom_prompt_file: str = "review_prompt.md" # Markdown file with extra rules/context
99
99
  file_extensions_filter: list = field(default_factory=list) # File extensions to include (empty = all)