pdd-cli 0.0.24__py3-none-any.whl → 0.0.25__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 +7 -1
- pdd/bug_main.py +5 -1
- pdd/bug_to_unit_test.py +16 -5
- pdd/change.py +2 -1
- pdd/change_main.py +407 -189
- pdd/cli.py +853 -301
- pdd/code_generator.py +2 -1
- pdd/conflicts_in_prompts.py +2 -1
- pdd/construct_paths.py +377 -222
- pdd/context_generator.py +2 -1
- pdd/continue_generation.py +3 -2
- pdd/crash_main.py +55 -20
- pdd/detect_change.py +2 -1
- pdd/fix_code_loop.py +465 -160
- pdd/fix_code_module_errors.py +7 -4
- pdd/fix_error_loop.py +9 -9
- pdd/fix_errors_from_unit_tests.py +207 -365
- pdd/fix_main.py +31 -4
- pdd/fix_verification_errors.py +60 -34
- pdd/fix_verification_errors_loop.py +842 -768
- pdd/fix_verification_main.py +412 -0
- pdd/generate_output_paths.py +427 -189
- pdd/generate_test.py +3 -2
- pdd/increase_tests.py +2 -2
- pdd/llm_invoke.py +14 -3
- pdd/preprocess.py +3 -3
- pdd/process_csv_change.py +466 -154
- pdd/prompts/extract_prompt_update_LLM.prompt +11 -5
- pdd/prompts/extract_unit_code_fix_LLM.prompt +2 -2
- pdd/prompts/fix_code_module_errors_LLM.prompt +29 -0
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +5 -5
- pdd/prompts/generate_test_LLM.prompt +9 -3
- pdd/prompts/update_prompt_LLM.prompt +3 -3
- pdd/split.py +6 -5
- pdd/split_main.py +13 -4
- pdd/trace_main.py +7 -0
- pdd/xml_tagger.py +2 -1
- {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.25.dist-info}/METADATA +4 -4
- {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.25.dist-info}/RECORD +43 -42
- {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.25.dist-info}/WHEEL +1 -1
- {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.25.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.25.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.25.dist-info}/top_level.txt +0 -0
pdd/cli.py
CHANGED
|
@@ -1,56 +1,128 @@
|
|
|
1
|
+
# pdd/cli.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
1
4
|
import os
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
from functools import wraps
|
|
6
|
-
from typing import Callable, List, Optional, Tuple
|
|
5
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
6
|
+
from pathlib import Path # Import Path
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
|
-
from rich import print as rprint
|
|
10
9
|
from rich.console import Console
|
|
11
|
-
from rich.
|
|
12
|
-
|
|
13
|
-
from .install_completion import install_completion as install_completion_main
|
|
14
|
-
import pdd.install_completion
|
|
15
|
-
from pdd import __version__
|
|
10
|
+
from rich.theme import Theme
|
|
11
|
+
from rich.markup import MarkupError, escape
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
21
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
|
|
22
24
|
from .context_generator_main import context_generator_main
|
|
23
|
-
from .
|
|
24
|
-
from .
|
|
25
|
+
from .crash_main import crash_main
|
|
26
|
+
from .detect_change_main import detect_change_main
|
|
25
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
|
|
26
31
|
from .split_main import split_main
|
|
27
|
-
from .change_main import change_main
|
|
28
|
-
from .update_main import update_main
|
|
29
|
-
from .detect_change_main import detect_change_main
|
|
30
|
-
from .conflicts_main import conflicts_main
|
|
31
|
-
from .crash_main import crash_main
|
|
32
32
|
from .trace_main import trace_main
|
|
33
|
-
from .bug_main import bug_main
|
|
34
33
|
from .track_cost import track_cost
|
|
35
|
-
from .
|
|
36
|
-
|
|
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
|
+
|
|
37
69
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@click.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
@click.option(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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")
|
|
51
123
|
@click.pass_context
|
|
52
124
|
def cli(
|
|
53
|
-
ctx,
|
|
125
|
+
ctx: click.Context,
|
|
54
126
|
force: bool,
|
|
55
127
|
strength: float,
|
|
56
128
|
temperature: float,
|
|
@@ -58,10 +130,11 @@ def cli(
|
|
|
58
130
|
quiet: bool,
|
|
59
131
|
output_cost: Optional[str],
|
|
60
132
|
review_examples: bool,
|
|
61
|
-
local: bool
|
|
133
|
+
local: bool,
|
|
62
134
|
):
|
|
63
135
|
"""
|
|
64
|
-
PDD
|
|
136
|
+
Main entry point for the PDD CLI. Handles global options and initializes context.
|
|
137
|
+
Supports multi-command chaining.
|
|
65
138
|
"""
|
|
66
139
|
ctx.ensure_object(dict)
|
|
67
140
|
ctx.obj["force"] = force
|
|
@@ -69,68 +142,184 @@ def cli(
|
|
|
69
142
|
ctx.obj["temperature"] = temperature
|
|
70
143
|
ctx.obj["verbose"] = verbose
|
|
71
144
|
ctx.obj["quiet"] = quiet
|
|
72
|
-
ctx.obj["output_cost"] = output_cost
|
|
145
|
+
ctx.obj["output_cost"] = output_cost
|
|
73
146
|
ctx.obj["review_examples"] = review_examples
|
|
74
|
-
ctx.obj[
|
|
147
|
+
ctx.obj["local"] = local
|
|
75
148
|
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
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":
|
|
79
155
|
try:
|
|
156
|
+
if not quiet:
|
|
157
|
+
console.print("[info]Checking for updates...[/info]")
|
|
158
|
+
# Removed quiet=quiet argument as it caused TypeError
|
|
80
159
|
auto_update()
|
|
81
|
-
except
|
|
82
|
-
|
|
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
|
+
|
|
83
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]")
|
|
84
227
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
+
)
|
|
88
239
|
@click.pass_context
|
|
89
240
|
@track_cost
|
|
90
|
-
def generate(ctx, prompt_file: str, output: Optional[str]) -> Tuple[str, float, str]:
|
|
241
|
+
def generate(ctx: click.Context, prompt_file: str, output: Optional[str]) -> Optional[Tuple[str, float, str]]: # Modified return type
|
|
91
242
|
"""Create runnable code from a prompt file."""
|
|
92
|
-
|
|
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
|
|
93
255
|
|
|
94
256
|
|
|
95
|
-
@cli.command()
|
|
96
|
-
@click.argument("prompt_file", type=click.Path(exists=True))
|
|
97
|
-
@click.argument("code_file", type=click.Path(exists=True))
|
|
98
|
-
@click.option(
|
|
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
|
+
)
|
|
99
266
|
@click.pass_context
|
|
100
267
|
@track_cost
|
|
101
|
-
def example(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
109
283
|
|
|
110
284
|
|
|
111
|
-
@cli.command()
|
|
112
|
-
@click.argument("prompt_file", type=click.Path(exists=True))
|
|
113
|
-
@click.argument("code_file", type=click.Path(exists=True))
|
|
114
|
-
@click.option(
|
|
115
|
-
|
|
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.")
|
|
116
295
|
@click.option(
|
|
117
296
|
"--coverage-report",
|
|
118
|
-
type=click.Path(exists=True),
|
|
297
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
119
298
|
default=None,
|
|
120
|
-
help="Path to
|
|
299
|
+
help="Path to the coverage report file for existing tests.",
|
|
121
300
|
)
|
|
122
301
|
@click.option(
|
|
123
302
|
"--existing-tests",
|
|
124
|
-
type=click.Path(exists=True),
|
|
303
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
125
304
|
default=None,
|
|
126
|
-
help="
|
|
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.",
|
|
127
318
|
)
|
|
128
|
-
@click.option("--target-coverage", type=float, default=None, help="Desired coverage percentage.")
|
|
129
|
-
@click.option("--merge", is_flag=True, default=False, help="Merge new tests into existing tests.")
|
|
130
319
|
@click.pass_context
|
|
131
320
|
@track_cost
|
|
132
321
|
def test(
|
|
133
|
-
ctx,
|
|
322
|
+
ctx: click.Context,
|
|
134
323
|
prompt_file: str,
|
|
135
324
|
code_file: str,
|
|
136
325
|
output: Optional[str],
|
|
@@ -139,74 +328,145 @@ def test(
|
|
|
139
328
|
existing_tests: Optional[str],
|
|
140
329
|
target_coverage: Optional[float],
|
|
141
330
|
merge: bool,
|
|
142
|
-
) -> Tuple[str, float, str]:
|
|
143
|
-
"""
|
|
144
|
-
|
|
145
|
-
""
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
@
|
|
165
|
-
@click.
|
|
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
|
+
)
|
|
166
385
|
@click.pass_context
|
|
167
|
-
@track_cost
|
|
386
|
+
# No @track_cost as preprocessing is local, but return dummy tuple for callback
|
|
168
387
|
def preprocess(
|
|
169
|
-
ctx,
|
|
388
|
+
ctx: click.Context,
|
|
170
389
|
prompt_file: str,
|
|
171
390
|
output: Optional[str],
|
|
172
391
|
xml: bool,
|
|
173
392
|
recursive: bool,
|
|
174
393
|
double: bool,
|
|
175
|
-
exclude:
|
|
176
|
-
) -> Tuple[str, float, str]:
|
|
394
|
+
exclude: Optional[Tuple[str, ...]],
|
|
395
|
+
) -> Optional[Tuple[str, float, str]]: # Modified return type (Optional)
|
|
177
396
|
"""Preprocess prompt files and save the results."""
|
|
178
|
-
|
|
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
|
|
179
414
|
|
|
180
415
|
|
|
181
|
-
@cli.command()
|
|
182
|
-
@click.argument("prompt_file", type=click.Path(exists=True))
|
|
183
|
-
@click.argument("code_file", type=click.Path(exists=True))
|
|
184
|
-
@click.argument("unit_test_file", type=click.Path(exists=True))
|
|
185
|
-
@click.argument("error_file", type=click.Path(
|
|
186
|
-
@click.option(
|
|
187
|
-
|
|
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
|
+
)
|
|
188
433
|
@click.option(
|
|
189
434
|
"--output-results",
|
|
190
|
-
type=click.Path(),
|
|
191
|
-
|
|
435
|
+
type=click.Path(writable=True),
|
|
436
|
+
default=None,
|
|
437
|
+
help="Specify where to save the results log (file or directory).",
|
|
192
438
|
)
|
|
193
|
-
@click.option("--loop", is_flag=True, help="Enable iterative fixing process.")
|
|
439
|
+
@click.option("--loop", is_flag=True, default=False, help="Enable iterative fixing process.")
|
|
194
440
|
@click.option(
|
|
195
441
|
"--verification-program",
|
|
196
|
-
type=click.Path(exists=True),
|
|
197
|
-
|
|
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.",
|
|
198
459
|
)
|
|
199
|
-
@click.option("--max-attempts", type=int, default=3, help="Maximum fix attempts before giving up.")
|
|
200
|
-
@click.option("--budget", type=float, default=5.0, help="Maximum cost allowed for the fixing process.")
|
|
201
460
|
@click.option(
|
|
202
461
|
"--auto-submit",
|
|
203
462
|
is_flag=True,
|
|
204
|
-
|
|
463
|
+
default=False,
|
|
464
|
+
help="Automatically submit the example if all unit tests pass.",
|
|
205
465
|
)
|
|
206
466
|
@click.pass_context
|
|
207
|
-
@track_cost
|
|
467
|
+
@track_cost # fix_main returns cost/model info
|
|
208
468
|
def fix(
|
|
209
|
-
ctx,
|
|
469
|
+
ctx: click.Context,
|
|
210
470
|
prompt_file: str,
|
|
211
471
|
code_file: str,
|
|
212
472
|
unit_test_file: str,
|
|
@@ -218,276 +478,568 @@ def fix(
|
|
|
218
478
|
verification_program: Optional[str],
|
|
219
479
|
max_attempts: int,
|
|
220
480
|
budget: float,
|
|
221
|
-
auto_submit: bool
|
|
222
|
-
) -> Tuple[
|
|
223
|
-
"""Fix errors in code and unit tests based on error messages
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
+
)
|
|
247
535
|
@click.pass_context
|
|
248
536
|
@track_cost
|
|
249
537
|
def split(
|
|
250
|
-
ctx,
|
|
538
|
+
ctx: click.Context,
|
|
251
539
|
input_prompt: str,
|
|
252
540
|
input_code: str,
|
|
253
541
|
example_code: str,
|
|
254
542
|
output_sub: Optional[str],
|
|
255
543
|
output_modified: Optional[str],
|
|
256
|
-
) -> Tuple[str, str, float]:
|
|
257
|
-
"""Split large complex prompt files into smaller
|
|
258
|
-
|
|
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
|
|
259
561
|
|
|
260
562
|
|
|
261
|
-
@cli.command()
|
|
262
|
-
@click.argument("change_prompt_file", type=click.Path(exists=True))
|
|
263
|
-
@click.argument("input_code", type=click.Path(exists=True))
|
|
264
|
-
@click.argument("input_prompt_file", type=click.Path(exists=False), required=False)
|
|
265
|
-
@click.option(
|
|
266
|
-
|
|
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
|
+
)
|
|
267
580
|
@click.pass_context
|
|
268
581
|
@track_cost
|
|
269
582
|
def change(
|
|
270
|
-
ctx,
|
|
583
|
+
ctx: click.Context,
|
|
271
584
|
change_prompt_file: str,
|
|
272
585
|
input_code: str,
|
|
273
586
|
input_prompt_file: Optional[str],
|
|
274
587
|
output: Optional[str],
|
|
275
|
-
|
|
276
|
-
) -> Tuple[str, float, str]:
|
|
277
|
-
"""Modify
|
|
278
|
-
|
|
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 ---
|
|
279
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
|
|
280
622
|
|
|
281
|
-
|
|
282
|
-
@
|
|
283
|
-
@click.argument("
|
|
284
|
-
@click.argument("
|
|
285
|
-
@click.
|
|
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
|
+
)
|
|
286
634
|
@click.option(
|
|
287
635
|
"--git",
|
|
288
636
|
is_flag=True,
|
|
289
|
-
|
|
637
|
+
default=False,
|
|
638
|
+
help="Use git history to find the original code file.",
|
|
290
639
|
)
|
|
291
640
|
@click.pass_context
|
|
292
641
|
@track_cost
|
|
293
642
|
def update(
|
|
294
|
-
ctx,
|
|
643
|
+
ctx: click.Context,
|
|
295
644
|
input_prompt_file: str,
|
|
296
645
|
modified_code_file: str,
|
|
297
646
|
input_code_file: Optional[str],
|
|
298
647
|
output: Optional[str],
|
|
299
648
|
git: bool,
|
|
300
|
-
) -> Tuple[str, float, str]:
|
|
301
|
-
"""Update the original prompt file based on
|
|
302
|
-
|
|
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.")
|
|
303
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
|
|
304
671
|
|
|
305
|
-
|
|
306
|
-
@
|
|
307
|
-
@click.argument("
|
|
308
|
-
@click.
|
|
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
|
+
)
|
|
309
682
|
@click.pass_context
|
|
310
683
|
@track_cost
|
|
311
684
|
def detect(
|
|
312
|
-
ctx,
|
|
313
|
-
prompt_files:
|
|
685
|
+
ctx: click.Context,
|
|
686
|
+
prompt_files: Tuple[str, ...],
|
|
314
687
|
change_file: str,
|
|
315
|
-
output: Optional[str]
|
|
316
|
-
) -> Tuple[List[
|
|
317
|
-
"""Analyze
|
|
318
|
-
|
|
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.")
|
|
319
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
|
|
320
707
|
|
|
321
|
-
|
|
322
|
-
@
|
|
323
|
-
@click.argument("
|
|
324
|
-
@click.
|
|
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
|
+
)
|
|
325
718
|
@click.pass_context
|
|
326
719
|
@track_cost
|
|
327
720
|
def conflicts(
|
|
328
|
-
ctx,
|
|
721
|
+
ctx: click.Context,
|
|
329
722
|
prompt1: str,
|
|
330
723
|
prompt2: str,
|
|
331
|
-
output: Optional[str]
|
|
332
|
-
) -> Tuple[List[
|
|
333
|
-
"""Analyze two prompt files to find conflicts
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
+
)
|
|
347
774
|
@click.pass_context
|
|
348
775
|
@track_cost
|
|
349
776
|
def crash(
|
|
350
|
-
ctx,
|
|
777
|
+
ctx: click.Context,
|
|
351
778
|
prompt_file: str,
|
|
352
779
|
code_file: str,
|
|
353
780
|
program_file: str,
|
|
354
781
|
error_file: str,
|
|
355
|
-
output: Optional[str],
|
|
782
|
+
output: Optional[str], # Maps to output_code
|
|
356
783
|
output_program: Optional[str],
|
|
357
784
|
loop: bool,
|
|
358
785
|
max_attempts: int,
|
|
359
|
-
budget: float
|
|
360
|
-
) -> Tuple[
|
|
361
|
-
"""Fix errors in a code module that caused a
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
|
383
814
|
|
|
384
815
|
|
|
385
|
-
@cli.command()
|
|
386
|
-
@click.argument("prompt_file", type=click.Path(exists=True))
|
|
387
|
-
@click.argument("code_file", type=click.Path(exists=True))
|
|
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))
|
|
388
819
|
@click.argument("code_line", type=int)
|
|
389
|
-
@click.option(
|
|
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
|
+
)
|
|
390
826
|
@click.pass_context
|
|
391
827
|
@track_cost
|
|
392
828
|
def trace(
|
|
393
|
-
ctx,
|
|
829
|
+
ctx: click.Context,
|
|
394
830
|
prompt_file: str,
|
|
395
831
|
code_file: str,
|
|
396
832
|
code_line: int,
|
|
397
|
-
output: Optional[str]
|
|
398
|
-
) -> Tuple[str, float, str]:
|
|
399
|
-
"""
|
|
400
|
-
|
|
401
|
-
""
|
|
402
|
-
|
|
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)
|
|
403
857
|
|
|
404
858
|
|
|
405
|
-
@cli.command()
|
|
406
|
-
@click.argument("prompt_file", type=click.Path(exists=True))
|
|
407
|
-
@click.argument("code_file", type=click.Path(exists=True))
|
|
408
|
-
@click.argument("program_file", type=click.Path(exists=True))
|
|
409
|
-
@click.argument("
|
|
410
|
-
@click.argument("
|
|
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))
|
|
411
865
|
@click.option(
|
|
412
866
|
"--output",
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
help="
|
|
867
|
+
type=click.Path(writable=True),
|
|
868
|
+
default=None,
|
|
869
|
+
help="Specify where to save the generated unit test (file or directory).",
|
|
416
870
|
)
|
|
417
|
-
@click.option("--language", default=
|
|
871
|
+
@click.option("--language", type=str, default=None, help="Specify the programming language (default: Python).")
|
|
418
872
|
@click.pass_context
|
|
419
873
|
@track_cost
|
|
420
874
|
def bug(
|
|
421
|
-
ctx,
|
|
875
|
+
ctx: click.Context,
|
|
422
876
|
prompt_file: str,
|
|
423
877
|
code_file: str,
|
|
424
878
|
program_file: str,
|
|
425
|
-
|
|
426
|
-
|
|
879
|
+
current_output_file: str,
|
|
880
|
+
desired_output_file: str,
|
|
427
881
|
output: Optional[str],
|
|
428
|
-
language: Optional[str]
|
|
429
|
-
) -> Tuple[str, float, str]:
|
|
430
|
-
"""
|
|
431
|
-
|
|
432
|
-
""
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
|
448
907
|
@click.option(
|
|
449
908
|
"--output",
|
|
450
|
-
type=click.Path(),
|
|
451
|
-
|
|
909
|
+
type=click.Path(writable=True),
|
|
910
|
+
default=None,
|
|
911
|
+
help="Specify where to save the modified prompt file (file or directory).",
|
|
452
912
|
)
|
|
453
913
|
@click.option(
|
|
454
914
|
"--csv",
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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).",
|
|
458
919
|
)
|
|
459
920
|
@click.option(
|
|
460
921
|
"--force-scan",
|
|
461
922
|
is_flag=True,
|
|
462
|
-
|
|
923
|
+
default=False,
|
|
924
|
+
help="Force rescanning of all potential dependency files.",
|
|
463
925
|
)
|
|
464
926
|
@click.pass_context
|
|
465
927
|
@track_cost
|
|
466
928
|
def auto_deps(
|
|
467
|
-
ctx,
|
|
929
|
+
ctx: click.Context,
|
|
468
930
|
prompt_file: str,
|
|
469
931
|
directory_path: str,
|
|
470
932
|
output: Optional[str],
|
|
471
|
-
|
|
472
|
-
force_scan: bool
|
|
473
|
-
) -> Tuple[str, float, str]:
|
|
474
|
-
"""
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if directory_path.startswith('"') and directory_path.endswith('"'):
|
|
480
|
-
directory_path = directory_path[1:-1]
|
|
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('\"')
|
|
481
941
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
+
"--max-attempts",
|
|
974
|
+
type=int,
|
|
975
|
+
default=3,
|
|
976
|
+
show_default=True,
|
|
977
|
+
help="Maximum number of fix attempts within the verification loop.",
|
|
978
|
+
)
|
|
979
|
+
@click.option(
|
|
980
|
+
"--budget",
|
|
981
|
+
type=float,
|
|
982
|
+
default=5.0,
|
|
983
|
+
show_default=True,
|
|
984
|
+
help="Maximum cost allowed for the verification and fixing process.",
|
|
985
|
+
)
|
|
986
|
+
@click.pass_context
|
|
987
|
+
@track_cost
|
|
988
|
+
def verify(
|
|
989
|
+
ctx: click.Context,
|
|
990
|
+
prompt_file: str,
|
|
991
|
+
code_file: str,
|
|
992
|
+
program_file: str,
|
|
993
|
+
output_results: Optional[str],
|
|
994
|
+
output_code: Optional[str],
|
|
995
|
+
max_attempts: int,
|
|
996
|
+
budget: float,
|
|
997
|
+
) -> Optional[Tuple[Dict[str, Any], float, str]]: # Modified return type
|
|
998
|
+
"""Verify code correctness against prompt using LLM judgment."""
|
|
999
|
+
quiet = ctx.obj.get("quiet", False)
|
|
1000
|
+
command_name = "verify"
|
|
1001
|
+
try:
|
|
1002
|
+
success, final_program, final_code, attempts, cost, model = fix_verification_main(
|
|
1003
|
+
ctx=ctx,
|
|
1004
|
+
prompt_file=prompt_file,
|
|
1005
|
+
code_file=code_file,
|
|
1006
|
+
program_file=program_file,
|
|
1007
|
+
output_results=output_results,
|
|
1008
|
+
output_code=output_code,
|
|
1009
|
+
loop=True,
|
|
1010
|
+
verification_program=program_file,
|
|
1011
|
+
max_attempts=max_attempts,
|
|
1012
|
+
budget=budget,
|
|
1013
|
+
)
|
|
1014
|
+
result_data = {
|
|
1015
|
+
"success": success,
|
|
1016
|
+
"attempts": attempts,
|
|
1017
|
+
"verified_code_path": output_code,
|
|
1018
|
+
"results_log_path": output_results,
|
|
1019
|
+
}
|
|
1020
|
+
return result_data, cost, model
|
|
1021
|
+
except Exception as e:
|
|
1022
|
+
handle_error(e, command_name, quiet)
|
|
1023
|
+
return None # Return None on failure
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
@cli.command("install_completion")
|
|
1027
|
+
@click.pass_context
|
|
1028
|
+
# No @track_cost
|
|
1029
|
+
def install_completion_cmd(ctx: click.Context) -> None: # Return type remains None
|
|
1030
|
+
"""Install shell completion for PDD."""
|
|
1031
|
+
quiet = ctx.obj.get("quiet", False)
|
|
1032
|
+
command_name = "install_completion"
|
|
1033
|
+
try:
|
|
1034
|
+
install_completion(quiet=quiet)
|
|
1035
|
+
# Return None on success (as intended)
|
|
1036
|
+
return None
|
|
1037
|
+
except Exception as e:
|
|
1038
|
+
handle_error(e, command_name, quiet)
|
|
1039
|
+
# Explicitly return None on failure as well, consistent with other commands
|
|
1040
|
+
return None
|
|
490
1041
|
|
|
491
1042
|
|
|
1043
|
+
# --- Entry Point ---
|
|
492
1044
|
if __name__ == "__main__":
|
|
493
|
-
cli()
|
|
1045
|
+
cli()
|