pdd-cli 0.0.28__py3-none-any.whl → 0.0.29__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 pdd-cli might be problematic. Click here for more details.
- pdd/__init__.py +1 -1
- pdd/cli.py +4 -1
- pdd/cli_2_0_1_0_20250510_005101.py +1054 -0
- pdd/cli_3_0_1_0_20250510_005101.py +1054 -0
- pdd/cli_4_0_1_0_20250510_005101.py +1054 -0
- {pdd_cli-0.0.28.dist-info → pdd_cli-0.0.29.dist-info}/METADATA +3 -3
- {pdd_cli-0.0.28.dist-info → pdd_cli-0.0.29.dist-info}/RECORD +11 -8
- {pdd_cli-0.0.28.dist-info → pdd_cli-0.0.29.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.28.dist-info → pdd_cli-0.0.29.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.28.dist-info → pdd_cli-0.0.29.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.28.dist-info → pdd_cli-0.0.29.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1054 @@
|
|
|
1
|
+
# pdd/cli.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
6
|
+
from pathlib import Path # Import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.theme import Theme
|
|
11
|
+
from rich.markup import MarkupError, escape
|
|
12
|
+
|
|
13
|
+
# --- Relative Imports for Internal Modules ---
|
|
14
|
+
from . import DEFAULT_STRENGTH, __version__
|
|
15
|
+
from .auto_deps_main import auto_deps_main
|
|
16
|
+
from .auto_update import auto_update
|
|
17
|
+
from .bug_main import bug_main
|
|
18
|
+
from .change_main import change_main
|
|
19
|
+
from .cmd_test_main import cmd_test_main
|
|
20
|
+
from .code_generator_main import code_generator_main
|
|
21
|
+
from .conflicts_main import conflicts_main
|
|
22
|
+
# Need to import construct_paths for tests patching pdd.cli.construct_paths
|
|
23
|
+
from .construct_paths import construct_paths
|
|
24
|
+
from .context_generator_main import context_generator_main
|
|
25
|
+
from .crash_main import crash_main
|
|
26
|
+
from .detect_change_main import detect_change_main
|
|
27
|
+
from .fix_main import fix_main
|
|
28
|
+
from .fix_verification_main import fix_verification_main
|
|
29
|
+
from .install_completion import install_completion
|
|
30
|
+
from .preprocess_main import preprocess_main
|
|
31
|
+
from .split_main import split_main
|
|
32
|
+
from .trace_main import trace_main
|
|
33
|
+
from .track_cost import track_cost
|
|
34
|
+
from .update_main import update_main
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# --- Initialize Rich Console ---
|
|
38
|
+
# Define a custom theme for consistent styling
|
|
39
|
+
custom_theme = Theme({
|
|
40
|
+
"info": "cyan",
|
|
41
|
+
"warning": "yellow",
|
|
42
|
+
"error": "bold red",
|
|
43
|
+
"success": "green",
|
|
44
|
+
"path": "dim blue",
|
|
45
|
+
"command": "bold magenta",
|
|
46
|
+
})
|
|
47
|
+
console = Console(theme=custom_theme)
|
|
48
|
+
|
|
49
|
+
# --- Helper Function for Error Handling ---
|
|
50
|
+
def handle_error(e: Exception, command_name: str, quiet: bool):
|
|
51
|
+
"""Prints error messages using Rich console.""" # Modified docstring
|
|
52
|
+
if not quiet:
|
|
53
|
+
console.print(f"[error]Error during '{command_name}' command:[/error]", style="error")
|
|
54
|
+
if isinstance(e, FileNotFoundError):
|
|
55
|
+
console.print(f" [error]File not found:[/error] {e}", style="error")
|
|
56
|
+
elif isinstance(e, (ValueError, IOError)):
|
|
57
|
+
console.print(f" [error]Input/Output Error:[/error] {e}", style="error")
|
|
58
|
+
elif isinstance(e, click.UsageError): # Handle Click usage errors explicitly if needed
|
|
59
|
+
console.print(f" [error]Usage Error:[/error] {e}", style="error")
|
|
60
|
+
# click.UsageError should typically exit with 2, but we are handling it.
|
|
61
|
+
elif isinstance(e, MarkupError):
|
|
62
|
+
console.print(" [error]Markup Error:[/error] Invalid Rich markup encountered.", style="error")
|
|
63
|
+
# Print the error message safely escaped
|
|
64
|
+
console.print(escape(str(e)))
|
|
65
|
+
else:
|
|
66
|
+
console.print(f" [error]An unexpected error occurred:[/error] {e}", style="error")
|
|
67
|
+
# Do NOT re-raise e here. Let the command function return None.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# --- Main CLI Group ---
|
|
71
|
+
@click.group(chain=True, help="PDD (Prompt-Driven Development) Command Line Interface.")
|
|
72
|
+
@click.option(
|
|
73
|
+
"--force",
|
|
74
|
+
is_flag=True,
|
|
75
|
+
default=False,
|
|
76
|
+
help="Overwrite existing files without asking for confirmation.",
|
|
77
|
+
)
|
|
78
|
+
@click.option(
|
|
79
|
+
"--strength",
|
|
80
|
+
type=click.FloatRange(0.0, 1.0),
|
|
81
|
+
default=DEFAULT_STRENGTH,
|
|
82
|
+
show_default=True,
|
|
83
|
+
help="Set the strength of the AI model (0.0 to 1.0).",
|
|
84
|
+
)
|
|
85
|
+
@click.option(
|
|
86
|
+
"--temperature",
|
|
87
|
+
type=click.FloatRange(0.0, 2.0), # Allow higher temperatures if needed
|
|
88
|
+
default=0.0,
|
|
89
|
+
show_default=True,
|
|
90
|
+
help="Set the temperature of the AI model.",
|
|
91
|
+
)
|
|
92
|
+
@click.option(
|
|
93
|
+
"--verbose",
|
|
94
|
+
is_flag=True,
|
|
95
|
+
default=False,
|
|
96
|
+
help="Increase output verbosity for more detailed information.",
|
|
97
|
+
)
|
|
98
|
+
@click.option(
|
|
99
|
+
"--quiet",
|
|
100
|
+
is_flag=True,
|
|
101
|
+
default=False,
|
|
102
|
+
help="Decrease output verbosity for minimal information.",
|
|
103
|
+
)
|
|
104
|
+
@click.option(
|
|
105
|
+
"--output-cost",
|
|
106
|
+
type=click.Path(dir_okay=False, writable=True),
|
|
107
|
+
default=None,
|
|
108
|
+
help="Enable cost tracking and output a CSV file with usage details.",
|
|
109
|
+
)
|
|
110
|
+
@click.option(
|
|
111
|
+
"--review-examples",
|
|
112
|
+
is_flag=True,
|
|
113
|
+
default=False,
|
|
114
|
+
help="Review and optionally exclude few-shot examples before command execution.",
|
|
115
|
+
)
|
|
116
|
+
@click.option(
|
|
117
|
+
"--local",
|
|
118
|
+
is_flag=True,
|
|
119
|
+
default=False,
|
|
120
|
+
help="Run commands locally instead of in the cloud.",
|
|
121
|
+
)
|
|
122
|
+
@click.version_option(version=__version__, package_name="pdd-cli")
|
|
123
|
+
@click.pass_context
|
|
124
|
+
def cli(
|
|
125
|
+
ctx: click.Context,
|
|
126
|
+
force: bool,
|
|
127
|
+
strength: float,
|
|
128
|
+
temperature: float,
|
|
129
|
+
verbose: bool,
|
|
130
|
+
quiet: bool,
|
|
131
|
+
output_cost: Optional[str],
|
|
132
|
+
review_examples: bool,
|
|
133
|
+
local: bool,
|
|
134
|
+
):
|
|
135
|
+
"""
|
|
136
|
+
Main entry point for the PDD CLI. Handles global options and initializes context.
|
|
137
|
+
Supports multi-command chaining.
|
|
138
|
+
"""
|
|
139
|
+
ctx.ensure_object(dict)
|
|
140
|
+
ctx.obj["force"] = force
|
|
141
|
+
ctx.obj["strength"] = strength
|
|
142
|
+
ctx.obj["temperature"] = temperature
|
|
143
|
+
ctx.obj["verbose"] = verbose
|
|
144
|
+
ctx.obj["quiet"] = quiet
|
|
145
|
+
ctx.obj["output_cost"] = output_cost
|
|
146
|
+
ctx.obj["review_examples"] = review_examples
|
|
147
|
+
ctx.obj["local"] = local
|
|
148
|
+
|
|
149
|
+
# Suppress verbose if quiet is enabled
|
|
150
|
+
if quiet:
|
|
151
|
+
ctx.obj["verbose"] = False
|
|
152
|
+
|
|
153
|
+
# Perform auto-update check unless disabled
|
|
154
|
+
if os.getenv("PDD_AUTO_UPDATE", "true").lower() != "false":
|
|
155
|
+
try:
|
|
156
|
+
if not quiet:
|
|
157
|
+
console.print("[info]Checking for updates...[/info]")
|
|
158
|
+
# Removed quiet=quiet argument as it caused TypeError
|
|
159
|
+
auto_update()
|
|
160
|
+
except Exception as e:
|
|
161
|
+
if not quiet:
|
|
162
|
+
console.print(f"[warning]Auto-update check failed:[/warning] {e}", style="warning")
|
|
163
|
+
|
|
164
|
+
# --- Result Callback for Chained Commands ---
|
|
165
|
+
@cli.result_callback()
|
|
166
|
+
@click.pass_context
|
|
167
|
+
def process_commands(ctx: click.Context, results: List[Optional[Tuple[Any, float, str]]], **kwargs):
|
|
168
|
+
"""
|
|
169
|
+
Processes the results from chained commands.
|
|
170
|
+
|
|
171
|
+
Receives a list of tuples, typically (result, cost, model_name),
|
|
172
|
+
or None from each command function.
|
|
173
|
+
"""
|
|
174
|
+
total_chain_cost = 0.0
|
|
175
|
+
# Get invoked subcommands directly from the group context if available (safer for testing)
|
|
176
|
+
# Note: This might yield "Unknown Command" during tests with CliRunner
|
|
177
|
+
invoked_subcommands = getattr(ctx, 'invoked_subcommands', [])
|
|
178
|
+
num_commands = len(invoked_subcommands)
|
|
179
|
+
num_results = len(results) # Number of results actually received
|
|
180
|
+
|
|
181
|
+
if not ctx.obj.get("quiet"):
|
|
182
|
+
console.print("\n[info]--- Command Chain Execution Summary ---[/info]")
|
|
183
|
+
|
|
184
|
+
for i, result_tuple in enumerate(results):
|
|
185
|
+
# Use the retrieved subcommand name (might be "Unknown Command X" in tests)
|
|
186
|
+
command_name = invoked_subcommands[i] if i < num_commands else f"Unknown Command {i+1}"
|
|
187
|
+
|
|
188
|
+
# Check if the command failed (returned None)
|
|
189
|
+
if result_tuple is None:
|
|
190
|
+
if not ctx.obj.get("quiet"):
|
|
191
|
+
# Check if it was install_completion (which normally returns None)
|
|
192
|
+
if command_name == "install_completion":
|
|
193
|
+
console.print(f" [info]Step {i+1} ({command_name}):[/info] Command completed.")
|
|
194
|
+
# Check if it was preprocess (which returns a dummy tuple on success)
|
|
195
|
+
# This case handles actual failure for preprocess
|
|
196
|
+
elif command_name == "preprocess":
|
|
197
|
+
console.print(f" [error]Step {i+1} ({command_name}):[/error] Command failed.")
|
|
198
|
+
else:
|
|
199
|
+
console.print(f" [error]Step {i+1} ({command_name}):[/error] Command failed.")
|
|
200
|
+
# Check if the result is the expected tuple structure from @track_cost or preprocess success
|
|
201
|
+
elif isinstance(result_tuple, tuple) and len(result_tuple) == 3:
|
|
202
|
+
_result_data, cost, model_name = result_tuple
|
|
203
|
+
total_chain_cost += cost
|
|
204
|
+
if not ctx.obj.get("quiet"):
|
|
205
|
+
# Special handling for preprocess success message (check actual command name)
|
|
206
|
+
actual_command_name = invoked_subcommands[i] if i < num_commands else None # Get actual name if possible
|
|
207
|
+
if actual_command_name == "preprocess" and cost == 0.0 and model_name == "local":
|
|
208
|
+
console.print(f" [info]Step {i+1} ({command_name}):[/info] Command completed (local).")
|
|
209
|
+
else:
|
|
210
|
+
# Generic output using potentially "Unknown Command" name
|
|
211
|
+
console.print(f" [info]Step {i+1} ({command_name}):[/info] Cost: ${cost:.6f}, Model: {model_name}")
|
|
212
|
+
else:
|
|
213
|
+
# Handle unexpected return types if necessary
|
|
214
|
+
if not ctx.obj.get("quiet"):
|
|
215
|
+
# Provide more detail on the unexpected type
|
|
216
|
+
console.print(f" [warning]Step {i+1} ({command_name}):[/warning] Unexpected result format: {type(result_tuple).__name__} - {str(result_tuple)[:50]}...")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if not ctx.obj.get("quiet"):
|
|
220
|
+
# Only print total cost if at least one command potentially contributed cost
|
|
221
|
+
if any(res is not None and isinstance(res, tuple) and len(res) == 3 for res in results):
|
|
222
|
+
console.print(f"[info]Total Estimated Cost for Chain:[/info] ${total_chain_cost:.6f}")
|
|
223
|
+
# Indicate if the chain might have been incomplete due to errors
|
|
224
|
+
if num_results < num_commands and not all(res is None for res in results): # Avoid printing if all failed
|
|
225
|
+
console.print("[warning]Note: Chain may have terminated early due to errors.[/warning]")
|
|
226
|
+
console.print("[info]-------------------------------------[/info]")
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# --- Command Definitions ---
|
|
230
|
+
|
|
231
|
+
@cli.command("generate")
|
|
232
|
+
@click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
233
|
+
@click.option(
|
|
234
|
+
"--output",
|
|
235
|
+
type=click.Path(writable=True), # Allows file or dir
|
|
236
|
+
default=None,
|
|
237
|
+
help="Specify where to save the generated code (file or directory).",
|
|
238
|
+
)
|
|
239
|
+
@click.pass_context
|
|
240
|
+
@track_cost
|
|
241
|
+
def generate(ctx: click.Context, prompt_file: str, output: Optional[str]) -> Optional[Tuple[str, float, str]]: # Modified return type
|
|
242
|
+
"""Create runnable code from a prompt file."""
|
|
243
|
+
quiet = ctx.obj.get("quiet", False)
|
|
244
|
+
command_name = "generate"
|
|
245
|
+
try:
|
|
246
|
+
generated_code, total_cost, model_name = code_generator_main(
|
|
247
|
+
ctx=ctx,
|
|
248
|
+
prompt_file=prompt_file,
|
|
249
|
+
output=output,
|
|
250
|
+
)
|
|
251
|
+
return generated_code, total_cost, model_name
|
|
252
|
+
except Exception as e:
|
|
253
|
+
handle_error(e, command_name, quiet)
|
|
254
|
+
return None # Return None on failure
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@cli.command("example")
|
|
258
|
+
@click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
259
|
+
@click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
|
|
260
|
+
@click.option(
|
|
261
|
+
"--output",
|
|
262
|
+
type=click.Path(writable=True),
|
|
263
|
+
default=None,
|
|
264
|
+
help="Specify where to save the generated example code (file or directory).",
|
|
265
|
+
)
|
|
266
|
+
@click.pass_context
|
|
267
|
+
@track_cost
|
|
268
|
+
def example(ctx: click.Context, prompt_file: str, code_file: str, output: Optional[str]) -> Optional[Tuple[str, float, str]]: # Modified return type
|
|
269
|
+
"""Create a compact example demonstrating functionality."""
|
|
270
|
+
quiet = ctx.obj.get("quiet", False)
|
|
271
|
+
command_name = "example"
|
|
272
|
+
try:
|
|
273
|
+
example_code, total_cost, model_name = context_generator_main(
|
|
274
|
+
ctx=ctx,
|
|
275
|
+
prompt_file=prompt_file,
|
|
276
|
+
code_file=code_file,
|
|
277
|
+
output=output,
|
|
278
|
+
)
|
|
279
|
+
return example_code, total_cost, model_name
|
|
280
|
+
except Exception as e:
|
|
281
|
+
handle_error(e, command_name, quiet)
|
|
282
|
+
return None # Return None on failure
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@cli.command("test")
|
|
286
|
+
@click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
287
|
+
@click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
|
|
288
|
+
@click.option(
|
|
289
|
+
"--output",
|
|
290
|
+
type=click.Path(writable=True),
|
|
291
|
+
default=None,
|
|
292
|
+
help="Specify where to save the generated test file (file or directory).",
|
|
293
|
+
)
|
|
294
|
+
@click.option("--language", type=str, default=None, help="Specify the programming language.")
|
|
295
|
+
@click.option(
|
|
296
|
+
"--coverage-report",
|
|
297
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
298
|
+
default=None,
|
|
299
|
+
help="Path to the coverage report file for existing tests.",
|
|
300
|
+
)
|
|
301
|
+
@click.option(
|
|
302
|
+
"--existing-tests",
|
|
303
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
304
|
+
default=None,
|
|
305
|
+
help="Path to the existing unit test file.",
|
|
306
|
+
)
|
|
307
|
+
@click.option(
|
|
308
|
+
"--target-coverage",
|
|
309
|
+
type=click.FloatRange(0.0, 100.0),
|
|
310
|
+
default=None, # Use None, default handled in cmd_test_main or env var
|
|
311
|
+
help="Desired code coverage percentage (default: 90.0 or PDD_TEST_COVERAGE_TARGET).",
|
|
312
|
+
)
|
|
313
|
+
@click.option(
|
|
314
|
+
"--merge",
|
|
315
|
+
is_flag=True,
|
|
316
|
+
default=False,
|
|
317
|
+
help="Merge new tests with existing test file instead of creating a separate file.",
|
|
318
|
+
)
|
|
319
|
+
@click.pass_context
|
|
320
|
+
@track_cost
|
|
321
|
+
def test(
|
|
322
|
+
ctx: click.Context,
|
|
323
|
+
prompt_file: str,
|
|
324
|
+
code_file: str,
|
|
325
|
+
output: Optional[str],
|
|
326
|
+
language: Optional[str],
|
|
327
|
+
coverage_report: Optional[str],
|
|
328
|
+
existing_tests: Optional[str],
|
|
329
|
+
target_coverage: Optional[float],
|
|
330
|
+
merge: bool,
|
|
331
|
+
) -> Optional[Tuple[str, float, str]]: # Modified return type
|
|
332
|
+
"""Generate or enhance unit tests."""
|
|
333
|
+
quiet = ctx.obj.get("quiet", False)
|
|
334
|
+
command_name = "test"
|
|
335
|
+
try:
|
|
336
|
+
generated_test_code, total_cost, model_name = cmd_test_main(
|
|
337
|
+
ctx=ctx,
|
|
338
|
+
prompt_file=prompt_file,
|
|
339
|
+
code_file=code_file,
|
|
340
|
+
output=output,
|
|
341
|
+
language=language,
|
|
342
|
+
coverage_report=coverage_report,
|
|
343
|
+
existing_tests=existing_tests,
|
|
344
|
+
target_coverage=target_coverage,
|
|
345
|
+
merge=merge,
|
|
346
|
+
)
|
|
347
|
+
return generated_test_code, total_cost, model_name
|
|
348
|
+
except Exception as e:
|
|
349
|
+
handle_error(e, command_name, quiet)
|
|
350
|
+
return None # Return None on failure
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@cli.command("preprocess")
|
|
354
|
+
@click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
355
|
+
@click.option(
|
|
356
|
+
"--output",
|
|
357
|
+
type=click.Path(writable=True),
|
|
358
|
+
default=None,
|
|
359
|
+
help="Specify where to save the preprocessed prompt file (file or directory).",
|
|
360
|
+
)
|
|
361
|
+
@click.option(
|
|
362
|
+
"--xml",
|
|
363
|
+
is_flag=True,
|
|
364
|
+
default=False,
|
|
365
|
+
help="Insert XML delimiters for structure (minimal preprocessing).",
|
|
366
|
+
)
|
|
367
|
+
@click.option(
|
|
368
|
+
"--recursive",
|
|
369
|
+
is_flag=True,
|
|
370
|
+
default=False,
|
|
371
|
+
help="Recursively preprocess includes.",
|
|
372
|
+
)
|
|
373
|
+
@click.option(
|
|
374
|
+
"--double",
|
|
375
|
+
is_flag=True,
|
|
376
|
+
default=False,
|
|
377
|
+
help="Double curly brackets.",
|
|
378
|
+
)
|
|
379
|
+
@click.option(
|
|
380
|
+
"--exclude",
|
|
381
|
+
multiple=True,
|
|
382
|
+
default=None,
|
|
383
|
+
help="List of keys to exclude from curly bracket doubling.",
|
|
384
|
+
)
|
|
385
|
+
@click.pass_context
|
|
386
|
+
# No @track_cost as preprocessing is local, but return dummy tuple for callback
|
|
387
|
+
def preprocess(
|
|
388
|
+
ctx: click.Context,
|
|
389
|
+
prompt_file: str,
|
|
390
|
+
output: Optional[str],
|
|
391
|
+
xml: bool,
|
|
392
|
+
recursive: bool,
|
|
393
|
+
double: bool,
|
|
394
|
+
exclude: Optional[Tuple[str, ...]],
|
|
395
|
+
) -> Optional[Tuple[str, float, str]]: # Modified return type (Optional)
|
|
396
|
+
"""Preprocess prompt files and save the results."""
|
|
397
|
+
quiet = ctx.obj.get("quiet", False)
|
|
398
|
+
command_name = "preprocess"
|
|
399
|
+
try:
|
|
400
|
+
preprocess_main(
|
|
401
|
+
ctx=ctx,
|
|
402
|
+
prompt_file=prompt_file,
|
|
403
|
+
output=output,
|
|
404
|
+
xml=xml,
|
|
405
|
+
recursive=recursive,
|
|
406
|
+
double=double,
|
|
407
|
+
exclude=list(exclude) if exclude else [],
|
|
408
|
+
)
|
|
409
|
+
# Return dummy values ONLY on success
|
|
410
|
+
return "Preprocessing complete.", 0.0, "local"
|
|
411
|
+
except Exception as e:
|
|
412
|
+
handle_error(e, command_name, quiet)
|
|
413
|
+
return None # Return None on failure
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
@cli.command("fix")
|
|
417
|
+
@click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
418
|
+
@click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
|
|
419
|
+
@click.argument("unit_test_file", type=click.Path(exists=True, dir_okay=False))
|
|
420
|
+
@click.argument("error_file", type=click.Path(dir_okay=False)) # Allow non-existent for loop mode
|
|
421
|
+
@click.option(
|
|
422
|
+
"--output-test",
|
|
423
|
+
type=click.Path(writable=True),
|
|
424
|
+
default=None,
|
|
425
|
+
help="Specify where to save the fixed unit test file (file or directory).",
|
|
426
|
+
)
|
|
427
|
+
@click.option(
|
|
428
|
+
"--output-code",
|
|
429
|
+
type=click.Path(writable=True),
|
|
430
|
+
default=None,
|
|
431
|
+
help="Specify where to save the fixed code file (file or directory).",
|
|
432
|
+
)
|
|
433
|
+
@click.option(
|
|
434
|
+
"--output-results",
|
|
435
|
+
type=click.Path(writable=True),
|
|
436
|
+
default=None,
|
|
437
|
+
help="Specify where to save the results log (file or directory).",
|
|
438
|
+
)
|
|
439
|
+
@click.option("--loop", is_flag=True, default=False, help="Enable iterative fixing process.")
|
|
440
|
+
@click.option(
|
|
441
|
+
"--verification-program",
|
|
442
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
443
|
+
default=None,
|
|
444
|
+
help="Path to a Python program that verifies the fix.",
|
|
445
|
+
)
|
|
446
|
+
@click.option(
|
|
447
|
+
"--max-attempts",
|
|
448
|
+
type=int,
|
|
449
|
+
default=3,
|
|
450
|
+
show_default=True,
|
|
451
|
+
help="Maximum number of fix attempts.",
|
|
452
|
+
)
|
|
453
|
+
@click.option(
|
|
454
|
+
"--budget",
|
|
455
|
+
type=float,
|
|
456
|
+
default=5.0,
|
|
457
|
+
show_default=True,
|
|
458
|
+
help="Maximum cost allowed for the fixing process.",
|
|
459
|
+
)
|
|
460
|
+
@click.option(
|
|
461
|
+
"--auto-submit",
|
|
462
|
+
is_flag=True,
|
|
463
|
+
default=False,
|
|
464
|
+
help="Automatically submit the example if all unit tests pass.",
|
|
465
|
+
)
|
|
466
|
+
@click.pass_context
|
|
467
|
+
@track_cost # fix_main returns cost/model info
|
|
468
|
+
def fix(
|
|
469
|
+
ctx: click.Context,
|
|
470
|
+
prompt_file: str,
|
|
471
|
+
code_file: str,
|
|
472
|
+
unit_test_file: str,
|
|
473
|
+
error_file: str,
|
|
474
|
+
output_test: Optional[str],
|
|
475
|
+
output_code: Optional[str],
|
|
476
|
+
output_results: Optional[str],
|
|
477
|
+
loop: bool,
|
|
478
|
+
verification_program: Optional[str],
|
|
479
|
+
max_attempts: int,
|
|
480
|
+
budget: float,
|
|
481
|
+
auto_submit: bool,
|
|
482
|
+
) -> Optional[Tuple[Dict[str, Any], float, str]]: # Modified return type
|
|
483
|
+
"""Fix errors in code and unit tests based on error messages."""
|
|
484
|
+
quiet = ctx.obj.get("quiet", False)
|
|
485
|
+
command_name = "fix"
|
|
486
|
+
try:
|
|
487
|
+
|
|
488
|
+
# fix_main returns: success, fixed_test_content, fixed_code_content, attempts, cost, model
|
|
489
|
+
# We need to adapt this to the (result, cost, model) structure for the callback
|
|
490
|
+
success, fixed_test, fixed_code, attempts, cost, model = fix_main(
|
|
491
|
+
ctx=ctx,
|
|
492
|
+
prompt_file=prompt_file,
|
|
493
|
+
code_file=code_file,
|
|
494
|
+
unit_test_file=unit_test_file,
|
|
495
|
+
error_file=error_file,
|
|
496
|
+
output_test=output_test,
|
|
497
|
+
output_code=output_code,
|
|
498
|
+
output_results=output_results,
|
|
499
|
+
loop=loop,
|
|
500
|
+
verification_program=verification_program,
|
|
501
|
+
max_attempts=max_attempts,
|
|
502
|
+
budget=budget,
|
|
503
|
+
auto_submit=auto_submit,
|
|
504
|
+
)
|
|
505
|
+
# Package results into a dictionary for the first element of the tuple
|
|
506
|
+
result_data = {
|
|
507
|
+
"success": success,
|
|
508
|
+
"attempts": attempts,
|
|
509
|
+
"fixed_test_path": output_test,
|
|
510
|
+
"fixed_code_path": output_code,
|
|
511
|
+
"results_log_path": output_results,
|
|
512
|
+
}
|
|
513
|
+
return result_data, cost, model
|
|
514
|
+
except Exception as e:
|
|
515
|
+
handle_error(e, command_name, quiet)
|
|
516
|
+
return None # Return None on failure
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
@cli.command("split")
|
|
520
|
+
@click.argument("input_prompt", type=click.Path(exists=True, dir_okay=False))
|
|
521
|
+
@click.argument("input_code", type=click.Path(exists=True, dir_okay=False))
|
|
522
|
+
@click.argument("example_code", type=click.Path(exists=True, dir_okay=False))
|
|
523
|
+
@click.option(
|
|
524
|
+
"--output-sub",
|
|
525
|
+
type=click.Path(writable=True),
|
|
526
|
+
default=None,
|
|
527
|
+
help="Specify where to save the generated sub-prompt file (file or directory).",
|
|
528
|
+
)
|
|
529
|
+
@click.option(
|
|
530
|
+
"--output-modified",
|
|
531
|
+
type=click.Path(writable=True),
|
|
532
|
+
default=None,
|
|
533
|
+
help="Specify where to save the modified prompt file (file or directory).",
|
|
534
|
+
)
|
|
535
|
+
@click.pass_context
|
|
536
|
+
@track_cost
|
|
537
|
+
def split(
|
|
538
|
+
ctx: click.Context,
|
|
539
|
+
input_prompt: str,
|
|
540
|
+
input_code: str,
|
|
541
|
+
example_code: str,
|
|
542
|
+
output_sub: Optional[str],
|
|
543
|
+
output_modified: Optional[str],
|
|
544
|
+
) -> Optional[Tuple[Dict[str, str], float, str]]: # Modified return type
|
|
545
|
+
"""Split large complex prompt files into smaller ones."""
|
|
546
|
+
quiet = ctx.obj.get("quiet", False)
|
|
547
|
+
command_name = "split"
|
|
548
|
+
try:
|
|
549
|
+
result_data, total_cost, model_name = split_main(
|
|
550
|
+
ctx=ctx,
|
|
551
|
+
input_prompt_file=input_prompt,
|
|
552
|
+
input_code_file=input_code,
|
|
553
|
+
example_code_file=example_code,
|
|
554
|
+
output_sub=output_sub,
|
|
555
|
+
output_modified=output_modified,
|
|
556
|
+
)
|
|
557
|
+
return result_data, total_cost, model_name
|
|
558
|
+
except Exception as e:
|
|
559
|
+
handle_error(e, command_name, quiet)
|
|
560
|
+
return None # Return None on failure
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
@cli.command("change")
|
|
564
|
+
@click.argument("change_prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
565
|
+
@click.argument("input_code", type=click.Path(exists=True)) # Can be file or dir
|
|
566
|
+
@click.argument("input_prompt_file", type=click.Path(exists=True, dir_okay=False), required=False)
|
|
567
|
+
@click.option(
|
|
568
|
+
"--output",
|
|
569
|
+
type=click.Path(writable=True),
|
|
570
|
+
default=None,
|
|
571
|
+
help="Specify where to save the modified prompt file (file or directory).",
|
|
572
|
+
)
|
|
573
|
+
@click.option(
|
|
574
|
+
"--csv",
|
|
575
|
+
"use_csv",
|
|
576
|
+
is_flag=True,
|
|
577
|
+
default=False,
|
|
578
|
+
help="Use a CSV file for batch change prompts.",
|
|
579
|
+
)
|
|
580
|
+
@click.pass_context
|
|
581
|
+
@track_cost
|
|
582
|
+
def change(
|
|
583
|
+
ctx: click.Context,
|
|
584
|
+
change_prompt_file: str,
|
|
585
|
+
input_code: str,
|
|
586
|
+
input_prompt_file: Optional[str],
|
|
587
|
+
output: Optional[str],
|
|
588
|
+
use_csv: bool,
|
|
589
|
+
) -> Optional[Tuple[str | Dict, float, str]]: # Modified return type
|
|
590
|
+
"""Modify prompt(s) based on change instructions."""
|
|
591
|
+
quiet = ctx.obj.get("quiet", False)
|
|
592
|
+
command_name = "change"
|
|
593
|
+
try:
|
|
594
|
+
# --- ADD VALIDATION LOGIC HERE ---
|
|
595
|
+
input_code_path = Path(input_code) # Convert to Path object
|
|
596
|
+
if use_csv:
|
|
597
|
+
if not input_code_path.is_dir():
|
|
598
|
+
raise click.UsageError("INPUT_CODE must be a directory when using --csv.")
|
|
599
|
+
if input_prompt_file:
|
|
600
|
+
raise click.UsageError("Cannot use --csv and specify an INPUT_PROMPT_FILE simultaneously.")
|
|
601
|
+
else: # Not using CSV
|
|
602
|
+
if not input_prompt_file:
|
|
603
|
+
# This check might be better inside change_main, but can be here too
|
|
604
|
+
raise click.UsageError("INPUT_PROMPT_FILE is required when not using --csv.")
|
|
605
|
+
if not input_code_path.is_file():
|
|
606
|
+
# This check might be better inside change_main, but can be here too
|
|
607
|
+
raise click.UsageError("INPUT_CODE must be a file when not using --csv.")
|
|
608
|
+
# --- END VALIDATION LOGIC ---
|
|
609
|
+
|
|
610
|
+
result_data, total_cost, model_name = change_main(
|
|
611
|
+
ctx=ctx,
|
|
612
|
+
change_prompt_file=change_prompt_file,
|
|
613
|
+
input_code=input_code,
|
|
614
|
+
input_prompt_file=input_prompt_file,
|
|
615
|
+
output=output,
|
|
616
|
+
use_csv=use_csv,
|
|
617
|
+
)
|
|
618
|
+
return result_data, total_cost, model_name
|
|
619
|
+
except (click.UsageError, Exception) as e: # Catch specific and general exceptions
|
|
620
|
+
handle_error(e, command_name, quiet)
|
|
621
|
+
return None # Return None on failure
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
@cli.command("update")
|
|
625
|
+
@click.argument("input_prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
626
|
+
@click.argument("modified_code_file", type=click.Path(exists=True, dir_okay=False))
|
|
627
|
+
@click.argument("input_code_file", type=click.Path(exists=True, dir_okay=False), required=False)
|
|
628
|
+
@click.option(
|
|
629
|
+
"--output",
|
|
630
|
+
type=click.Path(writable=True),
|
|
631
|
+
default=None,
|
|
632
|
+
help="Specify where to save the updated prompt file (file or directory).",
|
|
633
|
+
)
|
|
634
|
+
@click.option(
|
|
635
|
+
"--git",
|
|
636
|
+
is_flag=True,
|
|
637
|
+
default=False,
|
|
638
|
+
help="Use git history to find the original code file.",
|
|
639
|
+
)
|
|
640
|
+
@click.pass_context
|
|
641
|
+
@track_cost
|
|
642
|
+
def update(
|
|
643
|
+
ctx: click.Context,
|
|
644
|
+
input_prompt_file: str,
|
|
645
|
+
modified_code_file: str,
|
|
646
|
+
input_code_file: Optional[str],
|
|
647
|
+
output: Optional[str],
|
|
648
|
+
git: bool,
|
|
649
|
+
) -> Optional[Tuple[str, float, str]]: # Modified return type
|
|
650
|
+
"""Update the original prompt file based on modified code."""
|
|
651
|
+
quiet = ctx.obj.get("quiet", False)
|
|
652
|
+
command_name = "update"
|
|
653
|
+
try:
|
|
654
|
+
if git and input_code_file:
|
|
655
|
+
raise click.UsageError("Cannot use --git and specify an INPUT_CODE_FILE simultaneously.")
|
|
656
|
+
if not git and not input_code_file:
|
|
657
|
+
raise click.UsageError("INPUT_CODE_FILE is required when not using --git.")
|
|
658
|
+
|
|
659
|
+
updated_prompt, total_cost, model_name = update_main(
|
|
660
|
+
ctx=ctx,
|
|
661
|
+
input_prompt_file=input_prompt_file,
|
|
662
|
+
modified_code_file=modified_code_file,
|
|
663
|
+
input_code_file=input_code_file,
|
|
664
|
+
output=output,
|
|
665
|
+
git=git,
|
|
666
|
+
)
|
|
667
|
+
return updated_prompt, total_cost, model_name
|
|
668
|
+
except (click.UsageError, Exception) as e: # Catch specific and general exceptions
|
|
669
|
+
handle_error(e, command_name, quiet)
|
|
670
|
+
return None # Return None on failure
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
@cli.command("detect")
|
|
674
|
+
@click.argument("prompt_files", nargs=-1, type=click.Path(exists=True, dir_okay=False))
|
|
675
|
+
@click.argument("change_file", type=click.Path(exists=True, dir_okay=False))
|
|
676
|
+
@click.option(
|
|
677
|
+
"--output",
|
|
678
|
+
type=click.Path(writable=True),
|
|
679
|
+
default=None,
|
|
680
|
+
help="Specify where to save the CSV analysis results (file or directory).",
|
|
681
|
+
)
|
|
682
|
+
@click.pass_context
|
|
683
|
+
@track_cost
|
|
684
|
+
def detect(
|
|
685
|
+
ctx: click.Context,
|
|
686
|
+
prompt_files: Tuple[str, ...],
|
|
687
|
+
change_file: str,
|
|
688
|
+
output: Optional[str],
|
|
689
|
+
) -> Optional[Tuple[List[Dict[str, str]], float, str]]: # Modified return type
|
|
690
|
+
"""Analyze prompts and a change description to find needed changes."""
|
|
691
|
+
quiet = ctx.obj.get("quiet", False)
|
|
692
|
+
command_name = "detect"
|
|
693
|
+
try:
|
|
694
|
+
if not prompt_files:
|
|
695
|
+
raise click.UsageError("At least one PROMPT_FILE must be provided.")
|
|
696
|
+
|
|
697
|
+
changes_list, total_cost, model_name = detect_change_main(
|
|
698
|
+
ctx=ctx,
|
|
699
|
+
prompt_files=list(prompt_files),
|
|
700
|
+
change_file=change_file,
|
|
701
|
+
output=output,
|
|
702
|
+
)
|
|
703
|
+
return changes_list, total_cost, model_name
|
|
704
|
+
except (click.UsageError, Exception) as e: # Catch specific and general exceptions
|
|
705
|
+
handle_error(e, command_name, quiet)
|
|
706
|
+
return None # Return None on failure
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
@cli.command("conflicts")
|
|
710
|
+
@click.argument("prompt1", type=click.Path(exists=True, dir_okay=False))
|
|
711
|
+
@click.argument("prompt2", type=click.Path(exists=True, dir_okay=False))
|
|
712
|
+
@click.option(
|
|
713
|
+
"--output",
|
|
714
|
+
type=click.Path(writable=True),
|
|
715
|
+
default=None,
|
|
716
|
+
help="Specify where to save the CSV conflict analysis results (file or directory).",
|
|
717
|
+
)
|
|
718
|
+
@click.pass_context
|
|
719
|
+
@track_cost
|
|
720
|
+
def conflicts(
|
|
721
|
+
ctx: click.Context,
|
|
722
|
+
prompt1: str,
|
|
723
|
+
prompt2: str,
|
|
724
|
+
output: Optional[str],
|
|
725
|
+
) -> Optional[Tuple[List[Dict[str, str]], float, str]]: # Modified return type
|
|
726
|
+
"""Analyze two prompt files to find conflicts."""
|
|
727
|
+
quiet = ctx.obj.get("quiet", False)
|
|
728
|
+
command_name = "conflicts"
|
|
729
|
+
try:
|
|
730
|
+
conflicts_list, total_cost, model_name = conflicts_main(
|
|
731
|
+
ctx=ctx,
|
|
732
|
+
prompt1=prompt1,
|
|
733
|
+
prompt2=prompt2,
|
|
734
|
+
output=output,
|
|
735
|
+
)
|
|
736
|
+
return conflicts_list, total_cost, model_name
|
|
737
|
+
except Exception as e:
|
|
738
|
+
handle_error(e, command_name, quiet)
|
|
739
|
+
return None # Return None on failure
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
@cli.command("crash")
|
|
743
|
+
@click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
744
|
+
@click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
|
|
745
|
+
@click.argument("program_file", type=click.Path(exists=True, dir_okay=False))
|
|
746
|
+
@click.argument("error_file", type=click.Path(dir_okay=False)) # Allow non-existent
|
|
747
|
+
@click.option(
|
|
748
|
+
"--output", # Corresponds to output_code in crash_main
|
|
749
|
+
type=click.Path(writable=True),
|
|
750
|
+
default=None,
|
|
751
|
+
help="Specify where to save the fixed code file (file or directory).",
|
|
752
|
+
)
|
|
753
|
+
@click.option(
|
|
754
|
+
"--output-program",
|
|
755
|
+
type=click.Path(writable=True),
|
|
756
|
+
default=None,
|
|
757
|
+
help="Specify where to save the fixed program file (file or directory).",
|
|
758
|
+
)
|
|
759
|
+
@click.option("--loop", is_flag=True, default=False, help="Enable iterative fixing process.")
|
|
760
|
+
@click.option(
|
|
761
|
+
"--max-attempts",
|
|
762
|
+
type=int,
|
|
763
|
+
default=3,
|
|
764
|
+
show_default=True,
|
|
765
|
+
help="Maximum number of fix attempts.",
|
|
766
|
+
)
|
|
767
|
+
@click.option(
|
|
768
|
+
"--budget",
|
|
769
|
+
type=float,
|
|
770
|
+
default=5.0,
|
|
771
|
+
show_default=True,
|
|
772
|
+
help="Maximum cost allowed for the fixing process.",
|
|
773
|
+
)
|
|
774
|
+
@click.pass_context
|
|
775
|
+
@track_cost
|
|
776
|
+
def crash(
|
|
777
|
+
ctx: click.Context,
|
|
778
|
+
prompt_file: str,
|
|
779
|
+
code_file: str,
|
|
780
|
+
program_file: str,
|
|
781
|
+
error_file: str,
|
|
782
|
+
output: Optional[str], # Maps to output_code
|
|
783
|
+
output_program: Optional[str],
|
|
784
|
+
loop: bool,
|
|
785
|
+
max_attempts: int,
|
|
786
|
+
budget: float,
|
|
787
|
+
) -> Optional[Tuple[Dict[str, Any], float, str]]: # Modified return type
|
|
788
|
+
"""Fix errors in a code module and calling program that caused a crash."""
|
|
789
|
+
quiet = ctx.obj.get("quiet", False)
|
|
790
|
+
command_name = "crash"
|
|
791
|
+
try:
|
|
792
|
+
success, fixed_code, fixed_program, attempts, cost, model = crash_main(
|
|
793
|
+
ctx=ctx,
|
|
794
|
+
prompt_file=prompt_file,
|
|
795
|
+
code_file=code_file,
|
|
796
|
+
program_file=program_file,
|
|
797
|
+
error_file=error_file,
|
|
798
|
+
output=output,
|
|
799
|
+
output_program=output_program,
|
|
800
|
+
loop=loop,
|
|
801
|
+
max_attempts=max_attempts,
|
|
802
|
+
budget=budget,
|
|
803
|
+
)
|
|
804
|
+
result_data = {
|
|
805
|
+
"success": success,
|
|
806
|
+
"attempts": attempts,
|
|
807
|
+
"fixed_code_path": output,
|
|
808
|
+
"fixed_program_path": output_program,
|
|
809
|
+
}
|
|
810
|
+
return result_data, cost, model
|
|
811
|
+
except Exception as e:
|
|
812
|
+
handle_error(e, command_name, quiet)
|
|
813
|
+
return None # Return None on failure
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
@cli.command("trace")
|
|
817
|
+
@click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
818
|
+
@click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
|
|
819
|
+
@click.argument("code_line", type=int)
|
|
820
|
+
@click.option(
|
|
821
|
+
"--output",
|
|
822
|
+
type=click.Path(writable=True),
|
|
823
|
+
default=None,
|
|
824
|
+
help="Specify where to save the trace analysis results log (file or directory).",
|
|
825
|
+
)
|
|
826
|
+
@click.pass_context
|
|
827
|
+
@track_cost
|
|
828
|
+
def trace(
|
|
829
|
+
ctx: click.Context,
|
|
830
|
+
prompt_file: str,
|
|
831
|
+
code_file: str,
|
|
832
|
+
code_line: int,
|
|
833
|
+
output: Optional[str],
|
|
834
|
+
) -> Optional[Tuple[int | str, float, str]]: # Modified return type
|
|
835
|
+
"""Find the associated line number between a prompt file and generated code."""
|
|
836
|
+
quiet = ctx.obj.get("quiet", False)
|
|
837
|
+
command_name = "trace"
|
|
838
|
+
try:
|
|
839
|
+
prompt_line_result, total_cost, model_name = trace_main(
|
|
840
|
+
ctx=ctx,
|
|
841
|
+
prompt_file=prompt_file,
|
|
842
|
+
code_file=code_file,
|
|
843
|
+
code_line=code_line,
|
|
844
|
+
output=output,
|
|
845
|
+
)
|
|
846
|
+
# Check if trace_main indicated failure (e.g., by returning None or specific error)
|
|
847
|
+
# This depends on trace_main's implementation; assuming it raises exceptions for now.
|
|
848
|
+
if prompt_line_result is None and total_cost == 0.0 and model_name == 'local_error': # Example check if trace_main returns specific tuple on failure
|
|
849
|
+
# Optionally handle specific non-exception failures differently if needed
|
|
850
|
+
# For now, rely on exceptions being raised for errors like out-of-range.
|
|
851
|
+
pass
|
|
852
|
+
return prompt_line_result, total_cost, model_name
|
|
853
|
+
except Exception as e:
|
|
854
|
+
handle_error(e, command_name, quiet)
|
|
855
|
+
# Exit with non-zero status code on any exception
|
|
856
|
+
ctx.exit(1)
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
@cli.command("bug")
|
|
860
|
+
@click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
861
|
+
@click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
|
|
862
|
+
@click.argument("program_file", type=click.Path(exists=True, dir_okay=False))
|
|
863
|
+
@click.argument("current_output_file", type=click.Path(exists=True, dir_okay=False))
|
|
864
|
+
@click.argument("desired_output_file", type=click.Path(exists=True, dir_okay=False))
|
|
865
|
+
@click.option(
|
|
866
|
+
"--output",
|
|
867
|
+
type=click.Path(writable=True),
|
|
868
|
+
default=None,
|
|
869
|
+
help="Specify where to save the generated unit test (file or directory).",
|
|
870
|
+
)
|
|
871
|
+
@click.option("--language", type=str, default=None, help="Specify the programming language (default: Python).")
|
|
872
|
+
@click.pass_context
|
|
873
|
+
@track_cost
|
|
874
|
+
def bug(
|
|
875
|
+
ctx: click.Context,
|
|
876
|
+
prompt_file: str,
|
|
877
|
+
code_file: str,
|
|
878
|
+
program_file: str,
|
|
879
|
+
current_output_file: str,
|
|
880
|
+
desired_output_file: str,
|
|
881
|
+
output: Optional[str],
|
|
882
|
+
language: Optional[str],
|
|
883
|
+
) -> Optional[Tuple[str, float, str]]: # Modified return type
|
|
884
|
+
"""Generate a unit test based on observed and desired outputs."""
|
|
885
|
+
quiet = ctx.obj.get("quiet", False)
|
|
886
|
+
command_name = "bug"
|
|
887
|
+
try:
|
|
888
|
+
unit_test_content, total_cost, model_name = bug_main(
|
|
889
|
+
ctx=ctx,
|
|
890
|
+
prompt_file=prompt_file,
|
|
891
|
+
code_file=code_file,
|
|
892
|
+
program_file=program_file,
|
|
893
|
+
current_output=current_output_file,
|
|
894
|
+
desired_output=desired_output_file,
|
|
895
|
+
output=output,
|
|
896
|
+
language=language,
|
|
897
|
+
)
|
|
898
|
+
return unit_test_content, total_cost, model_name
|
|
899
|
+
except Exception as e:
|
|
900
|
+
handle_error(e, command_name, quiet)
|
|
901
|
+
return None # Return None on failure
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
@cli.command("auto-deps")
|
|
905
|
+
@click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
906
|
+
@click.argument("directory_path", type=str) # Path with potential glob pattern
|
|
907
|
+
@click.option(
|
|
908
|
+
"--output",
|
|
909
|
+
type=click.Path(writable=True),
|
|
910
|
+
default=None,
|
|
911
|
+
help="Specify where to save the modified prompt file (file or directory).",
|
|
912
|
+
)
|
|
913
|
+
@click.option(
|
|
914
|
+
"--csv",
|
|
915
|
+
"auto_deps_csv_path",
|
|
916
|
+
type=click.Path(dir_okay=False), # CSV path is a file
|
|
917
|
+
default=None, # Default handled by auto_deps_main or env var
|
|
918
|
+
help="Specify the CSV file for dependency info (default: project_dependencies.csv or PDD_AUTO_DEPS_CSV_PATH).",
|
|
919
|
+
)
|
|
920
|
+
@click.option(
|
|
921
|
+
"--force-scan",
|
|
922
|
+
is_flag=True,
|
|
923
|
+
default=False,
|
|
924
|
+
help="Force rescanning of all potential dependency files.",
|
|
925
|
+
)
|
|
926
|
+
@click.pass_context
|
|
927
|
+
@track_cost
|
|
928
|
+
def auto_deps(
|
|
929
|
+
ctx: click.Context,
|
|
930
|
+
prompt_file: str,
|
|
931
|
+
directory_path: str,
|
|
932
|
+
output: Optional[str],
|
|
933
|
+
auto_deps_csv_path: Optional[str],
|
|
934
|
+
force_scan: bool,
|
|
935
|
+
) -> Optional[Tuple[str, float, str]]: # Modified return type
|
|
936
|
+
"""Analyze prompt and insert dependencies from a directory."""
|
|
937
|
+
quiet = ctx.obj.get("quiet", False)
|
|
938
|
+
command_name = "auto-deps"
|
|
939
|
+
try:
|
|
940
|
+
clean_directory_path = directory_path.strip('\"')
|
|
941
|
+
|
|
942
|
+
modified_prompt, total_cost, model_name = auto_deps_main(
|
|
943
|
+
ctx=ctx,
|
|
944
|
+
prompt_file=prompt_file,
|
|
945
|
+
directory_path=clean_directory_path,
|
|
946
|
+
auto_deps_csv_path=auto_deps_csv_path,
|
|
947
|
+
output=output,
|
|
948
|
+
force_scan=force_scan,
|
|
949
|
+
)
|
|
950
|
+
return modified_prompt, total_cost, model_name
|
|
951
|
+
except Exception as e:
|
|
952
|
+
handle_error(e, command_name, quiet)
|
|
953
|
+
return None # Return None on failure
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
@cli.command("verify")
|
|
957
|
+
@click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
958
|
+
@click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
|
|
959
|
+
@click.argument("program_file", type=click.Path(exists=True, dir_okay=False))
|
|
960
|
+
@click.option(
|
|
961
|
+
"--output-results",
|
|
962
|
+
type=click.Path(writable=True),
|
|
963
|
+
default=None,
|
|
964
|
+
help="Specify where to save the verification results log (file or directory).",
|
|
965
|
+
)
|
|
966
|
+
@click.option(
|
|
967
|
+
"--output-code",
|
|
968
|
+
type=click.Path(writable=True),
|
|
969
|
+
default=None,
|
|
970
|
+
help="Specify where to save the verified code file (file or directory).",
|
|
971
|
+
)
|
|
972
|
+
@click.option(
|
|
973
|
+
"--output-program",
|
|
974
|
+
type=click.Path(writable=True),
|
|
975
|
+
default=None,
|
|
976
|
+
help="Specify where to save the verified program file (file or directory).",
|
|
977
|
+
)
|
|
978
|
+
@click.option(
|
|
979
|
+
"--max-attempts",
|
|
980
|
+
type=int,
|
|
981
|
+
default=3,
|
|
982
|
+
show_default=True,
|
|
983
|
+
help="Maximum number of fix attempts within the verification loop.",
|
|
984
|
+
)
|
|
985
|
+
@click.option(
|
|
986
|
+
"--budget",
|
|
987
|
+
type=float,
|
|
988
|
+
default=5.0,
|
|
989
|
+
show_default=True,
|
|
990
|
+
help="Maximum cost allowed for the verification and fixing process.",
|
|
991
|
+
)
|
|
992
|
+
@click.pass_context
|
|
993
|
+
@track_cost
|
|
994
|
+
def verify(
|
|
995
|
+
ctx: click.Context,
|
|
996
|
+
prompt_file: str,
|
|
997
|
+
code_file: str,
|
|
998
|
+
program_file: str,
|
|
999
|
+
output_results: Optional[str],
|
|
1000
|
+
output_code: Optional[str],
|
|
1001
|
+
output_program: Optional[str],
|
|
1002
|
+
max_attempts: int,
|
|
1003
|
+
budget: float,
|
|
1004
|
+
) -> Optional[Tuple[Dict[str, Any], float, str]]: # Modified return type
|
|
1005
|
+
"""Verify code correctness against prompt using LLM judgment."""
|
|
1006
|
+
quiet = ctx.obj.get("quiet", False)
|
|
1007
|
+
command_name = "verify"
|
|
1008
|
+
try:
|
|
1009
|
+
success, final_program, final_code, attempts, cost, model = fix_verification_main(
|
|
1010
|
+
ctx=ctx,
|
|
1011
|
+
prompt_file=prompt_file,
|
|
1012
|
+
code_file=code_file,
|
|
1013
|
+
program_file=program_file,
|
|
1014
|
+
output_results=output_results,
|
|
1015
|
+
output_code=output_code,
|
|
1016
|
+
output_program=output_program,
|
|
1017
|
+
loop=True,
|
|
1018
|
+
verification_program=program_file,
|
|
1019
|
+
max_attempts=max_attempts,
|
|
1020
|
+
budget=budget,
|
|
1021
|
+
)
|
|
1022
|
+
result_data = {
|
|
1023
|
+
"success": success,
|
|
1024
|
+
"attempts": attempts,
|
|
1025
|
+
"verified_code_path": output_code,
|
|
1026
|
+
"verified_program_path": output_program,
|
|
1027
|
+
"results_log_path": output_results,
|
|
1028
|
+
}
|
|
1029
|
+
return result_data, cost, model
|
|
1030
|
+
except Exception as e:
|
|
1031
|
+
handle_error(e, command_name, quiet)
|
|
1032
|
+
return None # Return None on failure
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
@cli.command("install_completion")
|
|
1036
|
+
@click.pass_context
|
|
1037
|
+
# No @track_cost
|
|
1038
|
+
def install_completion_cmd(ctx: click.Context) -> None: # Return type remains None
|
|
1039
|
+
"""Install shell completion for PDD."""
|
|
1040
|
+
quiet = ctx.obj.get("quiet", False)
|
|
1041
|
+
command_name = "install_completion"
|
|
1042
|
+
try:
|
|
1043
|
+
install_completion(quiet=quiet)
|
|
1044
|
+
# Return None on success (as intended)
|
|
1045
|
+
return None
|
|
1046
|
+
except Exception as e:
|
|
1047
|
+
handle_error(e, command_name, quiet)
|
|
1048
|
+
# Explicitly return None on failure as well, consistent with other commands
|
|
1049
|
+
return None
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
# --- Entry Point ---
|
|
1053
|
+
if __name__ == "__main__":
|
|
1054
|
+
cli()
|