claude-dev-cli 0.10.1__py3-none-any.whl → 0.12.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.
- claude_dev_cli/__init__.py +1 -1
- claude_dev_cli/cli.py +617 -53
- claude_dev_cli/input_sources.py +231 -0
- claude_dev_cli/path_utils.py +174 -0
- {claude_dev_cli-0.10.1.dist-info → claude_dev_cli-0.12.0.dist-info}/METADATA +114 -8
- {claude_dev_cli-0.10.1.dist-info → claude_dev_cli-0.12.0.dist-info}/RECORD +10 -8
- {claude_dev_cli-0.10.1.dist-info → claude_dev_cli-0.12.0.dist-info}/WHEEL +0 -0
- {claude_dev_cli-0.10.1.dist-info → claude_dev_cli-0.12.0.dist-info}/entry_points.txt +0 -0
- {claude_dev_cli-0.10.1.dist-info → claude_dev_cli-0.12.0.dist-info}/licenses/LICENSE +0 -0
- {claude_dev_cli-0.10.1.dist-info → claude_dev_cli-0.12.0.dist-info}/top_level.txt +0 -0
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('
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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")
|
|
@@ -976,23 +1060,375 @@ def gen_docs(
|
|
|
976
1060
|
sys.exit(1)
|
|
977
1061
|
|
|
978
1062
|
|
|
1063
|
+
@generate.command('code')
|
|
1064
|
+
@click.option('--description', help='Inline code specification')
|
|
1065
|
+
@click.option('-f', '--file', 'spec_file', type=click.Path(exists=True), help='Read specification from file')
|
|
1066
|
+
@click.option('--pdf', type=click.Path(exists=True), help='Read specification from PDF')
|
|
1067
|
+
@click.option('--url', help='Fetch specification from URL')
|
|
1068
|
+
@click.option('-o', '--output', required=True, type=click.Path(), help='Output file path (required)')
|
|
1069
|
+
@click.option('--language', help='Target language (auto-detected from output extension)')
|
|
1070
|
+
@click.option('-m', '--model', help='Model profile to use')
|
|
1071
|
+
@click.option('-a', '--api', help='API config to use')
|
|
1072
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
|
|
1073
|
+
@click.option('--auto-context', is_flag=True, help='Include project context')
|
|
1074
|
+
@click.pass_context
|
|
1075
|
+
def gen_code(
|
|
1076
|
+
ctx: click.Context,
|
|
1077
|
+
description: Optional[str],
|
|
1078
|
+
spec_file: Optional[str],
|
|
1079
|
+
pdf: Optional[str],
|
|
1080
|
+
url: Optional[str],
|
|
1081
|
+
output: str,
|
|
1082
|
+
language: Optional[str],
|
|
1083
|
+
model: Optional[str],
|
|
1084
|
+
api: Optional[str],
|
|
1085
|
+
interactive: bool,
|
|
1086
|
+
auto_context: bool
|
|
1087
|
+
) -> None:
|
|
1088
|
+
"""Generate code from a specification.
|
|
1089
|
+
|
|
1090
|
+
Reads specification from text, file, PDF, or URL and generates complete code.
|
|
1091
|
+
|
|
1092
|
+
Examples:
|
|
1093
|
+
cdc generate code --description "REST API client" -o client.py
|
|
1094
|
+
cdc generate code --file spec.md -o implementation.go
|
|
1095
|
+
cdc generate code --pdf requirements.pdf -o app.js
|
|
1096
|
+
cdc generate code --url https://example.com/spec -o service.py
|
|
1097
|
+
"""
|
|
1098
|
+
console = ctx.obj['console']
|
|
1099
|
+
from claude_dev_cli.input_sources import get_input_content
|
|
1100
|
+
from pathlib import Path
|
|
1101
|
+
|
|
1102
|
+
try:
|
|
1103
|
+
# Get specification content
|
|
1104
|
+
spec_content, source_desc = get_input_content(
|
|
1105
|
+
description=description,
|
|
1106
|
+
file_path=spec_file,
|
|
1107
|
+
pdf_path=pdf,
|
|
1108
|
+
url=url,
|
|
1109
|
+
console=console
|
|
1110
|
+
)
|
|
1111
|
+
|
|
1112
|
+
# Detect language from output extension if not specified
|
|
1113
|
+
if not language:
|
|
1114
|
+
ext = Path(output).suffix.lstrip('.')
|
|
1115
|
+
language_map = {
|
|
1116
|
+
'py': 'Python',
|
|
1117
|
+
'js': 'JavaScript',
|
|
1118
|
+
'ts': 'TypeScript',
|
|
1119
|
+
'go': 'Go',
|
|
1120
|
+
'rs': 'Rust',
|
|
1121
|
+
'java': 'Java',
|
|
1122
|
+
'cpp': 'C++',
|
|
1123
|
+
'c': 'C',
|
|
1124
|
+
'cs': 'C#',
|
|
1125
|
+
'rb': 'Ruby',
|
|
1126
|
+
'php': 'PHP',
|
|
1127
|
+
'swift': 'Swift',
|
|
1128
|
+
'kt': 'Kotlin',
|
|
1129
|
+
}
|
|
1130
|
+
language = language_map.get(ext, ext.upper() if ext else None)
|
|
1131
|
+
|
|
1132
|
+
console.print(f"[cyan]Generating code from:[/cyan] {source_desc}")
|
|
1133
|
+
if language:
|
|
1134
|
+
console.print(f"[cyan]Target language:[/cyan] {language}")
|
|
1135
|
+
console.print(f"[cyan]Output:[/cyan] {output}\n")
|
|
1136
|
+
|
|
1137
|
+
# Build prompt
|
|
1138
|
+
prompt = f"Specification:\n\n{spec_content}\n\n"
|
|
1139
|
+
if language:
|
|
1140
|
+
prompt += f"Generate complete, production-ready {language} code that implements this specification. "
|
|
1141
|
+
else:
|
|
1142
|
+
prompt += "Generate complete, production-ready code that implements this specification. "
|
|
1143
|
+
prompt += "Include proper error handling, documentation, and best practices."
|
|
1144
|
+
|
|
1145
|
+
# Add context if requested
|
|
1146
|
+
if auto_context:
|
|
1147
|
+
from claude_dev_cli.context import ContextGatherer
|
|
1148
|
+
|
|
1149
|
+
with console.status("[bold blue]Gathering project context..."):
|
|
1150
|
+
gatherer = ContextGatherer()
|
|
1151
|
+
# Gather context from current directory
|
|
1152
|
+
context = gatherer.gather_for_file(".", include_git=True)
|
|
1153
|
+
context_info = context.format_for_prompt()
|
|
1154
|
+
|
|
1155
|
+
console.print("[dim]✓ Context gathered[/dim]")
|
|
1156
|
+
prompt = f"{context_info}\n\n{prompt}"
|
|
1157
|
+
|
|
1158
|
+
# Generate code
|
|
1159
|
+
with console.status(f"[bold blue]Generating code..."):
|
|
1160
|
+
client = ClaudeClient(api_config_name=api, model=model)
|
|
1161
|
+
result = client.call(prompt)
|
|
1162
|
+
|
|
1163
|
+
# Interactive refinement
|
|
1164
|
+
if interactive:
|
|
1165
|
+
console.print("\n[bold]Initial Code:[/bold]\n")
|
|
1166
|
+
console.print(result)
|
|
1167
|
+
|
|
1168
|
+
client = ClaudeClient(api_config_name=api, model=model)
|
|
1169
|
+
conversation_context = [result]
|
|
1170
|
+
|
|
1171
|
+
while True:
|
|
1172
|
+
console.print("\n[dim]Commands: 'save' to save and exit, 'exit' to discard, or ask for changes[/dim]")
|
|
1173
|
+
user_input = console.input("[cyan]You:[/cyan] ").strip()
|
|
1174
|
+
|
|
1175
|
+
if user_input.lower() == 'exit':
|
|
1176
|
+
console.print("[yellow]Discarded changes[/yellow]")
|
|
1177
|
+
return
|
|
1178
|
+
|
|
1179
|
+
if user_input.lower() == 'save':
|
|
1180
|
+
result = conversation_context[-1]
|
|
1181
|
+
break
|
|
1182
|
+
|
|
1183
|
+
if not user_input:
|
|
1184
|
+
continue
|
|
1185
|
+
|
|
1186
|
+
# Get refinement
|
|
1187
|
+
refinement_prompt = f"Previous code:\n\n{conversation_context[-1]}\n\nUser request: {user_input}\n\nProvide the updated code."
|
|
1188
|
+
|
|
1189
|
+
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
1190
|
+
response_parts = []
|
|
1191
|
+
for chunk in client.call_streaming(refinement_prompt):
|
|
1192
|
+
console.print(chunk, end='')
|
|
1193
|
+
response_parts.append(chunk)
|
|
1194
|
+
console.print()
|
|
1195
|
+
|
|
1196
|
+
result = ''.join(response_parts)
|
|
1197
|
+
conversation_context.append(result)
|
|
1198
|
+
|
|
1199
|
+
# Save output
|
|
1200
|
+
Path(output).write_text(result)
|
|
1201
|
+
console.print(f"\n[green]✓[/green] Code saved to: {output}")
|
|
1202
|
+
|
|
1203
|
+
except Exception as e:
|
|
1204
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1205
|
+
sys.exit(1)
|
|
1206
|
+
|
|
1207
|
+
|
|
1208
|
+
@generate.command('feature')
|
|
1209
|
+
@click.argument('paths', nargs=-1, type=click.Path(exists=True))
|
|
1210
|
+
@click.option('--description', help='Inline feature specification')
|
|
1211
|
+
@click.option('-f', '--file', 'spec_file', type=click.Path(exists=True), help='Read specification from file')
|
|
1212
|
+
@click.option('--pdf', type=click.Path(exists=True), help='Read specification from PDF')
|
|
1213
|
+
@click.option('--url', help='Fetch specification from URL')
|
|
1214
|
+
@click.option('--max-files', type=int, default=10, help='Maximum files to modify (default: 10)')
|
|
1215
|
+
@click.option('-m', '--model', help='Model profile to use')
|
|
1216
|
+
@click.option('-a', '--api', help='API config to use')
|
|
1217
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
|
|
1218
|
+
@click.option('--auto-context', is_flag=True, help='Include project context')
|
|
1219
|
+
@click.option('--preview', is_flag=True, help='Preview changes without applying')
|
|
1220
|
+
@click.pass_context
|
|
1221
|
+
def gen_feature(
|
|
1222
|
+
ctx: click.Context,
|
|
1223
|
+
paths: tuple,
|
|
1224
|
+
description: Optional[str],
|
|
1225
|
+
spec_file: Optional[str],
|
|
1226
|
+
pdf: Optional[str],
|
|
1227
|
+
url: Optional[str],
|
|
1228
|
+
max_files: int,
|
|
1229
|
+
model: Optional[str],
|
|
1230
|
+
api: Optional[str],
|
|
1231
|
+
interactive: bool,
|
|
1232
|
+
auto_context: bool,
|
|
1233
|
+
preview: bool
|
|
1234
|
+
) -> None:
|
|
1235
|
+
"""Generate code to add a feature to existing project.
|
|
1236
|
+
|
|
1237
|
+
Analyzes existing code and generates changes to implement a feature.
|
|
1238
|
+
|
|
1239
|
+
Examples:
|
|
1240
|
+
cdc generate feature --description "Add authentication" src/
|
|
1241
|
+
cdc generate feature --file feature-spec.md
|
|
1242
|
+
cdc generate feature --pdf requirements.pdf --preview
|
|
1243
|
+
cdc generate feature --url https://example.com/spec src/
|
|
1244
|
+
"""
|
|
1245
|
+
console = ctx.obj['console']
|
|
1246
|
+
from claude_dev_cli.input_sources import get_input_content
|
|
1247
|
+
from claude_dev_cli.path_utils import expand_paths, auto_detect_files
|
|
1248
|
+
|
|
1249
|
+
try:
|
|
1250
|
+
# Get feature specification
|
|
1251
|
+
spec_content, source_desc = get_input_content(
|
|
1252
|
+
description=description,
|
|
1253
|
+
file_path=spec_file,
|
|
1254
|
+
pdf_path=pdf,
|
|
1255
|
+
url=url,
|
|
1256
|
+
console=console
|
|
1257
|
+
)
|
|
1258
|
+
|
|
1259
|
+
# Determine files to analyze
|
|
1260
|
+
if paths:
|
|
1261
|
+
files = expand_paths(list(paths), max_files=max_files)
|
|
1262
|
+
else:
|
|
1263
|
+
files = auto_detect_files()
|
|
1264
|
+
if files:
|
|
1265
|
+
console.print(f"[dim]Auto-detected {len(files)} file(s) from project[/dim]")
|
|
1266
|
+
|
|
1267
|
+
if not files:
|
|
1268
|
+
console.print("[yellow]No files found. Specify paths or run in a project directory.[/yellow]")
|
|
1269
|
+
return
|
|
1270
|
+
|
|
1271
|
+
console.print(f"[cyan]Feature specification from:[/cyan] {source_desc}")
|
|
1272
|
+
console.print(f"[cyan]Analyzing:[/cyan] {len(files)} file(s)\n")
|
|
1273
|
+
|
|
1274
|
+
# Show files
|
|
1275
|
+
if len(files) > 1:
|
|
1276
|
+
console.print(f"[bold]Files to analyze:[/bold]")
|
|
1277
|
+
for f in files[:5]:
|
|
1278
|
+
console.print(f" • {f}")
|
|
1279
|
+
if len(files) > 5:
|
|
1280
|
+
console.print(f" ... and {len(files) - 5} more")
|
|
1281
|
+
console.print()
|
|
1282
|
+
|
|
1283
|
+
# Build codebase content
|
|
1284
|
+
codebase_content = ""
|
|
1285
|
+
for file_path in files:
|
|
1286
|
+
try:
|
|
1287
|
+
with open(file_path, 'r') as f:
|
|
1288
|
+
content = f.read()
|
|
1289
|
+
codebase_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
|
+
|
|
1293
|
+
# Build prompt
|
|
1294
|
+
prompt = f"Feature Specification:\n\n{spec_content}\n\n"
|
|
1295
|
+
prompt += f"Existing Codebase:{codebase_content}\n\n"
|
|
1296
|
+
prompt += "Analyze the existing code and provide:\n"
|
|
1297
|
+
prompt += "1. Implementation plan for the feature\n"
|
|
1298
|
+
prompt += "2. List of files to modify or create\n"
|
|
1299
|
+
prompt += "3. Complete code changes (diffs or new files)\n"
|
|
1300
|
+
prompt += "4. Any necessary setup or configuration changes\n\n"
|
|
1301
|
+
prompt += "Be specific and provide complete, working code."
|
|
1302
|
+
|
|
1303
|
+
# Add context if requested
|
|
1304
|
+
if auto_context:
|
|
1305
|
+
from claude_dev_cli.context import ContextGatherer
|
|
1306
|
+
|
|
1307
|
+
with console.status("[bold blue]Gathering project context..."):
|
|
1308
|
+
gatherer = ContextGatherer()
|
|
1309
|
+
context = gatherer.gather_for_file(files[0], include_git=True)
|
|
1310
|
+
context_info = context.format_for_prompt()
|
|
1311
|
+
|
|
1312
|
+
console.print("[dim]✓ Context gathered[/dim]")
|
|
1313
|
+
prompt = f"{context_info}\n\n{prompt}"
|
|
1314
|
+
|
|
1315
|
+
# Generate feature implementation
|
|
1316
|
+
with console.status(f"[bold blue]Analyzing codebase and generating feature implementation..."):
|
|
1317
|
+
client = ClaudeClient(api_config_name=api, model=model)
|
|
1318
|
+
result = client.call(prompt)
|
|
1319
|
+
|
|
1320
|
+
# Show result
|
|
1321
|
+
from rich.markdown import Markdown
|
|
1322
|
+
md = Markdown(result)
|
|
1323
|
+
console.print(md)
|
|
1324
|
+
|
|
1325
|
+
if preview:
|
|
1326
|
+
console.print("\n[yellow]Preview mode - no changes applied[/yellow]")
|
|
1327
|
+
console.print("[dim]Remove --preview flag to apply changes[/dim]")
|
|
1328
|
+
return
|
|
1329
|
+
|
|
1330
|
+
# Interactive refinement
|
|
1331
|
+
if interactive:
|
|
1332
|
+
client = ClaudeClient(api_config_name=api, model=model)
|
|
1333
|
+
conversation_context = [result]
|
|
1334
|
+
|
|
1335
|
+
while True:
|
|
1336
|
+
console.print("\n[dim]Ask for changes, 'apply' to confirm, or 'exit' to cancel[/dim]")
|
|
1337
|
+
user_input = console.input("[cyan]You:[/cyan] ").strip()
|
|
1338
|
+
|
|
1339
|
+
if user_input.lower() == 'exit':
|
|
1340
|
+
console.print("[yellow]Cancelled[/yellow]")
|
|
1341
|
+
return
|
|
1342
|
+
|
|
1343
|
+
if user_input.lower() == 'apply':
|
|
1344
|
+
console.print("[green]✓[/green] Implementation plan ready")
|
|
1345
|
+
console.print("[dim]Apply the changes manually from the output above[/dim]")
|
|
1346
|
+
return
|
|
1347
|
+
|
|
1348
|
+
if not user_input:
|
|
1349
|
+
continue
|
|
1350
|
+
|
|
1351
|
+
# Get refinement
|
|
1352
|
+
refinement_prompt = f"Previous implementation plan:\n\n{conversation_context[-1]}\n\nUser request: {user_input}\n\nProvide the updated implementation."
|
|
1353
|
+
|
|
1354
|
+
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
1355
|
+
response_parts = []
|
|
1356
|
+
for chunk in client.call_streaming(refinement_prompt):
|
|
1357
|
+
console.print(chunk, end='')
|
|
1358
|
+
response_parts.append(chunk)
|
|
1359
|
+
console.print()
|
|
1360
|
+
|
|
1361
|
+
result = ''.join(response_parts)
|
|
1362
|
+
conversation_context.append(result)
|
|
1363
|
+
else:
|
|
1364
|
+
console.print("\n[green]✓[/green] Feature implementation generated")
|
|
1365
|
+
console.print("[dim]Apply the changes manually from the output above[/dim]")
|
|
1366
|
+
|
|
1367
|
+
except Exception as e:
|
|
1368
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1369
|
+
sys.exit(1)
|
|
1370
|
+
|
|
1371
|
+
|
|
979
1372
|
@main.command('review')
|
|
980
|
-
@click.argument('
|
|
1373
|
+
@click.argument('paths', nargs=-1, type=click.Path(exists=True))
|
|
981
1374
|
@click.option('-a', '--api', help='API config to use')
|
|
982
1375
|
@click.option('-i', '--interactive', is_flag=True, help='Interactive follow-up questions')
|
|
983
1376
|
@click.option('--auto-context', is_flag=True, help='Automatically include git, dependencies, and related files')
|
|
1377
|
+
@click.option('--max-files', type=int, default=20, help='Maximum files to review (default: 20)')
|
|
984
1378
|
@click.pass_context
|
|
985
1379
|
def review(
|
|
986
1380
|
ctx: click.Context,
|
|
987
|
-
|
|
1381
|
+
paths: tuple,
|
|
988
1382
|
api: Optional[str],
|
|
989
1383
|
interactive: bool,
|
|
990
|
-
auto_context: bool
|
|
1384
|
+
auto_context: bool,
|
|
1385
|
+
max_files: int
|
|
991
1386
|
) -> None:
|
|
992
|
-
"""Review code for bugs and improvements.
|
|
1387
|
+
"""Review code for bugs and improvements.
|
|
1388
|
+
|
|
1389
|
+
Can review multiple files, directories, or auto-detect git changes:
|
|
1390
|
+
|
|
1391
|
+
cdc review file1.py file2.py # Multiple files
|
|
1392
|
+
cdc review src/ # Directory
|
|
1393
|
+
cdc review # Auto-detect git changes
|
|
1394
|
+
"""
|
|
993
1395
|
console = ctx.obj['console']
|
|
1396
|
+
from claude_dev_cli.path_utils import expand_paths, auto_detect_files
|
|
994
1397
|
|
|
995
1398
|
try:
|
|
1399
|
+
# Determine files to review
|
|
1400
|
+
if paths:
|
|
1401
|
+
# Expand paths (handles directories, multiple files)
|
|
1402
|
+
files = expand_paths(list(paths), max_files=max_files)
|
|
1403
|
+
else:
|
|
1404
|
+
# Auto-detect files from git
|
|
1405
|
+
files = auto_detect_files()
|
|
1406
|
+
if files:
|
|
1407
|
+
console.print(f"[dim]Auto-detected {len(files)} file(s) from git changes[/dim]")
|
|
1408
|
+
|
|
1409
|
+
if not files:
|
|
1410
|
+
console.print("[yellow]No files to review. Specify files or make some changes.[/yellow]")
|
|
1411
|
+
return
|
|
1412
|
+
|
|
1413
|
+
# Show files being reviewed
|
|
1414
|
+
if len(files) > 1:
|
|
1415
|
+
console.print(f"\n[bold]Reviewing {len(files)} file(s):[/bold]")
|
|
1416
|
+
for f in files[:10]: # Show first 10
|
|
1417
|
+
console.print(f" • {f}")
|
|
1418
|
+
if len(files) > 10:
|
|
1419
|
+
console.print(f" ... and {len(files) - 10} more")
|
|
1420
|
+
console.print()
|
|
1421
|
+
|
|
1422
|
+
# Build combined prompt for multiple files
|
|
1423
|
+
files_content = ""
|
|
1424
|
+
for file_path in files:
|
|
1425
|
+
try:
|
|
1426
|
+
with open(file_path, 'r') as f:
|
|
1427
|
+
content = f.read()
|
|
1428
|
+
files_content += f"\n\n## File: {file_path}\n\n```\n{content}\n```\n"
|
|
1429
|
+
except Exception as e:
|
|
1430
|
+
console.print(f"[yellow]Warning: Could not read {file_path}: {e}[/yellow]")
|
|
1431
|
+
|
|
996
1432
|
# Gather context if requested
|
|
997
1433
|
context_info = ""
|
|
998
1434
|
if auto_context:
|
|
@@ -1000,31 +1436,24 @@ def review(
|
|
|
1000
1436
|
|
|
1001
1437
|
with console.status("[bold blue]Gathering context..."):
|
|
1002
1438
|
gatherer = ContextGatherer()
|
|
1003
|
-
context
|
|
1439
|
+
# Use first file for context gathering
|
|
1440
|
+
context = gatherer.gather_for_review(files[0])
|
|
1004
1441
|
context_info = context.format_for_prompt()
|
|
1005
1442
|
|
|
1006
1443
|
console.print("[dim]✓ Context gathered (git, dependencies, tests)[/dim]")
|
|
1007
1444
|
|
|
1008
|
-
with console.status("[bold blue]Reviewing
|
|
1009
|
-
|
|
1445
|
+
with console.status(f"[bold blue]Reviewing {len(files)} file(s)..."):
|
|
1446
|
+
client = ClaudeClient(api_config_name=api)
|
|
1010
1447
|
if context_info:
|
|
1011
|
-
|
|
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)
|
|
1448
|
+
prompt = f"{context_info}\n\nFiles to review:{files_content}\n\nPlease review this code for bugs and improvements."
|
|
1017
1449
|
else:
|
|
1018
|
-
|
|
1450
|
+
prompt = f"Files to review:{files_content}\n\nPlease review this code for bugs, security issues, and improvements."
|
|
1451
|
+
result = client.call(prompt)
|
|
1019
1452
|
|
|
1020
1453
|
md = Markdown(result)
|
|
1021
1454
|
console.print(md)
|
|
1022
1455
|
|
|
1023
1456
|
if interactive:
|
|
1024
|
-
client = ClaudeClient(api_config_name=api)
|
|
1025
|
-
with open(file_path, 'r') as f:
|
|
1026
|
-
file_content = f.read()
|
|
1027
|
-
|
|
1028
1457
|
console.print("\n[dim]Ask follow-up questions about the review, or 'exit' to quit[/dim]")
|
|
1029
1458
|
|
|
1030
1459
|
while True:
|
|
@@ -1036,7 +1465,7 @@ def review(
|
|
|
1036
1465
|
if not user_input:
|
|
1037
1466
|
continue
|
|
1038
1467
|
|
|
1039
|
-
follow_up_prompt = f"Code review:\n\n{result}\n\
|
|
1468
|
+
follow_up_prompt = f"Code review:\n\n{result}\n\nFiles:{files_content}\n\nUser question: {user_input}"
|
|
1040
1469
|
|
|
1041
1470
|
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
1042
1471
|
for chunk in client.call_streaming(follow_up_prompt):
|
|
@@ -1106,49 +1535,95 @@ def debug(
|
|
|
1106
1535
|
|
|
1107
1536
|
|
|
1108
1537
|
@main.command('refactor')
|
|
1109
|
-
@click.argument('
|
|
1110
|
-
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
1538
|
+
@click.argument('paths', nargs=-1, type=click.Path(exists=True))
|
|
1539
|
+
@click.option('-o', '--output', type=click.Path(), help='Output file path (single file only)')
|
|
1111
1540
|
@click.option('-a', '--api', help='API config to use')
|
|
1112
1541
|
@click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
|
|
1113
1542
|
@click.option('--auto-context', is_flag=True, help='Automatically include git, dependencies, and related files')
|
|
1543
|
+
@click.option('--max-files', type=int, default=20, help='Maximum files to refactor (default: 20)')
|
|
1114
1544
|
@click.pass_context
|
|
1115
1545
|
def refactor(
|
|
1116
1546
|
ctx: click.Context,
|
|
1117
|
-
|
|
1547
|
+
paths: tuple,
|
|
1118
1548
|
output: Optional[str],
|
|
1119
1549
|
api: Optional[str],
|
|
1120
1550
|
interactive: bool,
|
|
1121
|
-
auto_context: bool
|
|
1551
|
+
auto_context: bool,
|
|
1552
|
+
max_files: int
|
|
1122
1553
|
) -> None:
|
|
1123
|
-
"""Suggest refactoring improvements.
|
|
1554
|
+
"""Suggest refactoring improvements.
|
|
1555
|
+
|
|
1556
|
+
Can refactor multiple files, directories, or auto-detect git changes:
|
|
1557
|
+
|
|
1558
|
+
cdc refactor file1.py file2.py # Multiple files
|
|
1559
|
+
cdc refactor src/ # Directory
|
|
1560
|
+
cdc refactor # Auto-detect git changes
|
|
1561
|
+
"""
|
|
1124
1562
|
console = ctx.obj['console']
|
|
1563
|
+
from claude_dev_cli.path_utils import expand_paths, auto_detect_files
|
|
1125
1564
|
|
|
1126
1565
|
try:
|
|
1566
|
+
# Determine files to refactor
|
|
1567
|
+
if paths:
|
|
1568
|
+
files = expand_paths(list(paths), max_files=max_files)
|
|
1569
|
+
else:
|
|
1570
|
+
files = auto_detect_files()
|
|
1571
|
+
if files:
|
|
1572
|
+
console.print(f"[dim]Auto-detected {len(files)} file(s) from git changes[/dim]")
|
|
1573
|
+
|
|
1574
|
+
if not files:
|
|
1575
|
+
console.print("[yellow]No files to refactor. Specify files or make some changes.[/yellow]")
|
|
1576
|
+
return
|
|
1577
|
+
|
|
1578
|
+
# Output only works with single file
|
|
1579
|
+
if output and len(files) > 1:
|
|
1580
|
+
console.print("[yellow]Warning: --output only works with single file. Ignoring output option.[/yellow]")
|
|
1581
|
+
output = None
|
|
1582
|
+
|
|
1583
|
+
# Show files being refactored
|
|
1584
|
+
if len(files) > 1:
|
|
1585
|
+
console.print(f"\n[bold]Refactoring {len(files)} file(s):[/bold]")
|
|
1586
|
+
for f in files[:10]:
|
|
1587
|
+
console.print(f" • {f}")
|
|
1588
|
+
if len(files) > 10:
|
|
1589
|
+
console.print(f" ... and {len(files) - 10} more")
|
|
1590
|
+
console.print()
|
|
1591
|
+
|
|
1592
|
+
# Build combined prompt
|
|
1593
|
+
files_content = ""
|
|
1594
|
+
for file_path in files:
|
|
1595
|
+
try:
|
|
1596
|
+
with open(file_path, 'r') as f:
|
|
1597
|
+
content = f.read()
|
|
1598
|
+
files_content += f"\n\n## File: {file_path}\n\n```\n{content}\n```\n"
|
|
1599
|
+
except Exception as e:
|
|
1600
|
+
console.print(f"[yellow]Warning: Could not read {file_path}: {e}[/yellow]")
|
|
1601
|
+
|
|
1127
1602
|
# Gather context if requested
|
|
1128
1603
|
if auto_context:
|
|
1129
1604
|
from claude_dev_cli.context import ContextGatherer
|
|
1130
1605
|
|
|
1131
1606
|
with console.status("[bold blue]Gathering context..."):
|
|
1132
1607
|
gatherer = ContextGatherer()
|
|
1133
|
-
context = gatherer.gather_for_file(
|
|
1608
|
+
context = gatherer.gather_for_file(files[0])
|
|
1134
1609
|
context_info = context.format_for_prompt()
|
|
1135
1610
|
|
|
1136
1611
|
console.print("[dim]✓ Context gathered[/dim]")
|
|
1137
1612
|
|
|
1138
|
-
# Use context-aware refactoring
|
|
1139
1613
|
client = ClaudeClient(api_config_name=api)
|
|
1140
|
-
|
|
1141
|
-
result = client.call(enhanced_prompt)
|
|
1614
|
+
prompt = f"{context_info}\n\nFiles:{files_content}\n\nPlease suggest refactoring improvements."
|
|
1142
1615
|
else:
|
|
1143
|
-
with console.status("[bold blue]Analyzing
|
|
1144
|
-
|
|
1616
|
+
with console.status(f"[bold blue]Analyzing {len(files)} file(s)..."):
|
|
1617
|
+
client = ClaudeClient(api_config_name=api)
|
|
1618
|
+
prompt = f"Files to refactor:{files_content}\n\nPlease suggest refactoring improvements focusing on code quality, maintainability, and performance."
|
|
1619
|
+
|
|
1620
|
+
result = client.call(prompt)
|
|
1145
1621
|
|
|
1146
1622
|
if interactive:
|
|
1147
1623
|
console.print("\n[bold]Initial Refactoring:[/bold]\n")
|
|
1148
1624
|
md = Markdown(result)
|
|
1149
1625
|
console.print(md)
|
|
1150
1626
|
|
|
1151
|
-
client = ClaudeClient(api_config_name=api)
|
|
1152
1627
|
conversation_context = [result]
|
|
1153
1628
|
|
|
1154
1629
|
while True:
|
|
@@ -1233,6 +1708,95 @@ def git_commit(ctx: click.Context, api: Optional[str], auto_context: bool) -> No
|
|
|
1233
1708
|
sys.exit(1)
|
|
1234
1709
|
|
|
1235
1710
|
|
|
1711
|
+
@git.command('review')
|
|
1712
|
+
@click.option('-a', '--api', help='API config to use')
|
|
1713
|
+
@click.option('--staged', is_flag=True, help='Review only staged changes')
|
|
1714
|
+
@click.option('--branch', help='Review changes in branch (e.g., main..HEAD)')
|
|
1715
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive follow-up questions')
|
|
1716
|
+
@click.pass_context
|
|
1717
|
+
def git_review(
|
|
1718
|
+
ctx: click.Context,
|
|
1719
|
+
api: Optional[str],
|
|
1720
|
+
staged: bool,
|
|
1721
|
+
branch: Optional[str],
|
|
1722
|
+
interactive: bool
|
|
1723
|
+
) -> None:
|
|
1724
|
+
"""Review git changes.
|
|
1725
|
+
|
|
1726
|
+
Examples:
|
|
1727
|
+
cdc git review --staged # Review staged changes
|
|
1728
|
+
cdc git review # Review all changes
|
|
1729
|
+
cdc git review --branch main..HEAD # Review branch changes
|
|
1730
|
+
"""
|
|
1731
|
+
console = ctx.obj['console']
|
|
1732
|
+
from claude_dev_cli.path_utils import get_git_changes
|
|
1733
|
+
|
|
1734
|
+
try:
|
|
1735
|
+
# Get changed files
|
|
1736
|
+
if branch:
|
|
1737
|
+
files = get_git_changes(commit_range=branch)
|
|
1738
|
+
scope = f"branch {branch}"
|
|
1739
|
+
elif staged:
|
|
1740
|
+
files = get_git_changes(staged_only=True)
|
|
1741
|
+
scope = "staged changes"
|
|
1742
|
+
else:
|
|
1743
|
+
files = get_git_changes(staged_only=False)
|
|
1744
|
+
scope = "all changes"
|
|
1745
|
+
|
|
1746
|
+
if not files:
|
|
1747
|
+
console.print(f"[yellow]No changes found in {scope}.[/yellow]")
|
|
1748
|
+
return
|
|
1749
|
+
|
|
1750
|
+
console.print(f"\n[bold]Reviewing {len(files)} changed file(s) from {scope}:[/bold]")
|
|
1751
|
+
for f in files[:10]:
|
|
1752
|
+
console.print(f" • {f}")
|
|
1753
|
+
if len(files) > 10:
|
|
1754
|
+
console.print(f" ... and {len(files) - 10} more")
|
|
1755
|
+
console.print()
|
|
1756
|
+
|
|
1757
|
+
# Build files content
|
|
1758
|
+
files_content = ""
|
|
1759
|
+
for file_path in files:
|
|
1760
|
+
try:
|
|
1761
|
+
with open(file_path, 'r') as f:
|
|
1762
|
+
content = f.read()
|
|
1763
|
+
files_content += f"\n\n## File: {file_path}\n\n```\n{content}\n```\n"
|
|
1764
|
+
except Exception as e:
|
|
1765
|
+
console.print(f"[yellow]Warning: Could not read {file_path}: {e}[/yellow]")
|
|
1766
|
+
|
|
1767
|
+
# Review
|
|
1768
|
+
with console.status(f"[bold blue]Reviewing {len(files)} file(s)..."):
|
|
1769
|
+
client = ClaudeClient(api_config_name=api)
|
|
1770
|
+
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."
|
|
1771
|
+
result = client.call(prompt)
|
|
1772
|
+
|
|
1773
|
+
md = Markdown(result)
|
|
1774
|
+
console.print(md)
|
|
1775
|
+
|
|
1776
|
+
if interactive:
|
|
1777
|
+
console.print("\n[dim]Ask follow-up questions about the review, or 'exit' to quit[/dim]")
|
|
1778
|
+
|
|
1779
|
+
while True:
|
|
1780
|
+
user_input = console.input("\n[cyan]You:[/cyan] ").strip()
|
|
1781
|
+
|
|
1782
|
+
if user_input.lower() == 'exit':
|
|
1783
|
+
break
|
|
1784
|
+
|
|
1785
|
+
if not user_input:
|
|
1786
|
+
continue
|
|
1787
|
+
|
|
1788
|
+
follow_up_prompt = f"Code review:\n\n{result}\n\nChanged files:{files_content}\n\nUser question: {user_input}"
|
|
1789
|
+
|
|
1790
|
+
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
1791
|
+
for chunk in client.call_streaming(follow_up_prompt):
|
|
1792
|
+
console.print(chunk, end='')
|
|
1793
|
+
console.print()
|
|
1794
|
+
|
|
1795
|
+
except Exception as e:
|
|
1796
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1797
|
+
sys.exit(1)
|
|
1798
|
+
|
|
1799
|
+
|
|
1236
1800
|
@main.group()
|
|
1237
1801
|
def context() -> None:
|
|
1238
1802
|
"""Context gathering tools and information."""
|