pdd-cli 0.0.28__py3-none-any.whl → 0.0.30__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 CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.0.28"
1
+ __version__ = "0.0.30"
2
2
 
3
3
  # Strength parameter used for LLM extraction across the codebase
4
4
  # Used in postprocessing, XML tagging, code generation, and other extraction operations. The module should have a large context window and be affordable.
pdd/cli.py CHANGED
@@ -26,7 +26,7 @@ from .crash_main import crash_main
26
26
  from .detect_change_main import detect_change_main
27
27
  from .fix_main import fix_main
28
28
  from .fix_verification_main import fix_verification_main
29
- from .install_completion import install_completion
29
+ from .install_completion import install_completion, get_local_pdd_path
30
30
  from .preprocess_main import preprocess_main
31
31
  from .split_main import split_main
32
32
  from .trace_main import trace_main
@@ -136,6 +136,9 @@ def cli(
136
136
  Main entry point for the PDD CLI. Handles global options and initializes context.
137
137
  Supports multi-command chaining.
138
138
  """
139
+ # Ensure PDD_PATH is set before any commands run
140
+ get_local_pdd_path()
141
+
139
142
  ctx.ensure_object(dict)
140
143
  ctx.obj["force"] = force
141
144
  ctx.obj["strength"] = strength
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdd-cli
3
- Version: 0.0.28
3
+ Version: 0.0.30
4
4
  Summary: PDD (Prompt-Driven Development) Command Line Interface
5
5
  Author: Greg Tanaka
6
6
  Author-email: glt@alumni.caltech.edu
@@ -46,7 +46,7 @@ Requires-Dist: pytest-asyncio; extra == "dev"
46
46
  Requires-Dist: z3-solver; extra == "dev"
47
47
  Dynamic: license-file
48
48
 
49
- .. image:: https://img.shields.io/badge/pdd--cli-v0.0.28-blue
49
+ .. image:: https://img.shields.io/badge/pdd--cli-v0.0.30-blue
50
50
  :alt: PDD-CLI Version
51
51
 
52
52
  PDD (Prompt-Driven Development) Command Line Interface
@@ -97,6 +97,29 @@ Key Features
97
97
  Quick Installation
98
98
  ------------------
99
99
 
100
+ **Recommended: Using uv (Faster & Better Dependency Management)**
101
+
102
+ We recommend installing PDD using the `uv <https://github.com/astral-sh/uv>`_ package manager for better dependency management and automatic environment configuration:
103
+
104
+ .. code-block:: console
105
+
106
+ # Install uv if you haven't already
107
+ curl -sSf https://astral.sh/uv/install.sh | sh
108
+
109
+ # Install PDD using uv tool install
110
+ uv tool install pdd-cli
111
+
112
+ This installation method ensures:
113
+
114
+ - Faster installations with optimized dependency resolution
115
+ - Automatic environment setup without manual configuration
116
+ - Proper handling of the PDD_PATH environment variable
117
+ - Better isolation from other Python packages
118
+
119
+ **Alternative: Using pip**
120
+
121
+ If you prefer, you can install with pip:
122
+
100
123
  .. code-block:: console
101
124
 
102
125
  pip install pdd-cli
@@ -107,7 +130,7 @@ After installation, verify:
107
130
 
108
131
  pdd --version
109
132
 
110
- You'll see the current PDD version (e.g., 0.0.28).
133
+ You'll see the current PDD version (e.g., 0.0.30).
111
134
 
112
135
  Advanced Installation Tips
113
136
  --------------------------
@@ -127,7 +150,9 @@ Create and activate a virtual environment, then install pdd-cli:
127
150
  # On Unix/MacOS:
128
151
  source pdd-env/bin/activate
129
152
 
130
- # Install PDD
153
+ # Install PDD (with uv - recommended)
154
+ uv tool install pdd-cli
155
+ # OR with pip
131
156
  pip install pdd-cli
132
157
 
133
158
 
@@ -1,4 +1,4 @@
1
- pdd/__init__.py,sha256=CycaqzrnXoqfdnmd7eip_wy6yA3MHNhHK0n9EMo6YHc,477
1
+ pdd/__init__.py,sha256=UlmLFvxuZysa-xxH1RaK3HNVs1VCgVD7DZxe8y-jEv8,477
2
2
  pdd/auto_deps_main.py,sha256=NVLqL5FHxe2eorViXTuh8z2zH9Sb-b6MNN9aZ1hqevY,3552
3
3
  pdd/auto_include.py,sha256=aCa2QXDlOdKbh4vS3uDjWptkHB_Qv3QBNCbZe6mGWoo,6074
4
4
  pdd/auto_update.py,sha256=Pfav1hrqQIDjZIPuIvryBeM7k-Rc72feVUTJZPtigaU,2889
@@ -6,8 +6,7 @@ pdd/bug_main.py,sha256=cSGBnHmFIA8WrkGiohJFVRuM2086v-wlPvTJqTv00WQ,4631
6
6
  pdd/bug_to_unit_test.py,sha256=oejqoKomLseKknYDFlQKQ04TNT3soqOjMPghxty8Guo,6311
7
7
  pdd/change.py,sha256=EKmv7WvXNX24rjLCnrcaoo4xOVkNhCa9HLRbpMAxQSw,5036
8
8
  pdd/change_main.py,sha256=5IAFW1_eazYCr3F1DlfO6FDpNDZ4LgkXBWLb6rKW-PQ,25643
9
- pdd/cli.py,sha256=oYQJiuc5txvR24YJyCt9jwBJlwr3ufAGhzxVhIjj0oI,37360
10
- pdd/cli_1_0_1_0_20250510_005101.py,sha256=oYQJiuc5txvR24YJyCt9jwBJlwr3ufAGhzxVhIjj0oI,37360
9
+ pdd/cli.py,sha256=Z5AE8uVUnzZiuEyZqe9lMu6hUG6pK2_qg9NBwEPNIks,37463
11
10
  pdd/cmd_test_main.py,sha256=aSCxRnSurg15AvPcJDAPp9xy8p_qqnjU1oV14Hi2R54,5301
12
11
  pdd/code_generator.py,sha256=DqQNN6jCNjSJvHi0IFyqkSfak6LeDG-yQHPYnvd4AJQ,4424
13
12
  pdd/code_generator_main.py,sha256=G2eRBPXc1cGszkk0PbIPmJZHPaf_dw5d2yZbsvQZA3c,4793
@@ -99,9 +98,9 @@ pdd/prompts/trim_results_start_LLM.prompt,sha256=OKz8fAf1cYWKWgslFOHEkUpfaUDARh3
99
98
  pdd/prompts/unfinished_prompt_LLM.prompt,sha256=-JgBpiPTQZdWOAwOG1XpfpD9waynFTAT3Jo84eQ4bTw,1543
100
99
  pdd/prompts/update_prompt_LLM.prompt,sha256=prIc8uLp2jqnLTHt6JvWDZGanPZipivhhYeXe0lVaYw,1328
101
100
  pdd/prompts/xml_convertor_LLM.prompt,sha256=YGRGXJeg6EhM9690f-SKqQrKqSJjLFD51UrPOlO0Frg,2786
102
- pdd_cli-0.0.28.dist-info/licenses/LICENSE,sha256=-1bjYH-CEjGEQ8VixtnRYuu37kN6F9NxmZSDkBuUQ9o,1062
103
- pdd_cli-0.0.28.dist-info/METADATA,sha256=z1ybx-VFXHKkNrUztLK-Hmq8sEZIOjsnFTynL8bLBNg,6949
104
- pdd_cli-0.0.28.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
105
- pdd_cli-0.0.28.dist-info/entry_points.txt,sha256=Kr8HtNVb8uHZtQJNH4DnF8j7WNgWQbb7_Pw5hECSR-I,36
106
- pdd_cli-0.0.28.dist-info/top_level.txt,sha256=xjnhIACeMcMeDfVNREgQZl4EbTni2T11QkL5r7E-sbE,4
107
- pdd_cli-0.0.28.dist-info/RECORD,,
101
+ pdd_cli-0.0.30.dist-info/licenses/LICENSE,sha256=-1bjYH-CEjGEQ8VixtnRYuu37kN6F9NxmZSDkBuUQ9o,1062
102
+ pdd_cli-0.0.30.dist-info/METADATA,sha256=P77AbVeiMa4ylyAk6skGkZ73Lx5LY9gZSXlRBUmcJJ8,7762
103
+ pdd_cli-0.0.30.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
104
+ pdd_cli-0.0.30.dist-info/entry_points.txt,sha256=Kr8HtNVb8uHZtQJNH4DnF8j7WNgWQbb7_Pw5hECSR-I,36
105
+ pdd_cli-0.0.30.dist-info/top_level.txt,sha256=xjnhIACeMcMeDfVNREgQZl4EbTni2T11QkL5r7E-sbE,4
106
+ pdd_cli-0.0.30.dist-info/RECORD,,
@@ -1,1054 +0,0 @@
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()