claude-dev-cli 0.10.1__py3-none-any.whl → 0.11.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.

Potentially problematic release.


This version of claude-dev-cli might be problematic. Click here for more details.

@@ -9,7 +9,7 @@ Features:
9
9
  - Interactive and single-shot modes
10
10
  """
11
11
 
12
- __version__ = "0.10.1"
12
+ __version__ = "0.11.0"
13
13
  __author__ = "Julio"
14
14
  __license__ = "MIT"
15
15
 
claude_dev_cli/cli.py CHANGED
@@ -806,41 +806,83 @@ def generate() -> None:
806
806
 
807
807
 
808
808
  @generate.command('tests')
809
- @click.argument('file_path', type=click.Path(exists=True))
810
- @click.option('-o', '--output', type=click.Path(), help='Output file path')
809
+ @click.argument('paths', nargs=-1, type=click.Path(exists=True))
810
+ @click.option('-o', '--output', type=click.Path(), help='Output file path (single file only)')
811
811
  @click.option('-a', '--api', help='API config to use')
812
812
  @click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
813
813
  @click.option('--auto-context', is_flag=True, help='Include dependencies and related files')
814
+ @click.option('--max-files', type=int, default=10, help='Maximum files to process (default: 10)')
814
815
  @click.pass_context
815
816
  def gen_tests(
816
817
  ctx: click.Context,
817
- file_path: str,
818
+ paths: tuple,
818
819
  output: Optional[str],
819
820
  api: Optional[str],
820
821
  interactive: bool,
821
- auto_context: bool
822
+ auto_context: bool,
823
+ max_files: int
822
824
  ) -> None:
823
- """Generate pytest tests for a Python file."""
825
+ """Generate pytest tests for Python files.
826
+
827
+ Can generate tests for multiple files or directories:
828
+
829
+ cdc generate tests file1.py file2.py
830
+ cdc generate tests src/
831
+ """
824
832
  console = ctx.obj['console']
833
+ from claude_dev_cli.path_utils import expand_paths
825
834
 
826
835
  try:
836
+ if not paths:
837
+ console.print("[yellow]No files specified. Provide file paths.[/yellow]")
838
+ return
839
+
840
+ files = expand_paths(list(paths), max_files=max_files)
841
+ if not files:
842
+ console.print("[yellow]No files found.[/yellow]")
843
+ return
844
+
845
+ # Output only works with single file
846
+ if output and len(files) > 1:
847
+ console.print("[yellow]Warning: --output only works with single file. Ignoring output option.[/yellow]")
848
+ output = None
849
+
850
+ if len(files) > 1:
851
+ console.print(f"\n[bold]Generating tests for {len(files)} file(s):[/bold]")
852
+ for f in files[:5]:
853
+ console.print(f" • {f}")
854
+ if len(files) > 5:
855
+ console.print(f" ... and {len(files) - 5} more")
856
+ console.print()
857
+
858
+ # Build combined prompt
859
+ files_content = ""
860
+ for file_path in files:
861
+ try:
862
+ with open(file_path, 'r') as f:
863
+ content = f.read()
864
+ files_content += f"\n\n## File: {file_path}\n\n```\n{content}\n```\n"
865
+ except Exception as e:
866
+ console.print(f"[yellow]Warning: Could not read {file_path}: {e}[/yellow]")
867
+
827
868
  if auto_context:
828
869
  from claude_dev_cli.context import ContextGatherer
829
870
 
830
871
  with console.status("[bold blue]Gathering context..."):
831
872
  gatherer = ContextGatherer()
832
- context = gatherer.gather_for_file(Path(file_path), include_git=False)
873
+ context = gatherer.gather_for_file(files[0], include_git=False)
833
874
  context_info = context.format_for_prompt()
834
875
 
835
876
  console.print("[dim]✓ Context gathered (dependencies, related files)[/dim]")
836
877
 
837
- # Use context-aware test generation
838
878
  client = ClaudeClient(api_config_name=api)
839
- enhanced_prompt = f"{context_info}\n\nPlease generate comprehensive pytest tests for the main file, including fixtures, edge cases, and proper mocking where needed."
840
- result = client.call(enhanced_prompt)
879
+ prompt = f"{context_info}\n\nFiles:{files_content}\n\nPlease generate comprehensive pytest tests for these files, including fixtures, edge cases, and proper mocking where needed."
841
880
  else:
842
- with console.status("[bold blue]Generating tests..."):
843
- result = generate_tests(file_path, api_config_name=api)
881
+ with console.status(f"[bold blue]Generating tests for {len(files)} file(s)..."):
882
+ client = ClaudeClient(api_config_name=api)
883
+ prompt = f"Files to test:{files_content}\n\nPlease generate comprehensive pytest tests for these files, including fixtures, edge cases, and proper mocking where needed."
884
+
885
+ result = client.call(prompt)
844
886
 
845
887
  if interactive:
846
888
  # Show initial result
@@ -892,41 +934,83 @@ def gen_tests(
892
934
 
893
935
 
894
936
  @generate.command('docs')
895
- @click.argument('file_path', type=click.Path(exists=True))
896
- @click.option('-o', '--output', type=click.Path(), help='Output file path')
937
+ @click.argument('paths', nargs=-1, type=click.Path(exists=True))
938
+ @click.option('-o', '--output', type=click.Path(), help='Output file path (single file only)')
897
939
  @click.option('-a', '--api', help='API config to use')
898
940
  @click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
899
941
  @click.option('--auto-context', is_flag=True, help='Include dependencies and related files')
942
+ @click.option('--max-files', type=int, default=10, help='Maximum files to process (default: 10)')
900
943
  @click.pass_context
901
944
  def gen_docs(
902
945
  ctx: click.Context,
903
- file_path: str,
946
+ paths: tuple,
904
947
  output: Optional[str],
905
948
  api: Optional[str],
906
949
  interactive: bool,
907
- auto_context: bool
950
+ auto_context: bool,
951
+ max_files: int
908
952
  ) -> None:
909
- """Generate documentation for a Python file."""
953
+ """Generate documentation for files.
954
+
955
+ Can generate docs for multiple files or directories:
956
+
957
+ cdc generate docs file1.py file2.py
958
+ cdc generate docs src/
959
+ """
910
960
  console = ctx.obj['console']
961
+ from claude_dev_cli.path_utils import expand_paths
911
962
 
912
963
  try:
964
+ if not paths:
965
+ console.print("[yellow]No files specified. Provide file paths.[/yellow]")
966
+ return
967
+
968
+ files = expand_paths(list(paths), max_files=max_files)
969
+ if not files:
970
+ console.print("[yellow]No files found.[/yellow]")
971
+ return
972
+
973
+ # Output only works with single file
974
+ if output and len(files) > 1:
975
+ console.print("[yellow]Warning: --output only works with single file. Ignoring output option.[/yellow]")
976
+ output = None
977
+
978
+ if len(files) > 1:
979
+ console.print(f"\n[bold]Generating docs for {len(files)} file(s):[/bold]")
980
+ for f in files[:5]:
981
+ console.print(f" • {f}")
982
+ if len(files) > 5:
983
+ console.print(f" ... and {len(files) - 5} more")
984
+ console.print()
985
+
986
+ # Build combined prompt
987
+ files_content = ""
988
+ for file_path in files:
989
+ try:
990
+ with open(file_path, 'r') as f:
991
+ content = f.read()
992
+ files_content += f"\n\n## File: {file_path}\n\n```\n{content}\n```\n"
993
+ except Exception as e:
994
+ console.print(f"[yellow]Warning: Could not read {file_path}: {e}[/yellow]")
995
+
913
996
  if auto_context:
914
997
  from claude_dev_cli.context import ContextGatherer
915
998
 
916
999
  with console.status("[bold blue]Gathering context..."):
917
1000
  gatherer = ContextGatherer()
918
- context = gatherer.gather_for_file(Path(file_path), include_git=False)
1001
+ context = gatherer.gather_for_file(files[0], include_git=False)
919
1002
  context_info = context.format_for_prompt()
920
1003
 
921
1004
  console.print("[dim]✓ Context gathered (dependencies, related files)[/dim]")
922
1005
 
923
- # Use context-aware documentation generation
924
1006
  client = ClaudeClient(api_config_name=api)
925
- enhanced_prompt = f"{context_info}\n\nPlease generate comprehensive documentation for the main file, including API reference, usage examples, and integration notes."
926
- result = client.call(enhanced_prompt)
1007
+ prompt = f"{context_info}\n\nFiles:{files_content}\n\nPlease generate comprehensive documentation for these files, including API reference, usage examples, and integration notes."
927
1008
  else:
928
- with console.status("[bold blue]Generating documentation..."):
929
- result = generate_docs(file_path, api_config_name=api)
1009
+ with console.status(f"[bold blue]Generating documentation for {len(files)} file(s)..."):
1010
+ client = ClaudeClient(api_config_name=api)
1011
+ prompt = f"Files to document:{files_content}\n\nPlease generate comprehensive documentation for these files, including API reference, usage examples, and integration notes."
1012
+
1013
+ result = client.call(prompt)
930
1014
 
931
1015
  if interactive:
932
1016
  console.print("\n[bold]Initial Documentation:[/bold]\n")
@@ -977,22 +1061,65 @@ def gen_docs(
977
1061
 
978
1062
 
979
1063
  @main.command('review')
980
- @click.argument('file_path', type=click.Path(exists=True))
1064
+ @click.argument('paths', nargs=-1, type=click.Path(exists=True))
981
1065
  @click.option('-a', '--api', help='API config to use')
982
1066
  @click.option('-i', '--interactive', is_flag=True, help='Interactive follow-up questions')
983
1067
  @click.option('--auto-context', is_flag=True, help='Automatically include git, dependencies, and related files')
1068
+ @click.option('--max-files', type=int, default=20, help='Maximum files to review (default: 20)')
984
1069
  @click.pass_context
985
1070
  def review(
986
1071
  ctx: click.Context,
987
- file_path: str,
1072
+ paths: tuple,
988
1073
  api: Optional[str],
989
1074
  interactive: bool,
990
- auto_context: bool
1075
+ auto_context: bool,
1076
+ max_files: int
991
1077
  ) -> None:
992
- """Review code for bugs and improvements."""
1078
+ """Review code for bugs and improvements.
1079
+
1080
+ Can review multiple files, directories, or auto-detect git changes:
1081
+
1082
+ cdc review file1.py file2.py # Multiple files
1083
+ cdc review src/ # Directory
1084
+ cdc review # Auto-detect git changes
1085
+ """
993
1086
  console = ctx.obj['console']
1087
+ from claude_dev_cli.path_utils import expand_paths, auto_detect_files
994
1088
 
995
1089
  try:
1090
+ # Determine files to review
1091
+ if paths:
1092
+ # Expand paths (handles directories, multiple files)
1093
+ files = expand_paths(list(paths), max_files=max_files)
1094
+ else:
1095
+ # Auto-detect files from git
1096
+ files = auto_detect_files()
1097
+ if files:
1098
+ console.print(f"[dim]Auto-detected {len(files)} file(s) from git changes[/dim]")
1099
+
1100
+ if not files:
1101
+ console.print("[yellow]No files to review. Specify files or make some changes.[/yellow]")
1102
+ return
1103
+
1104
+ # Show files being reviewed
1105
+ if len(files) > 1:
1106
+ console.print(f"\n[bold]Reviewing {len(files)} file(s):[/bold]")
1107
+ for f in files[:10]: # Show first 10
1108
+ console.print(f" • {f}")
1109
+ if len(files) > 10:
1110
+ console.print(f" ... and {len(files) - 10} more")
1111
+ console.print()
1112
+
1113
+ # Build combined prompt for multiple files
1114
+ files_content = ""
1115
+ for file_path in files:
1116
+ try:
1117
+ with open(file_path, 'r') as f:
1118
+ content = f.read()
1119
+ files_content += f"\n\n## File: {file_path}\n\n```\n{content}\n```\n"
1120
+ except Exception as e:
1121
+ console.print(f"[yellow]Warning: Could not read {file_path}: {e}[/yellow]")
1122
+
996
1123
  # Gather context if requested
997
1124
  context_info = ""
998
1125
  if auto_context:
@@ -1000,31 +1127,24 @@ def review(
1000
1127
 
1001
1128
  with console.status("[bold blue]Gathering context..."):
1002
1129
  gatherer = ContextGatherer()
1003
- context = gatherer.gather_for_review(Path(file_path))
1130
+ # Use first file for context gathering
1131
+ context = gatherer.gather_for_review(files[0])
1004
1132
  context_info = context.format_for_prompt()
1005
1133
 
1006
1134
  console.print("[dim]✓ Context gathered (git, dependencies, tests)[/dim]")
1007
1135
 
1008
- with console.status("[bold blue]Reviewing code..."):
1009
- # If we have context, prepend it to the file analysis
1136
+ with console.status(f"[bold blue]Reviewing {len(files)} file(s)..."):
1137
+ client = ClaudeClient(api_config_name=api)
1010
1138
  if context_info:
1011
- # Read file separately for context-aware review
1012
- result = code_review(file_path, api_config_name=api)
1013
- # The context module already includes the file, so we use it differently
1014
- client = ClaudeClient(api_config_name=api)
1015
- enhanced_prompt = f"{context_info}\n\nPlease review this code for bugs and improvements."
1016
- result = client.call(enhanced_prompt)
1139
+ prompt = f"{context_info}\n\nFiles to review:{files_content}\n\nPlease review this code for bugs and improvements."
1017
1140
  else:
1018
- result = code_review(file_path, api_config_name=api)
1141
+ prompt = f"Files to review:{files_content}\n\nPlease review this code for bugs, security issues, and improvements."
1142
+ result = client.call(prompt)
1019
1143
 
1020
1144
  md = Markdown(result)
1021
1145
  console.print(md)
1022
1146
 
1023
1147
  if interactive:
1024
- client = ClaudeClient(api_config_name=api)
1025
- with open(file_path, 'r') as f:
1026
- file_content = f.read()
1027
-
1028
1148
  console.print("\n[dim]Ask follow-up questions about the review, or 'exit' to quit[/dim]")
1029
1149
 
1030
1150
  while True:
@@ -1036,7 +1156,7 @@ def review(
1036
1156
  if not user_input:
1037
1157
  continue
1038
1158
 
1039
- follow_up_prompt = f"Code review:\n\n{result}\n\nOriginal code:\n\n{file_content}\n\nUser question: {user_input}"
1159
+ follow_up_prompt = f"Code review:\n\n{result}\n\nFiles:{files_content}\n\nUser question: {user_input}"
1040
1160
 
1041
1161
  console.print("\n[bold green]Claude:[/bold green] ", end='')
1042
1162
  for chunk in client.call_streaming(follow_up_prompt):
@@ -1106,49 +1226,95 @@ def debug(
1106
1226
 
1107
1227
 
1108
1228
  @main.command('refactor')
1109
- @click.argument('file_path', type=click.Path(exists=True))
1110
- @click.option('-o', '--output', type=click.Path(), help='Output file path')
1229
+ @click.argument('paths', nargs=-1, type=click.Path(exists=True))
1230
+ @click.option('-o', '--output', type=click.Path(), help='Output file path (single file only)')
1111
1231
  @click.option('-a', '--api', help='API config to use')
1112
1232
  @click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
1113
1233
  @click.option('--auto-context', is_flag=True, help='Automatically include git, dependencies, and related files')
1234
+ @click.option('--max-files', type=int, default=20, help='Maximum files to refactor (default: 20)')
1114
1235
  @click.pass_context
1115
1236
  def refactor(
1116
1237
  ctx: click.Context,
1117
- file_path: str,
1238
+ paths: tuple,
1118
1239
  output: Optional[str],
1119
1240
  api: Optional[str],
1120
1241
  interactive: bool,
1121
- auto_context: bool
1242
+ auto_context: bool,
1243
+ max_files: int
1122
1244
  ) -> None:
1123
- """Suggest refactoring improvements."""
1245
+ """Suggest refactoring improvements.
1246
+
1247
+ Can refactor multiple files, directories, or auto-detect git changes:
1248
+
1249
+ cdc refactor file1.py file2.py # Multiple files
1250
+ cdc refactor src/ # Directory
1251
+ cdc refactor # Auto-detect git changes
1252
+ """
1124
1253
  console = ctx.obj['console']
1254
+ from claude_dev_cli.path_utils import expand_paths, auto_detect_files
1125
1255
 
1126
1256
  try:
1257
+ # Determine files to refactor
1258
+ if paths:
1259
+ files = expand_paths(list(paths), max_files=max_files)
1260
+ else:
1261
+ files = auto_detect_files()
1262
+ if files:
1263
+ console.print(f"[dim]Auto-detected {len(files)} file(s) from git changes[/dim]")
1264
+
1265
+ if not files:
1266
+ console.print("[yellow]No files to refactor. Specify files or make some changes.[/yellow]")
1267
+ return
1268
+
1269
+ # Output only works with single file
1270
+ if output and len(files) > 1:
1271
+ console.print("[yellow]Warning: --output only works with single file. Ignoring output option.[/yellow]")
1272
+ output = None
1273
+
1274
+ # Show files being refactored
1275
+ if len(files) > 1:
1276
+ console.print(f"\n[bold]Refactoring {len(files)} file(s):[/bold]")
1277
+ for f in files[:10]:
1278
+ console.print(f" • {f}")
1279
+ if len(files) > 10:
1280
+ console.print(f" ... and {len(files) - 10} more")
1281
+ console.print()
1282
+
1283
+ # Build combined prompt
1284
+ files_content = ""
1285
+ for file_path in files:
1286
+ try:
1287
+ with open(file_path, 'r') as f:
1288
+ content = f.read()
1289
+ files_content += f"\n\n## File: {file_path}\n\n```\n{content}\n```\n"
1290
+ except Exception as e:
1291
+ console.print(f"[yellow]Warning: Could not read {file_path}: {e}[/yellow]")
1292
+
1127
1293
  # Gather context if requested
1128
1294
  if auto_context:
1129
1295
  from claude_dev_cli.context import ContextGatherer
1130
1296
 
1131
1297
  with console.status("[bold blue]Gathering context..."):
1132
1298
  gatherer = ContextGatherer()
1133
- context = gatherer.gather_for_file(Path(file_path))
1299
+ context = gatherer.gather_for_file(files[0])
1134
1300
  context_info = context.format_for_prompt()
1135
1301
 
1136
1302
  console.print("[dim]✓ Context gathered[/dim]")
1137
1303
 
1138
- # Use context-aware refactoring
1139
1304
  client = ClaudeClient(api_config_name=api)
1140
- enhanced_prompt = f"{context_info}\n\nPlease suggest refactoring improvements for the main file."
1141
- result = client.call(enhanced_prompt)
1305
+ prompt = f"{context_info}\n\nFiles:{files_content}\n\nPlease suggest refactoring improvements."
1142
1306
  else:
1143
- with console.status("[bold blue]Analyzing code..."):
1144
- result = refactor_code(file_path, api_config_name=api)
1307
+ with console.status(f"[bold blue]Analyzing {len(files)} file(s)..."):
1308
+ client = ClaudeClient(api_config_name=api)
1309
+ prompt = f"Files to refactor:{files_content}\n\nPlease suggest refactoring improvements focusing on code quality, maintainability, and performance."
1310
+
1311
+ result = client.call(prompt)
1145
1312
 
1146
1313
  if interactive:
1147
1314
  console.print("\n[bold]Initial Refactoring:[/bold]\n")
1148
1315
  md = Markdown(result)
1149
1316
  console.print(md)
1150
1317
 
1151
- client = ClaudeClient(api_config_name=api)
1152
1318
  conversation_context = [result]
1153
1319
 
1154
1320
  while True:
@@ -1233,6 +1399,95 @@ def git_commit(ctx: click.Context, api: Optional[str], auto_context: bool) -> No
1233
1399
  sys.exit(1)
1234
1400
 
1235
1401
 
1402
+ @git.command('review')
1403
+ @click.option('-a', '--api', help='API config to use')
1404
+ @click.option('--staged', is_flag=True, help='Review only staged changes')
1405
+ @click.option('--branch', help='Review changes in branch (e.g., main..HEAD)')
1406
+ @click.option('-i', '--interactive', is_flag=True, help='Interactive follow-up questions')
1407
+ @click.pass_context
1408
+ def git_review(
1409
+ ctx: click.Context,
1410
+ api: Optional[str],
1411
+ staged: bool,
1412
+ branch: Optional[str],
1413
+ interactive: bool
1414
+ ) -> None:
1415
+ """Review git changes.
1416
+
1417
+ Examples:
1418
+ cdc git review --staged # Review staged changes
1419
+ cdc git review # Review all changes
1420
+ cdc git review --branch main..HEAD # Review branch changes
1421
+ """
1422
+ console = ctx.obj['console']
1423
+ from claude_dev_cli.path_utils import get_git_changes
1424
+
1425
+ try:
1426
+ # Get changed files
1427
+ if branch:
1428
+ files = get_git_changes(commit_range=branch)
1429
+ scope = f"branch {branch}"
1430
+ elif staged:
1431
+ files = get_git_changes(staged_only=True)
1432
+ scope = "staged changes"
1433
+ else:
1434
+ files = get_git_changes(staged_only=False)
1435
+ scope = "all changes"
1436
+
1437
+ if not files:
1438
+ console.print(f"[yellow]No changes found in {scope}.[/yellow]")
1439
+ return
1440
+
1441
+ console.print(f"\n[bold]Reviewing {len(files)} changed file(s) from {scope}:[/bold]")
1442
+ for f in files[:10]:
1443
+ console.print(f" • {f}")
1444
+ if len(files) > 10:
1445
+ console.print(f" ... and {len(files) - 10} more")
1446
+ console.print()
1447
+
1448
+ # Build files content
1449
+ files_content = ""
1450
+ for file_path in files:
1451
+ try:
1452
+ with open(file_path, 'r') as f:
1453
+ content = f.read()
1454
+ files_content += f"\n\n## File: {file_path}\n\n```\n{content}\n```\n"
1455
+ except Exception as e:
1456
+ console.print(f"[yellow]Warning: Could not read {file_path}: {e}[/yellow]")
1457
+
1458
+ # Review
1459
+ with console.status(f"[bold blue]Reviewing {len(files)} file(s)..."):
1460
+ client = ClaudeClient(api_config_name=api)
1461
+ prompt = f"Changed files in {scope}:{files_content}\n\nPlease review these git changes for bugs, security issues, code quality, and potential improvements. Focus on what changed and why it might be problematic."
1462
+ result = client.call(prompt)
1463
+
1464
+ md = Markdown(result)
1465
+ console.print(md)
1466
+
1467
+ if interactive:
1468
+ console.print("\n[dim]Ask follow-up questions about the review, or 'exit' to quit[/dim]")
1469
+
1470
+ while True:
1471
+ user_input = console.input("\n[cyan]You:[/cyan] ").strip()
1472
+
1473
+ if user_input.lower() == 'exit':
1474
+ break
1475
+
1476
+ if not user_input:
1477
+ continue
1478
+
1479
+ follow_up_prompt = f"Code review:\n\n{result}\n\nChanged files:{files_content}\n\nUser question: {user_input}"
1480
+
1481
+ console.print("\n[bold green]Claude:[/bold green] ", end='')
1482
+ for chunk in client.call_streaming(follow_up_prompt):
1483
+ console.print(chunk, end='')
1484
+ console.print()
1485
+
1486
+ except Exception as e:
1487
+ console.print(f"[red]Error: {e}[/red]")
1488
+ sys.exit(1)
1489
+
1490
+
1236
1491
  @main.group()
1237
1492
  def context() -> None:
1238
1493
  """Context gathering tools and information."""
@@ -0,0 +1,174 @@
1
+ """Path expansion and git change detection utilities."""
2
+
3
+ import subprocess
4
+ from pathlib import Path
5
+ from typing import List, Set, Optional
6
+
7
+ # Common code file extensions
8
+ CODE_EXTENSIONS = {
9
+ '.py', '.js', '.ts', '.jsx', '.tsx', '.go', '.rs', '.java', '.cpp', '.c',
10
+ '.h', '.hpp', '.cs', '.rb', '.php', '.swift', '.kt', '.scala', '.r',
11
+ '.m', '.mm', '.sh', '.bash', '.zsh', '.fish', '.lua', '.pl', '.sql',
12
+ '.html', '.css', '.scss', '.sass', '.less', '.vue', '.svelte'
13
+ }
14
+
15
+
16
+ def is_code_file(path: Path) -> bool:
17
+ """Check if file is a code file based on extension."""
18
+ return path.suffix.lower() in CODE_EXTENSIONS
19
+
20
+
21
+ def expand_paths(
22
+ paths: List[str],
23
+ max_files: Optional[int] = None,
24
+ recursive: bool = True
25
+ ) -> List[Path]:
26
+ """Expand paths (files, directories, globs) to list of code files.
27
+
28
+ Args:
29
+ paths: List of file/directory paths
30
+ max_files: Maximum number of files to return (None = unlimited)
31
+ recursive: Whether to recursively search directories
32
+
33
+ Returns:
34
+ List of Path objects for code files
35
+ """
36
+ result_files: Set[Path] = set()
37
+
38
+ for path_str in paths:
39
+ path = Path(path_str).resolve()
40
+
41
+ if not path.exists():
42
+ continue
43
+
44
+ if path.is_file():
45
+ # Add single file
46
+ result_files.add(path)
47
+ elif path.is_dir():
48
+ # Expand directory
49
+ if recursive:
50
+ # Recursively find all code files
51
+ for file_path in path.rglob('*'):
52
+ if file_path.is_file() and is_code_file(file_path):
53
+ result_files.add(file_path)
54
+ if max_files and len(result_files) >= max_files:
55
+ break
56
+ else:
57
+ # Only direct children
58
+ for file_path in path.glob('*'):
59
+ if file_path.is_file() and is_code_file(file_path):
60
+ result_files.add(file_path)
61
+ if max_files and len(result_files) >= max_files:
62
+ break
63
+
64
+ if max_files and len(result_files) >= max_files:
65
+ break
66
+
67
+ # Sort for consistent ordering
68
+ return sorted(result_files)
69
+
70
+
71
+ def get_git_changes(
72
+ staged_only: bool = False,
73
+ include_untracked: bool = False,
74
+ commit_range: Optional[str] = None
75
+ ) -> List[Path]:
76
+ """Get list of changed files from git.
77
+
78
+ Args:
79
+ staged_only: Only return staged files
80
+ include_untracked: Include untracked files
81
+ commit_range: Git commit range (e.g., "main..HEAD")
82
+
83
+ Returns:
84
+ List of Path objects for changed files
85
+ """
86
+ files: Set[Path] = set()
87
+
88
+ try:
89
+ if commit_range:
90
+ # Get files changed in commit range
91
+ result = subprocess.run(
92
+ ['git', 'diff', '--name-only', commit_range],
93
+ capture_output=True,
94
+ text=True,
95
+ check=True
96
+ )
97
+ for line in result.stdout.strip().split('\n'):
98
+ if line:
99
+ path = Path(line)
100
+ if path.exists():
101
+ files.add(path)
102
+ elif staged_only:
103
+ # Get only staged files
104
+ result = subprocess.run(
105
+ ['git', 'diff', '--cached', '--name-only'],
106
+ capture_output=True,
107
+ text=True,
108
+ check=True
109
+ )
110
+ for line in result.stdout.strip().split('\n'):
111
+ if line:
112
+ path = Path(line)
113
+ if path.exists():
114
+ files.add(path)
115
+ else:
116
+ # Get all modified files (staged + unstaged)
117
+ result = subprocess.run(
118
+ ['git', 'diff', '--name-only', 'HEAD'],
119
+ capture_output=True,
120
+ text=True,
121
+ check=True
122
+ )
123
+ for line in result.stdout.strip().split('\n'):
124
+ if line:
125
+ path = Path(line)
126
+ if path.exists():
127
+ files.add(path)
128
+
129
+ if include_untracked:
130
+ # Add untracked files
131
+ result = subprocess.run(
132
+ ['git', 'ls-files', '--others', '--exclude-standard'],
133
+ capture_output=True,
134
+ text=True,
135
+ check=True
136
+ )
137
+ for line in result.stdout.strip().split('\n'):
138
+ if line:
139
+ path = Path(line)
140
+ if path.exists():
141
+ files.add(path)
142
+ except subprocess.CalledProcessError:
143
+ # Not a git repo or git command failed
144
+ return []
145
+
146
+ return sorted(files)
147
+
148
+
149
+ def auto_detect_files(cwd: Optional[Path] = None) -> List[Path]:
150
+ """Auto-detect files to process based on git status.
151
+
152
+ Priority:
153
+ 1. Staged files
154
+ 2. Modified files (staged + unstaged)
155
+ 3. All code files in current directory
156
+
157
+ Returns:
158
+ List of Path objects, empty list if none found
159
+ """
160
+ if cwd is None:
161
+ cwd = Path.cwd()
162
+
163
+ # Try staged files first
164
+ files = get_git_changes(staged_only=True)
165
+ if files:
166
+ return files
167
+
168
+ # Try all modified files
169
+ files = get_git_changes(staged_only=False)
170
+ if files:
171
+ return files
172
+
173
+ # Fallback: all code files in current directory (non-recursive)
174
+ return expand_paths([str(cwd)], recursive=False)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-dev-cli
3
- Version: 0.10.1
3
+ Version: 0.11.0
4
4
  Summary: A powerful CLI tool for developers using Claude AI with multi-API routing, test generation, code review, and usage tracking
5
5
  Author-email: Julio <thinmanj@users.noreply.github.com>
6
6
  License: MIT
@@ -46,7 +46,7 @@ Dynamic: license-file
46
46
 
47
47
  [![PyPI version](https://badge.fury.io/py/claude-dev-cli.svg)](https://badge.fury.io/py/claude-dev-cli)
48
48
  [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
49
- [![Tests](https://img.shields.io/badge/tests-260%20passing-brightgreen.svg)](https://github.com/thinmanj/claude-dev-cli)
49
+ [![Tests](https://img.shields.io/badge/tests-285%20passing-brightgreen.svg)](https://github.com/thinmanj/claude-dev-cli)
50
50
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
51
51
  [![Homebrew](https://img.shields.io/badge/homebrew-available-orange.svg)](https://github.com/thinmanj/homebrew-tap)
52
52
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
@@ -72,6 +72,19 @@ A powerful command-line tool for developers using Claude AI with multi-API routi
72
72
  - `smart`: Claude Sonnet 4 ($3.00/$15.00 per Mtok) - default
73
73
  - `powerful`: Claude Opus 4 ($15.00/$75.00 per Mtok)
74
74
 
75
+ ### 📁 Multi-File Support (v0.11.0+)
76
+ - **Batch Processing**: Review, refactor, test, or document multiple files at once
77
+ - **Directory Support**: Process all code files in a directory with `--max-files` limit
78
+ - **Auto-Detection**: Commands auto-detect git changes when no files specified
79
+ - `cdc review` → reviews staged files, falls back to modified files, then current directory
80
+ - **Git Integration**: New `cdc git review` command for reviewing changes
81
+ - `--staged`: Review only staged changes
82
+ - `--branch <range>`: Review branch changes (e.g., `main..HEAD`)
83
+ - **Multi-Language**: Supports 25+ file extensions (Python, JS/TS, Go, Rust, Java, C++, etc.)
84
+ - **Smart Display**: Shows file list preview (first 5-10 files, then "... and N more")
85
+ - Commands with multi-file support:
86
+ - `review`, `refactor`, `generate tests`, `generate docs`
87
+
75
88
  ### 🧪 Developer Tools
76
89
  - **Test Generation**: Automatic pytest test generation for Python code
77
90
  - **Code Review**: Comprehensive code reviews with security, performance, and best practice checks
@@ -232,36 +245,62 @@ cdc generate tests -m smart mymodule.py # Balanced approach
232
245
  ### 3. Developer Commands
233
246
 
234
247
  ```bash
235
- # Generate tests
248
+ # Generate tests (single file)
236
249
  cdc generate tests mymodule.py -o tests/test_mymodule.py
237
250
 
251
+ # Generate tests for multiple files (NEW in v0.11.0)
252
+ cdc generate tests file1.py file2.py file3.py
253
+ cdc generate tests src/ --max-files 10
254
+
238
255
  # Generate tests with interactive refinement
239
256
  cdc generate tests mymodule.py --interactive
240
257
 
241
258
  # Generate tests with context (includes dependencies, related files) - NEW in v0.8.1
242
259
  cdc generate tests mymodule.py --auto-context
243
260
 
244
- # Code review
261
+ # Code review (single file)
245
262
  cdc review mymodule.py
246
263
 
264
+ # Code review multiple files (NEW in v0.11.0)
265
+ cdc review file1.py file2.py file3.py
266
+ cdc review src/ # Review entire directory
267
+ cdc review # Auto-detect git changes (staged → modified → current dir)
268
+
247
269
  # Code review with auto-context (includes git, dependencies, tests)
248
270
  cdc review mymodule.py --auto-context
249
271
 
250
272
  # Code review with interactive follow-up questions
251
273
  cdc review mymodule.py --interactive
252
274
 
275
+ # Review git changes (NEW in v0.11.0)
276
+ cdc git review --staged # Review only staged changes
277
+ cdc git review --branch main..HEAD # Review branch changes
278
+ cdc git review # Review all modified files
279
+
253
280
  # Debug errors with intelligent error parsing
254
281
  python script.py 2>&1 | cdc debug --auto-context
255
282
 
256
- # Generate documentation
283
+ # Generate documentation (single file)
257
284
  cdc generate docs mymodule.py
258
285
 
286
+ # Generate docs for multiple files (NEW in v0.11.0)
287
+ cdc generate docs file1.py file2.py file3.py
288
+ cdc generate docs src/ --max-files 10
289
+
259
290
  # Generate docs with interactive refinement
260
291
  cdc generate docs mymodule.py --interactive
261
292
 
262
293
  # Generate docs with context (includes dependencies) - NEW in v0.8.1
263
294
  cdc generate docs mymodule.py --auto-context
264
295
 
296
+ # Refactor (single file)
297
+ cdc refactor legacy_code.py
298
+
299
+ # Refactor multiple files (NEW in v0.11.0)
300
+ cdc refactor file1.py file2.py file3.py
301
+ cdc refactor src/
302
+ cdc refactor # Auto-detect git changes
303
+
265
304
  # Refactor with context (includes related files)
266
305
  cdc refactor legacy_code.py --auto-context
267
306
 
@@ -1,10 +1,11 @@
1
- claude_dev_cli/__init__.py,sha256=DWmrHqIGVEZMF3tIGWVH8EwF8vIyRk2vMvccg5wVehY,470
2
- claude_dev_cli/cli.py,sha256=kdEocOqRYTFsFlsfZMe5LTk9gBj6iIEqOG1JB42L1WI,68521
1
+ claude_dev_cli/__init__.py,sha256=XSFQv1m1nRRX-T9POeBmjHrA0pylgusfmwtA92XCEzo,470
2
+ claude_dev_cli/cli.py,sha256=RkoR4ocGUfP5A37yl_xObdNdd0QKeyqvEf2YOq1CL0U,79000
3
3
  claude_dev_cli/commands.py,sha256=RKGx2rv56PM6eErvA2uoQ20hY8babuI5jav8nCUyUOk,3964
4
4
  claude_dev_cli/config.py,sha256=ZnPvzwlXsoY9YhqTl4S__fwY1MzJXKIaYK0nIIelNXk,19978
5
5
  claude_dev_cli/context.py,sha256=1TlLzpREFZDEIuU7RAtlkjxARKWZpnxHHvK283sUAZE,26714
6
6
  claude_dev_cli/core.py,sha256=4tKBgPQzvhM-jtlHaIy2K54vc2yIb4ycNDPLpoIoqN0,6621
7
7
  claude_dev_cli/history.py,sha256=26EjNW68JuFQJhUp1j8UdB19S-eYz3eqevkpCOATwP0,10510
8
+ claude_dev_cli/path_utils.py,sha256=FFwweSkXe9OiG2Dej_UDKcY8-ZCYjL89ow6c7LZGe80,5564
8
9
  claude_dev_cli/secure_storage.py,sha256=KcZuQMLTbQpMAi2Cyh-_JkNcK9vHzAITOgjTcM9sr98,8161
9
10
  claude_dev_cli/template_manager.py,sha256=wtcrNuxFoJLJIPmIxUzrPKrE8kUvdqEd53EnG3jARhg,9277
10
11
  claude_dev_cli/templates.py,sha256=lKxH943ySfUKgyHaWa4W3LVv91SgznKgajRtSRp_4UY,2260
@@ -17,9 +18,9 @@ claude_dev_cli/plugins/base.py,sha256=H4HQet1I-a3WLCfE9F06Lp8NuFvVoIlou7sIgyJFK-
17
18
  claude_dev_cli/plugins/diff_editor/__init__.py,sha256=gqR5S2TyIVuq-sK107fegsutQ7Z-sgAIEbtc71FhXIM,101
18
19
  claude_dev_cli/plugins/diff_editor/plugin.py,sha256=M1bUoqpasD3ZNQo36Fu_8g92uySPZyG_ujMbj5UplsU,3073
19
20
  claude_dev_cli/plugins/diff_editor/viewer.py,sha256=1IOXIKw_01ppJx5C1dQt9Kr6U1TdAHT8_iUT5r_q0NM,17169
20
- claude_dev_cli-0.10.1.dist-info/licenses/LICENSE,sha256=DGueuJwMJtMwgLO5mWlS0TaeBrFwQuNpNZ22PU9J2bw,1062
21
- claude_dev_cli-0.10.1.dist-info/METADATA,sha256=vchis2gXoTR9ECg76Vd9yS37vHvmDD0DkUhqLLyCOeg,19569
22
- claude_dev_cli-0.10.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
23
- claude_dev_cli-0.10.1.dist-info/entry_points.txt,sha256=zymgUIIVpFTARkFmxAuW2A4BQsNITh_L0uU-XunytHg,85
24
- claude_dev_cli-0.10.1.dist-info/top_level.txt,sha256=m7MF6LOIuTe41IT5Fgt0lc-DK1EgM4gUU_IZwWxK0pg,15
25
- claude_dev_cli-0.10.1.dist-info/RECORD,,
21
+ claude_dev_cli-0.11.0.dist-info/licenses/LICENSE,sha256=DGueuJwMJtMwgLO5mWlS0TaeBrFwQuNpNZ22PU9J2bw,1062
22
+ claude_dev_cli-0.11.0.dist-info/METADATA,sha256=5fvuAb55hqZwnB_j-wzQNFv6ZXZbdnvEbAzt9pvmxtM,21330
23
+ claude_dev_cli-0.11.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
24
+ claude_dev_cli-0.11.0.dist-info/entry_points.txt,sha256=zymgUIIVpFTARkFmxAuW2A4BQsNITh_L0uU-XunytHg,85
25
+ claude_dev_cli-0.11.0.dist-info/top_level.txt,sha256=m7MF6LOIuTe41IT5Fgt0lc-DK1EgM4gUU_IZwWxK0pg,15
26
+ claude_dev_cli-0.11.0.dist-info/RECORD,,